Java 利用 ByteArrayOutputStream 和 ByteArrayInputStream 避免重复读取配置文件
最近参与了github上的一个开源项目 Mycat,是一个mysql的分库分表的中间件。发现其中读取配置文件的代码,存在频繁多次重复打开,读取,关闭的问题,代码写的很初级,稍微看过一些框架源码的人,是不会犯这样的错误的。于是对其进行了一些优化。
优化之前的代码如下所示:
private static Element loadRoot() {
InputStream dtd = null;
InputStream xml = null;
Element root = null;
try {
dtd = ConfigFactory.class.getResourceAsStream("/mycat.dtd");
xml = ConfigFactory.class.getResourceAsStream("/mycat.xml");
root = ConfigUtil.getDocument(dtd, xml).getDocumentElement();
} catch (ConfigException e) {
throw e;
} catch (Exception e) {
throw new ConfigException(e);
} finally {
if (dtd != null) {
try {
dtd.close();
} catch (IOException e) { }
}
if (xml != null) {
try {
xml.close();
} catch (IOException e) { }
}
}
return root;
}
然后其它方法频繁调用 loadRoot():
@Override
public UserConfig getUserConfig(String user) {
Element root = loadRoot();
loadUsers(root);
return this.users.get(user);
} @Override
public Map<String, UserConfig> getUserConfigs() {
Element root = loadRoot();
loadUsers(root);
return users;
} @Override
public SystemConfig getSystemConfig() {
Element root = loadRoot();
loadSystem(root);
return system;
}
// ... ...
ConfigUtil.getDocument(dtd, xml) 方法如下:
public static Document getDocument(final InputStream dtd, InputStream xml) throws ParserConfigurationException,
SAXException, IOException {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
//factory.setValidating(false);
factory.setNamespaceAware(false);
DocumentBuilder builder = factory.newDocumentBuilder();
builder.setEntityResolver(new EntityResolver() {
@Override
public InputSource resolveEntity(String publicId, String systemId) {
return new InputSource(dtd);
}
});
builder.setErrorHandler(new ErrorHandler() {
@Override
public void warning(SAXParseException e) {
}
@Override
public void error(SAXParseException e) throws SAXException {
throw e;
}
@Override
public void fatalError(SAXParseException e) throws SAXException {
throw e;
}
});
return builder.parse(xml);
}
显然这不是很好的处理方式。因为会多次重复读取配置文件。
1. 第一次优化:
为什么不读取一次,然后缓存起来呢?然后其它方法在调用 loadRoot() 时,就直接使用缓存中的就行了。但是遇到一个问题,InputStream 是不能被缓存,然后重复读取的,因为 InputStream 一旦被读取之后,其 pos 指针,等等都会发生变化,无法进行重复读取。所以只能将配置文件的内容读取处理,放入 byte[] 中缓存起来,然后配合 ByteArrayOutputStream,就可以重复读取 byte[] 缓存中的内容了。然后利用 ByteArrayOutputStream 来构造 InputStream 就达到了读取配置文件一次,然后重复构造 InputStream 进行重复读取,相关代码如下:
// 为了避免原代码中频繁调用 loadRoot 去频繁读取 /mycat.dtd 和 /mycat.xml,所以将两个文件进行缓存,
// 注意这里并不会一直缓存在内存中,随着 LocalLoader 对象的回收,缓存占用的内存自然也会被回收。
private static byte[] xmlBuffer = null;
private static byte[] dtdBuffer = null;
private static ByteArrayOutputStream xmlBaos = null;
private static ByteArrayOutputStream dtdBaos = null; static {
InputStream input = ConfigFactory.class.getResourceAsStream("/mycat.dtd");
if(input != null){
dtdBuffer = new byte[1024 * 512];
dtdBaos = new ByteArrayOutputStream();
bufferFileStream(input, dtdBuffer, dtdBaos);
} input = ConfigFactory.class.getResourceAsStream("/mycat.xml");
if(input != null){
xmlBuffer = new byte[1024 * 512];
xmlBaos = new ByteArrayOutputStream();
bufferFileStream(input, xmlBuffer, xmlBaos);
}
}
bufferFileStream 方法:
private static void bufferFileStream(InputStream input, byte[] buffer, ByteArrayOutputStream baos){
int len = -1;
try {
while ((len = input.read(buffer)) > -1 ) {
baos.write(buffer, 0, len);
}
baos.flush();
} catch (IOException e) {
e.printStackTrace();
logger.error(" bufferFileStream error: " + e.getMessage());
}
}
loadRoat 优化之后如下:
private static Element loadRoot() {
Element root = null;
InputStream mycatXml = null;
InputStream mycatDtd = null;
if(xmlBaos != null)
mycatXml = new ByteArrayInputStream(xmlBaos.toByteArray());
if(dtdBaos != null)
mycatDtd = new ByteArrayInputStream(dtdBaos.toByteArray());
try {
root = ConfigUtil.getDocument(mycatDtd, mycatXml).getDocumentElement();
} catch (ParserConfigurationException | SAXException | IOException e1) {
e1.printStackTrace();
logger.error("loadRoot error: " + e1.getMessage());
}finally{
if(mycatXml != null){
try { mycatXml.close(); } catch (IOException e) {}
}
if(mycatDtd != null){
try { mycatDtd.close(); } catch (IOException e) {}
}
}
return root;
}
这样优化之后,即使有很多方法频繁调用 loadRoot() 方法,也不会重复读取配置文件了,而是使用 byte[] 内容,重复构造 InputStream 而已。
其实其原理,就是利用 byte[] 作为一个中间容器,对byte进行缓存,ByteArrayOutputStream 将 InputStream 读取的 byte 存放如 byte[]容器,然后利用 ByteArrayInputStream 从 byte[]容器中读取内容,构造 InputStream,只要 byte[] 这个缓存容器存在,就可以多次重复构造出 InputStream。 于是达到了读取一次配置文件,而重复构造出InputStream,避免了每构造一次InputStream,就读取一次配置文件的问题。
2. 第二次优化:
可能你会想到更好的方法,比如:
为什么我们不将 private static Element root = null; 作为类属性,缓存起来,这样就不需要重复打开和关闭配置文件了,修改如下:
public class LocalLoader implements ConfigLoader {
private static final Logger logger = LoggerFactory.getLogger("LocalLoader");
// ... ..
private static Element root = null;
// 然后 loadRoot 方法改为:
private static Element loadRoot() {
InputStream dtd = null;
InputStream xml = null;
// Element root = null;
if(root == null){
try {
dtd = ConfigFactory.class.getResourceAsStream("/mycat.dtd");
xml = ConfigFactory.class.getResourceAsStream("/mycat.xml");
root = ConfigUtil.getDocument(dtd, xml).getDocumentElement();
} catch (ConfigException e) {
throw e;
} catch (Exception e) {
throw new ConfigException(e);
} finally {
if (dtd != null) {
try {
dtd.close();
} catch (IOException e) { }
}
if (xml != null) {
try {
xml.close();
} catch (IOException e) { }
}
}
}
return root;
}
这样就不需要也不会重复 打开和关闭配置文件了。只要 root 属性没有被回收,那么 root 引入的 Document 对象也会在缓存中。这样显然比第一次优化要好很多,因为第一次优化,还是要从 byte[] 重复构造 InputStream, 然后重复 build 出 Document 对象。
3. 第三次优化
上面是将 private static Element root = null; 作为一个属性进行缓存,避免重复读取。那么我们干嘛不直接将 Document 对象作为一个属性,进行缓存呢。而且具有更好的语义,代码更好理解。代码如下:
public class LocalLoader implements ConfigLoader {
private static final Logger logger = LoggerFactory.getLogger("LocalLoader");
// ... ...
// 为了避免原代码中频繁调用 loadRoot 去频繁读取 /mycat.dtd 和 /mycat.xml,所以将 Document 进行缓存,
private static Document document = null;
private static Element loadRoot() {
InputStream dtd = null;
InputStream xml = null;
if(document == null){
try {
dtd = ConfigFactory.class.getResourceAsStream("/mycat.dtd");
xml = ConfigFactory.class.getResourceAsStream("/mycat.xml");
document = ConfigUtil.getDocument(dtd, xml);
return document.getDocumentElement();
} catch (Exception e) {
logger.error(" loadRoot error: " + e.getMessage());
throw new ConfigException(e);
} finally {
if (dtd != null) {
try { dtd.close(); } catch (IOException e) { }
}
if (xml != null) {
try { xml.close(); } catch (IOException e) { }
}
}
}
return document.getDocumentElement();
}
这样才是比较合格的实现。anyway, 第一种优化,学习到了 ByteArrayOutputStream 和 ByteArrayInputStream 同 byte[] 配合使用的方法。
---------------------分割线------------------------------------
参考文章:http://blog.csdn.net/it_magician/article/details/9240727 原文如下:
有时候我们需要对同一个InputStream对象使用多次。比如,客户端从服务器获取数据 ,利用HttpURLConnection的getInputStream()方法获得Stream对象,这时既要把数据显示到前台(第一次读取),又想把数据写进文件缓存到本地(第二次读取)。
但第一次读取InputStream对象后,第二次再读取时可能已经到Stream的结尾了(EOFException)或者Stream已经close掉了。
而InputStream对象本身不能复制,因为它没有实现Cloneable接口。此时,可以先把InputStream转化成ByteArrayOutputStream,后面要使用InputStream对象时,再从ByteArrayOutputStream转化回来就好了。代码实现如下:
InputStream input = httpconn.getInputStream(); ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len;
while ((len = input.read(buffer)) > -1 ) {
baos.write(buffer, 0, len);
}
baos.flush(); InputStream stream1 = new ByteArrayInputStream(baos.toByteArray()); //TODO:显示到前台 InputStream stream2 = new ByteArrayInputStream(baos.toByteArray()); //TODO:本地缓存
Java 利用 ByteArrayOutputStream 和 ByteArrayInputStream 避免重复读取配置文件的更多相关文章
- SpringBoot2 java配置方式 Configuration和PropertySource结合读取配置文件
JdbcConfig.java Configuration是配置文件 PropertySource 引入配置文件 value读取配置文件内容 package cn.itcast.config; imp ...
- java 使用ByteArrayOutputStream和ByteArrayInputStream实现深拷贝
首先介绍Java中的浅拷贝(浅克隆)和深拷贝(深克隆)的基本概念: 浅拷贝: 被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象.浅复制仅仅复制所考虑的对象,而 ...
- 【转】ByteArrayOutputStream和ByteArrayInputStream详解
ByteArrayOutputStream类是在创建它的实例时,程序内部创建一个byte型别数组的缓冲区,然后利用ByteArrayOutputStream和ByteArrayInputStream的 ...
- InputStream为什么不能被重复读取?
最近上传阿里云的时候同一个文件上传两个服务地址,第一个文件读取以后第二个再去读取就拿不到了.代码如下: //内网上传OSS获取key值 String ossKey = OSSClientUtil.ge ...
- Java IO --ByteArrayOutputStream (六)***
Java提供了很丰富的io接口,已经可以满足我们大部分读取数据的需求,这个在C读取数据需要自己定义缓冲区数组大小,而且要小心翼翼的防止缓冲区溢出的情况相当不同.一般情况下我们读取的数据都是直接读取成为 ...
- Java基础-IO流对象之内存操作流(ByteArrayOutputStream与ByteArrayInputStream)
Java基础-IO流对象之内存操作流(ByteArrayOutputStream与ByteArrayInputStream) 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.内存 ...
- 利用java反射机制 读取配置文件 实现动态类载入以及动态类型转换
作者:54dabang 在spring的学习过程之中,我们能够看出通过配置文件来动态管理bean对象的优点(松耦合 能够让零散部分组成一个总体,而这些总体并不在意之间彼此的细节,从而达到了真正的物理上 ...
- 【java】内存流:java.io.ByteArrayInputStream、java.io.ByteArrayOutputStream、java.io.CharArrayReader、java.io.CharArrayWriter
package 内存流; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java. ...
- 【转载】 java利用snmp4j包来读取snmp协议数据(Manager端)
https://www.cnblogs.com/xdp-gacl/p/4187089.html http://doc.okbase.net/yuanfy008/archive/265663.html ...
随机推荐
- 基于TCP和多线程实现无线鼠标键盘-Socket(1)
把手机作为移动鼠标.键盘使用非常方便,本文将实现这一功能.该应用分为两部分:Windows服务端和Android客户端. 本文源代码的下载地址:http://download.csdn.net/det ...
- 【Java每日一题】20161021
20161020问题解析请点击今日问题下方的"[Java每日一题]20161021"查看 package Oct2016; public class Ques1021 { publ ...
- java Servlet+mysql 调用带有输入参数和返回值的存储过程(原创)
这个数据访问的功能,我在.NET+Mysql .NET+Sqlserver PHP+Mysql上都实现过,并且都发布在了我博客园里面,因为我觉得这个功能实在是太重要,会让你少写很多SQL语句不说,还 ...
- Scalaz(18)- Monad: ReaderWriterState-可以是一种简单的编程语言
说道FP,我们马上会联想到Monad.我们说过Monad的代表函数flatMap可以把两个运算F[A],F[B]连续起来,这样就可以从程序的意义上形成一种串型的流程(workflow).更直白的讲法是 ...
- <welcome-file-list>标签的控制作用以及在springmvc中此标签的的配置方式
我们在写安全性较高的网站时必然会对网站的入口进行限制, 而在这其中其关键作用的就是网站的根目录下WEB-INF中的web.xml中<welcome-file-list> <welc ...
- Linux CentOS 6.6安装JDK1.7
Linux CentOS 6.6安装JDK1.7 目录 1.下载JDK 2.卸载JDK 3.安装JDK 3.1..rpm后缀格式JDK安装方式 3.2..tar.gz后缀格式JDK安装方式 4.验证安 ...
- Java经典实例:正则表达式,替换匹配的文本
import java.util.regex.Matcher; import java.util.regex.Pattern; /** * Created by Frank * 替换匹配的文本 */ ...
- GJM :自定义基于 VLC 的视频播放器 [转载]
感谢您的阅读.喜欢的.有用的就请大哥大嫂们高抬贵手"推荐一下"吧!你的精神支持是博主强大的写作动力以及转载收藏动力.欢迎转载! 版权声明:本文原创发表于 [请点击连接前往] ,未经 ...
- jbox演示30种不同的调用方法
在线预览 插件说明 - jbox 是一款基于 jQuery 的多功能对话框插件,能够实现网站的整体风格效果,给用户一个新的视觉享受. 运行环境 - 兼容 IE6+.Firefox.Chrome.Sa ...
- 站长必备:10个好用的 WordPress 备份插件
网站备份对于站长来说极其重要的.任何的事情都可能发生,这可能会导致你失去所有的辛勤工作:您的网站可能被黑客攻破,你可以安装一个了插件导致冲突,你的服务器可能被攻击,你可能在编辑文件时犯了一个错误等等, ...