前言 io流用到的地方不少,就好比上傳下載,傳輸,設計模式等....基礎打紮實了,才能玩更高端的。javascript
在博主認爲真正懂IO流的優秀程序員每次在使用IO流以前都會明確分析以下四點:html
(1)明確要操做的數據是數據源仍是數據目的(也就是要讀仍是要寫) (2)明確要操做的設備上的數據是字節仍是文本 (3)明確數據所在的具體設備 (4)明確是否須要額外功能(好比是否須要轉換流、高效流等)java
以上四點將會在文章告白IO流的四點明確裏面小結一下,若是各位真能熟練以上四點,我以爲這篇文章你就不必看了,由於你已經把IO玩弄與股掌之中,萬物皆可被你盤也就也再也不話下了。程序員
@[toc]面試
(1)明確要操做的數據是數據源仍是數據目的(要讀仍是要寫)編程
源: <font color=red> InputStream Reader</font>windows
目的: <font color=red> OutputStream Writer</font>設計模式
(2)明確要操做的設備上的數據是字節仍是文本數組
源:緩存
字節:<font color=red> InputStream</font>
文本:<font color=red> Reader</font>
目的:
字節:<font color=red> OutputStream</font>
文本:<font color=red> Writer</font>
(3)明確數據所在的具體設備
源設備:
硬盤:文件
File
開頭內存:數組,字符串
鍵盤:
System.in
網絡:
Socket
對應目的設備:
硬盤:文件
File
開頭內存:數組,字符串
屏幕:
System.out
網絡:
Socket
(4)明確是否須要額外功能
須要轉換——<font color=red> 轉換流 InputStreamReader 、OutputStreamWriter</font>
須要高效——<font color=red> 緩衝流Bufferedxxx</font>
多個源—— 序列流 SequenceInputStream
對象序列化—— ObjectInputStream、ObjectOutputStream
保證數據的輸出形式——<font color=red> 打印流PrintStream 、Printwriter</font>
操做基本數據,保證字節原樣性——DataOutputStream、DataInputStream
到這裏,咱們再來看看IO流的分類吧 OK,準備好了告白IO流了咩?
至於IO流,也就是輸入輸出流,從文件出發到文件結束,至始至終都離不開文件,因此IO流還得從文件File類講起。
java.io.File
類是專門對文件進行操做的類,只能對文件自己進行操做,不能對文件內容進行操做。 java.io.File
類是文件和目錄路徑名的抽象表示,主要用於文件和目錄的建立、查找和刪除等操做。
怎麼理解上面兩句話?其實很簡單!
第一句就是說File跟流無關,File類不能對文件進行讀和寫也就是輸入和輸出! 第二句就是說File主要表示相似D:\\文件目錄1
與D:\\文件目錄1\\文件.txt
,前者是文件夾(Directory)後者則是文件(file),而File類就是操做這二者的類。
在java中,一切皆是對象,File類也不例外,不管是哪一個對象都應該從該對象的構造提及,因此博主來分析分析File
類的構造方法。首先從API開始着手 咱們主要來學習一下比較經常使用的三個:
一、 public File(String pathname)
:經過將給定的路徑名字符串轉換爲抽象路徑名來建立新的 File實例。
二、 public File(String parent, String child)
:從父路徑名字符串和子路徑名字符串建立新的 File實例。 三、 public File(File parent, String child)
:從父抽象路徑名和子路徑名字符串建立新的 File實例。
看字描述不夠生動不夠形象不得勁?沒得事,下面進行構造舉例,立刻就生動形象了,代碼以下:
1. 一個File對象表明硬盤中實際存在的一個文件或者目錄。 2. File類構造方法不會給你檢驗這個文件或文件夾是否真實存在,所以不管該路徑下是否存在文件或者目錄,都不影響File對象的建立。 // 文件路徑名 String path = "D:\\123.txt"; File file1 = new File(path); // 文件路徑名 String path2 = "D:\\1\\2.txt"; File file2 = new File(path2); -------------至關於D:\\1\\2.txt // 經過父路徑和子路徑字符串 String parent = "F:\\aaa"; String child = "bbb.txt"; File file3 = new File(parent, child); --------至關於F:\\aaa\\bbb.txt // 經過父級File對象和子路徑字符串 File parentDir = new File("F:\\aaa"); String child = "bbb.txt"; File file4 = new File(parentDir, child); --------至關於F:\\aaa\\bbb.txt
File類的注意點:
- 一個File對象表明硬盤中實際存在的一個文件或者目錄。
- File類構造方法不會給你檢驗這個文件或文件夾是否真實存在,所以不管該路徑下是否存在文件或者目錄,都不影響File對象的建立。
File的經常使用方法主要分爲獲取功能、獲取絕對路徑和相對路徑、判斷功能、建立刪除功能的方法
一、public String getAbsolutePath()
:返回此File的絕對路徑名字符串。
二、public String getPath()
:將此File轉換爲路徑名字符串。
三、public String getName()
:返回由此File表示的文件或目錄的名稱。
四、public long length()
:返回由此File表示的文件的長度。
以上方法測試,代碼以下【注意測試以本身的電腦文件夾爲準】:
public class FileGet { public static void main(String[] args) { File f = new File("d:/aaa/bbb.java"); System.out.println("文件絕對路徑:"+f.getAbsolutePath()); System.out.println("文件構造路徑:"+f.getPath()); System.out.println("文件名稱:"+f.getName()); System.out.println("文件長度:"+f.length()+"字節"); File f2 = new File("d:/aaa"); System.out.println("目錄絕對路徑:"+f2.getAbsolutePath()); System.out.println("目錄構造路徑:"+f2.getPath()); System.out.println("目錄名稱:"+f2.getName()); System.out.println("目錄長度:"+f2.length()); } } 輸出結果: 文件絕對路徑:d:\aaa\bbb.java 文件構造路徑:d:\aaa\bbb.java 文件名稱:bbb.java 文件長度:2116字節 目錄絕對路徑:d:\aaa 目錄構造路徑:d:\aaa 目錄名稱:aaa 目錄長度:3236
注意:
length()
,表示文件的長度。可是File
對象表示目錄,則返回值未指定。
絕對路徑:一個完整的路徑,以盤符開頭,例如F://aaa.txt
。 相對路徑:一個簡化的路徑,不以盤符開頭,例如//aaa.txt//b.txt
。
一、<font color=red>路徑是不區分大小寫</font> 二、路徑中的文件名稱分隔符windows使用反斜槓,反斜槓是轉義字符,兩個反斜槓表明一個普通的反斜槓
//絕對路徑 public class FilePath { public static void main(String[] args) { // D盤下的bbb.java文件 File f = new File("D:\\bbb.java"); System.out.println(f.getAbsolutePath()); // 項目下的bbb.java文件 File f2 = new File("bbb.java"); System.out.println(f2.getAbsolutePath()); } } 輸出結果: D:\bbb.java D:\java\bbb.java
一、 public boolean exists()
:此File表示的文件或目錄是否實際存在。 二、 public boolean isDirectory()
:此File表示的是否爲目錄。 三、public boolean isFile()
:此File表示的是否爲文件。
方法演示,代碼以下:
public class FileIs { public static void main(String[] args) { File f = new File("d:\\aaa\\bbb.java"); File f2 = new File("d:\\aaa"); // 判斷是否存在 System.out.println("d:\\aaa\\bbb.java 是否存在:"+f.exists()); System.out.println("d:\\aaa 是否存在:"+f2.exists()); // 判斷是文件仍是目錄 System.out.println("d:\\aaa 文件?:"+f2.isFile()); System.out.println("d:\\aaa 目錄?:"+f2.isDirectory()); } } 輸出結果: d:\aaa\bbb.java 是否存在:true d:\aaa 是否存在:true d:\aaa 文件?:false d:\aaa 目錄?:true
public boolean createNewFile()
:文件不存在,建立一個新的空文件並返回true
,文件存在,不建立文件並返回false
。public boolean delete()
:刪除由此File表示的文件或目錄。public boolean mkdir()
:建立由此File表示的目錄。public boolean mkdirs()
:建立由此File表示的目錄,包括任何須需但不存在的父目錄。其中,mkdirs()
和mkdir()
方法相似,但mkdir()
,只能建立一級目錄,mkdirs()
能夠建立多級目錄好比//a//b//c
,因此<font color=red>開發中通常用mkdirs()
</font>;
這些方法中值得注意的是<font color=red>createNewFile</font>方法以及<font color=red>mkdir</font>與<font color=red>mkdirs</font>的區別
方法測試,代碼以下:
public class FileCreateDelete { public static void main(String[] args) throws IOException { // 文件的建立 File f = new File("aaa.txt"); System.out.println("是否存在:"+f.exists()); // false System.out.println("是否建立:"+f.createNewFile()); // true System.out.println("是否建立:"+f.createNewFile()); // 以及建立過了因此再使用createNewFile返回false System.out.println("是否存在:"+f.exists()); // true // 目錄的建立 File f2= new File("newDir"); System.out.println("是否存在:"+f2.exists());// false System.out.println("是否建立:"+f2.mkdir()); // true System.out.println("是否存在:"+f2.exists());// true // 建立多級目錄 File f3= new File("newDira\\newDirb"); System.out.println(f3.mkdir());// false File f4= new File("newDira\\newDirb"); System.out.println(f4.mkdirs());// true // 文件的刪除 System.out.println(f.delete());// true // 目錄的刪除 System.out.println(f2.delete());// true System.out.println(f4.delete());// false } }
注意:
delete
方法,若是此File
表示目錄,則目錄必須爲空才能刪除。
public String[] list()
:返回一個String數組,表示該File目錄中的全部子文件或目錄。
public File[] listFiles()
:返回一個File數組,表示該File目錄中的全部的子文件或目錄。
public class FileFor { public static void main(String[] args) { File dir = new File("G:\光標"); //獲取當前目錄下的文件以及文件夾的名稱。 String[] names = dir.list(); for(String name : names){ System.out.println(name); } //獲取當前目錄下的文件以及文件夾對象,只要拿到了文件對象,那麼就能夠獲取更多信息 File[] files = dir.listFiles(); for (File file : files) { System.out.println(file); } } }
<font color=red>listFiles</font>在獲取指定目錄下的文件或者文件夾時必須知足下面兩個條件
1,<font color=red>指定的目錄必須存在</font>
2,<font color=red>指定的必須是目錄。不然容易引起返回數組爲null,出現NullPointerException異常</font>
不說啥了,直接上代碼:
package File; import java.io.File; //遞歸遍歷文件夾下全部的文件 public class RecursionDirectory { public static void main(String[] args) { File file=new File("D:\\java專屬IO測試"); Recursion(file); } public static void Recursion(File file){ //一、判斷傳入的是不是目錄 if(!file.isDirectory()){ //不是目錄直接退出 return; } //已經確保了傳入的file是目錄 File[] files = file.listFiles(); //遍歷files for (File f: files) { //若是該目錄下文件仍是個文件夾就再進行遞歸遍歷其子目錄 if(f.isDirectory()){ //遞歸 Recursion(f); }else { //若是該目錄下文件是個文件,則打印對應的名字 System.out.println(f.getName()); } } } }
若是對上面的代碼有疑問,能夠隨時聯繫我,博主一直都在!
我想在座各位確定經歷都過這樣的場景。當你編輯一個文本文件也好用eclipse打代碼也罷,忘記了ctrl+s
,在你關閉文件的哪一瞬間手殘點了個不應點的按鈕,但你反應過來,心早已拔涼拔涼的了。
咱們把這種數據的傳輸,能夠看作是一種數據的流動,按照流動的方向,之內存爲基準,分爲輸入input
和輸出output
,即流向內存是輸入流,流出內存的輸出流。
Java中I/O操做主要是指使用java.io
包下的內容,進行輸入、輸出操做。<font color=red>輸入也叫作讀取數據,輸出也叫作做寫出數據</font>。
根據數據的流向分爲:<font color=red>輸入流</font> 和 <font color=red>輸出流</font>。
其餘設備
上讀取到內存
中的流。內存
中寫出到其餘設備
上的流。根據數據的類型分爲:<font color=red>字節流</font> 和 <font color=red>字符流</font>。
分類以後對應的超類(V8提示:超類也就是父類的意思) | | 輸入流 | 輸出流 |--|--|--| | 字節流 | 字節輸入流 InputStream |字節輸出流 OutputStream | | 字符流 | 字符輸入流 Reader|字符輸出流 Writer|
注: <font color=red>由這四個類的子類名稱基本都是以其父類名做爲子類名的後綴</font>。 如:InputStream的子類FileInputStream。 如:Reader的子類FileReader。
啥都不說了,看圖吧
OutputStream與InputStream的繼承關係
咱們必須明確一點的是,一切文件數據(文本、圖片、視頻等)在存儲時,都是以二進制數字的形式保存,都一個一個的字節,那麼傳輸時同樣如此。因此,字節流能夠傳輸任意文件數據。在操做流的時候,咱們要時刻明確,不管使用什麼樣的流對象,底層傳輸的始終爲二進制數據。
java.io.OutputStream
抽象類是表示字節輸出流的全部類的超類(父類),將指定的字節信息寫出到目的地。它定義了字節輸出流的基本共性功能方法,不要問我OutputStream
爲啥能夠定義字節輸出流的基本共性功能方法,熊dei啊,上一句說過了<font color=red>OutputStream是字節輸出流的全部類的超類</font>,繼承知識,懂?(若是是真的不理解的小白同窗,能夠點擊藍色字體繼承進入補習)
字節輸出流的基本共性功能方法:
一、
public void close()
:關閉此輸出流並釋放與此流相關聯的任何系統資源。
二、public void flush()
:刷新此輸出流並強制任何緩衝的輸出字節被寫出。
三、public void write(byte[] b)
:將 b.length個字節從指定的字節數組寫入此輸出流。
四、public void write(byte[] b, int off, int len)
:從指定的字節數組寫入 len字節,從偏移量 off開始輸出到此輸出流。 也就是說從off個字節數開始讀取一直到len個字節結束 五、public abstract void write(int b)
:將指定的字節輸出流。
<font color=red>以上五個方法則是字節輸出流都具備的方法,由父類OutputStream定義提供,子類都會共享以上方法</font>
OutputStream
有不少子類,咱們從最簡單的一個子類FileOutputStream開始。看名字就知道是文件輸出流,用於將數據寫出到文件。
無論學啥子,只有是對象,就從構造方法開始!
一、
public FileOutputStream(File file)
:根據<font color=red>File對象</font>爲參數建立對象。 </font> 二、public FileOutputStream(String name)
: 根據<font color=red>名稱字符串</font>爲參數建立對象。</font>
<font color=red>推薦第二種構造方法</font>【開發經常使用】:
FileOutputStream outputStream = new FileOutputStream("abc.txt");
就以上面這句代碼來說,相似這樣建立字節輸出流對象都作了<font color=red>三件事情</font>: 一、調用系統功能去建立文件【輸出流對象纔會自動建立】 二、建立outputStream對象 三、把foutputStream對象指向這個文件
<font color=red>注意</font>: 建立輸出流對象的時候,系統會自動去對應位置建立對應文件,而建立輸出流對象的時候,文件不存在則會報FileNotFoundException異常,也就是系統找不到指定的文件異常。
當你建立一個流對象時,必須直接或者間接傳入一個文件路徑。好比如今咱們建立一個FileOutputStream
流對象,在該路徑下,若是沒有這個文件,會建立該文件。若是有這個文件,會清空這個文件的數據。有興趣的童鞋能夠測試一下,具體代碼以下:
public class FileOutputStreamConstructor throws IOException { public static void main(String[] args) { // 使用File對象建立流對象 File file = new File("G:\\自動建立的文件夾\\a.txt"); FileOutputStream fos = new FileOutputStream(file); // 使用文件名稱建立流對象 FileOutputStream fos = new FileOutputStream("G:\\b.txt"); } }
使用FileOutputStream寫出字節數據主要經過Write
方法,而write
方法分以下三種
public void write(int b) public void write(byte[] b) public void write(byte[] b,int off,int len) //從`off`索引開始,`len`個字節
write(int b)
方法,每次能夠寫出一個字節數據,代碼以下:public class IoWrite { public static void main(String[] args) throws IOException { // 使用文件名稱建立流對象 FileOutputStream fos = new FileOutputStream("fos.txt"); // 寫出數據 fos.write(97); // 寫出第1個字節 fos.write(98); // 寫出第2個字節 fos.write(99); // 寫出第3個字節 // 關閉資源 fos.close(); } } 輸出結果: abc
- 雖然參數爲int類型四個字節,可是隻會保留一個字節的信息寫出。
- 流操做完畢後,必須釋放系統資源,調用close方法,千萬記得。
write(byte[] b)
,每次能夠寫出數組中的數據,代碼使用演示:public class FOSWrite { public static void main(String[] args) throws IOException { // 使用文件名稱建立流對象 FileOutputStream fos = new FileOutputStream("fos.txt"); // 字符串轉換爲字節數組 byte[] b = "麻麻我想吃烤山藥".getBytes(); // 寫出字節數組數據 fos.write(b); // 關閉資源 fos.close(); } } 輸出結果: 麻麻我想吃烤山藥
write(byte[] b, int off, int len)
,每次寫出從off
索引開始,len
個字節,代碼以下:public class FOSWrite { public static void main(String[] args) throws IOException { // 使用文件名稱建立流對象 FileOutputStream fos = new FileOutputStream("fos.txt"); // 字符串轉換爲字節數組 byte[] b = "abcde".getBytes(); // 寫出從索引2開始,2個字節。索引2是c,兩個字節,也就是cd。 fos.write(b,2,2); // 關閉資源 fos.close(); } } 輸出結果: cd
通過以上的代碼測試,每次程序運行,每次建立輸出流對象,都會清空目標文件中的數據。如何保留目標文件中數據,還能繼續追加新數據呢?而且實現換行呢?其實很簡單,這個時候咱們又要再學習FileOutputStream
的另外兩個構造方法了,以下:
一、public FileOutputStream(File file, boolean append)
二、public FileOutputStream(String name, boolean append)
這兩個構造方法,第二個參數中都須要傳入一個boolean類型的值,true
表示追加數據,false
表示不追加也就是清空原有數據。這樣建立的輸出流對象,就能夠指定是否追加續寫了,至於Windows換行則是 \n\r
,下面將會詳細講到。
實現數據追加續寫代碼以下:
public class FOSWrite { public static void main(String[] args) throws IOException { // 使用文件名稱建立流對象 FileOutputStream fos = new FileOutputStream("fos.txt",true); // 字符串轉換爲字節數組 byte[] b = "abcde".getBytes(); // 寫出從索引2開始,2個字節。索引2是c,兩個字節,也就是cd。 fos.write(b); // 關閉資源 fos.close(); } } 文件操做前:cd 文件操做後:cdabcde
Windows系統裏,換行符號是\r\n
,具體代碼以下:
public class FOSWrite { public static void main(String[] args) throws IOException { // 使用文件名稱建立流對象 FileOutputStream fos = new FileOutputStream("fos.txt"); // 定義字節數組 byte[] words = {97,98,99,100,101}; // 遍歷數組 for (int i = 0; i < words.length; i++) { // 寫出一個字節 fos.write(words[i]); // 寫出一個換行, 換行符號轉成數組寫出 fos.write("\r\n".getBytes()); } // 關閉資源 fos.close(); } } 輸出結果: a b c d e
- 回車符
\r
和換行符\n
:
- 回車符:回到一行的開頭(return)。
- 換行符:下一行(newline)。
- 系統中的換行:
- Windows系統裏,每行結尾是
回車+換行
,即\r\n
;- Unix系統裏,每行結尾只有
換行
,即\n
;- Mac系統裏,每行結尾是
回車
,即\r
。從 Mac OS X開始與Linux統一。
java.io.InputStream
抽象類是表示字節輸入流的全部類的超類(父類),能夠讀取字節信息到內存中。它定義了字節輸入流的基本共性功能方法。
字節輸入流的基本共性功能方法:
一、
public void close()
:關閉此輸入流並釋放與此流相關聯的任何系統資源。
二、public abstract int read()
: 從輸入流讀取數據的下一個字節。三、
public int read(byte[] b)
: 該方法返回的int值表明的是讀取了多少個字節,讀到幾個返回幾個,讀取不到返回-1
java.io.FileInputStream
類是文件輸入流,從文件中讀取字節。
一、
FileInputStream(File file)
: 經過打開與實際文件的鏈接來建立一個 FileInputStream ,該文件由文件系統中的 File對象 file命名。 二、FileInputStream(String name)
: 經過打開與實際文件的鏈接來建立一個 FileInputStream ,該文件由文件系統中的路徑名name命名。
一樣的,<font color=red>推薦使用第二種構造方法</font>:
FileInputStream inputStream = new FileInputStream("a.txt");
當你建立一個流對象時,必須傳入一個文件路徑。該路徑下,若是沒有該文件,會拋出FileNotFoundException
。
構造舉例,代碼以下:
public class FileInputStreamConstructor throws IOException{ public static void main(String[] args) { // 使用File對象建立流對象 File file = new File("a.txt"); FileInputStream fos = new FileInputStream(file); // 使用文件名稱建立流對象 FileInputStream fos = new FileInputStream("b.txt"); } }
read
方法,每次能夠讀取一個字節的數據,提高爲int類型,讀取到文件末尾,返回-1
,代碼測試以下【read.txt文件中內容爲abcde】:public class FISRead { public static void main(String[] args) throws IOException{ // 使用文件名稱建立流對象 FileInputStream fis = new FileInputStream("read.txt");//read.txt文件中內容爲abcde // 讀取數據,返回一個字節 int read = fis.read(); System.out.println((char) read); read = fis.read(); System.out.println((char) read); read = fis.read(); System.out.println((char) read); read = fis.read(); System.out.println((char) read); read = fis.read(); System.out.println((char) read); // 讀取到末尾,返回-1 read = fis.read(); System.out.println( read); // 關閉資源 fis.close(); } } 輸出結果: a b c d e -1
循環改進讀取方式,代碼使用演示:
public class FISRead { public static void main(String[] args) throws IOException{ // 使用文件名稱建立流對象 FileInputStream fis = new FileInputStream("read.txt"); // 定義變量,保存數據 int b ; // 循環讀取 while ((b = fis.read())!=-1) { System.out.println((char)b); } // 關閉資源 fis.close(); } } 輸出結果: a b c d e
read(byte[] b)
,每次讀取b的長度個字節到數組中,返回讀取到的有效字節個數,讀取到末尾時,返回-1
,代碼使用演示:public class FISRead { public static void main(String[] args) throws IOException{ // 使用文件名稱建立流對象. FileInputStream fis = new FileInputStream("read.txt"); // read.txt文件中內容爲abcde // 定義變量,做爲有效個數 int len ; // 定義字節數組,做爲裝字節數據的容器 byte[] b = new byte[2]; // 循環讀取 while (( len= fis.read(b))!=-1) { // 每次讀取後,把數組變成字符串打印 System.out.println(new String(b)); } // 關閉資源 fis.close(); } } 輸出結果: ab cd ed
因爲read.txt
文件中內容爲abcde
,而錯誤數據d
,是因爲最後一次讀取時,只讀取一個字節e
,數組中,上次讀取的數據沒有被徹底替換【注意是替換,看下圖】,因此要經過len
,獲取有效的字節 代碼以下:
public class FISRead { public static void main(String[] args) throws IOException{ // 使用文件名稱建立流對象. FileInputStream fis = new FileInputStream("read.txt"); // 文件中爲abcde // 定義變量,做爲有效個數 int len ; // 定義字節數組,做爲裝字節數據的容器 byte[] b = new byte[2]; // 循環讀取 while (( len= fis.read(b))!=-1) { // 每次讀取後,把數組的有效字節部分,變成字符串打印 System.out.println(new String(b,0,len));// len 每次讀取的有效字節個數 } // 關閉資源 fis.close(); } } 輸出結果: ab cd e
在開發中通常強烈推薦使用數組讀取文件,代碼以下:
package io; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; public class input2 { public static void main(String args[]){ FileInputStream inputStream = null; try { inputStream = new FileInputStream("a.txt"); int len = 0 ; byte[] bys = new byte[1024]; while ((len = inputStream.read(bys)) != -1) { System.out.println(new String(bys,0,len)); } } catch (IOException e) { e.printStackTrace(); }finally { try { inputStream.close(); } catch (IOException e) { e.printStackTrace(); } } } }
複製圖片原理
代碼實現
複製圖片文件,代碼以下:
public class Copy { public static void main(String[] args) throws IOException { // 1.建立流對象 // 1.1 指定數據源 FileInputStream fis = new FileInputStream("D:\\test.jpg"); // 1.2 指定目的地 FileOutputStream fos = new FileOutputStream("test_copy.jpg"); // 2.讀寫數據 // 2.1 定義數組 byte[] b = new byte[1024]; // 2.2 定義長度 int len; // 2.3 循環讀取 while ((len = fis.read(b))!=-1) { // 2.4 寫出數據 fos.write(b, 0 , len); } // 3.關閉資源 fos.close(); fis.close(); } }
注:複製文本、圖片、mp三、視頻等的方式同樣。
到這裏,已經從File類講到了字節流OutputStream與InputStream,而如今將主要從字符流Reader和Writer的故事開展。
字符流Reader和Writer的故事從它們的繼承圖開始,啥都不說了,直接看圖
字符流的由來:由於數據編碼的不一樣,於是有了對字符進行高效操做的流對象,字符流本質其實就是基於字節流讀取時,去查了指定的碼錶,而字節流直接讀取數據會有亂碼的問題(讀中文會亂碼),這個時候小白同窗就看不懂了,沒事,咋們先來看個程序:
package IO; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; public class CharaterStream { public static void main(String[] args) throws Exception { //FileInputStream爲操做文件的字符輸入流 FileInputStream inputStream = new FileInputStream("a.txt");//內容爲哥敢摸屎 int len; while ((len=inputStream.read())!=-1){ System.out.print((char)len); } } } 運行結果: ??¥??¢????±
具體現狀分析 話說,就是你哥我敢摸si,那你哥我確定也不認識這玩意啊:
??¥??¢????±
字節流讀取中文字符時,可能不會顯示完整的字符,那是由於一箇中文字符佔用多個字節存儲。
那字節流就沒辦法了嗎?不,字節流依舊有辦法,只是麻煩了點,代碼以下:
public class CharaterStream { public static void main(String[] args) throws Exception { FileInputStream inputStream = new FileInputStream("a.txt"); byte[] bytes = new byte[1024]; int len; while ((len=inputStream.read(bytes))!=-1){ System.out.print(new String(bytes,0,len)); } } } 運行結果: 哥敢摸屎
這是爲啥呢?沒錯解碼的正是String
,查看new String()
的源碼,String
構造方法有解碼功能,而且默認編碼是utf-8
,代碼以下:
this.value = StringCoding.decode(bytes, offset, length); 再點進decode,按部就班發現,默認編碼是UTF-8
儘管字節流也能有辦法決絕亂碼問題,可是仍是比較麻煩,因而java就有了字符流,字符爲單位
讀寫數據,字符流專門用於處理文本
文件。若是處理純文本的數據優先考慮字符流,其餘狀況就只能用字節流了(圖片、視頻、等等只文本
例外)。
從另外一角度來講:字符流 = 字節流 + 編碼表
java.io.Reader
抽象類是字符輸入流的全部類的超類(父類),能夠讀取字符信息到內存中。它定義了字符輸入流的基本共性功能方法。
字符輸入流的共性方法:
一、
public void close()
:關閉此流並釋放與此流相關聯的任何系統資源。
二、public int read()
: 從輸入流讀取一個字符。 三、public int read(char[] cbuf)
: 從輸入流中讀取一些字符,並將它們存儲到字符數組cbuf
中
java.io.FileReader
類是讀取字符文件的便利類。構造時使用系統默認的字符編碼和默認字節緩衝區。
一、
FileReader(File file)
: 建立一個新的 FileReader ,給定要讀取的File對象。
二、FileReader(String fileName)
: 建立一個新的 FileReader ,給定要讀取的文件的字符串名稱。
構造方法的使用就算不寫應該都很熟悉了吧,代碼以下:
public class FileReaderConstructor throws IOException{ public static void main(String[] args) { // 使用File對象建立流對象 File file = new File("a.txt"); FileReader fr = new FileReader(file); // 使用文件名稱建立流對象 FileReader fr = new FileReader("b.txt"); } }
read
方法,每次能夠讀取一個字符的數據,提高爲int類型,讀取到文件末尾,返回-1
,循環讀取,代碼使用演示:public class FRRead { public static void main(String[] args) throws IOException { // 使用文件名稱建立流對象 FileReader fr = new FileReader("a.txt"); // 定義變量,保存數據 int b ; // 循環讀取 while ((b = fr.read())!=-1) { System.out.println((char)b); } // 關閉資源 fr.close(); } }
至於讀取的寫法相似字節流的寫法,只是讀取單位不一樣罷了。
java.io.Writer
抽象類是字符輸出流的全部類的超類(父類),將指定的字符信息寫出到目的地。它一樣定義了字符輸出流的基本共性功能方法。
字符輸出流的基本共性功能方法:
一、
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寫的字符個數。 六、void flush()
刷新該流的緩衝。
七、void close()
關閉此流,但要先刷新它。
java.io.FileWriter
類是寫出字符到文件的便利類。構造時使用系統默認的字符編碼和默認字節緩衝區。
一、 FileWriter(File file)
: 建立一個新的 FileWriter,給定要讀取的File對象。
二、FileWriter(String fileName)
: 建立一個新的 FileWriter,給定要讀取的文件的名稱。
依舊是熟悉的構造舉例,代碼以下:
public class FileWriterConstructor { public static void main(String[] args) throws IOException { // 第一種:使用File對象建立流對象 File file = new File("a.txt"); FileWriter fw = new FileWriter(file); // 第二種:使用文件名稱建立流對象 FileWriter fw = new FileWriter("b.txt"); } }
寫出字符:write(int b)
方法,每次能夠寫出一個字符數據,代碼使用演示:
public class FWWrite { public static void main(String[] args) throws IOException { // 使用文件名稱建立流對象 FileWriter fw = new FileWriter("fw.txt"); // 寫出數據 fw.write(97); // 寫出第1個字符 fw.write('b'); // 寫出第2個字符 fw.write('C'); // 寫出第3個字符 //關閉資源時,與FileOutputStream不一樣。 若是不關閉,數據只是保存到緩衝區,並未保存到文件。 // fw.close(); } } 輸出結果: abC
<font color=red>【注意】關閉資源時,與FileOutputStream不一樣。 若是不關閉,數據只是保存到緩衝區,並未保存到文件。</font>
由於內置緩衝區的緣由,若是不關閉輸出流,沒法寫出字符到文件中。可是關閉的流對象,是沒法繼續寫出數據的。若是咱們既想寫出數據,又想繼續使用流,就須要flush
方法了。
flush
:刷新緩衝區,流對象能夠繼續使用。 close
:先刷新緩衝區,而後通知系統釋放資源。流對象不能夠再被使用了。
flush仍是比較有趣的,童鞋們不本身運行一下還真很差體會,如今博主就寫個程序讓你體會體會: 字符流
public class FlushDemo { public static void main(String[] args) throws Exception { //源 也就是輸入流【讀取流】 讀取a.txt文件 FileReader fr=new FileReader("a.txt"); //必需要存在a.txt文件,不然報FileNotFoundException異常 //目的地 也就是輸出流 FileWriter fw=new FileWriter("b.txt"); //系統會自動建立b.txt,由於它是輸出流! int len; while((len=fr.read())!=-1){ fw.write(len); } 注意這裏是沒有使用close關閉流,開發中不能這樣作,可是爲了更好的體會flush的做用 } }
運行效果是怎麼樣的呢?答案是b.txt文件中依舊是空的,是的並無任何東西,爲啥呢?熊dei啊,我在上面就用紅色字體特別標註過了,就是這句話: <font color=red>【注意】關閉資源時,與FileOutputStream不一樣。 若是不關閉,數據只是保存到緩衝區,並未保存到文件。</font>這個時候反應過來了吧,可見實踐例子的重要性,<font color=red>編程就是這樣,不去敲,永遠學不會</font>!!!因此必定要去敲,博主沒敲過10萬行代碼真的沒有臉出去說本身是學java的。因此,你們必定要多思考,多敲啊!!!
因此,咱們在以上的代碼中再添加下面三句代碼,就完美了,b.txt文件就能複製到源文件的數據了!
fr.close(); fw.flush(); fw.close();
flush()
這個函數是清空的意思,用於清空緩衝區的數據流,進行流的操做時,數據先被讀到內存中,而後再用數據寫到文件中,那麼當你數據讀完時,咱們若是這時調用close()
方法關閉讀寫流,這時就可能形成數據丟失,爲何呢?由於,讀入數據完成時不表明寫入數據完成,一部分數據可能會留在緩存區中,這個時候flush()
方法就格外重要了。
好了,接下來close使用代碼以下:
public class FWWrite { public static void main(String[] args) throws IOException { // 使用文件名稱建立流對象 FileWriter fw = new FileWriter("fw.txt"); // 寫出數據,經過flush fw.write('刷'); // 寫出第1個字符 fw.flush(); fw.write('新'); // 繼續寫出第2個字符,寫出成功 fw.flush(); // 寫出數據,經過close fw.write('關'); // 寫出第1個字符 fw.close(); fw.write('閉'); // 繼續寫出第2個字符,【報錯】java.io.IOException: Stream closed fw.close(); } }
即使是flush方法寫出了數據,操做的最後仍是要調用close方法,釋放系統資源。
續寫和換行:操做相似於FileOutputStream操做(上一篇博客講到過),直接上代碼:
public class FWWrite { public static void main(String[] args) throws IOException { // 使用文件名稱建立流對象,能夠續寫數據 FileWriter fw = new FileWriter("fw.txt",true); // 寫出字符串 fw.write("哥敢"); // 寫出換行 fw.write("\r\n"); // 寫出字符串 fw.write("摸屎"); // 關閉資源 fw.close(); } } 輸出結果: 哥敢 摸屎
直接上代碼:
import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; public class CopyFile { public static void main(String[] args) throws IOException { //建立輸入流對象 FileReader fr=new FileReader("F:\\新建文件夾\\aa.txt");//文件不存在會拋出java.io.FileNotFoundException //建立輸出流對象 FileWriter fw=new FileWriter("C:\\copyaa.txt"); /*建立輸出流作的工做: * 一、調用系統資源建立了一個文件 * 二、建立輸出流對象 * 三、把輸出流對象指向文件 * */ //文本文件複製,一次讀一個字符 copyMethod1(fr, fw); //文本文件複製,一次讀一個字符數組 copyMethod2(fr, fw); fr.close(); fw.close(); } public static void copyMethod1(FileReader fr, FileWriter fw) throws IOException { int ch; while((ch=fr.read())!=-1) {//讀數據 fw.write(ch);//寫數據 } fw.flush(); } public static void copyMethod2(FileReader fr, FileWriter fw) throws IOException { char chs[]=new char[1024]; int len=0; while((len=fr.read(chs))!=-1) {//讀數據 fw.write(chs,0,len);//寫數據 } fw.flush(); } } CopyFile
最後再次強調: 字符流,只能操做文本文件,不能操做圖片,視頻等非文本文件。當咱們單純讀或者寫文本文件時 使用字符流 其餘狀況使用字節流
咱們在學習的過程當中可能習慣把異常拋出,而實際開發中並不能這樣處理,建議使用try...catch...finally
代碼塊,處理異常部分,格式代碼以下:
public class HandleException1 { public static void main(String[] args) { // 聲明變量 FileWriter fw = null; try { //建立流對象 fw = new FileWriter("fw.txt"); // 寫出數據 fw.write("哥敢摸si"); //哥敢摸si } catch (IOException e) { e.printStackTrace(); } finally { try { if (fw != null) { fw.close(); } } catch (IOException e) { e.printStackTrace(); } } } }
若是對異常不是特別熟練的童鞋能夠參考這篇文章【java基礎之異常】死了都要try,不淋漓盡致地catch我不痛快!
好了,到這裏,字符流Reader和Writer的故事的到這裏了!
前面主要寫了一些基本的流做爲IO流的入門。從這裏開始將要見識一些更強大的流。好比可以高效讀寫的緩衝流,可以轉換編碼的轉換流,可以持久化存儲對象的序列化流等等,而這些強大的流都是在基本的流對象基礎之上而來的!這些強大的流將伴隨着咱們從此的開發!
首先咱們來認識認識一下緩衝流,也叫高效流,是對4個FileXxx
流的「加強流」。
<font color=red>緩衝流的基本原理</font>:
一、使用了底層流對象從具體設備上獲取數據,並將數據存儲到緩衝區的數組內。 二、經過緩衝區的read()方法從緩衝區獲取具體的字符數據,這樣就提升了效率。 三、若是用read方法讀取字符數據,並存儲到另外一個容器中,直到讀取到了換行符時,將另外一個容器臨時存儲的數據轉成字符串返回,就造成了readLine()功能。
也就是說在建立流對象時,會建立一個內置的默認大小的緩衝區數組,經過緩衝區讀寫,減小系統IO次數,從而提升讀寫的效率。
緩衝書寫格式爲BufferedXxx
,按照數據類型分類:
BufferedInputStream
,BufferedOutputStream
BufferedReader
,BufferedWriter
public BufferedInputStream(InputStream in)
:建立一個新的緩衝輸入流,注意參數類型爲<font color=red>InputStream</font>。public BufferedOutputStream(OutputStream out)
: 建立一個新的緩衝輸出流,注意參數類型爲<font color=red>OutputStream</font>。構造舉例代碼以下:
//構造方式一: 建立字節緩衝輸入流【可是開發中通常經常使用下面的格式申明】 FileInputStream fps = new FileInputStream(b.txt); BufferedInputStream bis = new BufferedInputStream(fps) //構造方式一: 建立字節緩衝輸入流 BufferedInputStream bis = new BufferedInputStream(new FileInputStream("b.txt")); ///構造方式二: 建立字節緩衝輸出流 BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("b.txt"));
緩衝流讀寫方法與基本的流是一致的,咱們經過複製370多MB的大文件,測試它的效率。
public class BufferedDemo { public static void main(String[] args) throws FileNotFoundException { // 記錄開始時間 long start = System.currentTimeMillis(); // 建立流對象 try ( FileInputStream fis = new FileInputStream("py.exe");//exe文件夠大 FileOutputStream fos = new FileOutputStream("copyPy.exe") ){ // 讀寫數據 int b; while ((b = fis.read()) != -1) { fos.write(b); } } catch (IOException e) { e.printStackTrace(); } // 記錄結束時間 long end = System.currentTimeMillis(); System.out.println("普通流複製時間:"+(end - start)+" 毫秒"); } } 很差意思十分鐘過去了還在玩命複製中...
public class BufferedDemo { public static void main(String[] args) throws FileNotFoundException { // 記錄開始時間 long start = System.currentTimeMillis(); // 建立流對象 try ( BufferedInputStream bis = new BufferedInputStream(new FileInputStream("py.exe")); BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("copyPy.exe")); ){ // 讀寫數據 int b; while ((b = bis.read()) != -1) { bos.write(b); } } catch (IOException e) { e.printStackTrace(); } // 記錄結束時間 long end = System.currentTimeMillis(); System.out.println("緩衝流複製時間:"+(end - start)+" 毫秒"); } } 緩衝流複製時間:8016 毫秒
有的童鞋就要說了,我要更快的速度!最近看速度與激情7有點上頭,能不能再快些?答案是固然能夠
想要更快可使用數組的方式,代碼以下:
public class BufferedDemo { public static void main(String[] args) throws FileNotFoundException { // 記錄開始時間 long start = System.currentTimeMillis(); // 建立流對象 try ( BufferedInputStream bis = new BufferedInputStream(new FileInputStream("py.exe")); BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("copyPy.exe")); ){ // 讀寫數據 int len; byte[] bytes = new byte[8*1024]; while ((len = bis.read(bytes)) != -1) { bos.write(bytes, 0 , len); } } catch (IOException e) { e.printStackTrace(); } // 記錄結束時間 long end = System.currentTimeMillis(); System.out.println("緩衝流使用數組複製時間:"+(end - start)+" 毫秒"); } } 緩衝流使用數組複製時間:521 毫秒
相同的來看看其構造,其格式以及原理和字節緩衝流是同樣同樣的!
public BufferedReader(Reader in)
:建立一個新的緩衝輸入流,注意參數類型爲<font color=red>Reader</font>。public BufferedWriter(Writer out)
: 建立一個新的緩衝輸出流,注意參數類型爲<font color=red>Writer</font>。構造舉例,代碼以下:
// 建立字符緩衝輸入流 BufferedReader br = new BufferedReader(new FileReader("b.txt")); // 建立字符緩衝輸出流 BufferedWriter bw = new BufferedWriter(new FileWriter("b.txt"));
字符緩衝流的基本方法與普通字符流調用方式一致,這裏再也不闡述,咱們來看字符緩衝流具有的<font color=red>特有</font>方法。
public String readLine()
: 讀一行數據。 <font color=red>讀取到最後返回null</font>public void newLine()
: 換行,由系統屬性定義符號。readLine
方法演示代碼以下:
public class BufferedReaderDemo { public static void main(String[] args) throws IOException { // 建立流對象 BufferedReader br = new BufferedReader(new FileReader("a.txt")); // 定義字符串,保存讀取的一行文字 String line = null; // 循環讀取,讀取到最後返回null while ((line = br.readLine())!=null) { System.out.print(line); System.out.println("------"); } // 釋放資源 br.close(); } }
newLine
方法演示代碼以下:
public class BufferedWriterDemo throws IOException { public static void main(String[] args) throws IOException { // 建立流對象 BufferedWriter bw = new BufferedWriter(new FileWriter("b.txt")); // 寫出數據 bw.write("哥"); // 寫出換行 bw.newLine(); bw.write("敢"); bw.newLine(); bw.write("摸屎"); bw.newLine(); bw.write("你敢嗎?"); bw.newLine(); // 釋放資源 bw.close(); } } 輸出效果: 哥 敢 摸屎 你敢嗎?
字符緩衝流練習啥捏?先放鬆一下吧各位,先欣賞欣賞我寫的下面的詩篇
6.你說你的程序叫簡單,我說個人代碼叫詩篇 1.一想到你我就哦豁豁豁豁豁豁豁豁豁豁....哦nima個頭啊,徹底不理人家受得了受不了 8.Just 簡單你和我 ,Just 簡單程序員 3.約了地點卻忘了見面 ,懂得寂寞才明白浩瀚 5.沉默是最大的發言權
2.老是喜歡坐在電腦前, 老是喜歡工做到很晚 7.向左走 又向右走,咱們轉了好多的彎 4.你歷來就不問我,你仍是不是那個程序員
欣賞完了咩?沒錯咋們就練習如何使用緩衝流的技術把上面的詩篇歸順序,都編過號了~就是前面的1到8的編號~
分析:首先用字符輸入緩衝流建立個源,裏面放沒有排過序的文字,以後用字符輸出緩衝流建立個目標接收,排序的過程就要本身寫方法了哦,能夠從每條詩詞的共同點「.」符號下手!
public class BufferedTest { public static void main(String[] args) throws IOException { // 建立map集合,保存文本數據,鍵爲序號,值爲文字 HashMap<String, String> lineMap = new HashMap<>(); // 建立流對象 源 BufferedReader br = new BufferedReader(new FileReader("a.txt")); //目標 BufferedWriter bw = new BufferedWriter(new FileWriter("b.txt")); // 讀取數據 String line = null; while ((line = br.readLine())!=null) { // 解析文本 String[] split = line.split("\\."); // 保存到集合 lineMap.put(split[0],split[1]); } // 釋放資源 br.close(); // 遍歷map集合 for (int i = 1; i <= lineMap.size(); i++) { String key = String.valueOf(i); // 獲取map中文本 String value = lineMap.get(key); // 寫出拼接文本 bw.write(key+"."+value); // 寫出換行 bw.newLine(); } // 釋放資源 bw.close(); } }
運行效果
1.一想到你我就哦豁豁豁豁豁豁豁豁豁豁…哦nima個頭啊,徹底不理人家受得了受不了 2.老是喜歡坐在電腦前, 老是喜歡工做到很晚 3.約了地點卻忘了見面 ,懂得寂寞才明白浩瀚 4.你歷來就不問我,你仍是不是那個程序員 5.沉默是最大的發言權 6.你說你的程序叫簡單,我說個人代碼叫詩篇 7.向左走 又向右走,咱們轉了好多的彎 8.Just 簡單你和我 ,Just 簡單程序員
何謂轉換流?爲什麼由來?暫時帶着問題讓咱們先來了解了解字符編碼和字符集!
衆所周知,計算機中儲存的信息都是用二進制數表示的,而咱們在屏幕上看到的數字、英文、標點符號、漢字等字符是二進制數轉換以後的結果。按照某種規則,將字符存儲到計算機中,稱爲編碼 。反之,將存儲在計算機中的二進制數按照某種規則解析顯示出來,稱爲解碼 。好比說,按照A
規則存儲,一樣按照A
規則解析,那麼就能顯示正確的文本符號。反之,按照A
規則存儲,再按照B
規則解析,就會致使亂碼現象。
簡單一點的說就是:
編碼:字符(能看懂的)--字節(看不懂的)
解碼:字節(看不懂的)-->字符(能看懂的)
代碼解釋則是
String(byte[] bytes, String charsetName):經過指定的字符集解碼字節數組 byte[] getBytes(String charsetName):使用指定的字符集合把字符串編碼爲字節數組 編碼:把看得懂的變成看不懂的 String -- byte[] 解碼:把看不懂的變成看得懂的 byte[] -- String
字符編碼 Character Encoding
: 就是一套天然語言的字符與二進制數之間的對應規則。
而編碼表則是生活中文字和計算機中二進制的對應規則
Charset
:也叫編碼表。是一個系統支持的全部字符的集合,包括各國家文字、標點符號、圖形符號、數字等。計算機要準確的存儲和識別各類字符集符號,須要進行字符編碼,一套字符集必然至少有一套字符編碼。常見字符集有ASCII
字符集、GBK
字符集、Unicode
字符集等。
可見,當指定了編碼,它所對應的字符集天然就指定了,因此編碼纔是咱們最終要關心的。
在java開發工具IDEA中,使用FileReader
讀取項目中的文本文件。因爲IDEA的設置,都是默認的UTF-8
編碼,因此沒有任何問題。可是,當讀取Windows系統中建立的文本文件時,因爲Windows系統的默認是GBK編碼,就會出現亂碼。
public class ReaderDemo { public static void main(String[] args) throws IOException { FileReader fileReader = new FileReader("C:\\a.txt"); int read; while ((read = fileReader.read()) != -1) { System.out.print((char)read); } fileReader.close(); } } 輸出結果:���
那麼如何讀取GBK編碼的文件呢? 這個時候就得講講轉換流了!
從另外一角度來說:字符流=字節流+編碼表
轉換流java.io.InputStreamReader
,是Reader
的子類,從字面意思能夠看出它是從字節流到字符流的橋樑。它讀取字節,並使用指定的字符集將其解碼爲字符。它的字符集能夠由名稱指定,也能夠接受平臺的默認字符集。
InputStreamReader(InputStream in)
: 建立一個使用默認字符集的字符流。InputStreamReader(InputStream in, String charsetName)
: 建立一個指定字符集的字符流。
構造代碼以下:
InputStreamReader isr = new InputStreamReader(new FileInputStream("in.txt")); InputStreamReader isr2 = new InputStreamReader(new FileInputStream("in.txt") , "GBK");
public class ReaderDemo2 { public static void main(String[] args) throws IOException { // 定義文件路徑,文件爲gbk編碼 String FileName = "C:\\A.txt"; // 建立流對象,默認UTF8編碼 InputStreamReader isr = new InputStreamReader(new FileInputStream(FileName)); // 建立流對象,指定GBK編碼 InputStreamReader isr2 = new InputStreamReader(new FileInputStream(FileName) , "GBK"); // 定義變量,保存字符 int read; // 使用默認編碼字符流讀取,亂碼 while ((read = isr.read()) != -1) { System.out.print((char)read); // �����ʺ } isr.close(); // 使用指定編碼字符流讀取,正常解析 while ((read = isr2.read()) != -1) { System.out.print((char)read);// 哥敢摸屎 } isr2.close(); } }
轉換流java.io.OutputStreamWriter
,是Writer的子類,字面看容易混淆會誤覺得是轉爲字符流,其實否則,OutputStreamWriter爲從字符流到字節流的橋樑。使用指定的字符集將字符編碼爲字節。它的字符集能夠由名稱指定,也能夠接受平臺的默認字符集。
OutputStreamWriter(OutputStream in)
: 建立一個使用默認字符集的字符流。OutputStreamWriter(OutputStream in, String charsetName)
: 建立一個指定字符集的字符流。
構造舉例,代碼以下:
OutputStreamWriter isr = new OutputStreamWriter(new FileOutputStream("a.txt")); OutputStreamWriter isr2 = new OutputStreamWriter(new FileOutputStream("b.txt") , "GBK");
public class OutputDemo { public static void main(String[] args) throws IOException { // 定義文件路徑 String FileName = "C:\\s.txt"; // 建立流對象,默認UTF8編碼 OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(FileName)); // 寫出數據 osw.write("哥敢"); // 保存爲6個字節 osw.close(); // 定義文件路徑 String FileName2 = "D:\\A.txt"; // 建立流對象,指定GBK編碼 OutputStreamWriter osw2 = new OutputStreamWriter(new FileOutputStream(FileName2),"GBK"); // 寫出數據 osw2.write("摸屎");// 保存爲4個字節 osw2.close(); } }
爲了達到<font color=red>最高效率</font>,能夠考慮在
BufferedReader
內包裝 InputStreamReader
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
(1)能夠把對象寫入文本文件或者在網絡中傳輸 (2)如何實現序列化呢? 讓被序列化的對象所屬類實現序列化接口。 該接口是一個標記接口。沒有功能須要實現。 (3)注意問題: 把數據寫到文件後,在去修改類會產生一個問題。 如何解決該問題呢? 在類文件中,給出一個固定的序列化id值。 並且,這樣也能夠解決黃色警告線問題 (4)面試題: 何時序列化? 如何實現序列化? 什麼是反序列化?
Java 提供了一種對象序列化的機制。用一個字節序列能夠表示一個對象,該字節序列包含該對象的數據
、對象的類型
和對象中存儲的屬性
等信息。字節序列寫出到文件以後,至關於文件中持久保存了一個對象的信息。
反之,該字節序列還能夠從文件中讀取回來,重構對象,對它進行反序列化。對象的數據
、對象的類型
和對象中存儲的數據
信息,均可以用來在內存中建立對象。看圖理解序列化:
java.io.ObjectOutputStream
類,將Java對象的原始數據類型寫出到文件,實現對象的持久存儲。
public ObjectOutputStream(OutputStream out)
: 建立一個指定OutputStream的ObjectOutputStream。
構造代碼以下:
FileOutputStream fileOut = new FileOutputStream("aa.txt"); ObjectOutputStream out = new ObjectOutputStream(fileOut);
該類必須實現java.io.Serializable
接口,Serializable
是一個標記接口,不實現此接口的類將不會使任何狀態序列化或反序列化,會拋出NotSerializableException
。
該類的全部屬性必須是可序列化的。若是有一個屬性不須要可序列化的,則該屬性必須註明是瞬態的,使用transient
關鍵字修飾。
public class Employee implements java.io.Serializable { public String name; public String address; public transient int age; // transient瞬態修飾成員,不會被序列化 public void addressCheck() { System.out.println("Address check : " + name + " -- " + address); } }
2.寫出對象方法
public final void writeObject (Object obj)
: 將指定的對象寫出。
public class SerializeDemo{ public static void main(String [] args) { Employee e = new Employee(); e.name = "zhangsan"; e.address = "beiqinglu"; e.age = 20; try { // 建立序列化流對象 ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("employee.txt")); // 寫出對象 out.writeObject(e); // 釋放資源 out.close(); fileOut.close(); System.out.println("Serialized data is saved"); // 姓名,地址被序列化,年齡沒有被序列化。 } catch(IOException i) { i.printStackTrace(); } } } 輸出結果: Serialized data is saved
ObjectInputStream反序列化流,將以前使用ObjectOutputStream序列化的原始數據恢復爲對象。
public ObjectInputStream(InputStream in)
: 建立一個指定InputStream的ObjectInputStream。
若是能找到一個對象的class文件,咱們能夠進行反序列化操做,調用ObjectInputStream
讀取對象的方法:
public final Object readObject ()
: 讀取一個對象。public class DeserializeDemo { public static void main(String [] args) { Employee e = null; try { // 建立反序列化流 FileInputStream fileIn = new FileInputStream("employee.txt"); ObjectInputStream in = new ObjectInputStream(fileIn); // 讀取一個對象 e = (Employee) in.readObject(); // 釋放資源 in.close(); fileIn.close(); }catch(IOException i) { // 捕獲其餘異常 i.printStackTrace(); return; }catch(ClassNotFoundException c) { // 捕獲類找不到異常 System.out.println("Employee class not found"); c.printStackTrace(); return; } // 無異常,直接打印輸出 System.out.println("Name: " + e.name); // zhangsan System.out.println("Address: " + e.address); // beiqinglu System.out.println("age: " + e.age); // 0 } }
對於JVM能夠反序列化對象,它必須是可以找到class文件的類。若是找不到該類的class文件,則拋出一個 ClassNotFoundException
異常。
另外,當JVM反序列化對象時,能找到class文件,可是class文件在序列化對象以後發生了修改,那麼反序列化操做也會失敗,拋出一個InvalidClassException
異常。發生這個異常的緣由以下:
一、該類的序列版本號與從流中讀取的類描述符的版本號不匹配 二、該類包含未知數據類型 二、該類沒有可訪問的無參數構造方法
Serializable
接口給須要序列化的類,提供了一個序列版本號。serialVersionUID
該版本號的目的在於驗證序列化的對象和對應類是否版本匹配。
public class Employee implements java.io.Serializable { // 加入序列版本號 private static final long serialVersionUID = 1L; public String name; public String address; // 添加新的屬性 ,從新編譯, 能夠反序列化,該屬性賦爲默認值. public int eid; public void addressCheck() { System.out.println("Address check : " + name + " -- " + address); } }
list.txt
文件中。list.txt
,並遍歷集合,打印對象信息。public class SerTest { public static void main(String[] args) throws Exception { // 建立 學生對象 Student student = new Student("老王", "laow"); Student student2 = new Student("老張", "laoz"); Student student3 = new Student("老李", "laol"); ArrayList<Student> arrayList = new ArrayList<>(); arrayList.add(student); arrayList.add(student2); arrayList.add(student3); // 序列化操做 // serializ(arrayList); // 反序列化 ObjectInputStream ois = new ObjectInputStream(new FileInputStream("list.txt")); // 讀取對象,強轉爲ArrayList類型 ArrayList<Student> list = (ArrayList<Student>)ois.readObject(); for (int i = 0; i < list.size(); i++ ){ Student s = list.get(i); System.out.println(s.getName()+"--"+ s.getPwd()); } } private static void serializ(ArrayList<Student> arrayList) throws Exception { // 建立 序列化流 ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("list.txt")); // 寫出對象 oos.writeObject(arrayList); // 釋放資源 oos.close(); } }
平時咱們在控制檯打印輸出,是調用print
方法和println
方法完成的,各位用了這麼久的輸出語句確定沒想過這兩個方法都來自於java.io.PrintStream
類吧,哈哈。該類可以方便地打印各類數據類型的值,是一種便捷的輸出方式。
打印流分類:
字節打印流PrintStream,字符打印流PrintWriter
打印流特色:
A:只操做目的地,不操做數據源 B:能夠操做任意類型的數據 C:若是啓用了自動刷新,在調用println()方法的時候,可以換行並刷新 D:能夠直接操做文件
這個時候有同窗就要問了,哪些流能夠直接操做文件呢?答案很簡單,<font color=red>若是該流的構造方法可以同時接收File和String類型的參數,通常都是能夠直接操做文件的</font>!
PrintStream是OutputStream的子類,PrintWriter是Writer的子類,二者處於對等的位置上,因此它們的API是很是類似的。兩者區別無非一個是字節打印流,一個是字符打印流。
import java.io.BufferedReader; import java.io.FileReader; import java.io.IOException; import java.io.PrintStream; public class PrintStreamDemo { public static void main(String[] args) throws IOException { BufferedReader br=new BufferedReader(new FileReader("copy.txt")); PrintStream ps=new PrintStream("printcopy.txt"); String line; while((line=br.readLine())!=null) { ps.println(line); } br.close(); ps.close(); } }
import java.io.BufferedReader; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import java.io.PrintWriter; /** * 使用打印流複製文本文件 */ public class PrintWriterDemo { public static void main(String[] args) throws IOException { BufferedReader br=new BufferedReader(new FileReader("aa.txt")); PrintWriter pw=new PrintWriter("printcopyaa.txt"); String line; while((line=br.readLine())!=null) { pw.println(line); } br.close(); pw.close(); } }
我想各位對這個Properties類多多少少也接觸過了,首先Properties類並不在IO包下,那爲啥要和IO流一塊兒講呢?緣由很簡單由於properties類常常和io流的聯合一塊兒使用。
(1)是一個集合類,Hashtable的子類 (2)特有功能 A:public Object setProperty(String key,String value) B:public String getProperty(String key) C:public Set<String> stringPropertyNames() (3)和IO流結合的方法 把鍵值對形式的文本文件內容加載到集合中 public void load(Reader reader) public void load(InputStream inStream) 把集合中的數據存儲到文本文件中 public void store(Writer writer,String comments) public void store(OutputStream out,String comments)
java.util.Properties
繼承於 Hashtable
,來表示一個持久的屬性集。它使用鍵值結構存儲數據,每一個鍵及其對應值都是一個字符串。該類也被許多Java類使用,好比獲取系統屬性時,System.getProperties
方法就是返回一個Properties
對象。
public Properties()
:建立一個空的屬性列表。
public Object setProperty(String key, String value)
: 保存一對屬性。public String getProperty(String key)
:使用此屬性列表中指定的鍵搜索屬性值。public Set<String> stringPropertyNames()
:全部鍵的名稱的集合。public class ProDemo { public static void main(String[] args) throws FileNotFoundException { // 建立屬性集對象 Properties properties = new Properties(); // 添加鍵值對元素 properties.setProperty("filename", "a.txt"); properties.setProperty("length", "209385038"); properties.setProperty("location", "D:\\a.txt"); // 打印屬性集對象 System.out.println(properties); // 經過鍵,獲取屬性值 System.out.println(properties.getProperty("filename")); System.out.println(properties.getProperty("length")); System.out.println(properties.getProperty("location")); // 遍歷屬性集,獲取全部鍵的集合 Set<String> strings = properties.stringPropertyNames(); // 打印鍵值對 for (String key : strings ) { System.out.println(key+" -- "+properties.getProperty(key)); } } } 輸出結果: {filename=a.txt, length=209385038, location=D:\a.txt} a.txt 209385038 D:\a.txt filename -- a.txt length -- 209385038 location -- D:\a.txt
public void load(InputStream inStream)
: 從字節輸入流中讀取鍵值對。
參數中使用了字節輸入流,經過流對象,能夠關聯到某文件上,這樣就可以加載文本中的數據了。如今文本數據格式以下:
filename=Properties.txt length=123 location=C:\Properties.txt
加載代碼演示:
public class ProDemo { public static void main(String[] args) throws FileNotFoundException { // 建立屬性集對象 Properties pro = new Properties(); // 加載文本中信息到屬性集 pro.load(new FileInputStream("Properties.txt")); // 遍歷集合並打印 Set<String> strings = pro.stringPropertyNames(); for (String key : strings ) { System.out.println(key+" -- "+pro.getProperty(key)); } } } 輸出結果: filename -- Properties.txt length -- 123 location -- C:\Properties.txt
文本中的數據,必須是鍵值對形式,可使用空格、等號、冒號等符號分隔。
怎麼說呢,io流的基礎回顧就先告一段落了,淺嘗輒止。按部就班,實踐中慢慢總結!更況且我還很low,依舊任重而道遠。
如今jdk已經出到13了,io流也有了許多的變化。有時間會從頭整理一下,必定會有機會的!
最後,歡迎各位關注個人公衆號,一塊兒探討技術,嚮往技術,追求技術...
原文出處:https://www.cnblogs.com/yichunguo/p/11775270.html