Java使用ByteArrayOutputStream 和 ByteArrayInputStream 避免重复读取配置文件的方法
bytearrayoutputstream类是在创建它的实例时,程序内部创建一个byte型别数组的缓冲区,然后利用bytearrayoutputstream和bytearrayinputstream的实例向数组中写入或读出byte型数据。在网络传输中我们往往要传输很多变量,我们可以利用bytearrayoutputstream把所有的变量收集到一起,然后一次性把数据发送出去。具体用法如下:
bytearrayoutputstream: 可以捕获内存缓冲区的数据,转换成字节数组。
bytearrayinputstream: 可以将字节数组转化为输入流
bytearrayinputstream类有两个默认的构造函数:
bytearrayinputstream(byte[] b): 使用一个字节数组当中所有的数据做为数据源,程序可以像输入流方式一样读取字节,可以看做一个虚拟的文件,用文件的方式去读取它里面的数据。
bytearrayinputstream(byte[] b,int offset,int length): 从数组当中的第offset开始,一直取出length个这个字节做为数据源。
bytearrayoutputstream类也有两个默认的构造函数:
bytearrayoutputstream(): 创建一个32个字节的缓冲区
bytearrayoutputstream(int): 根据参数指定大小创建缓冲区
最近参与了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[] 配合使用的方法。
---------------------分割线------------------------------------
参考文章: 原文如下:
有时候我们需要对同一个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中bytearrayinputstream和bytearrayoutputstream类用法
bytearrayinputstream和bytearrayoutputstream,用于以io流的方式来完成对字节数组内容的读写,来支持类似内存虚拟文件或者内存映射文件的功能
实例:
import java.io.*; public class bytearraystreamtest { public static void main(string [] args) { string str = "abcdef"; bytearrayinputstream in = new bytearrayinputstream(str.getbytes()); bytearrayoutputstream out = new bytearrayoutputstream(); transform(in, out); byte[] result = out.tobytearray(); system.out.println(out); system.out.println(new string(result)); transform(system.in, system.out); // 从键盘读,输出到显示器 } public static void transform(inputstream in, outputstream out) { int ch = 0; try { while ((ch = in.read()) != -1) { int upperchar = character.touppercase((char)ch); out.write(upperchar); } // close while } catch (except