你的位置:首页 > Java教程

[Java教程]Java 利用 ByteArrayOutputStream 和 ByteArrayInputStream 避免重复读取配置文件

最近参与了github上的一个开源项目 Mycat,是一个mysql的分库分表的中间件。发现其中读取配置文件的代码,存在频繁多次重复打开,读取,关闭的问题,代码写的很初级,稍微看过一些框架源码的人,是不会犯这样的错误的。于是对其进行了一些优化。

优化之前的代码如下所示:

  private static Element loadRoot() {    InputStream dtd = null;    InputStream 

然后其它方法频繁调用 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,

  public static Document getDocument(final InputStream dtd, InputStream 

显然这不是很好的处理方式。因为会多次重复配置文件。

1. 第一次优化:

为什么不读取一次,然后缓存起来呢?然后其它方法在调用 loadRoot() 时,就直接使用缓存中的就行了。但是遇到一个问题,InputStream 是不能被缓存,然后重复读取的,因为 InputStream 一旦被读取之后,其 pos 指针,等等都会发生变化,无法进行重复读取。所以只能将配置文件的内容读取处理,放入  byte[] 中缓存起来,然后配合 ByteArrayOutputStream,就可以重复读取 byte[] 缓存中的内容了。然后利用 ByteArrayOutputStream 来构造 InputStream 就达到了读取配置文件一次,然后重复构造 InputStream 进行重复读取,相关代码如下:

  // 为了避免原代码中频繁调用 loadRoot 去频繁读取 /mycat.dtd 和 /mycat.

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 mycat

这样优化之后,即使有很多方法频繁调用 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 = null;//    Element root = null;    if(root == null){      try {        dtd = ConfigFactory.class.getResourceAsStream("/mycat.dtd");        = ConfigFactory.class.getResourceAsStream("/mycat.);        root = ConfigUtil.getDocument(dtd, catch (ConfigException e) {        throw e;      } catch (Exception e) {        throw new ConfigException(e);      } finally {        if (dtd != null) {          try {            dtd.close();          } catch (IOException e) { }        }        if (null) {          try {            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.

这样才是比较合格的实现。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:本地缓存