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 |