Java使用ByteArrayOutputStream 和 ByteArrayInputStream 避免重復(fù)讀取配置文件的方法
ByteArrayOutputStream類是在創(chuàng)建它的實例時,程序內(nèi)部創(chuàng)建一個byte型別數(shù)組的緩沖區(qū),然后利用ByteArrayOutputStream和ByteArrayInputStream的實例向數(shù)組中寫入或讀出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類有兩個默認(rèn)的構(gòu)造函數(shù):
ByteArrayInputStream(byte[] b): 使用一個字節(jié)數(shù)組當(dāng)中所有的數(shù)據(jù)做為數(shù)據(jù)源,程序可以像輸入流方式一樣讀取字節(jié),可以看做一個虛擬的文件,用文件的方式去讀取它里面的數(shù)據(jù)。
ByteArrayInputStream(byte[] b,int offset,int length): 從數(shù)組當(dāng)中的第offset開始,一直取出length個這個字節(jié)做為數(shù)據(jù)源。
ByteArrayOutputStream類也有兩個默認(rèn)的構(gòu)造函數(shù):
ByteArrayOutputStream(): 創(chuàng)建一個32個字節(jié)的緩沖區(qū)
ByteArrayOutputStream(int): 根據(jù)參數(shù)指定大小創(chuàng)建緩沖區(qū)
最近參與了github上的一個開源項目 Mycat,是一個mysql的分庫分表的中間件。發(fā)現(xiàn)其中讀取配置文件的代碼,存在頻繁多次重復(fù)打開,讀取,關(guā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);
}
顯然這不是很好的處理方式。因為會多次重復(fù)配置文件。
1. 第一次優(yōu)化:
為什么不讀取一次,然后緩存起來呢?然后其它方法在調(diào)用 loadRoot() 時,就直接使用緩存中的就行了。但是遇到一個問題,InputStream 是不能被緩存,然后重復(fù)讀取的,因為 InputStream 一旦被讀取之后,其 pos 指針,等等都會發(fā)生變化,無法進行重復(fù)讀取。所以只能將配置文件的內(nèi)容讀取處理,放入 byte[] 中緩存起來,然后配合 ByteArrayOutputStream,就可以重復(fù)讀取 byte[] 緩存中的內(nèi)容了。然后利用 ByteArrayOutputStream 來構(gòu)造 InputStream 就達到了讀取配置文件一次,然后重復(fù)構(gòu)造 InputStream 進行重復(fù)讀取,相關(guān)代碼如下:
// 為了避免原代碼中頻繁調(diào)用 loadRoot 去頻繁讀取 /mycat.dtd 和 /mycat.xml,所以將兩個文件進行緩存,
// 注意這里并不會一直緩存在內(nèi)存中,隨著 LocalLoader 對象的回收,緩存占用的內(nèi)存自然也會被回收。
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() 方法,也不會重復(fù)讀取配置文件了,而是使用 byte[] 內(nèi)容,重復(fù)構(gòu)造 InputStream 而已。
其實其原理,就是利用 byte[] 作為一個中間容器,對byte進行緩存,ByteArrayOutputStream 將 InputStream 讀取的 byte 存放如 byte[]容器,然后利用 ByteArrayInputStream 從 byte[]容器中讀取內(nèi)容,構(gòu)造 InputStream,只要 byte[] 這個緩存容器存在,就可以多次重復(fù)構(gòu)造出 InputStream。 于是達到了讀取一次配置文件,而重復(fù)構(gòu)造出InputStream,避免了每構(gòu)造一次InputStream,就讀取一次配置文件的問題。
2. 第二次優(yōu)化:
可能你會想到更好的方法,比如:
為什么我們不將 private static Element root = null; 作為類屬性,緩存起來,這樣就不需要重復(fù)打開和關(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;
}
這樣就不需要也不會重復(fù) 打開和關(guān)閉配置文件了。只要 root 屬性沒有被回收,那么 root 引入的 Document 對象也會在緩存中。這樣顯然比第一次優(yōu)化要好很多,因為第一次優(yōu)化,還是要從 byte[] 重復(fù)構(gòu)造 InputStream, 然后重復(fù) build 出 Document 對象。
3. 第三次優(yōu)化
上面是將 private static Element root = null; 作為一個屬性進行緩存,避免重復(fù)讀取。那么我們干嘛不直接將 Document 對象作為一個屬性,進行緩存呢。而且具有更好的語義,代碼更好理解。代碼如下:
public class LocalLoader implements ConfigLoader {
private static final Logger logger = LoggerFactory.getLogger("LocalLoader");
// ... ...
// 為了避免原代碼中頻繁調(diào)用 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();
}
這樣才是比較合格的實現(xiàn)。anyway, 第一種優(yōu)化,學(xué)習(xí)到了 ByteArrayOutputStream 和 ByteArrayInputStream 同 byte[] 配合使用的方法。
---------------------分割線------------------------------------
參考文章:http://blog.csdn.net/it_magician/article/details/9240727 原文如下:
有時候我們需要對同一個InputStream對象使用多次。比如,客戶端從服務(wù)器獲取數(shù)據(jù) ,利用HttpURLConnection的getInputStream()方法獲得Stream對象,這時既要把數(shù)據(jù)顯示到前臺(第一次讀?。?,又想把數(shù)據(jù)寫進文件緩存到本地(第二次讀?。?。
但第一次讀取InputStream對象后,第二次再讀取時可能已經(jīng)到Stream的結(jié)尾了(EOFException)或者Stream已經(jīng)close掉了。
而InputStream對象本身不能復(fù)制,因為它沒有實現(xiàn)Cloneable接口。此時,可以先把InputStream轉(zhuǎn)化成ByteArrayOutputStream,后面要使用InputStream對象時,再從ByteArrayOutputStream轉(zhuǎn)化回來就好了。代碼實現(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:顯示到前臺
InputStream stream2 = new ByteArrayInputStream(baos.toByteArray());
//TODO:本地緩存
java中ByteArrayInputStream和ByteArrayOutputStream類用法
ByteArrayInputStream和ByteArrayOutputStream,用于以IO流的方式來完成對字節(jié)數(shù)組內(nèi)容的讀寫,來支持類似內(nèi)存虛擬文件或者內(nèi)存映射文件的功能
實例:
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
相關(guān)文章
淺析Java中靜態(tài)代理和動態(tài)代理的應(yīng)用與區(qū)別
代理模式在我們生活中很常見,而Java中常用的兩個的代理模式就是動態(tài)代理與靜態(tài)代理,這篇文章主要為大家介紹了二者的應(yīng)用與區(qū)別,需要的可以參考下2023-08-08
SpringBoot整合Flink CDC實現(xiàn)實時追蹤mysql數(shù)據(jù)變動
我們將整合Spring Boot和Apache Flink CDC(Change Data Capture)來實現(xiàn)實時數(shù)據(jù)追蹤,下面是一個基本的實踐流程代碼,包括搭建Spring Boot項目、整合Flink CDC以及實現(xiàn)數(shù)據(jù)變動的實時追蹤,需要的朋友可以參考下2024-07-07
Springboot前后端分離項目配置跨域?qū)崿F(xiàn)過程解析
這篇文章主要介紹了Springboot前后端分離項目配置跨域?qū)崿F(xiàn)過程解析,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2020-08-08
SpringBoot security安全認(rèn)證登錄的實現(xiàn)方法
這篇文章主要介紹了SpringBoot security安全認(rèn)證登錄的實現(xiàn)方法,也就是使用默認(rèn)用戶和密碼登錄的操作方法,本文結(jié)合實例代碼給大家介紹的非常詳細,需要的朋友可以參考下2023-02-02
SpringSecurity?默認(rèn)登錄認(rèn)證的實現(xiàn)原理解析
這篇文章主要介紹了SpringSecurity?默認(rèn)登錄認(rèn)證的實現(xiàn)原理解析,本文通過圖文示例相結(jié)合給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-12-12
Java聊天室之實現(xiàn)運行服務(wù)器與等待客戶端連接
這篇文章主要為大家詳細介紹了Java簡易聊天室之實現(xiàn)運行服務(wù)器程序與等待客戶端程序連接功能,文中的示例代碼講解詳細,需要的可以了解一下2022-10-10

