因為工作事宜,又有一段時間沒有寫博客了,趁着今天不是很忙開始IO之路;IO往往是我們忽略但是卻又非常重要的部分,在這個講究人機交互體驗的年代,IO問題漸漸成了核心問題。
一、File類
在講解File類之前,我們先認識和了解一下流的概念;流的概念可能比較抽象,可以想象一下水流的樣子。
1.流
IO在本質上是單個字節的移動,而流可以說是字節移動的載體和方式,它不停的向目標處移動數據,我們要做的就是根據流的方向從流中讀取數據或者向流中寫入數據。
想象下倒水的場景:倒一杯水,水是連成一片往地上流動,而不是等杯中的水全部倒出懸浮在空中,然后一起掉落地面。最簡單的Java流的例子就是下載電影,肯定不是等電影全部下載在內存中再保存到磁盤上,本質上是下載一個字節就保存一個字節。
一個流,必有源和目標,它們可以是計算機內存的某些區域,也可以是磁盤文件,甚至可以是Internet上的某個URL。流的方向是重要的,根據流的方向,流可分為兩類:輸入流和輸出流。我們從輸入流讀取數據,向輸出流寫入數據。
2.流的分類
根據流向:輸入流(input)和輸出流(output)
根據處理數據:字節流(二進制,可以處理一切文件,文本,音頻等) 字符流(文本文件,只能是純文本)
根據功能:節點流包裹源頭 處理流:增強功能,提供性能
3.文本文件 text-files
有些文件被當作字符序列,並擁有一些使二進制數字對程序和編輯器來說就像字符一樣的流和方法,這樣的文件就稱之為文本文件。
4.二進制文件 binary-files
有些文件內容必須作為二進制數字序列處理的文件則稱之為二進制文件
文本文件是為人類使用而設計的,而二進制文件是為程序讀取計算機使用而設計的。
5.File 操作
在整個io包中,唯一表示與文件本身有關的類就是File類。使用File類可以進行創建或刪除文件等常用操作,要想使用File類,則首先要觀察File類的構造方法,此類的常用構造方法如下:
File類中的主要方法和常量:
方法或常量 |
類型 |
描述 |
public static final String pathSeparator |
常量 |
表示路徑的分隔符(windows:‘;’) |
public static final String separator |
常量 |
表示路徑分隔符(windows:‘\’) |
public File(String pathname) |
構造 |
創建File類對象,傳入完整的路徑 |
public boolean createNewFile() throws IOException |
普通 |
創建新文件 |
public boolean exists() |
普通 |
判斷文件是否存在 |
public boolean delete() |
普通 |
刪除文件 |
public boolean isDirectory() |
普通 |
判斷給定的路徑是否是一個目錄 |
public long length() |
普通 |
返回文件的大小 |
public String[] list() |
普通 |
列出指定目錄的全部內容,只是名稱 |
public File[] listFiles() |
普通 |
列出指定目錄的全部內容,會列出路徑。 |
public boolean mkdir() |
普通 |
創建一個目錄 |
public boolean renameTo(File dest) |
普通 |
為已有的文件重命名 |
下面我們將針對主要方法進行講解和代碼示例演練:
1)File 常量
package com.pony1223.file; import java.io.File; /** * 兩個常量 * 1、路徑分隔符 ; * 2、名稱分隔符 /(windows) /(linux 等) * @author Pony * */ public class Demo01 { public static void main(String[] args) { System.out.println(File.pathSeparator); System.out.println(File.separator); String path = "E:\\study\\java\\HelloWorld.java"; String path1 = "E:"+File.separator+"study"+File.separator+"java"+File.separator+"HelloWorld.java"; String path2 = "E:/study/java/HelloWorld.java";//推薦方式 } }
2)使用絕對路徑和相對路徑構造文件File類
package com.pony1223.file; import java.io.File; /** * 兩個常量 * 1、路徑分隔符 ; * 2、名稱分隔符 /(windows) /(linux 等) * @author Pony * */ public class Demo02 { public static void main(String[] args) { String parentPath = "E:/study/java"; String name = "HelloWorld.java"; //相對路徑 File src =new File(parentPath,name); src =new File(new File(parentPath),name); //輸出 System.out.println(src.getName()); System.out.println(src.getPath()); //絕對路徑 src =new File("E:/study/java/HelloWorld.java"); System.out.println(src.getName()); System.out.println(src.getPath()); //沒有盤符: 以 user.dir構建 src =new File("test.txt");//使用相對路徑,注意如果在路徑中沒有盤符,文件則放在工程項目下 //src =new File("."); System.out.println(src.getName());//test.txt System.out.println(src.getPath());//test.txt System.out.println(src.getAbsolutePath());//G:\DevelopeHome\MyEclipseWorkSpace\Collections\test.txt /** * getPath:如果構建文件路徑是絕對路徑則返回完整路徑,否則返回相對路徑 * getAbsolutePath:返回絕對路徑(完整路徑) * getCanonicalPath:不但是全路徑,而且把..或者.這樣的符號解析出來。 */ } }
3)判斷文件(true/false)
//判斷文件是否存在 System.out.println(f.exists());//true //判斷文件是否可讀,可寫canWrite() System.out.println(f.canRead());//true //判斷文件路徑是否為絕對路徑,有盤符則為絕對路徑 System.out.println(f.isAbsolute());//true //判斷是文件isFile還是文件夾isDirectory System.out.println(f.isDirectory());//false
4)返回文件長度,以字節為單位
假設文件內容為aswdwdad,長度為8個字節,注意如果是文件夾,字節數不為0,這里也返回0 即 只有文件才能讀出長度,文件夾不可
System.out.println(f.length());//8
5)創建和刪除文件
(1)創建文件createNewFile()
若文件已經存在,則創建會返回false,若文件名為操作系統關鍵字,比如con,也會返回false
(2)刪除文件delete()
File f=new File("F:/Java/test.txt"); if(!f.exists()){ try { boolean flag = f.createNewFile(); System.out.println(flag?"success":"fail");//fail } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } f.delete();
6)兩個靜態方法創建臨時文件
File f1=File.createTempFile("wees", ".temp",new File("F:/JAVA")); //F:\JAVA\wees7063380687067821023.temp System.out.println(f1.getAbsolutePath()); File f2=File.createTempFile("wes", ".temp"); //C:\Users\wwyDEPP\AppData\Local\Temp\wes8047158297297613408.temp System.out.println(f2.getAbsolutePath());
注意點;
當我們創建一個新的File對象,所給的路徑不是根盤路徑時,文件會自動放在項目文件夾下;但是使用靜態方法創建一個臨時文件,所給路徑不是根盤路徑時,文件是放在C盤下的某文件夾下的
7)操作目錄
(1)mkdir()創建目錄,確保父目錄存在,如果不存在,創建失敗
(2)mkdirs()創建目錄,如果父目錄鏈不存在,一同創建
(3)list()輸出目錄文件夾下所包括的文件名
(4)listFiles()輸出目錄文件夾下所包括的文件
(5)listFiles(filter)輸出目錄文件夾下所包括的特定文件(.txt)
String path="F:/Picture/test"; File file=new File(path); file.mkdir(); System.out.println("輸出目錄文件夾下所包括的文件名"); String path2="F:/Picture"; File file2=new File(path2); if(file2.isDirectory()){ String[] strs=file2.list(); for(String s:strs){ System.out.println(s); } } System.out.println("輸出目錄文件夾下所包括的文件"); if(file2.isDirectory()){ File[] files=file2.listFiles(); for(File f:files){ System.out.println(f.getAbsolutePath()); } } System.out.println("輸出目錄文件夾下所包括的特定文件(.txt),命令設計模式"); if(file2.isDirectory()){ File[] files=file2.listFiles(); files=file2.listFiles(new FilenameFilter(){ @Override public boolean accept(File dir, String name) { return new File(dir,name).isFile()&&name.endsWith(".txt"); } }); for(File f:files){ System.out.println(f.getAbsolutePath()); } }
結果:
輸出目錄文件夾下所包括的文件名
EFI.png
NTFS.png
test
test.txt
輸出目錄文件夾下所包括的文件
F:\Picture\EFI.png
F:\Picture\NTFS.png
F:\Picture\test
F:\Picture\test.txt
輸出目錄文件夾下所包括的特定文件(.txt),命令設計模式
F:\Picture\test.txt
二、字節流與字符流
字節流包括輸入流InputStream和輸出流OutputStream。字符流包括輸入流Reader 輸出流Write
InputStream相關類圖如下,只列舉了一級子類:
InputStream提供了一些read方法供子類繼承,用來讀取字節。
OutputStream相關類圖如下:
OutputStream提供了一些write方法供子類繼承,用來寫入字節。
Reader相關類圖如下:
Reader提供了一些read方法供子類繼承,用來讀取字符。
Writer相關類圖如下:
Writer提供了一些write方法供子類繼承,用來寫入字符。
每個字符流子類幾乎都會有一個相對應的字節流子類,兩者功能一樣,差別只是在於操作的是字節還是字符。例如CharArrayReader和 ByteArrayInputStream,兩者都是在內存中建立數組緩沖區作為輸入流,不同的只是前者數組用來存放字符,每次從數組中讀取一個字符;后者則是針對字節。主要的:
ByteArrayInputStream、CharArrayReader | 為多線程的通信提供緩沖區操作功能。常用於讀取網絡中的定長數據包 |
ByteArrayOutputStream、CharArrayWriter | 為多線程的通信提供緩沖區操作功能。常用於接收足夠長度的數據后進行一次性寫入 |
FileInputStream、FileReader | 把文件寫入內存作為輸入流,實現對文件的讀取操作 |
FileOutputStream、FileWriter | 把內存中的數據作為輸出流寫入文件,實現對文件的寫操作 |
StringReader | 讀取String的內容作為輸入流 |
StringWriter | 將數據寫入一個String |
SequenceInputStream | 將多個輸入流中的數據合並為一個數據流 |
PipedInputStream、PipedReader、PipedOutputStream、PipedWriter | 管道流,主要用於2個線程之間傳遞數據 |
ObjectInputStream | 讀取對象數據作為輸入流,對象中的 transient 和 static 類型的成員變量不會被讀取或寫入 |
ObjectOutputStream | 將數據寫入對象 |
FilterInputStream、FilterOutputStream、FilterReader、FilterWriter | 過濾流通常源和目標是其他的輸入輸出流,大家可以看到有眾多的子類,各有用途,就不一一介紹了 |
字符流和字節流的區別:
字節流就是按照byte單位來讀取,可以用來讀取其他格式的文件
字符流是在字節流的基礎上實現的,用來讀取文本文件,一個字符一個字符的讀取
如果字節流是一滴水一滴水的轉移,那么字符流是用勺子一勺一勺水的轉移,速度明顯加快了
當然使用緩沖Buffer以后,就是一桶一桶水的轉移了
一個字節占8位,java采用unicode編碼,占兩個字節,即16位,也就是java一個字符是2byte,16位,
那么在文本copy的時候,用字節流就是一byte-byte的copy,字符流就是一個字符一個字符的copy
1)文件的讀取和寫出
要讀取一個文件,有以下幾個步驟:
1.建立與文件的聯系:File對象,文件必須存在
2.選擇流:按字節流讀取,文件輸入流 InputStream FileInputStream
3.操作:byte[] car=new byte[1024]+read
4.釋放資源,注意jdk1.7后會自動關閉了
InputStream是一個抽象類,不能new一個新的對象,所以這里使用多態
InputStream is=new FileInputStream(......);
選擇構造方法,第一個和第三個比較常用,本質上來說這兩種方法是一致的,通過String name 創建一個新對象,
它內部還是會通過name包裝成File
在讀取文件時,使用循環不斷進行讀取,定義一個制定長度的byte數組,則這個數組就是每次讀取文件的長度,
如果要輸出,就創建一個String對象,String info = new String(car, 開始, 結束);
樣例:
package com.pony1223.byteio; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; public class Demo01 { public static void main(String[] args) { // 1、建立聯系 File對象 File src = new File("E:/study/java/HelloWorld.java"); // 2、選擇流 InputStream is = null; // 提升作用域 try { is = new FileInputStream(src); // 3、操作 不斷讀取 緩沖數組 byte[] car = new byte[1024]; int len = 0; // 接收 實際讀取大小 // 循環讀取 StringBuilder sb = new StringBuilder(); while (-1 != (len = is.read(car))) { // 輸出 字節數組轉成字符串 String info = new String(car, 0, len); sb.append(info); } System.out.println(sb.toString()); } catch (FileNotFoundException e) { e.printStackTrace(); System.out.println("文件不存在"); } catch (IOException e) { e.printStackTrace(); System.out.println("讀取文件失敗"); } finally { try { // 4、釋放資源 if (null != is) { is.close(); } } catch (Exception e2) { System.out.println("關閉文件輸入流失敗"); } } } }
文件寫出,有以下幾個步驟:
1.建立與文件的聯系:File對象,文件可不存在
2.選擇流:按字節流寫出,文件輸出流 OutputStream FileOutputStream
3.操作:write+flush
4.釋放資源,注意jdk1.7后會自動關閉了
FileOutputStream的構造方法
FileOutputStream(File file,boolean append) 如果選擇true,則是追加,false則覆蓋
構造方法中沒有append參數的,則默認false,覆蓋
package com.pony1223.byteio; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; public class Demo02 { public static void main(String[] args) { File dest=new File("E:/study/java/test.txt"); OutputStream os=null; try { os=new FileOutputStream(dest); String str="hahahaha"; //字符串轉字節數組 byte[] data=str.getBytes(); os.write(data,0,data.length); //強制刷新出去 os.flush(); } catch (FileNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); }finally { try { if(null!=os){ os.close(); } } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } }
2)文件的拷貝
文件拷貝需要以下幾個步驟:
1.建立聯系:兩個File對象,源頭和目的地
2.選擇流:
文件輸入流:InputStream FileInputStream
文件輸出流:OutputStream FileOutputStream
3.操作:拷貝
byte[] car=new byte[1024];+read
whrite+flush
4.釋放資源:關閉兩個流
File是可以創建文件和文件夾的,但是注意,文件我們是可以通過流去讀取的,文件夾不可以;拷貝過程其實很簡單,我們在文件讀寫操作時,都用到了byte數組和String字符串。
讀取操作是用byte數組接收,轉化為String字符串便於輸出
假設我的數組長度為8,即8字節 byte[] buffer=new byte[8];
定義一個int變量,得到實際讀取的字節數 int len=0;
用一個循環來不斷的讀取數據,當文件中數據都讀完以后,is.read會返回(-1),那么只要沒返回-1,就會一直不停的讀取數據,每次讀取的長度就是設置的byte數組的長度,也就是這里的8
while(-1!=(len=is.read(buffer))){
String info=new String(buffer,0,len);
}
也就是這個buffer數組,是會一直往里面裝東西的,如果我的文件是18個字節,那么就需要用到3次這個數組,數組中的數據是會不斷被覆蓋的,當然定義一個大空間的byte數組可以避免這種問題,一般讀取設置byte數組大小為1024
讀取操作就完成了,可以輸出到控制台便於查看
寫出操作是將String轉化為byte,寫入文件
String str="qaz";
byte[] buffer=str.getBytes();
os.write(buffer,o,buffer.length);
這里的byte數組不設置大小
那么讀寫操作放在一起,其實代碼更加精簡
byte[] buffer=new byte[1024];
int len=0;
while(-1!=(len=is.read(buffer))){
os.write(buffer,0,buffer.length);
//os.write(buffer);也可行
}
樣例:
public class Demo02 { public static void main(String[] args) throws IOException { File src=new File("F:/Picture/1.jpg"); File dest=new File("F:/Picture/copy1.jpg"); InputStream is=null; OutputStream os=null; byte[] buffer=new byte[1024]; int len=0; try { is = new FileInputStream(src); os = new FileOutputStream(dest); //從文件夾讀取讀取 while (-1 != (len=is.read(buffer))) { //寫出到文件夾 os.write(buffer, 0, len); } //強制刷出 os.flush(); }finally{ //先打開的后關閉 if(os!=null){ os.close(); } if(is!=null){ is.close(); } } } }
3)文件夾的拷貝
文件夾用來把文件包裹起來,褪去這些外衣,說到底拷貝文件夾也就是拷貝文件
模擬實例:將F:/Picture/test 文件夾 拷貝到 F:/Picture/dir文件夾
該實例中test文件夾下只包含了test.txt文件
步驟分析:
1.通過路徑得到File對象
2.遞歸查找子孫級文件夾或者文件
3.復制文件(同文件拷貝)
那么重點是在第二個步驟,我們可以通過File對象的listFiles方法得到目標文件夾下所包括的文件,listFiles方法返回一個泛型為File的集合list,由此我們就得到了test文件夾下所有的文件,通過foreach循環語句遍歷這個list,得到的每一個File對象,首先要做的就是判斷這個File對象是文件還是文件夾,如果是文件就可直接copy,如果是文件夾,則需要再通過listFiles方法得到目標文件夾下所包括的文件,步驟與上面一致,這也就是遞歸的思想
需要注意的一點是,我們需要把整個test文件夾拷貝到dir文件夾,那么當遍歷到test文件夾下的test.txt文件時,我們在拷貝的時候,需要重新創建一個新的目標文件,dir/test/text.txt.,這就需要File的另一個構造方法
File(File parent, String child)
根據 parent 抽象路徑名和 child 路徑名字符串創建一個新 File
實例
在得到dir這個文件夾的時候,也應該用上述構造方法,得到dir/testFile新對象
在拷貝文件的時候,使用了不同的流,
之前拷貝文件使用的FileInputStream與FileOutputStream,
這里使用了BufferedInputStream與BufferedOutputStream,使用方法相似
InputStream is =new BufferedInputStream(new FileInputStream(src)); OutputStream os =new BufferedOutputStream(new FileOutputStream(dest));
樣例代碼:
public class Demo03 { /** * @param args */ public static void main(String[] args) { // 源目錄 String srcPath = "F:/Picture/test"; // 目標目錄 String destPath = "F:/Picture/dir"; //進行拷貝 try { copyDir(srcPath, destPath); } catch (FileNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } /** * 通過路徑獲得File對象 * * @param src源路徑 * @param dest目標路徑 * @throws IOException * @throws FileNotFoundException */ public static void copyDir(String srcPath,String destPath) throws FileNotFoundException, IOException{ //拒絕自己拷貝給自己 if(srcPath.equals(destPath)){ return ; } File src=new File(srcPath); File dest =new File(destPath); copyDir(src,dest); } /** * 拷貝文件夾 * @param src 源File對象 * @param dest 目標File對象 * @throws IOException * @throws FileNotFoundException */ public static void copyDir(File src,File dest) throws FileNotFoundException, IOException{ if(src.isDirectory()){ //文件夾 dest =new File(dest,src.getName()); if(dest.getAbsolutePath().contains(src.getAbsolutePath())){ System.out.println("父目錄不能拷貝到子目錄中"); return; } } copyDirDetail(src,dest); } /** * 拷貝文件夾細節 * @param src * @param dest */ public static void copyDirDetail(File src,File dest) throws FileNotFoundException,IOException{ if(src.isFile()){ //文件 copyFile(src, dest); }else if(src.isDirectory()){ //文件夾 //確保目標文件夾存在 dest.mkdirs(); //獲取下一級目錄|文件 for(File sub:src.listFiles()){ copyDirDetail(sub,new File(dest,sub.getName())); } } } /** * 文件的拷貝,得到File對象 * @param 源文件路徑 * @param 目錄文件路徑 * @throws FileNotFoundException,IOException * @return */ public static void copyFile(String srcPath,String destPath) throws FileNotFoundException,IOException { //1、建立聯系 源(存在且為文件) +目的地(文件可以不存在) copyFile(new File(srcPath),new File(destPath)); } /** * 文件的拷貝 * @param 源文件File對象 * @param 目錄文件File對象 * @throws FileNotFoundException,IOException * @return */ public static void copyFile(File src,File dest) throws FileNotFoundException,IOException { if(! src.isFile()){ //不是文件或者為null System.out.println("只能拷貝文件"); throw new IOException("只能拷貝文件"); } //dest為已經存在的文件夾,不能建立於文件夾同名的文件 if(dest.isDirectory()){ System.out.println(dest.getAbsolutePath()+"不能建立於文件夾同名的文件"); throw new IOException(dest.getAbsolutePath()+"不能建立於文件夾同名的文件"); } //2、選擇流 InputStream is =new BufferedInputStream(new FileInputStream(src)); OutputStream os =new BufferedOutputStream(new FileOutputStream(dest)); //3、文件拷貝 循環+讀取+寫出 byte[] flush =new byte[1024]; int len =0; //讀取 while(-1!=(len=is.read(flush))){ //寫出 os.write(flush, 0, len); } os.flush(); //強制刷出 //關閉流 os.close(); is.close(); } }
4)用字符流進行純文本的讀取和寫出
純文本的讀取,步驟:
1.建立聯系 file對象
2.選擇流: Reader FileReader
3.讀取:char[] flush=new char[1024];
4.關閉資源
思路和讀取文件基本是一致的,下面比較一下:
字節流讀取文件VS字符流讀取純文本
1.使用流不同,前者使用“stream”,后者是“reader”
2.讀取使用數組不同,前者是byte數組,后者是char數組
3.速度不同,后者速度要比前者快
代碼:
public class Demo05 { public static void main(String[] args) { /** * 純文本讀取 */ //1.建立聯系 File src=new File("F:/Picture/test/test.txt"); //2.選擇流 Reader reader=null; try { reader=new FileReader(src); //3.char數組讀取 char[] flush=new char[1024]; int len=0; while(-1!=(len=reader.read(flush))){ String str=new String(flush,0,len); System.out.println(str); } } catch (FileNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); }catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); }finally{ //4.關閉資源 if(reader!=null){ try { reader.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } } }
純文本的寫出,步驟:
1.建立聯系 file對象
2.選擇流:writer FileWriter
3.讀取while(字符數組,0,長度)+flush
4.關閉資源
之前文件寫出的時候,需要把String字符串轉化成byte數組,才可以使用writer方法寫出,
但是Writer提供了不同的writer方法,可以直接寫出字符串,如下:
代碼:
public class Demo05 { public static void main(String[] args) { /** * 純文本寫出 */ //1.獲取File對象 File dest=new File("F:/Picture/test/test2.txt"); //2.選擇流 Writer writer=null; try { //true代碼追加文件,false代碼覆蓋,默認false覆蓋 writer=new FileWriter(dest,true); String str="我們都是好孩子!"; //3.寫出,強制刷出 writer.write(str); //可追加 writer.append("hahaahaha"); writer.flush(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); }finally{ if(writer!=null){ try { writer.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } } }
字符流拷貝:
public class Demo05 { public static void main(String[] args) { /** * 純文本的拷貝 */ File src=new File("F:/Picture/test/test.txt"); File dest=new File("F:/Picture/test/test3.txt"); Reader re=null; Writer wr=null; try { re=new FileReader(src); wr=new FileWriter(dest); char[] buffer=new char[1024]; int len=0; while(-1!=(len=re.read(buffer))){ wr.write(buffer); wr.flush(); } } catch (FileNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); }catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } finally { try { if (wr != null) { wr.close(); } if (re != null) { re.close(); } } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } }
三、字節流和字符流轉換
任何數據的持久化和網絡傳輸都是以字節形式進行的,所以字節流和字符流之間必然存在轉換問題。字符轉字節是編碼過程,字節轉字符是解碼過程。io包中提供了InputStreamReader和OutputStreamWriter用於字符和字節的轉換。
來看一個小例子:
char[] charArr = new char[1]; StringBuffer sb = new StringBuffer(); FileReader fr = new FileReader("test.txt"); while(fr.read(charArr) != -1) { sb.append(charArr); } System.out.println("編碼:" + fr.getEncoding()); System.out.println("文件內容:" + sb.toString());
FileReader類其實就是簡單的包裝一下FileInputStream,但是它繼承InputStreamReader類,當調用read方法時其實調用的是StreamDecoder類的read方法,這個StreamDecoder正是完成字節到字符的解碼的實現類。如下圖:
InputStream 到 Reader 的過程要指定編碼字符集,否則將采用操作系統默認字符集,很可能會出現亂碼問題。上例代碼輸出如下:
編碼:UTF8
文件內容:hello�����Dz����ļ�!
再來看一個例子,換一個字符集:
char[] charArr = new char[1]; StringBuffer sb = new StringBuffer(); //設置編碼 InputStreamReader isr = new InputStreamReader( new FileInputStream("D:/test.txt") , "GBK"); while(isr.read(charArr) != -1) { sb.append(charArr); } System.out.println("編碼:" + isr.getEncoding()); System.out.println("文件內容:" + sb.toString());
輸出正常:
編碼:GBK
文件內容:hello!我是測試文件!
編碼過程也是類似的,就不再說了。