Java使用ByteArrayOutputStream 和 ByteArrayInputStream 避免重復(fù)讀取配置文件的方法
ByteArrayOutputStream類是在創(chuàng)建它的實(shí)例時(shí),程序內(nèi)部創(chuàng)建一個(gè)byte型別數(shù)組的緩沖區(qū),然后利用ByteArrayOutputStream和ByteArrayInputStream的實(shí)例向數(shù)組中寫(xiě)入或讀出byte型數(shù)據(jù)。在網(wǎng)絡(luò)傳輸中我們往往要傳輸很多變量,我們可以利用ByteArrayOutputStream把所有的變量收集到一起,然后一次性把數(shù)據(jù)發(fā)送出去。具體用法如下:
ByteArrayOutputStream: 可以捕獲內(nèi)存緩沖區(qū)的數(shù)據(jù),轉(zhuǎn)換成字節(jié)數(shù)組。
ByteArrayInputStream: 可以將字節(jié)數(shù)組轉(zhuǎn)化為輸入流
ByteArrayInputStream類有兩個(gè)默認(rèn)的構(gòu)造函數(shù):
ByteArrayInputStream(byte[] b): 使用一個(gè)字節(jié)數(shù)組當(dāng)中所有的數(shù)據(jù)做為數(shù)據(jù)源,程序可以像輸入流方式一樣讀取字節(jié),可以看做一個(gè)虛擬的文件,用文件的方式去讀取它里面的數(shù)據(jù)。
ByteArrayInputStream(byte[] b,int offset,int length): 從數(shù)組當(dāng)中的第offset開(kāi)始,一直取出length個(gè)這個(gè)字節(jié)做為數(shù)據(jù)源。
ByteArrayOutputStream類也有兩個(gè)默認(rèn)的構(gòu)造函數(shù):
ByteArrayOutputStream(): 創(chuàng)建一個(gè)32個(gè)字節(jié)的緩沖區(qū)
ByteArrayOutputStream(int): 根據(jù)參數(shù)指定大小創(chuàng)建緩沖區(qū)
最近參與了github上的一個(gè)開(kāi)源項(xiàng)目 Mycat,是一個(gè)mysql的分庫(kù)分表的中間件。發(fā)現(xiàn)其中讀取配置文件的代碼,存在頻繁多次重復(fù)打開(kāi),讀取,關(guān)閉的問(wèn)題,代碼寫(xiě)的很初級(jí),稍微看過(guò)一些框架源碼的人,是不會(huì)犯這樣的錯(cuò)誤的。于是對(duì)其進(jìn)行了一些優(yōu)化。
優(yōu)化之前的代碼如下所示:
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; }
然后其它方法頻繁調(diào)用 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); }
顯然這不是很好的處理方式。因?yàn)闀?huì)多次重復(fù)配置文件。
1. 第一次優(yōu)化:
為什么不讀取一次,然后緩存起來(lái)呢?然后其它方法在調(diào)用 loadRoot() 時(shí),就直接使用緩存中的就行了。但是遇到一個(gè)問(wèn)題,InputStream 是不能被緩存,然后重復(fù)讀取的,因?yàn)?InputStream 一旦被讀取之后,其 pos 指針,等等都會(huì)發(fā)生變化,無(wú)法進(jìn)行重復(fù)讀取。所以只能將配置文件的內(nèi)容讀取處理,放入 byte[] 中緩存起來(lái),然后配合 ByteArrayOutputStream,就可以重復(fù)讀取 byte[] 緩存中的內(nèi)容了。然后利用 ByteArrayOutputStream 來(lái)構(gòu)造 InputStream 就達(dá)到了讀取配置文件一次,然后重復(fù)構(gòu)造 InputStream 進(jìn)行重復(fù)讀取,相關(guān)代碼如下:
// 為了避免原代碼中頻繁調(diào)用 loadRoot 去頻繁讀取 /mycat.dtd 和 /mycat.xml,所以將兩個(gè)文件進(jìn)行緩存, // 注意這里并不會(huì)一直緩存在內(nèi)存中,隨著 LocalLoader 對(duì)象的回收,緩存占用的內(nèi)存自然也會(huì)被回收。 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 優(yōu)化之后如下:
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; }
這樣優(yōu)化之后,即使有很多方法頻繁調(diào)用 loadRoot() 方法,也不會(huì)重復(fù)讀取配置文件了,而是使用 byte[] 內(nèi)容,重復(fù)構(gòu)造 InputStream 而已。
其實(shí)其原理,就是利用 byte[] 作為一個(gè)中間容器,對(duì)byte進(jìn)行緩存,ByteArrayOutputStream 將 InputStream 讀取的 byte 存放如 byte[]容器,然后利用 ByteArrayInputStream 從 byte[]容器中讀取內(nèi)容,構(gòu)造 InputStream,只要 byte[] 這個(gè)緩存容器存在,就可以多次重復(fù)構(gòu)造出 InputStream。 于是達(dá)到了讀取一次配置文件,而重復(fù)構(gòu)造出InputStream,避免了每構(gòu)造一次InputStream,就讀取一次配置文件的問(wèn)題。
2. 第二次優(yōu)化:
可能你會(huì)想到更好的方法,比如:
為什么我們不將 private static Element root = null; 作為類屬性,緩存起來(lái),這樣就不需要重復(fù)打開(kāi)和關(guān)閉配置文件了,修改如下:
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; }
這樣就不需要也不會(huì)重復(fù) 打開(kāi)和關(guān)閉配置文件了。只要 root 屬性沒(méi)有被回收,那么 root 引入的 Document 對(duì)象也會(huì)在緩存中。這樣顯然比第一次優(yōu)化要好很多,因?yàn)榈谝淮蝺?yōu)化,還是要從 byte[] 重復(fù)構(gòu)造 InputStream, 然后重復(fù) build 出 Document 對(duì)象。
3. 第三次優(yōu)化
上面是將 private static Element root = null; 作為一個(gè)屬性進(jìn)行緩存,避免重復(fù)讀取。那么我們干嘛不直接將 Document 對(duì)象作為一個(gè)屬性,進(jìn)行緩存呢。而且具有更好的語(yǔ)義,代碼更好理解。代碼如下:
public class LocalLoader implements ConfigLoader { private static final Logger logger = LoggerFactory.getLogger("LocalLoader"); // ... ... // 為了避免原代碼中頻繁調(diào)用 loadRoot 去頻繁讀取 /mycat.dtd 和 /mycat.xml,所以將 Document 進(jìn)行緩存, 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(); }
這樣才是比較合格的實(shí)現(xiàn)。anyway, 第一種優(yōu)化,學(xué)習(xí)到了 ByteArrayOutputStream 和 ByteArrayInputStream 同 byte[] 配合使用的方法。
---------------------分割線------------------------------------
參考文章:http://blog.csdn.net/it_magician/article/details/9240727 原文如下:
有時(shí)候我們需要對(duì)同一個(gè)InputStream對(duì)象使用多次。比如,客戶端從服務(wù)器獲取數(shù)據(jù) ,利用HttpURLConnection的getInputStream()方法獲得Stream對(duì)象,這時(shí)既要把數(shù)據(jù)顯示到前臺(tái)(第一次讀?。?,又想把數(shù)據(jù)寫(xiě)進(jìn)文件緩存到本地(第二次讀取)。
但第一次讀取InputStream對(duì)象后,第二次再讀取時(shí)可能已經(jīng)到Stream的結(jié)尾了(EOFException)或者Stream已經(jīng)close掉了。
而InputStream對(duì)象本身不能復(fù)制,因?yàn)樗鼪](méi)有實(shí)現(xiàn)Cloneable接口。此時(shí),可以先把InputStream轉(zhuǎn)化成ByteArrayOutputStream,后面要使用InputStream對(duì)象時(shí),再?gòu)腂yteArrayOutputStream轉(zhuǎn)化回來(lái)就好了。代碼實(shí)現(xiàn)如下:
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:顯示到前臺(tái) InputStream stream2 = new ByteArrayInputStream(baos.toByteArray()); //TODO:本地緩存
java中ByteArrayInputStream和ByteArrayOutputStream類用法
ByteArrayInputStream和ByteArrayOutputStream,用于以IO流的方式來(lái)完成對(duì)字節(jié)數(shù)組內(nèi)容的讀寫(xiě),來(lái)支持類似內(nèi)存虛擬文件或者內(nèi)存映射文件的功能
實(shí)例:
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); // 從鍵盤(pán)讀,輸出到顯示器 } 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
相關(guān)文章
解決mybatis執(zhí)行SQL語(yǔ)句部分參數(shù)返回NULL問(wèn)題
這篇文章主要介紹了mybatis執(zhí)行SQL語(yǔ)句部分參數(shù)返回NULL問(wèn)題,需要的的朋友參考下吧2017-06-06springboot整合mqtt實(shí)現(xiàn)消息訂閱和推送功能
mica-mqtt-client-spring-boot-starter是一個(gè)方便、高效、可靠的MQTT客戶端啟動(dòng)器,適用于需要使用MQTT協(xié)議進(jìn)行消息通信的Spring Boot應(yīng)用程序,這篇文章主要介紹了springboot整合mqtt實(shí)現(xiàn)消息訂閱和推送功能,需要的朋友可以參考下2024-02-02通過(guò)代碼示例了解submit與execute的區(qū)別
這篇文章主要介紹了通過(guò)代碼示例了解submit與execute的區(qū)別,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-09-09Java并發(fā)編程創(chuàng)建并運(yùn)行線程的方法對(duì)比
這篇文章主要為大家詳細(xì)介紹了Java并發(fā)編程創(chuàng)建并運(yùn)行線程的方法,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來(lái)幫助2022-03-03學(xué)習(xí)Java多線程之線程定義、狀態(tài)和屬性
這篇文章主要為大家詳細(xì)介紹了Java多線程之線程定義、狀態(tài)和屬性,感興趣的小伙伴們可以參考一下2016-02-02使用SpringMVC的@Validated注解驗(yàn)證的實(shí)現(xiàn)
這篇文章主要介紹了使用SpringMVC的@Validated注解驗(yàn)證的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-08-08java使用htmlparser提取網(wǎng)頁(yè)純文本例子
這篇文章主要介紹了java使用htmlparser提取網(wǎng)頁(yè)純文本例子,需要的朋友可以參考下2014-04-04java實(shí)現(xiàn)簡(jiǎn)易五子棋游戲
這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)簡(jiǎn)易五子棋游戲,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-06-06