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[] 配合使用的方法。

---------------------分割线------------------------------------

参考文章: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:本地缓存 

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
(0)

相关推荐

  • 详解Java程序读取properties配置文件的方法

    在我们平时写程序的时候,有些参数是经常改变的,而这种改变不是我们预知的.比如说我们开发了一个操作数据库的模块,在开发的时候我们连接本地的数据库那么IP ,数据库名称,表名称,数据库主机等信息是我们本地的,要使得这个操作数据的模块具有通用性,那么以上信息就不能写死在程序里.通常我们的做法是用配置文件来解决. 各种语言都有自己所支持的配置文件类型.比如Python ,他支持.ini 文件.因为他内部有一个ConfigParser 类来支持.ini 文件的读写,根据该类提供的方法程序员可以自由的来操作

  • 一个Java配置文件加密解密工具类分享

    常见的如: 数据库用户密码,短信平台用户密码,系统间校验的固定密码等.本工具类参考了 <Spring.3.x企业应用开发实战>一书 5.3节的实现.完整代码与注释信息如下: 复制代码 代码如下: package com.cncounter.util.comm; import java.security.Key;import java.security.SecureRandom; import javax.crypto.Cipher;import javax.crypto.KeyGenerato

  • Java 读写Properties配置文件详解

    Java 读写Properties配置文件 1.Properties类与Properties配置文件 Properties类继承自Hashtable类并且实现了Map接口,也是使用一种键值对的形式来保存属性集.不过Properties有特殊的地方,就是它的键和值都是字符串类型. 2.Properties中的主要方法 (1)load(InputStream inStream) 这个方法可以从.properties属性文件对应的文件输入流中,加载属性列表到Properties类对象.如下面的代码:

  • java读取properties配置文件的方法

    本文实例讲述了java读取properties配置文件的方法.分享给大家供大家参考.具体分析如下: 这两天做java项目,用到属性文件,到网上查资料,好半天也没有找到一个满意的方法能让我读取到.properties文件中属性值,很是郁闷,网上讲的获取属性值大概有以下方法,以下三种方法逐渐优化,以达到最好的效果以下都以date.properties文件为例,该文件放在src目录下,文件内容为: startdate=2011-02-07 totalweek=25 方法一: public class

  • java简单读取properties配置文件的方法示例

    本文实例讲述了java简单读取properties配置文件的方法.分享给大家供大家参考,具体如下: 读取配置文件,小结如下 import java.io.FileNotFoundException; import java.io.IOException; import java.util.Properties; public class loadConf { private Properties prop = new Properties(); private void loadconf() t

  • Java开发中读取XML与properties配置文件的方法

    相关阅读: 使用Ajax进行文件与其他参数的上传功能(java开发) 1. XML文件: 什么是XML?XML一般是指可扩展标记语言,标准通用标记语言的子集,是一种用于标记电子文件使其具有结构性的标记语言. 2.XML文件的优点: 1)XML文档内容和结构完全分离. 2)互操作性强. 3)规范统一. 4)支持多种编码. 5)可扩展性强. 3.如何解析XML文档: XML在不同的语言中解析XML文档都是一样的,只不过实现的语法不一样,基本的解析方式有两种,一种是SAX方式,是按照XML文件的顺序一

  • Java中spring读取配置文件的几种方法示例

    Spring读取配置XML文件分三步: 一.新建一个Java Bean: package springdemo; public class HelloBean { private String helloWorld; public String getHelloWorld() { return helloWorld; } public void setHelloWorld(String helloWorld) { this.helloWorld = helloWorld; } } 二.构建一个配

  • java中读取配置文件中数据的具体方法

    1.先在项目中创建一个包(如:config),再创建一个配置文件(如:a.properties),添加配置信息如下:比如: 复制代码 代码如下: name=kakaage=28 2.代码: 复制代码 代码如下: import java.io.IOException; import java.io.InputStream; import java.util.Properties; public class PropertyTest { public static void main(String[

  • Java中的几种读取properties配置文件的方式

    相信对于一名JAVA开发者开说properties文件一定再熟悉不过了,比如一下配置: config.properties会经常存放一些系统常量,版本号,路径之类的 database.properties存放数据库的连接参数 log4j.properties 日志的一些基本配置 redis.properties 缓存数据库的一些配置 当然前缀是根据用能自行定义的,一般来说文件的内容的格式是"键=值"的格式,文本注释信息可以用"#"来注释,下面来说说开发中如何读写pr

  • java使用dom4j解析xml配置文件实现抽象工厂反射示例

    逻辑描述: 现在我们想在B层和D层加上接口层,并使用工厂.而我们可以将创建B和创建D看作是两个系列,然后就可以使用抽象工厂进行创建了. 配置文件:beans-config.xml.service-class与dao-class分别对应两个系列的产品.子菜单中id对应接口的命名空间,class对应实现类的命名空间. 复制代码 代码如下: [html] view plaincopyprint? <?xml version="1.0" encoding="UTF-8"

  • Java读取properties配置文件时,出现中文乱码的解决方法

    如下所示: public static String getConfig(String key) { Properties pros = new Properties(); String value = ""; try { pros.load(new InputStreamReader(Object.class.getResourceAsStream("/properties.properties"), "UTF-8")); value = pr

随机推荐