Java使用ByteArrayOutputStream 和 ByteArrayInputStream 避免重複讀取設定檔的方法_java

來源:互聯網
上載者:User

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

@Overridepublic UserConfig getUserConfig(String user) {  Element root = loadRoot();  loadUsers(root);  return this.users.get(user);}@Overridepublic Map<String, UserConfig> getUserConfigs() {  Element root = loadRoot();  loadUsers(root);  return users;}@Overridepublic 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
相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.