在Java程序中,以“流”(stream)的方式對數(shù)據(jù)進行I/O操作惭蹂。
一、流分類
大方向分:
類型 | ||
---|---|---|
按數(shù)據(jù)流方向不同分 | 輸入流 | 輸出流 |
按功能不同分 | 節(jié)點流 | 處理流 |
按處理單位不同分 | 字節(jié)流 | 字符流 |
具體分:
注:直接套在數(shù)據(jù)源上獲取數(shù)據(jù)的管道是節(jié)點流割粮,節(jié)點流上套的其他的增強管道是處理流盾碗。
下面看一張完整的劃分:
二、字節(jié)流
API:
InputStream 類
方法 | 解釋 |
---|---|
public abstract int read() | 一個字節(jié)一個字節(jié)地讀數(shù)據(jù)舀瓢。費磁盤 |
public int read(byte b[]) | 以byte 數(shù)組位位單位來讀取字節(jié)數(shù)據(jù)廷雅。相當(dāng)于拿個桶舀水。 |
public int read(byte b[], int off, int len) | 從第 off 位置讀取 len 長度字節(jié)的數(shù)據(jù)放到 byte 數(shù)組中京髓,以 -1 來判斷是否讀取結(jié)束航缀。 |
public long skip(long n) | 跳過指定個數(shù)的字節(jié)。 |
public int available() | 返回可讀的字節(jié)數(shù)量堰怨。 |
public void close() | 讀取完關(guān)閉流谬盐,釋放資源。 |
public synchronized void mark(int readlimit) | 標記讀取位置诚些,下次還可以從這里開始讀取(使用前要看當(dāng)前流是否支持皇型,可以使用 markSupport() 方法判斷)诬烹。 |
public synchronized void reset() | 重置讀取位置為上次 mark 標記的位置。 |
public boolean markSupported() | 判斷當(dāng)前流是否支持標記流弃鸦,和上面兩個方法配套使用绞吁。 |
OutputStream 類
方法 | 解釋 |
---|---|
public abstract void write(int b) | 寫入一個字節(jié),參數(shù)是一個 int 類型唬格,對應(yīng)上面的讀方法家破,int 類型的 32 位,只有低 8 位才寫入购岗,高 24 位將舍棄汰聋。 |
public void write(byte b[]) | 以byte 數(shù)組為單位來寫入字節(jié)數(shù)據(jù)。 |
public void write(byte b[], int off, int len) | 將 byte 數(shù)組從 off 位置開始喊积,len 長度的字節(jié)寫入 |
public void flush() | 強制刷新烹困,將緩沖中的數(shù)據(jù)寫入 |
public void close() | 關(guān)閉輸出流,流被關(guān)閉后就不能再輸出數(shù)據(jù)了 |
問題1:為什么要使用flash乾吻?
flash把緩沖區(qū)的數(shù)據(jù)強行輸出髓梅,主要用在IO中拟蜻,即清空緩沖區(qū)數(shù)據(jù),一般在讀寫流(stream)的時候枯饿,數(shù)據(jù)是先被讀到了內(nèi)存中酝锅,再把數(shù)據(jù)寫到文件中,當(dāng)你數(shù)據(jù)讀完的時候不代表你的數(shù)據(jù)已經(jīng)寫完了奢方,因為還有一部分有可能會留在內(nèi)存這個緩沖區(qū)中搔扁。這時候如果你調(diào)用了close()方法關(guān)閉了讀寫流,那么這部分數(shù)據(jù)就會丟失袱巨,所以應(yīng)該在關(guān)閉讀寫流之前先flush()阁谆。
問題2:為什么InputStream.read()讀取一個byte卻返回一個int呢?
從先從源碼來看愉老,read方法在java層經(jīng)過IoBridge和LibCore,最終會JNI到native來實現(xiàn)场绿,而native是c++實現(xiàn)的,返回的是unsigned byte嫉入,取值范圍為[0~255]焰盗,在java中沒有對應(yīng)的類型,在java中byte范圍是[-128-127],無符號范圍是[0-127],所以[128-255]只能由int來接收咒林。
另外熬拒,在讀取byte數(shù)據(jù)時,我們知道127+1 = -128(0111 1111 -> 1000 0000,首位是1表示負數(shù)垫竞,負數(shù)轉(zhuǎn)int:先對各位取反澎粟,將其轉(zhuǎn)換為十進制數(shù),加上負號欢瞪,再減去1即-128)活烙,那么會有一種情況是連續(xù)8個1:1111 1111,按前面的計算公式遣鼓,最終會轉(zhuǎn)為-1啸盏,而-1表示讀取結(jié)束,顯然此時并沒有讀完骑祟,為了避免這種情況回懦,需要將byte提升為int,即向前補0來避免此問題發(fā)生次企。
因此:
FileInputStream的read方法實際是在做類型提升(將byte提升為int)怯晕,避免java byte無法對應(yīng)[128-255]范圍問題,避免-1問題缸棵。
FileOutputStream的write的方法實際在做類型強轉(zhuǎn)(將int強轉(zhuǎn)為byte)贫贝,只有低 8 位才寫入,高 24 位將舍棄。
三稚晚、字符流
與字節(jié)流相比,字符流是按一個個字符來讀寫崇堵。適合文本文件的讀寫。
Reader 類
方法 | 解釋 |
---|---|
public int read(java.nio.CharBuffer target) | 讀取字節(jié)到字符緩存中 |
public int read() | 讀取單個字符 |
public int read(char cbuf[]) | 讀取字符到指定的 char 數(shù)組中 |
abstract public int read(char cbuf[], int off, int len) | 從 off 位置讀取 len 長度的字符到 char 數(shù)組中 |
public long skip(long n) | 跳過指定長度的字符數(shù)量 |
public boolean ready() | 和上面的 available() 方法類似 |
public boolean markSupported() | 判斷當(dāng)前流是否支持標記流 |
public void mark(int readAheadLimit) | 標記讀取位置客燕,下次還可以從這里開始讀取鸳劳,使用前要看當(dāng)前流是否支持,可以使用 markSupport() 方法判斷 |
public void reset() | 重置讀取位置為上次 mark 標記的位置 |
abstract public void close() | 關(guān)閉流釋放相關(guān)資源 |
Writer 類
方法 | 解釋 |
---|---|
public void write(int c) | 寫入一個字符 |
public void write(char cbuf[]) | 以字符數(shù)組為單位寫入 |
abstract public void write(char cbuf[], int off, int len) | 從字符數(shù)組的 off 位置寫入 len 數(shù)量的字符 |
public void write(String str) | 寫入一個字符串 |
public void write(String str, int off, int len) | 從字符串的 off 位置寫入 len 數(shù)量的字符 |
public Writer append(CharSequence csq) | 追加寫入一個字符序列 |
public Writer append(CharSequence csq, int start, int end) | 追加寫入一個字符序列的一部分也搓,從 start 位置開始赏廓,end 位置結(jié)束 |
public Writer append(char c) | 追加寫入一個 16 位的字符 |
abstract public void flush() | 強制刷新,將緩沖中的數(shù)據(jù)寫入 |
abstract public void close() | 關(guān)閉輸出流傍妒,流被關(guān)閉后就不能再輸出數(shù)據(jù)了 |
問題:一個字符是幾個字節(jié)幔摸?
這個不同的編碼格式有差別,索性就打印一下看:
漢字:
try {
String str = "你";
System.out.println("UTF-16:"+str.getBytes("UTF-16").length);
System.out.println("UTF-8:"+str.getBytes("UTF-8").length);
System.out.println("GBK:"+str.getBytes("GBK").length);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
結(jié)果:
UTF-16:4
UTF-8:3
GBK:2
英文字母:
try {
String str = "A";
System.out.println("UTF-16:"+str.getBytes("UTF-16").length);
System.out.println("UTF-8:"+str.getBytes("UTF-8").length);
System.out.println("GBK:"+str.getBytes("GBK").length);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
結(jié)果:
UTF-16:4
UTF-8:1
GBK:1
四颤练、處理流
簡單總結(jié)幾個常用的處理流:
字節(jié):
字節(jié)流轉(zhuǎn)換為字符流:
InputStreamReader & OutputStreamWriter字節(jié)管道加buff:
BufferedInputStream & BufferedOutputStream
字符:
字符管道加buff:
BufferedRead & BufferedWriter-
PrintWriter
字符包裝寫PrintWriter 與 BufferedWriter區(qū)別:- PrintWriter 可以接受更多類型的參數(shù)既忆,在循環(huán)讀的過程中,方便拓展寫的內(nèi)容嗦玖,而BufferedWriter的write方法只能接受字符患雇、字符數(shù)組和字符串;
- PrintWriter的println方法自動添加換行宇挫,BufferedWriter需要顯示調(diào)用newLine方法苛吱;
- PrintWriter的方法不會拋異常,若關(guān)心異常器瘪,需要調(diào)用checkError方法看是否有異常發(fā)生翠储;
- PrintWriter構(gòu)造方法可指定參數(shù),實現(xiàn)自動刷新緩存(autoflush)橡疼;
- BufferedWriter可以任意設(shè)定緩沖大小
個人感覺援所,PrintWriter比BufferedWriter更靈活,推薦使用PrintWriter衰齐。
五、代碼案例
/**
* 字節(jié)流文件復(fù)制
* @param oriFile
* @param destFile
*/
public static void byteCopy(File oriFile, File destFile) {
if(oriFile == null || !oriFile.exists()){
return;
}
FileInputStream fis = null;
FileOutputStream fos = null;
try {
fis = new FileInputStream(oriFile);
fos = new FileOutputStream(destFile, true);//第二個參數(shù):是否追加寫继阻。
byte[] bytes = new byte[1024];
int len;
StringBuilder sb = new StringBuilder();
while ((len = fis.read(bytes)) != -1) {
fos.write(bytes, 0, len);
fos.flush();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (fos != null) {
fos.close();
}
if (fis != null) {
fis.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 字符流文件復(fù)制
* @param oriFile
* @param destFile
*/
public static void charCopy(File oriFile, File destFile) {
if(oriFile == null || !oriFile.exists()){
return;
}
FileReader fr = null;
FileWriter fw = null;
try {
fr = new FileReader(oriFile);
fw = new FileWriter(destFile, true);
char[] chars = new char[1024];
int len;
while ((len = fr.read(chars)) != -1) {
fw.write(chars, 0, len);
System.out.println(String.valueOf(chars));
fw.flush();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (fw != null) {
fw.close();
}
if (fr != null) {
fr.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 字節(jié)流Buffered復(fù)制
* @param oriFile
* @param destFile
*/
public static void bufferedByteCopy(File oriFile, File destFile) {
if(oriFile == null || !oriFile.exists()){
return;
}
BufferedInputStream bis = null;
BufferedOutputStream bos = null;
try {
bis = new BufferedInputStream(new FileInputStream(oriFile));
bos = new BufferedOutputStream(new FileOutputStream(destFile));
int len;
while ((len = bis.read()) != -1) {
bos.write(len);
System.out.print((char) len);
bos.flush();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (bis != null) {
bis.close();
}
if (bos != null) {
bos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 字符流Buffered復(fù)制
* @param oriFile
* @param destFile
*/
public static void bufferedCharCopy(File oriFile, File destFile) {
if(oriFile == null || !oriFile.exists()){
return;
}
BufferedReader br = null;
BufferedWriter bw = null;
try {
br = new BufferedReader(new FileReader(oriFile));
bw = new BufferedWriter(new FileWriter(destFile));
//int len = 0;
// while ((len = br.read()) != -1) {
// bw.write(len);
// System.out.print((char) len);
// }
//有更高級的讀法:
String buffer;
while ((buffer = br.readLine()) != null) { //一行行讀耻涛,判斷從-1變?yōu)閚ull了
bw.write(buffer);
System.out.print(buffer);
bw.flush();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (br != null) {
br.close();
}
if (bw != null) {
bw.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 字符流換printWrite實現(xiàn)復(fù)制
* @param oriFile
* @param destFile
*/
public static void printWriteCharCopy(File oriFile, File destFile) {
if(oriFile == null || !oriFile.exists()){
return;
}
BufferedReader br = null;
PrintWriter pw = null;
try {
br = new BufferedReader(new FileReader(oriFile));
pw = new PrintWriter(new FileWriter(destFile));
String buffer;
while ((buffer = br.readLine()) != null) {
pw.print(buffer);
System.out.print(buffer);
pw.flush();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (br != null) {
br.close();
}
if (pw != null) {
pw.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 寫了一個相對比較萬能的文件復(fù)制方法
* @param oriFile
* @param destFile
* @param byByte
* @param isAppend
*/
public static void fileCopy(File oriFile, File destFile, boolean byByte, boolean isAppend) {
if(oriFile == null || !oriFile.exists()){
return;
}
try {
FileInputStream fis = new FileInputStream(oriFile);
FileOutputStream fos = new FileOutputStream(destFile, isAppend);//isAppend 追加
if (byByte) {
BufferedInputStream bis = new BufferedInputStream(fis);
BufferedOutputStream bos = new BufferedOutputStream(fos);
byte[] bytes = new byte[1024];
int len;
while ((len = bis.read(bytes)) != -1) {
bos.write(bytes, 0, len);
bos.flush();
}
bos.close();
bis.close();
} else {
BufferedReader br = new BufferedReader(new InputStreamReader(fis));
//PrintWriter 構(gòu)造方法第二個參數(shù):autoFlash
PrintWriter pw = new PrintWriter(new OutputStreamWriter(fos), true);
String buffer;
while ((buffer = br.readLine()) != null) {
pw.println(buffer);
}
pw.close();
br.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
六、byte[] 瘟檩、char[] 抹缕、String相互轉(zhuǎn)換
順便總結(jié)下byte[] 、char[] 墨辛、String相互轉(zhuǎn)換的API卓研。
/**
* String 轉(zhuǎn) char[]
*/
public static char[] stringToCharArray(String str) {
return str.toCharArray();
}
/**
* char[] 轉(zhuǎn) String
*/
public static String charArrayToString(char[] chars) {
//return String.valueOf(chars);
return new String(chars);
}
/**
* String 轉(zhuǎn) byte[]
*/
public static byte[] stringToByteArray(String str) throws Exception {
return str.getBytes("UTF-8");
}
/**
* byte[] 轉(zhuǎn) String
*/
public static String byteArrayToString(byte[] bytes) {
return new String(bytes);
}
/**
* byte[] 轉(zhuǎn) char[]
*/
public static char[] byteArrayToCharArray(byte[] bytes) {
Charset cs = Charset.forName("UTF-8");
ByteBuffer bb = ByteBuffer.allocate(bytes.length);
bb.put(bytes);
bb.flip();
CharBuffer cb = cs.decode(bb);
return cb.array();
}
/**
* char[] 轉(zhuǎn) byte[]
*/
public static byte[] charArrayToByteArray(char[] chars) {
Charset cs = Charset.forName("UTF-8");
CharBuffer cb = CharBuffer.allocate(chars.length);
cb.put(chars);
cb.flip();
ByteBuffer bb = cs.encode(cb);
return bb.array();
}
七、NIO
JDK 1.4后,Java提供了一個全新的IO API奏赘,即 Java New IO寥闪,特點是面向緩沖區(qū),提供多路非阻塞式的IO操作磨淌。
核心組件:
- 通道(Channel)
- 緩沖區(qū)(Buffer)
- 選擇器(Selectors)
詳細介紹:
實例:實現(xiàn)文件復(fù)制
// 設(shè)置輸入源 & 輸出地 = 文件
String infile = “XXX";
String outfile = “XXX";
// 1. 獲取數(shù)據(jù)源 和 目標傳輸?shù)氐妮斎胼敵隽鳎ù颂幰詳?shù)據(jù)源 = 文件為例)
FileInputStream fin = new FileInputStream(infile);
FileOutputStream fout = new FileOutputStream(outfile);
// 2. 獲取數(shù)據(jù)源的輸入輸出通道
FileChannel fcin = fin.getChannel();
FileChannel fcout = fout.getChannel();
// 3. 創(chuàng)建緩沖區(qū)對象
ByteBuffer buff = ByteBuffer.allocate(1024);
while (true) {
// 4. 從通道讀取數(shù)據(jù) & 寫入到緩沖區(qū)
// 注:若 以讀取到該通道數(shù)據(jù)的末尾疲憋,則返回-1
int r = fcin.read(buff);
if (r == -1) {
break;
}
// 5. 傳出數(shù)據(jù)準備:調(diào)用flip()方法
buff.flip();
// 6. 從 Buffer 中讀取數(shù)據(jù) & 傳出數(shù)據(jù)到通道
fcout.write(buff);
// 7. 重置緩沖區(qū)
buff.clear();
}
}
與 Java IO區(qū)別:
NIO部分內(nèi)容向Carson_Ho學(xué)習(xí):http://www.reibang.com/p/d30893c4d6bb