文章目錄
一、IO概述
1. 什麼是IO?
- 把計算機上數據的傳輸,按照流動的方向,以內存爲基準,分爲輸入input和輸出output
- 所謂的
IO
操作,就是利用java.io
包下的內容,進行輸入輸出操作
2. IO的分類
2.1 按流動的方向分
- 輸入流:把數據從其他設備上讀取到內存中的流
- 輸出流:把數據從內存上寫出到其他設備上的流
2.2 按數據的類型分
- 字節流:以字節爲單位,讀寫數據的流
- 字節流處理的最基本單位是單個字節,通常用來處理二進制數據。
- 字符流:以字符爲單位,讀寫數據的流
- Java中的字符流處理的最基本單位是Unicode碼元,(大小2字節)。通常處理文本數據
3. IO流中的根類
二、字節流
1. 一切皆字節
- 一切文件數據(文本、圖片、視頻等)在存儲的時候,都是以二進制數據保存的,最後都是一個一個的字節,那麼在傳輸的時候肯定也是一樣的
- 所以,字節流可以傳輸任意文件數據,在操作流的時候,需要明確的一點是,無論使用什麼樣的流對象,底層傳輸的始終都只是二進制數據,也就是字節
2. 字節輸出流(抽象類):OutputStream
java.io.OutputStream
抽象類是表示字節輸出流的所有類的根類,將指定的字節信息輸出到目的地。它定義了字節輸出流的基本的共性的功能和方法:public void close()
:關閉輸出流,並釋放與此流相關的任何系統資源。- 當完成流的操作時,必須調用此方法,釋放系統資源。
public void flush()
:刷新此輸出流,並強制任何緩衝的輸出字節被寫出public void write(byte[] b)
:將b.length
長度的字節從指定的字節數組寫入此輸出流public void write(byte[] b, int off, int len)
:從指定的直接數組寫入長度爲len
的字節,從偏移量off
開始輸出到此輸出流public abstract void write(int b)
:寫入指定的字節到這個輸出流
3. FileOutputStream
java.io.FileOutputStream
:文件輸出流,將數據寫出到文件。也就是抽象類OutputStream
最簡單的一個繼承類
3.1 構造方法
-
public FileOutputStream(File file)
:創建文件輸出流以寫入指定的File
對象表示的文件 -
public FileOutputStream(String name)
:創建文件輸出流以指定的名稱寫入文件 -
當創建一個流對象的時候,必須傳入一個文件路徑。該路徑下:
- 如果沒有這個文件,則會創建該文件
- 如果有這個文件,會清空該文件的數據
-
示例代碼
public class FileOutputStreamConstructor_Demo01 {
public static void main(String[] args) throws IOException {
//使用File對象創建流對象
File file = new File("aa.txt");
FileOutputStream fos = new FileOutputStream(file);
//使用文件名創建流對象
FileOutputStream fos1 = new FileOutputStream("b.txt");
}
}
- 效果:在當前項目的根目錄下存在了兩個
txt
文件
3.2 寫出字節數據
3.2.1 寫出單個字節
- 寫出字節:
write(int b)
,每次可以寫出一個字節數據 - 代碼
public class FOSWrite_Demo02 {
public static void main(String[] args) throws IOException {
//0. 使用文件名創建流對象
FileOutputStream fos = new FileOutputStream("fos.txt");
//1. 寫出數據
fos.write(97);//1.1 對應ASCII碼中 的a
fos.write(98);
fos.write(99);
//2. 關閉資源
fos.close();
}
}
- 結果
- 注意這裏
int
類型的大小是4
個字節,但只會保留一個字節的信息寫出,也就是第一個字節的int值
3.2.2 寫出字節數組
-
write(byte[] b)
:每次寫出數組中的數據 -
代碼
public class FOSWrite_Demo03 {
public static void main(String[] args) throws IOException {
//0. 使用文件名創建流對象
FileOutputStream fos = new FileOutputStream("fos.txt");
//1. 字符串轉換爲字節數組
byte[] b = "正在學IO".getBytes();
//2. 寫出字節數組
fos.write(b);
//3. 關閉資源
fos.close();
}
}
- 效果
3.2.3 寫出指定起點和長度的字節數組
-
write(byte[] b, int off, int len)
:從off
開始,往後len
個字節 -
代碼
public class FOSWrite_Demo04 {
public static void main(String[] args) throws IOException {
//0. 使用文件名創建流對象
FileOutputStream fos = new FileOutputStream("fos.txt");
//1. 字符串轉換爲字節數組
byte[] b = "abcde".getBytes();
//2. 寫出指定開始位置和長度的 字節數組
//2.1 起始位置(索引) = 2:c
//2.2 長度=2,從起始位置算起, cd
fos.write(b,2,2);
//3. 關閉資源
fos.close();
}
}
-
效果
-
上述的寫出字節數據,可以這樣理解嗎:通過
fos
這個輸出流對象,把內存中的數據以字節爲單位輸出到了指定的一個文件中去
3.3 數據追加續寫(2個相關的構造方法)
- 上面的寫出方式中,每創建一個流對象,都會先清空掉文件中的內容
- 如何在保留文件內容的基礎上,再往後面進行寫入數據呢?
3.3.1 相關方法(構造方法)
-
public FileOutputStream(File file, boolean append)
:創建文件輸出流寫入由指定的File
對象表示的文件 -
public FileOutputStream(String name, boolean append)
:創建文件輸出流,以指定的名稱寫入文件 -
上述方法中的
boolean
類型的append
true
:表示追加數據false
:表示清空原有數據
-
代碼
public class FOSWrite_Demo05 {
public static void main(String[] args) throws IOException {
//0. 使用文件名創建流對象
FileOutputStream fos = new FileOutputStream("fos.txt",true);
//1. 字符串轉換爲字節數組
byte[] b = "追加寫入".getBytes();
//2. 寫出字節數組
fos.write(b);
//3. 關閉資源
fos.close();
}
}
- 寫入前
- 寫入後
3.4 寫出換行
3.4.1 不同操作系統換行的不同
- Windows系統裏,每行結尾是 回車+換行 ,即
\r\n
- 回車符
\r
:回到一行的開頭(return)。 - 換行符
\n
:下一行(newline)。
- 回車符
- Unix系統裏,每行結尾只有 換行 ,即
\n
- Mac系統裏,每行結尾是 回車 ,即`r `。從 Mac OS X開始與Linux統一
3.4.2 代碼
- 代碼
public class FOSWrite_Demo06 {
public static void main(String[] args) throws IOException {
//0. 使用文件名創建流對象
FileOutputStream fos = new FileOutputStream("fos.txt",true);
//1. 字符串轉換爲字節數組
String[] b = {"換","行"};
for (int i = 0; i < b.length; i++) {
//1.1 寫出
fos.write(b[i].getBytes());
//1.2 寫出換行, 將換行轉換爲數組寫出
fos.write("\r\n".getBytes());
}
//3. 關閉資源
fos.close();
}
}
- 效果
4. 字節輸入流InputStream
java.io.InputStream
抽象類時表示字節輸入流的所有類的超類,可以讀取字節信息到內存,它定義了字節輸入流的基本共性方法public void close()
:關閉輸入流並釋放與此流相關聯的任何系統資源public abstract int read()
:從輸入流讀取數據的下一個字節public int read(byte[] b)
:該方法返回的int
代表的是讀取了多少字節,讀到幾個返回幾個,沒有讀到返回-1
5. FileInputStream類
5.1 構造方法
其實就是分別通過文件對象和路徑名去指定到一個實際文件,然後創建對應的
FileInputStream
-
FileInputStream(File file)
:打開與實際文件的連接來創建一個FileInputStream
,該文件由文件系統中的File
對象file
命名。 -
FileInputStream(String name)
:打開與實際文件的連接來創建一個FileInputStream
,該文件由文件系統中的路徑名name
命名。 -
推薦使用第二種
public class FISRead_Demo01 {
public static void main(String[] args) throws FileNotFoundException {
//0. 通過file對象創建流對象
File file = new File("D:\\IO流學習\\aa.txt");
FileInputStream fis = new FileInputStream(file);
//1. 通過文件名 創建流對象
FileInputStream fis1 = new FileInputStream("D:\\IO流學習\\aa.txt");
}
}
5.2 FileInputStream讀取字節數據
5.2.1 read方法讀取單個字節數據
讀到文件末尾返回
-1
- 文件數據爲
public class FISRead_Demo02 {
public static void main(String[] args) throws IOException {
//0. 通過文件名 創建流對象
FileInputStream fis = new FileInputStream("D:\\IO流學習\\aa.txt");
//1. 開始讀取數據
int b;
while (( b = fis.read()) != -1){
System.out.println((char)b);
}
fis.close();
}
}
-
結果
-
每次讀取一個字節,而英文字符剛好佔據一個字節
-
本質上返回的是字符的
ASCII
碼,是int
類型的數字,這裏又轉換爲了char
5.2.2 使用字節數組按指定字節長度讀取:read(byte[] b)
每次讀取的是該字節數組長度個字節
public class FISRead_Demo03 {
public static void main(String[] args) throws IOException {
//0. 通過文件名 創建流對象
FileInputStream fis = new FileInputStream("D:\\IO流學習\\aa.txt");
//1. 開始讀取數據
byte[] b = new byte[2];
int len;
while ((len= fis.read(b)) != -1){
System.out.println(new String(b));
}
fis.close();
}
}
-
每次讀取2個字節
-
輸出
-
這個輸出存在一點小的問題,因爲我們本來預期的應該是
-
ab
-
cd
-
e
-
原因在於,上次讀取的內容沒有被全部替換
-
所以不能按上述代碼讀取,而要讀取有效字段
len
public class FISRead_Demo03 {
public static void main(String[] args) throws IOException {
//0. 通過文件名 創建流對象
FileInputStream fis = new FileInputStream("D:\\IO流學習\\aa.txt");
//1. 開始讀取數據
byte[] b = new byte[2];
int len;
while ((len= fis.read(b)) != -1){
System.out.println(new String(b,0,len));
}
fis.close();
}
}
-
輸出
-
此種方式正確
5.2.2.1 按字節數組讀取的模板代碼
- 在開發中,一般按照1024個字節長度,也就是1MB的大小進行讀取,下面是模板代碼
public class FISRead_Demo04 {
public static void main(String[] args) throws IOException {
//0. 通過文件名 創建流對象
FileInputStream fis = null;
try {
fis = new FileInputStream("D:\\IO流學習\\aa.txt");
//1. 開始讀取數據
byte[] b = new byte[1024];
int len;
while ((len= fis.read(b)) != -1){
System.out.println(new String(b,0,len));
}
}catch (IOException e){
e.printStackTrace();
}finally {
try {
fis.close();
}catch (IOException e){
e.printStackTrace();
}
}
}
}
5.3 FileInputStream和FileOutputStream複製圖片/文本/視頻
- 源數據
public class FISRead_Demo05 {
public static void main(String[] args) throws IOException {
FileInputStream fis = null;
FileOutputStream fos = null;
try {
//0. 指定數據源
fis = new FileInputStream("D:\\IO流學習\\1.jpg");
//1. 指定寫入目的地
fos = new FileOutputStream("D:\\IO流學習\\1_copy.jpg");
//2. 開始讀寫數據
byte[] b = new byte[1024];
int len;
while ((len= fis.read(b)) != -1){
//3. 從源數據紅讀取的 寫入目的地
fos.write(b,0,len);
}
}catch (IOException e){
e.printStackTrace();
}finally {
try {
fis.close();
fos.close();
}catch (IOException e){
e.printStackTrace();
}
}
}
}
三、字符流(Writer/Reader)
1. 字符流的由來
- 一切數據在底層都是字節,但是對於同一個字符來說,對於不同的編碼方式佔據的字節數不同。特別是中文字符在
UTF-8
編碼下,一箇中文佔3
個字節,在GBK
編碼下,一箇中文佔2
個字節 - 所以如果直接用字節流去讀取可能會發生亂碼,所需需要對字符直接進行高效流操作,這就需要字符流
- 字符流本質上還是基於字節流讀取,只是讀取的時候去查詢指定的碼錶
下面這個例子展示了直接用字節流去讀取中文字符產生的亂碼現象
public class CharIO_Demo01 {
public static void main(String[] args) throws IOException {
//0. 使用 字節輸入流創建對象
FileInputStream fis = new FileInputStream("D:\\IO流學習\\c.txt");
//1. 指定字節數組
byte[] bytes = new byte[2];
//2. 開始讀取
int len = 0;
while ((len = fis.read(bytes)) != -1){
System.out.println((char)len);
}
}
}
-
輸出
-
如果還是堅持使用字節流如何正確的讀出中文數據呢?
-
使用
String
public class CharIO_Demo01 {
public static void main(String[] args) throws IOException {
//0. 使用 字節輸入流創建對象
FileInputStream fis = new FileInputStream("D:\\IO流學習\\c.txt");
//1. 指定字節數組
byte[] bytes = new byte[1024];
//2. 開始讀取
int len = 0;
while ((len = fis.read(bytes)) != -1){
System.out.print(new String(bytes,0,len));
}
}
}
-
輸出
-
這是爲什麼呢?因爲
new String()
是帶有解碼功能的,而且默認的編碼是utf-8
,源碼如下
public String(byte bytes[], int offset, int length) {
checkBounds(bytes, offset, length);
this.value = StringCoding.decode(bytes, offset, length);
}
1.1 小結
- 在處理文本數據的時候,字節流也是可以的,不過比較麻煩
- 所以出現了字符流,以字符爲單位來讀取數據,字符流專門用於處理文本數據
- 所以需要處理文本數據的場景下優先考慮字符流,其他場景,如視頻,音樂,圖片就使用字節流
- 本質上:字符流=字節流+編碼表
2. 字符輸入流(Reader)
java.io.Reader
抽象類是字符輸入流的所有類的父類,可以讀取字符信息到內存中
共性方法:
public void close()
:關閉流、釋放資源public int read()
:從輸入流中讀取一個字符public int read(char[] cbuf)
:從輸入流中讀取一些字符,並存儲到字符數組cbuf
中
3. FileReader類
讀取字符文件的遍歷類,構造時使用系統默認的字符編碼和默認字節緩衝區
3.1 構造方法
FileReader(File file)
FileReader(String fileName)
3.1 FileReader讀取字符數據
3.1.1 read方法
- 每次去讀一個字符的數據,提升爲
int
類型,讀到文件末尾,返回-1
按字符讀取中文數據
public class CharIO_Demo02 {
public static void main(String[] args) throws IOException {
//0. 使用 字節輸入流創建對象
FileReader fr = new FileReader("D:\\IO流學習\\c.txt");
//1. 開始讀取
int len = 0;
while ((len = fr.read()) != -1){
System.out.print((char)len);
}
}
}
- 輸出
4. 字符輸出流(Writer)
java.io.Writer
是字符輸出流的所有類的超類,將字符信息寫入到指定目的地
共性方法
void close()
關閉此流,但要先刷新它。void flush()
刷新該流的緩衝。void write(int c)
寫入單個字符。void write(char[] cbuf)
寫入字符數組。abstract void write(char[] cbuf, int off, int len)
寫入字符數組的某一部分,off數組的開始索引,len寫的字符個數。void write(String str)
寫入字符串。void write(String str, int off, int len)
寫入字符串的某一部分,off字符串的開始索引,len寫的字符個數。
5. FileWriter類
寫出字符到文件的便利類。構造時使用系統默認的字符編碼和默認字節緩衝區
5.1 構造方法
FileWriter(File file)
FileWriter(String fileName)
5.2 FileWriter寫出數據
5.2.1 write(int b)
- 每次寫出一個字符數據
演示
public class CharIO_Demo03 {
public static void main(String[] args) throws IOException {
//0. 使用 字節輸入流創建對象
FileWriter fw = new FileWriter("D:\\IO流學習\\d.txt");
//1. 寫出數據
fw.write(99);
fw.write("字符流可以直接寫出數據");
fw.close();
}
}
- 輸出
5.2.2 不關閉FileWriter輸出流對象無法把字符寫入到文件
public class CharIO_Demo03 {
public static void main(String[] args) throws IOException {
//0. 使用 字節輸入流創建對象
FileWriter fw = new FileWriter("D:\\IO流學習\\e.txt");
//1. 寫出數據
fw.write(99);
fw.write("字符流可以直接寫出數據");
// fw.close();
}
}
- 輸出
5.3 關閉close和刷新flush
- 上面的例子我們看到了如果不關閉流對象,則無法寫出數據到文件
- 這是因爲我們的數據是先存放在一個緩衝區的,只有關閉的時候再從緩衝區到文件
- 但是一旦關閉了流,後續就無法使用流對象了
- 那麼如果我既想把緩衝區的數據寫到文件,同時又不想關閉流對象,那麼該如何做呢?
flush
下面展示如何使用flush
來完成上述的設想
public class CharIO_Demo04 {
public static void main(String[] args) throws IOException {
//0. 使用 字節輸入流創建對象
FileWriter fw = new FileWriter("D:\\IO流學習\\e.txt");
//1. 寫出數據
fw.write("使用flush");
fw.flush();
fw.write("就可以完成緩衝區寫入到文件");
fw.flush();
}
}
- 輸出
- 上述代碼並沒有使用
close()
依然可以完成字符到文件的寫入,但是在開發中還是需要使用close()
下面展示調用了close()
後再次寫入報錯
public class CharIO_Demo04 {
public static void main(String[] args) throws IOException {
//0. 使用 字節輸入流創建對象
FileWriter fw = new FileWriter("D:\\IO流學習\\e.txt");
//1. 寫出數據
fw.write("使用flush");
fw.close();
fw.write("就可以完成緩衝區寫入到文件");
fw.close();
}
}
- 輸出
5.4 FileWriter的續寫和換行
和
FileOutputStream
完全類似的操作
實驗
public class CharIO_Demo04 {
public static void main(String[] args) throws IOException {
//0. 使用 字節輸入流創建對象
FileWriter fw = new FileWriter("D:\\IO流學習\\f.txt",true);
//1. 寫出數據
fw.write("使用flush");
fw.write("\r\n");
fw.append("可以追加和換行");
fw.close();
}
}
- 輸出
5.5 FileReader和FileWriter完成文本文件的複製
- 一次讀取一個字符
public class CharIO_Demo05 {
public static void main(String[] args) throws IOException {
//0.創建 字符輸入流
FileReader fr = new FileReader("D:\\IO流學習\\f.txt");
//0.1 創建字符輸出流
FileWriter fw = new FileWriter("D:\\IO流學習\\f_copy.txt");
//1. 方法1 一次讀取一個字符
int ch;
while ((ch = fr.read()) != -1){
fw.write(ch);
}
fr.close();
fw.close();
}
}
- 一次讀取一個字符數組
public class CharIO_Demo06 {
public static void main(String[] args) throws IOException {
//0.創建 字符輸入流
FileReader fr = new FileReader("D:\\IO流學習\\f.txt");
//0.1 創建字符輸出流
FileWriter fw = new FileWriter("D:\\IO流學習\\f_copy_2.txt");
//1. 方法1 一次讀取一個字符
char[] chars = new char[1024];
int ch;
while ((ch = fr.read(chars)) != -1){
fw.write(chars,0,ch);
}
fr.close();
fw.close();
}
}
四、IO異常處理
-
一般使用
try...catch..finally
的順序來處理 -
一般按照如下模式
public class IOExceptionHandle {
public static void main(String[] args) {
//0. 聲明流對象
FileWriter fw = null;
try {
//1. 創建流對象
fw = new FileWriter("a.txt");
//2. 實際操作代碼
fw.write("我們");
}catch (IOException e){
e.printStackTrace();
}finally {
try {
if (fw != null){
fw.close();
}
}catch (IOException e){
e.printStackTrace();
}
}
}
}