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