這是一段對輸入流進行讀取和輸出的代碼
Resource classPathResource = new ClassPathResource("1.txt");
InputStream inputStream = pathResource.getInputStream();
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
int i;
while ((i=inputStream.read())!=-1){
byteArrayOutputStream.write(i);
}
System.out.println(byteArrayOutputStream.toString());
輸出數據如下:
以前一直對這樣的while循環理解不足,今天突然再度寫到這樣的代碼,就想着深入研究一下。
inputStream.read()
這個方法的官方定義如下
/**
* Reads the next byte of data from the input stream. The value byte is
* returned as an <code>int</code> in the range <code>0</code> to
* <code>255</code>. If no byte is available because the end of the stream
* has been reached, the value <code>-1</code> is returned. This method
* blocks until input data is available, the end of the stream is detected,
* or an exception is thrown.
*
* <p> A subclass must provide an implementation of this method.
*
* @return the next byte of data, or <code>-1</code> if the end of the
* stream is reached.
* @exception IOException if an I/O error occurs.
*/
public abstract int read() throws IOException;
就是說,從輸入流中讀取數據的下一個byte字節,並返回int類型的字節值,這個字節的值範圍在0~255之間。如果輸入流已經被讀完,就返回-1。
這裏還是先講一下byte的知識。
byte
bit:位。位是電子計算機中最小的數據單位。每一位的狀態只能是0或1。比爾蓋茨曾經說過,計算機世界都是由0和1組成的。計算機也只能讀懂0和1這種二進制數據。
byte:字節。8個二進制位構成1個"字節(Byte)",它是存儲空間的基本計量單位。即:1byte=8bit。
編碼:編碼的知識量太大,這裏只是簡單提一下開發中常用到的utf-8編碼。在utf-8編碼中,一個英文字符等於一個字節,一箇中文(含繁體)等於三個字節。無論是中文還是英文,都叫字符。中文標點佔三個字節,英文標點佔一個字節。也就是說一個“千”字,它要被計算機識別,首先轉化成對應它的三個byte字節(對應規則在相關的映射表中),再轉化成3*8=24(bit)。最終結果是11100101 10001101 10000011 ,這個二進制數字才能被計算機讀懂。而英文字母“Q”的轉化簡單些,它只需要一個字節去表示 10000001。
這裏分享個在線轉換utf-8編碼的地址http://www.mytju.com/classcode/tools/encode_utf8.asp
java中的流傳輸
我在開發過程中,遇到的流傳輸、數據傳輸,基本上都是用byte數組,所以這裏也主要講這個。從文章的第一段代碼中,不難看出,inputStream.read()將讀取到的文件1.txt的二進制數據賦予了變量i,ByteArrayOutputStream負責將i的值通過write()方法記錄起來,然後使用toString()方法將記錄的數據重新編碼輸出。這裏看了一下ByteArrayOutputStream的底層源碼,把重要的方法放上來,並註上中文註釋:
public class ByteArrayOutputStream extends OutputStream {
/**
* byte數組,用於存儲讀取到的byte字節。
*/
protected byte buf[];
/**
* 有效字節數
*/
protected int count;
/**
* 初始化byte[] 數組,32長度
*/
public ByteArrayOutputStream() {
this(32);
}
public ByteArrayOutputStream(int size) {
if (size < 0) {
throw new IllegalArgumentException("Negative initial size: "
+ size);
}
buf = new byte[size];
}
/**
* byte數組擴容,這是爲了防止傳入的數據量大於byte[]初始容量
* */
private void ensureCapacity(int minCapacity) {
// overflow-conscious code
if (minCapacity - buf.length > 0)
grow(minCapacity);
}
/**
* 定義byte數組最大容量
*/
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = buf.length;
int newCapacity = oldCapacity << 1;
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
buf = Arrays.copyOf(buf, newCapacity);
}
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
/**
* 把讀取的一個字節放入byte[]
*/
public synchronized void write(int b) {
ensureCapacity(count + 1);
buf[count] = (byte) b;
count += 1;
}
/**
* 將byte[]數組中存儲的字節以平臺默認編碼(這裏是utf-8)重新編碼,並返回String類型的數據,也就是文本的內容。
*/
public synchronized String toString() {
return new String(buf, 0, count);
}
源碼思路:定義一個byte[]變量,存儲所有讀到的字節,作爲一個緩衝域。這個緩衝域隨着對象不斷的讀入數據而不斷增長,當數據量大於緩衝域的空間,就會對緩衝域進行擴容處理。當數據全部讀入緩衝區中,裏面的內容大概是這樣的,buf[]={117,115,101,114...}。最後通過調用toString()方法將緩衝區的數據以特定編碼再次輸出。
講一下源碼中幾個關鍵的點:
- int newCapacity = oldCapacity << 1; 這裏的意思是數組空間擴容1倍。oldCapacity 左移一位,即左邊高位去掉,右邊地位補0。例:十進制8的二進制表示成 0000 1000 左移1位,8<<1,變成0001 0000 。再轉成十進制,即變成16。這就完成了擴容。
- new String(buf, 0, count) 表示 將字符數組buf 從第一個字符(java以0開始,也可以說是第0個字符)開始,長度爲count的的所有字符構建成一個字符串,也就是String了。這個方法幫我們完成了由字節轉化到字符的操作。
這樣來看,這個ByteArrayOutputStream 其實很簡單。我這裏自己也寫了個根據輸入流輸出文本數據的工具類,結果發現代碼真的差不多,就稍微封裝了一下。
/**
* 千里明月
*/
public class OutputStreamUtil {
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
private static int count=0;
private static byte[] bytes=new byte[10];
public static String write(InputStream inputStream) throws IOException {
int i =0 ;
while ((i=inputStream.read())!=-1){
write(i);
}
return encode();
}
public static void write(int i){
if (bytes.length<=count){
grow(count+1);
}
bytes[count]= (byte) i;
System.out.print(bytes[count]);
count++;
}
private static void grow(int capacity) {
int oldcapacity= bytes.length;
int newcapacity= oldcapacity<<1;
if (newcapacity<capacity){
newcapacity=capacity;
}
if (newcapacity<MAX_ARRAY_SIZE){
bytes = Arrays.copyOf(bytes, newcapacity);
}
}
public static String encode(){
return new String(bytes,0,count);
}
}
測試類:
public class TestResource {
public static void main(String[] args) throws IOException {
Resource classPathResource = new ClassPathResource("1.txt");
InputStream inputStream = classPathResource.getInputStream();
String write = OutputStreamUtil.write(inputStream);
System.out.println(write);
inputStream.close();
}
}