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的分库分表的中间件。发现其中读取配置文件的代码,存在频繁多次重复打开,读取,关闭的问题,代码写的很初级,稍微看过一些框架源码的人,是不会犯这样的错误的。于是对其进行了一些优化。
优化之前的代码如下所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
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():
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
@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) 方法如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
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 进行重复读取,相关代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
// 为了避免原代码中频繁调用 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 方法:
1
2
3
4
5
6
7
8
9
10
11
12
|
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 优化之后如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
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; 作为类属性,缓存起来,这样就不需要重复打开和关闭配置文件了,修改如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
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 对象作为一个属性,进行缓存呢。而且具有更好的语义,代码更好理解。代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
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转化回来就好了。代码实现如下:
1
2
3
4
5
6
7
8
9
10
11
12
|
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流的方式来完成对字节数组内容的读写,来支持类似内存虚拟文件或者内存映射文件的功能
实例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
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
|