# 字節流和字符流
[TOC]
## 流-再述
* 流是個抽象的概念,是對輸入輸出設備的抽象,輸入流可以看作一個輸入通道,輸出流可以看作一個輸出通道。
* 輸入流是相對程序而言的,外部傳入數據給程序需要借助輸入流。
* 輸出流是相對程序而言的,程序把數據傳輸到外部需要借助輸出流。
## 字節流
字節流常用于讀取圖像,音頻,視頻之類的二進制字節數據。
### 字節輸入流`InputStream`
`InputStream`是字節輸入流的父類,以下是`Inputstream`的子類結構圖

>[info]常用的輸入流有`FileInputStream`,`ObjectInputStream`,`BufferedInputStream`
#### 文件輸入流`FileInputStream`
該類用于從文件系統中的某個文件中獲得輸入字節,用于讀取諸如圖像數據之類的原始字節流。
**常用方法:**
| 方法名 | 描述 |
| :--- | :--- |
| public int read() | 從輸入流中讀取一個數據字節 |
| public int read(byte[] b) | 從輸入流中將最多b.length個字節的數據讀入到一個byte數組中 |
| public int read(byte[] b,int off,int len) | 從此輸入流中將最多`len`個字節的數據讀入一個 byte 數組中。 |
| public void close() | 關閉此文件輸入流并釋放與此流有關的所有系統資源。 |
>[warning] 除了第一個方法可以得到讀取到的下一個數據字節,這三個read()方法,如果返回值為-1,則表示已經到達文件末尾。可以用來作為文件是否讀完的一個標志。
~~~java
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
public class FileInputDemo {
public static void main(String[] args) {
//創建一個FileInputStream對象
try {
FileInputStream fis = new FileInputStream("d:"+File.separator + "fileTest" + File.separator + "abcNew.txt");
//********************無參read()方法使用**********************
/*int n = fis.read();
System.out.println((char)n);//得到H
*/
/*int n = fis.read();
while(n != -1) {
System.out.println((char)n);
n = fis.read();
}*/
//為了提升代碼的簡潔性
/*int n = 0;
while((n = fis.read()) != -1) {
System.out.println((char)n);
}*/
//*******************read(byte[] b)和read(byte[] b,int off,int len)方法的使用*****************
/*byte[] b = new byte[100];
fis.read(b);
System.out.println(new String(b));*/
byte[] b = new byte[100];
fis.read(b,0,5);
System.out.println(new String(b));
fis.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
~~~
### 字節輸出流`OutputStream`
`OutputStream`是字節輸出流的父類,以下是`Outputstream`的子類結構圖

>[info]常用的輸入流有`FileOutputStream`,`ObjectOutputStream`,`BufferedOutputStream`
#### 文件輸出流`FileOutputStream`
該類用來講數據寫入到文件系統的文件中
**常用方法:**
| 方法名 | 描述 |
| :--- | :--- |
| public void write(int b) | 將指定字節寫入此文件輸出流。 |
| public void write(byte[] b) | 將`b.length`個字節從指定 byte 數組寫入此文件輸出流中。 |
| public void write(byte[] b,int off,int len) | 將指定 byte 數組中從偏移量`off`開始的`len`個字節寫入此文件輸出流。 |
| public void close() | 關閉此文件輸出流并釋放與此流有關的所有系統資源。 |
~~~java
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
public class FileOutputDemo {
public static void main(String[] args) {
String path = "d:"+File.separator + "fileTest" + File.separator + "abcNew.txt";
FileOutputStream fos;
FileInputStream fis;
try {
fos = new FileOutputStream(path,true);
fis = new FileInputStream(path);
/*fos.write(50);
fos.write('e');
//編碼問題導致
System.out.println(fis.read());
System.out.println((char)fis.read());*/
String content = "\nsay Hello World again!";
fos.write(content.getBytes());
fos.close();
fis.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
~~~
**文件拷貝**
~~~java
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
public class FileOutputDemo {
public static void main(String[] args) {
String path = "d:"+File.separator + "picture" + File.separator + "20170315inHome.jpg";
String copyPath = "d:"+File.separator + "fileTest" + File.separator + "flower.jpg";
FileInputStream fis;
FileOutputStream fos;
try {
//創建輸入流
fis = new FileInputStream(path);
File file = new File(copyPath);
//判斷文件的父路徑是否存在,如果不存在則新建目錄
if(!file.getParentFile().exists()) {
file.getParentFile().mkdirs();
}
//創建新文件
file.createNewFile();
//創建輸出流
fos = new FileOutputStream(copyPath);
int n = 0;
byte[] b = new byte[1024];
while((n = fis.read(b)) != -1) {
//一邊讀取輸入流,一邊寫入到新建的文件中去
//fos.write(b);
//為了保證最后復制文件和源文件同樣大小
fos.write(b, 0, n);
}
fos.close();
fis.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
~~~
### 緩沖流
* 緩沖輸入流 `BufferedInputStream`
* 緩沖輸出流 `BufferedOutputStream`
相較于之前直接從硬盤中讀取數據,利用緩沖輸入輸出流,可以從內存中讀取數據,可以極大的提高讀寫速度。
以緩沖輸入流為例,它是不能直接讀取文件系統中的文件的,需要和文件輸入流結合。讀取原理是,文件輸入流首先從文件系統中讀取數據,讀取到數據后不是直接到程序中,而是給緩沖流讀取到字節數組中。輸出流也是一樣。
>[warning]針對于緩沖輸出流,如果緩沖區已滿,則執行文件寫入操作,如果緩沖區不滿需要先執行`flush()`方法刷新緩沖區。
~~~java
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
public class BufferedIODemo {
public final static String PATH = "d:"+File.separator + "fileTest" + File.separator + "abcNew.txt";
public static void main(String[] args) {
setBufferedIO();
}
public static void setBufferedIO() {
try {
//首先利用文件輸出流,執行文件的寫操作
FileOutputStream fos = new FileOutputStream(PATH);
BufferedOutputStream bos = new BufferedOutputStream(fos);
//讀操作
FileInputStream fis = new FileInputStream(PATH);
BufferedInputStream bis = new BufferedInputStream(fis);
//利用緩沖輸出流進行寫操作,需要注意的是此時數據是寫在緩沖區的,如果緩沖未滿,且沒有調用flush()方法,則不會寫入文件
bos.write(50);
bos.write('a');
//一般會要求同時寫flush和close
bos.flush();
System.out.println(bis.read());
System.out.println((char)bis.read());
fos.close();
//close()方法會釋放有關bos的一切資源,所以調用close()方法,也會執行寫入操作
bos.close();
fis.close();
bis.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
~~~
## 字符流
Java提供了字符流便于針對字符串的輸出,尤其是針對中文的數據處理,都會采用字符流的形式。
### 字符輸入流`Reader`
`Reader`是字符輸入流的父類,以下是字符輸入流的結構圖

>[info]常用的字符輸入流有`BufferedReader`,`InputStreamReader`,`FileReader`
#### 字節字符轉換輸入流
轉換流,可以將一個字節流轉換為字符流,也可以將一個字符流轉換為字節流。
`InputStreamReader`:**將輸入的字節流轉換為字符流輸入形式。**
~~~java
public class ReadDemo {
public final static String PATH = "d:"+File.separator + "fileTest" + File.separator + "abcNew.txt";
public static void main(String[] args) {
FileInputStream fis;
InputStreamReader isr;
try {
fis = new FileInputStream(PATH);
//這里其實是一個流的連接
isr = new InputStreamReader(fis);
int n = 0;
/*while((n = isr.read()) != -1) {
System.out.println((char)n);
}*/
char[] charArr = new char[15];
while((n = isr.read(charArr)) != -1) {
//System.out.println(new String(charArr));
//根據操作系統和字符編碼的不同,可能最后的讀取的效果不經如人意
System.out.println(new String(charArr,0,n));
}
fis.close();
isr.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
~~~
### 字符輸出流`Writer`
Writer是字符輸出流的父類,以下是字符輸出流的結構圖

>[info]常用的字符輸出流有`BufferedWriter`,`InputStreamWriter`,`FileWriter`
#### 字節字符轉換輸出流
轉換流,可以將一個字節流轉換為字符流,也可以將一個字符流轉換為字節流。
`OutputStreamWriter`:**可以將輸出的字符流轉換為字節流的輸出形式。**
~~~java
public class WriterDemo {
public final static String PATH = "d:"+File.separator + "fileTest" + File.separator + "abcNew.txt";
public final static String PATH2 = "d:"+File.separator + "fileTest" + File.separator + "abcNew2.txt";
public static void main(String[] args) {
FileInputStream fis;
InputStreamReader isr;
FileOutputStream fos;
OutputStreamWriter ost;
try {
fis = new FileInputStream(PATH);
isr = new InputStreamReader(fis,"UTF-8");
//其實這個文件并沒有但是文件輸出流會幫我們自動創建一個
fos = new FileOutputStream(PATH2);
ost = new OutputStreamWriter(fos,"UTF-8");
int n = 0;
char[] charArr = new char[13];
while((n = isr.read(charArr)) != -1) {
ost.write(charArr, 0, n);
ost.flush();
}
fis.close();
isr.close();
fos.close();
ost.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
~~~
>[danger]讀寫的時候需要保持編碼的一致
### 字節字符轉換緩沖流
`BufferedReader`和`BufferedWriter`
~~~java
public class BufferedRWDemo {
public final static String PATH = "d:"+File.separator + "fileTest" + File.separator + "abcNew.txt";
public final static String PATH2 = "d:"+File.separator + "fileTest" + File.separator + "abcNew2.txt";
public static void main(String[] args) {
FileInputStream fis;
InputStreamReader isr;
FileOutputStream fos;
OutputStreamWriter ost;
try {
fis = new FileInputStream(PATH);
isr = new InputStreamReader(fis,"UTF-8");
//其實與之前的緩沖輸入輸出流相比,這里出現的是一個三層的連接
BufferedReader br = new BufferedReader(isr);
//其實這個文件并沒有但是文件輸出流會幫我們自動創建一個
fos = new FileOutputStream(PATH2);
ost = new OutputStreamWriter(fos,"UTF-8");
BufferedWriter bw = new BufferedWriter(ost);
int n = 0;
char[] charArr = new char[13];
while((n = br.read(charArr)) != -1) {
bw.write(charArr, 0, n);
bw.flush();
}
fis.close();
isr.close();
fos.close();
ost.close();
br.close();
bw.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
~~~
## 總結
字符流與字節流中的類多種多樣,不可能全部講完,但是各個類的使用大多是大同小異。
在字符流中使用字節流去讀寫文件,其實只是為了模擬網絡文件的讀寫操作。字符流有著自己的讀寫文件的類`FileReader`和`FileWriter`