欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Java使用ByteArrayOutputStream 和 ByteArrayInputStream 避免重复读取配置文件的方法

程序员文章站 2024-03-07 16:16:09
bytearrayoutputstream类是在创建它的实例时,程序内部创建一个byte型别数组的缓冲区,然后利用bytearrayoutputstream和bytearr...

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