javaIO總體思想是圍繞以下四個抽象類來進行擴充展開。[凡以...stream結尾的類,都是字節(jié)流/比特流;凡以Reader或Writer結尾的類,都是字符流]
1.InputStream:輸入字節(jié)流(通常所說的從文件中讀取字節(jié),以字符形式顯示)。
2.OutputStream:輸出字節(jié)流(通常所說的將字節(jié)寫入到文件中)。
3.Reader:輸入字符流
4.Writer:輸出字符流
上述四個抽象類,必須要用相關的子類進行實例化。
Reader類及其子類提供的字符流的讀取char(16位,unicode編碼),inputStream及其子類提供字節(jié)流的讀取byte(8位),所以FileReader類是將文件按字符流的方式讀取,FileInputStream則按字節(jié)流的方式讀取文件;
FileInputStream以字節(jié)為單位(非 unicode )的流處理。字節(jié)序列即:二進制數據。與編碼無關,不存在亂碼問題。
FileInputStream :以字節(jié)流方式讀取;
FileReader :把文件轉換為字符流讀入;
常用的Reader類
FileReader ,InputStreamReader ,BufferedReader
FileReader 與 InputStreamReader 涉及編碼轉換,可能在不同的平臺上出現亂碼現象。
(FileInputStream 以二進制方式處理,不會出現亂碼現象。)
FileReader是InputStreamReader 類的子類。
InputStreamReader 的構造函數參數為InputStream 和編碼方式,當要指定編碼方式時,必須使用 InputStreamReader 類。
FileReader 構造函數的參數與 FileInputStream 同,為 File 對象或表示 path 的 String。
衍生了兩個重要的輸入輸出轉換流:InputStreaReader和OutputStreamWriter這兩個類是字節(jié)流和字符流之間的轉換橋梁。所以在使用這兩個類的構造方法進行實例化對象時,如果不指定編碼方式,則會自動采用當前操作系統(tǒng)的默認字符集進行編碼。該兩個類會牽涉到編碼設置。
InputStreaReader:可以將一個字節(jié)中流中的字節(jié)解碼生成字符
OutputStreamWriter:將寫入的字符編碼成字節(jié)后寫入字節(jié)流
編碼:字符串變成字節(jié)數組,寫入到文件中。寫的過程
解碼:字節(jié)數組變成字符串,以示人們能看懂。讀的過程
總結:源文件以什么方式編的碼,解碼也必須用原來編碼時的方式進行解碼,否則肯定會有亂碼出現。
注意:存放在文件中的內容都是字節(jié),而讀到內存中的才可能變?yōu)樽址?div style="height:15px;">
(1T=1024G, 1G=1024M, 1M=1024K, 1K=1024B, 1B=8bit)
以下示例主要是理解編碼與解碼。
例1.EncodeDemo.java
import java.io.UnsupportedEncodingException;
import java.util.Arrays;
/*功能:理解編碼與解碼。
* 編碼:字符串變成字節(jié)數組。string-----> byte [] str.getBytes();
* 解碼:字節(jié)數組變成字符串(字符串才可顯示看得懂)。byte []-----> string str = new String(byte[]);
* 字節(jié)和字符之間轉換要通過兩個對象(InputStreamReader,OutputStreamWriter)來完成,這兩個對象在構造時,就加入了字符集[編碼表]。
* 編碼表的由來:計算機只能識別二進制數據,早期由來是電信號。
* 為了方便應用計算機,讓它可以識別各個國家的文字
* 就將各個國家的文字用數字來表示,并一一對應,形成一張表。這就是編碼表。
*
*/
public class EncodeDemo {
public static void main(String args[]){
String s = "您好嗎";
byte[] b1 = null;
//b1 = s.getBytes("ISO8859-1");
//將s按操作系統(tǒng)默認的編碼gbk進行編碼
b1 = s.getBytes();
//static String toString(byte[] a) 返回指定數組內容的字符串表示形式。
System.out.println(Arrays.toString(b1));
String s1;
String s2;
try {
//將b1按ISO8859-1編碼進行解碼
s1 = new String(b1,"ISO8859-1");
//因為編碼時采用當前電腦操作系統(tǒng)的默認中文編碼,而解碼時采用了ISO8859-1,所以顯示時s1會亂碼。
System.out.println("s1="+s1);
//對s1進行iso8859-1編碼。等于再次編碼回去。
byte [] b2 = s1.getBytes("ISO8859-1");
System.out.println(Arrays.toString(b2));
//解碼.按照 gbk進行解
s2 = new String(b2,"gbk");
System.out.println("s2="+s2);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
//獲取系統(tǒng)當前編碼
//System.getProperty("file.encoding");
}
}
例2.EncodeDemo2.java
import java.util.*;
public class EncodeDemo2 {
/**
* 功能:當解碼的方式用錯了的時候,如何再次進行編碼后,再正確地進行解碼。
* 修改,如果將程序中的iso8859-1編碼全修改成utf-8,則結果又出意外。原因是,因為utf-8也識別中文(而iso8859-1不識別中文,反而更不會亂碼,所以,tomcat服務器為什么采用iso8859-1的原因在此),而經過gbk編碼后的字符,用utf-8解的時候,根本找不到對應的字符。
* 所以,會在未知區(qū)域中查找,就查出一堆相似的字符,返回。
* @param args
* @throws Exception
*/
public static void main(String args[]) throws Exception{
String s = "您好";
//將s以操作系統(tǒng)認的gbk方式進行編碼
byte [] b1 = s.getBytes();
//打印出字節(jié)數組的字符串形式
System.out.println(Arrays.toString(b1));
//將b1字節(jié)數組以iso8859-1方式進行解碼。正確的方式應該還是以gbk的方式進行解碼。
String s1 = new String(b1,"iso8859-1");
//打印出s1.肯定為亂碼。因為編碼和解碼的方式不統(tǒng)一
System.out.println("s1="+s1);
//對s1以iso8859-1進行編碼.因為上述的解碼方式錯了,所以,需要再次將錯誤的解碼方式進行反編碼
byte [] b2 = s1.getBytes("iso8859-1");
System.out.println(Arrays.toString(b2));
//將b2以gbk的方式進行解碼
String s2 = new String(b2,"gbk");
System.out.println("s2="+s2);
}
}
例3.EncodeDemo3.java
public class EncodeDemo3 {
/**
* 功能:將字符串轉換成二進制數
* @param args
* @throws Exception
*/
public static void main(String args[]) throws Exception{
String s = "聯(lián)通";
String ss = "q";
byte[] b = ss.getBytes("gbk");
for (int i = 0; i < b.length; i++) {
System.out.println("q的二進制為:"+Integer.toBinaryString(b[i]));
}
byte [] by = s.getBytes("gbk");
for (int i = 0; i < by.length; i++) {
System.out.println(by[i]+"的二進制數為:");
//將4個數字轉換成二進制打印出來
System.out.println(Integer.toBinaryString(by[i]));
System.out.println("取后8位為:"+Integer.toBinaryString(by[i]&255));
System.out.println("==============================");
}
}
}
例4.InputStreamReaderBigFile.java
/** 讀取大文件
* 功能:用java讀取文件的全部內容,讀取的內容較多時,不能一個字符一個字符地讀,可以一次讀一行。
*/
import java.io.*;
public class InputStreamReaderBigFile {
public static void main(String args[]) throws Exception{
readText();
}
public static void readText() throws Exception{
//假定utf-8.txt文件已存在,并且寫入時是以utf-8的方式編碼。
InputStreamReader isr = new InputStreamReader(new FileInputStream("utf-8.txt"),"utf-8");
//BufferedReader這個對象,可以一次讀取一行
BufferedReader br = new BufferedReader(isr);
String str;
while((str = br.readLine())!=null){
System.out.println(str);
}
br.close();
isr.close();
}
}
例5.OutputStreamWriterGBK.java
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.OutputStreamWriter;
/**
* 功能:將字符串寫入到指定文件中 ,主要是理解寫文件時編碼
* @author Administrator
*
*/
public class OutputStreamWriterGBK {
public static void main(String args[]) throws Exception{
osw();
}
public static void osw() throws Exception{
try {
//缺省編碼方式為操作系統(tǒng)的字符集gbk,所以gbk可以不寫
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("gbk.txt"),"gbk");
//調用父類Writer的write()方法
osw.write("您好");
osw.close();
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
例6.InputStreamReaderGBK.java
import java.io.FileInputStream;
import java.io.InputStreamReader;
/**
* 功能:用字符char的方式讀出gbk.txt文件中內容,并以gbk的方式讀取。主要是理解讀文件時解碼
* @author Administrator
*
*/
public class InputStreamReaderGBK {
public static void main(String []arg) throws Exception{
isr();
}
public static void isr() throws Exception{
InputStreamReader isr = new InputStreamReader(new FileInputStream("gbk.txt"),"gbk");
//unicode:一個字符占2個字節(jié),1個字節(jié)占8位
//因為知道了gbk.txt中的內容少,故在此用char來讀取
char [] buf = new char[10];
int len = isr.read(buf);
System.out.println("gbk.txt文件內容長度為:"+len+"個字符");
String string = new String(buf,0,len);
System.out.println(string);
isr.close();
}
}
例7.OutputStreamWriterUTF8.java
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.OutputStreamWriter;
/**
* 功能:將字符串寫入到指定文件中 。理解寫入字符到文件時的編碼,以utf-8
* @author Administrator
*
*/
public class OutputStreamWriterUTF8 {
public static void main(String args[]) throws Exception{
osw();
}
public static void osw() throws Exception{
try {
//缺省編碼方式為操作系統(tǒng)的字符集gbk,因為創(chuàng)建utf-8.txt文件時,指定了utf-8的編碼,所以在此必須明確寫上utf-8的編碼方式
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("utf-8.txt"),"utf-8");//在這個地方進行轉碼操作
osw.write("您好");
osw.close();
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
完例7相同的將字符串寫入文件功能。但用了不同的實現方式。
import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStream;
public class WriteFile {
public static void main(String[] args) throws Exception {
//假定utf-8.txt文件的編碼方式是以utf-8方式編碼的
String fileName="D:"+File.separator+"Workspaces"+File.separator+"charset"+File.separator+"utf-8.txt";
File f=new File(fileName);
OutputStream out=new FileOutputStream(f);
String s = "我們";
//表示以操作系統(tǒng)默認字符集編碼方式寫入文件
//out.write(s.getBytes());
//表示以utf-8的編碼方式寫入文件。以后讀取文件內容時,也必須用utf-8進行解碼讀取。將字符串轉換字節(jié)的方式
//s.getBytes("utf-8")表示將一字節(jié)數組寫進去
out.write(s.getBytes("utf-8"));//在這個地方進行設置編碼
out.close();
}
}
當然,上面的代碼也可修改成一個字節(jié)一個字節(jié)地進行寫入:
import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStream;
public class WriteFile {
public static void main(String[] args) throws Exception {
//假定utf-8.txt文件的編碼方式是以utf-8方式編碼的
String fileName="D:"+File.separator+"Workspaces"+File.separator+"charset"+File.separator+"utf-8.txt";
File f=new File(fileName);
OutputStream out = new FileOutputStream(f);
String s = "我們";
//表示以操作系統(tǒng)默認字符集編碼方式寫入文件
//in.write(s.getBytes());
//表示以utf-8的編碼方式寫入文件。以后讀取文件內容時,也必須用utf-8進行解碼讀取。將字符串轉換字節(jié)的方式
//s.getBytes("utf-8")表示將一字節(jié)數組寫進去
// out.write(s.getBytes("utf-8"));//在這個地方進行設置編碼
byte[] b = s.getBytes("utf-8");
for(int i=0; i<b.length; i++){
out.write(b[i]);
}
out.close();
}
}
最后,我們用字符流的方式將內容寫入文件。不需要轉碼操作。請看如下代碼。
import java.io.*;
public class WriteFile{
public static void main(String [] args){
String fileName="D:"+File.separator+"Workspaces"+File.separator+"charset"+File.separator+"utf-8.txt";
File f=new File(fileName);
Writer w = new FileWriter(f);
String str = "hello";
w.write(str);
w.close();
}
}
例8.InputStreamReaderUTF8.java
import java.io.FileInputStream;
import java.io.InputStreamReader;
/**
* 功能:用字符char的方式讀出gbk.txt文件中內容,并以utf-8的方式讀取 。理解讀取文件中的內容解碼 utf-8方式
* 將字節(jié)流轉換字符流的方式進行讀取文件中的內容
* @author Administrator
*
*/
public class InputStreamReaderUTF8 {
public static void main(String []arg) throws Exception{
isr();
}
public static void isr() throws Exception{
//指定以utf-8的方式創(chuàng)建一個輸入流.目的在于注意區(qū)分,如果創(chuàng)建文件時指定了gbk編碼方式,而讀取時如果指定別的編碼方式,則會產生亂碼。
InputStreamReader isr = new InputStreamReader(new FileInputStream("utf-8.txt"),"utf-8"); //在這個地方進行解碼,將字節(jié)流轉換為字符流
//unicode:一個字符占2個字節(jié),1個字節(jié)占8位
//因為知道了gbk.txt中的內容少,故在此用char來讀取
char [] buf = new char[10];
int len = isr.read(buf);
System.out.println("utf-8.txt文件內容長度為:"+len+"個字符");
String string = new String(buf,0,len);
System.out.println(string);
isr.close();
}
}
與例8完成相同的讀取文件內容功能,但用了不同的實現方式:
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
/**
* 功能:完成解碼與編碼,以字節(jié)流的方式讀取文件內容
* @author Administrator
*
*/
public class ReadFile {
public static void main(String[] args) throws Exception {
//假定utf-8.txt文件的編碼方式是以utf-8方式編碼的
String fileName="D:"+File.separator+"Workspaces"+File.separator+"charset"+File.separator+"utf-8.txt";
File f=new File(fileName);
InputStream in=new FileInputStream(f);
byte[] b=new byte[1030];
//將文件的內容讀到b數組中
in.read(b);
in.close();
//如果源文件的編碼方式是utf-8方式,讀取時,必須指定以同樣的編碼方式進行解碼。否則就會亂碼
System.out.println(new String(b,"utf-8")); //在這個地方進行解碼
/*用下面的方式進行顯示,就會有亂碼
System.out.println(new String(b,"gbk"));
System.out.println(new String(b));
*/
}
}
上述程序代碼讀的時候,會有大量的空格,我們可以利用in.read(b);的返回值來設計程序。如下:
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
/**
* 功能:完成解碼與編碼。以字節(jié)流的方式讀取文件內容
* @author Administrator
*
*/
public class ReadFile {
public static void main(String[] args) throws Exception {
//假定utf-8.txt文件的編碼方式是以utf-8方式編碼的
String fileName="D:"+File.separator+"Workspaces"+File.separator+"charset"+File.separator+"utf-8.txt";
File f=new File(fileName);
InputStream in=new FileInputStream(f);
byte[] b=new byte[1030];
int len = in.read(b);
in.close();
System.out.println("讀取文件的長度為:"+len);
//如果源文件的編碼方式是utf-8方式,讀取時,必須指定以同樣的編碼方式進行解碼。否則就會亂碼
System.out.println(new String(b,0,len,"utf-8")); //在這個地方進行解碼
/*用下面的方式進行顯示,就會有亂碼
System.out.println(new String(b,"gbk"));
System.out.println(new String(b));
*/
}
}
仔細觀察上面的代碼可以看出,我們預先申請了一個指定大小空間。但是,有可能這個空間太小,有時候可能太大。我們更需要準確的大小,這樣可以節(jié)省空間。請看如下代碼:
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
/**
* 功能:完成解碼與編碼。以字節(jié)流的方式讀取文件內容。根據文件長度來創(chuàng)建數據大小
* @author Administrator
*
*/
public class ReadFile {
public static void main(String[] args) throws Exception {
//假定utf-8.txt文件的編碼方式是以utf-8方式編碼的
String fileName="D:"+File.separator+"Workspaces"+File.separator+"charset"+File.separator+"utf-8.txt";
File f=new File(fileName);
InputStream in=new FileInputStream(f);
// byte[] b=new byte[1030];
//f.length();表示返回由此抽象路徑名表示的文件的長度。(int)f.length();表示進行數據類型的轉換。
//定義一個不定長的字節(jié)數組。長度根據文件的長度來創(chuàng)建
byte [] b = new byte[(int)f.length()];
in.read(b);
in.close();
System.out.println("文件的長度為:"+f.length());
//如果源文件的編碼方式是utf-8方式,讀取時,必須指定以同樣的編碼方式進行解碼。否則就會亂碼
System.out.println(new String(b,"utf-8")); //在這個地方進行解碼
/*用下面的方式進行顯示,就會有亂碼
System.out.println(new String(b,"gbk"));
System.out.println(new String(b));
*/
}
}
再將上述代碼進行優(yōu)化,一個字節(jié)一個字節(jié)地讀。更節(jié)省空間??慈缦麓a:
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
/**
* 功能:完成解碼與編碼。以字節(jié)流的方式讀取文件內容。一個字節(jié)一個字節(jié)地讀
* @author Administrator
*
*/
public class ReadFile {
public static void main(String[] args) throws Exception {
//假定utf-8.txt文件的編碼方式是以utf-8方式編碼的
String fileName="D:"+File.separator+"Workspaces"+File.separator+"charset"+File.separator+"utf-8.txt";
File f=new File(fileName);
InputStream in=new FileInputStream(f);
// byte[] b=new byte[1030];
//f.length();表示返回由此抽象路徑名表示的文件的長度。(int)f.length();表示進行數據類型的轉換。
//定義一個不定長的字節(jié)數組。長度根據文件的長度來創(chuàng)建
byte [] b = new byte[(int)f.length()];
for ( int i = 0; i<b.length; i++){
b[i] = in.read();
}
// in.read(b);
in.close();
System.out.println("文件的長度為:"+f.length());
//如果源文件的編碼方式是utf-8方式,讀取時,必須指定以同樣的編碼方式進行解碼。否則就會亂碼
System.out.println(new String(b,"utf-8")); //在這個地方進行解碼
/*用下面的方式進行顯示,就會有亂碼
System.out.println(new String(b,"gbk"));
System.out.println(new String(b));
*/
}
}
再仔細想:當我們不知道文件有多大時,這種情況下,需要判斷是否讀到文件末尾。采用while循環(huán)。請看如下代碼:
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
/**
* 功能:完成解碼與編碼。以字節(jié)流的方式讀取文件內容。增加判斷,是否讀到文件末尾
* @author Administrator
*
*/
public class ReadFile {
public static void main(String[] args) throws Exception {
//假定utf-8.txt文件的編碼方式是以utf-8方式編碼的
String fileName="D:"+File.separator+"Workspaces"+File.separator+"charset"+File.separator+"utf-8.txt";
File f=new File(fileName);
InputStream in=new FileInputStream(f);
byte[] b=new byte[1030];
int i= 0;
int tmp = 0;
while (( tmp=in.read())!= (-1)){
//整型強制轉換成byte型.每循環(huán)一次,字節(jié)數組中就增加一個元素
b[i++} = (byte)tmp;
}
//f.length();表示返回由此抽象路徑名表示的文件的長度。(int)f.length();表示進行數據類型的轉換。
//定義一個不定長的字節(jié)數組。長度根據文件的長度來創(chuàng)建
/* byte [] b = new byte[(int)f.length()];
for ( int i = 0; i<b.length; i++){
b[i] = in.read();
}*/
// in.read(b);
in.close();
System.out.println("文件的長度為:"+f.length());
//如果源文件的編碼方式是utf-8方式,讀取時,必須指定以同樣的編碼方式進行解碼。否則就會亂碼
System.out.println(new String(b,"utf-8")); //在這個地方進行解碼
/*用下面的方式進行顯示,就會有亂碼
System.out.println(new String(b,"gbk"));
System.out.println(new String(b));
*/
}
}
最后,以字符的方式從文件中讀取內容??慈缦麓a:
public class ReadFile{
public static void main(String [] args){
String fileName="D:"+File.separator+"Workspaces"+File.separator+"charset"+File.separator+"utf-8.txt";
File f=new File(fileName);
char [] ch = new char[100];
Reader r = new FileReader(f);
int count = r.read(ch);
r.close();
System.out.println("讀入的長度為:"+count);
System.out.println("內容為:"+new String(ch,0,count));
}
}
有時候,我們還不知道文件到底有多大,所以,我們更宜采用循環(huán)的方式讀取(以字符方式讀取)??慈缦麓a:
public static void main(String [] args){
String fileName="D:"+File.separator+"Workspaces"+File.separator+"charset"+File.separator+"utf-8.txt";
File f=new File(fileName);
char [] ch = new char[100];
Reader r = new FileReader(f);
int tmp = 0;
int i = 0;
while ((tmp = r.read())!= (-1)){
ch[i++] = (char)tmp;
}
// int count = r.read(ch);
r.close();
//System.out.println("讀入的長度為:"+count);
System.out.println("內容為:"+new String(ch,0,count));
}
}
關于字節(jié)流和字符流的區(qū)別
實際上字節(jié)流在操作的時候本身是不會用到緩沖區(qū)的,是文件本身的直接操作的,但是字符流在操作的時候下后是會用到緩沖區(qū)的,是通過緩沖區(qū)來操作文件的。
讀者可以試著將上面的字節(jié)流和字符流的程序的最后一行關閉文件的代碼注釋掉,然后運行程序看看。你就會發(fā)現使用字節(jié)流的話,文件中已經存在內容,但是使用字符流的時候,文件中還是沒有內容的,這個時候就要刷新緩沖區(qū)。
使用字節(jié)流好還是字符流好呢?
答案是字節(jié)流。首先因為硬盤上的所有文件都是以字節(jié)的形式進行傳輸或者保存的,包括圖片等內容。但是字符只是在內存中才會形成的,所以在開發(fā)中,字節(jié)流使用廣泛。
延伸:文件的復制?;舅悸愤€是從一個文件中讀入內容,邊讀邊寫入另一個文件,就是這么簡單.請看ReadANDWrite.java
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
/*
* 功能:將一個文件的內容讀出來后,將所有內容寫入到另一個文件。實現文件復制功能
*/
public class ReadANDWrite {
public static void main(String []args) throws Exception{
File file = new File("c:\\gbk.txt");
InputStream is = new FileInputStream(file);
//OutputStream os = new FileOutputStream(new File("c:\\gbk-copy.txt"));
Writer writer = new OutputStreamWriter(new FileOutputStream("c:\\gbk-copy.txt"));
byte [] b = new byte[100];
//int len = is.read(b);
int len;
int i=0;
//循環(huán)判斷是否讀到文件末尾
while ((len = is.read())!=(-1)) {
// String s = new String(b);
// writer.write(s);
//設置計數器??纯纯偣灿袔讉€字節(jié)
int c = i++;
//按字節(jié)進行讀取,讀到一個字節(jié)后,賦值給字節(jié)數組變量
b[c] = (byte)len;
System.out.println("b"+"["+i+"]"+":"+b[c]);
}
//將字節(jié)數組轉換成字符串,為了不出現多余的空格,必須需要接上加上相關參數,從哪開始讀,讀到哪結束
String s = new String(b,0,i);
writer.write(s);
System.out.println(new String(b,0,i));
//os.write((byte)is.read(b));
is.close();
writer.close();
//os.close();
}
}