Java使用ByteArrayOutputStream 和 ByteArrayInputStream 避免重复读取配置文件的方法
ByteArrayOutputStream类是在创建它的实例时,程序内部创建一个byte型别数组的缓冲区,然后利用ByteArrayOutputStream和ByteArrayInputStream的实例向数组中写入或读出byte型数据。在网络传输中我们往往要传输很多变量,我们可以利用ByteArrayOutputStream把所有的变量收集到一起,然后一次性把数据发送出去。具体用法如下:
ByteArrayOutputStream: 可以捕获内存缓冲区的数据,转换成字节数组。
ByteArrayInputStream:可以将字节数组转化为输入流
ByteArrayInputStream类有两个默认的构造函数:
ByteArrayInputStream(byte[]b):使用一个字节数组当中所有的数据做为数据源,程序可以像输入流方式一样读取字节,可以看做一个虚拟的文件,用文件的方式去读取它里面的数据。
ByteArrayInputStream(byte[]b,intoffset,intlength):从数组当中的第offset开始,一直取出length个这个字节做为数据源。
ByteArrayOutputStream类也有两个默认的构造函数:
ByteArrayOutputStream():创建一个32个字节的缓冲区
ByteArrayOutputStream(int):根据参数指定大小创建缓冲区
最近参与了github上的一个开源项目Mycat,是一个mysql的分库分表的中间件。发现其中读取配置文件的代码,存在频繁多次重复打开,读取,关闭的问题,代码写的很初级,稍微看过一些框架源码的人,是不会犯这样的错误的。于是对其进行了一些优化。
优化之前的代码如下所示:
privatestaticElementloadRoot(){ InputStreamdtd=null; InputStreamxml=null; Elementroot=null; try{ dtd=ConfigFactory.class.getResourceAsStream("/mycat.dtd"); xml=ConfigFactory.class.getResourceAsStream("/mycat.xml"); root=ConfigUtil.getDocument(dtd,xml).getDocumentElement(); }catch(ConfigExceptione){ throwe; }catch(Exceptione){ thrownewConfigException(e); }finally{ if(dtd!=null){ try{ dtd.close(); }catch(IOExceptione){} } if(xml!=null){ try{ xml.close(); }catch(IOExceptione){} } } returnroot; }
然后其它方法频繁调用loadRoot():
@Override publicUserConfiggetUserConfig(Stringuser){ Elementroot=loadRoot(); loadUsers(root); returnthis.users.get(user); } @Override publicMap<String,UserConfig>getUserConfigs(){ Elementroot=loadRoot(); loadUsers(root); returnusers; } @Override publicSystemConfiggetSystemConfig(){ Elementroot=loadRoot(); loadSystem(root); returnsystem; } //......
ConfigUtil.getDocument(dtd,xml)方法如下:
publicstaticDocumentgetDocument(finalInputStreamdtd,InputStreamxml)throwsParserConfigurationException, SAXException,IOException{ DocumentBuilderFactoryfactory=DocumentBuilderFactory.newInstance(); //factory.setValidating(false); factory.setNamespaceAware(false); DocumentBuilderbuilder=factory.newDocumentBuilder(); builder.setEntityResolver(newEntityResolver(){ @Override publicInputSourceresolveEntity(StringpublicId,StringsystemId){ returnnewInputSource(dtd); } }); builder.setErrorHandler(newErrorHandler(){ @Override publicvoidwarning(SAXParseExceptione){ } @Override publicvoiderror(SAXParseExceptione)throwsSAXException{ throwe; } @Override publicvoidfatalError(SAXParseExceptione)throwsSAXException{ throwe; } }); returnbuilder.parse(xml); }
显然这不是很好的处理方式。因为会多次重复配置文件。
1.第一次优化:
为什么不读取一次,然后缓存起来呢?然后其它方法在调用loadRoot()时,就直接使用缓存中的就行了。但是遇到一个问题,InputStream是不能被缓存,然后重复读取的,因为InputStream一旦被读取之后,其pos指针,等等都会发生变化,无法进行重复读取。所以只能将配置文件的内容读取处理,放入 byte[]中缓存起来,然后配合ByteArrayOutputStream,就可以重复读取byte[]缓存中的内容了。然后利用ByteArrayOutputStream来构造InputStream就达到了读取配置文件一次,然后重复构造InputStream进行重复读取,相关代码如下:
//为了避免原代码中频繁调用loadRoot去频繁读取/mycat.dtd和/mycat.xml,所以将两个文件进行缓存, //注意这里并不会一直缓存在内存中,随着LocalLoader对象的回收,缓存占用的内存自然也会被回收。 privatestaticbyte[]xmlBuffer=null; privatestaticbyte[]dtdBuffer=null; privatestaticByteArrayOutputStreamxmlBaos=null; privatestaticByteArrayOutputStreamdtdBaos=null; static{ InputStreaminput=ConfigFactory.class.getResourceAsStream("/mycat.dtd"); if(input!=null){ dtdBuffer=newbyte[1024*512]; dtdBaos=newByteArrayOutputStream(); bufferFileStream(input,dtdBuffer,dtdBaos); } input=ConfigFactory.class.getResourceAsStream("/mycat.xml"); if(input!=null){ xmlBuffer=newbyte[1024*512]; xmlBaos=newByteArrayOutputStream(); bufferFileStream(input,xmlBuffer,xmlBaos); } }
bufferFileStream方法:
privatestaticvoidbufferFileStream(InputStreaminput,byte[]buffer,ByteArrayOutputStreambaos){ intlen=-1; try{ while((len=input.read(buffer))>-1){ baos.write(buffer,0,len); } baos.flush(); }catch(IOExceptione){ e.printStackTrace(); logger.error("bufferFileStreamerror:"+e.getMessage()); } }
loadRoat优化之后如下:
privatestaticElementloadRoot(){ Elementroot=null; InputStreammycatXml=null; InputStreammycatDtd=null; if(xmlBaos!=null) mycatXml=newByteArrayInputStream(xmlBaos.toByteArray()); if(dtdBaos!=null) mycatDtd=newByteArrayInputStream(dtdBaos.toByteArray()); try{ root=ConfigUtil.getDocument(mycatDtd,mycatXml).getDocumentElement(); }catch(ParserConfigurationException|SAXException|IOExceptione1){ e1.printStackTrace(); logger.error("loadRooterror:"+e1.getMessage()); }finally{ if(mycatXml!=null){ try{mycatXml.close();}catch(IOExceptione){} } if(mycatDtd!=null){ try{mycatDtd.close();}catch(IOExceptione){} } } returnroot; }
这样优化之后,即使有很多方法频繁调用loadRoot()方法,也不会重复读取配置文件了,而是使用byte[]内容,重复构造InputStream而已。
其实其原理,就是利用byte[]作为一个中间容器,对byte进行缓存,ByteArrayOutputStream将InputStream读取的byte存放如byte[]容器,然后利用ByteArrayInputStream从byte[]容器中读取内容,构造InputStream,只要byte[]这个缓存容器存在,就可以多次重复构造出InputStream。于是达到了读取一次配置文件,而重复构造出InputStream,避免了每构造一次InputStream,就读取一次配置文件的问题。
2.第二次优化:
可能你会想到更好的方法,比如:
为什么我们不将privatestaticElementroot=null;作为类属性,缓存起来,这样就不需要重复打开和关闭配置文件了,修改如下:
publicclassLocalLoaderimplementsConfigLoader{ privatestaticfinalLoggerlogger=LoggerFactory.getLogger("LocalLoader"); //..... privatestaticElementroot=null; //然后loadRoot方法改为: privatestaticElementloadRoot(){ InputStreamdtd=null; InputStreamxml=null; //Elementroot=null; if(root==null){ try{ dtd=ConfigFactory.class.getResourceAsStream("/mycat.dtd"); xml=ConfigFactory.class.getResourceAsStream("/mycat.xml"); root=ConfigUtil.getDocument(dtd,xml).getDocumentElement(); }catch(ConfigExceptione){ throwe; }catch(Exceptione){ thrownewConfigException(e); }finally{ if(dtd!=null){ try{ dtd.close(); }catch(IOExceptione){} } if(xml!=null){ try{ xml.close(); }catch(IOExceptione){} } } } returnroot; }
这样就不需要也不会重复打开和关闭配置文件了。只要root属性没有被回收,那么root引入的Document对象也会在缓存中。这样显然比第一次优化要好很多,因为第一次优化,还是要从byte[]重复构造InputStream,然后重复build出Document对象。
3.第三次优化
上面是将privatestaticElementroot=null;作为一个属性进行缓存,避免重复读取。那么我们干嘛不直接将Document对象作为一个属性,进行缓存呢。而且具有更好的语义,代码更好理解。代码如下:
publicclassLocalLoaderimplementsConfigLoader{ privatestaticfinalLoggerlogger=LoggerFactory.getLogger("LocalLoader"); //...... //为了避免原代码中频繁调用loadRoot去频繁读取/mycat.dtd和/mycat.xml,所以将Document进行缓存, privatestaticDocumentdocument=null; privatestaticElementloadRoot(){ InputStreamdtd=null; InputStreamxml=null; if(document==null){ try{ dtd=ConfigFactory.class.getResourceAsStream("/mycat.dtd"); xml=ConfigFactory.class.getResourceAsStream("/mycat.xml"); document=ConfigUtil.getDocument(dtd,xml); returndocument.getDocumentElement(); }catch(Exceptione){ logger.error("loadRooterror:"+e.getMessage()); thrownewConfigException(e); }finally{ if(dtd!=null){ try{dtd.close();}catch(IOExceptione){} } if(xml!=null){ try{xml.close();}catch(IOExceptione){} } } } returndocument.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转化回来就好了。代码实现如下:
InputStreaminput=httpconn.getInputStream(); ByteArrayOutputStreambaos=newByteArrayOutputStream(); byte[]buffer=newbyte[1024]; intlen; while((len=input.read(buffer))>-1){ baos.write(buffer,0,len); } baos.flush(); InputStreamstream1=newByteArrayInputStream(baos.toByteArray()); //TODO:显示到前台 InputStreamstream2=newByteArrayInputStream(baos.toByteArray()); //TODO:本地缓存
java中ByteArrayInputStream和ByteArrayOutputStream类用法
ByteArrayInputStream和ByteArrayOutputStream,用于以IO流的方式来完成对字节数组内容的读写,来支持类似内存虚拟文件或者内存映射文件的功能
实例:
importjava.io.*; publicclassByteArrayStreamTest{ publicstaticvoidmain(String[]args){ Stringstr="abcdef"; ByteArrayInputStreamin=newByteArrayInputStream(str.getBytes()); ByteArrayOutputStreamout=newByteArrayOutputStream(); transform(in,out); byte[]result=out.toByteArray(); System.out.println(out); System.out.println(newString(result)); transform(System.in,System.out);//从键盘读,输出到显示器 } publicstaticvoidtransform(InputStreamin,OutputStreamout){ intch=0; try{ while((ch=in.read())!=-1){ intupperChar=Character.toUpperCase((char)ch); out.write(upperChar); }//closewhile }catch(Except