亂碼導(dǎo)火索:
在io流里,先誕生了字節(jié)流,但是字節(jié)流讀取數(shù)據(jù)會(huì)有亂碼的問(wèn)題(讀中文會(huì)亂碼)。比如:
FileInputStream fis = new FileInputStream("a.txt");
// int by = 0;
// while ((by=fis.read() )!= -1) {
// System.out.print((char)by);//bcdbcdbcdbcdbcdbcdhello ??-???
// //因?yàn)檫€正常,中文就亂碼了,有什么辦法解決嗎,有,就是有點(diǎn)麻煩
// }
從文件中讀取中文會(huì)有亂碼,當(dāng)然字節(jié)流有解決措施。
FileInputStream fis = new FileInputStream("a.txt");
byte[] bytes = new byte[1024];
int len = 0;
while ((len = fis.read(bytes)) != -1) {
System.out.println(new String(bytes,0,len));//bcdbcdbcdbcdbcdbcdhello 中國(guó)
//查看new String()的源碼,this.value = StringCoding.decode(bytes, offset, length);
//點(diǎn)進(jìn)decode,循序漸進(jìn)發(fā)現(xiàn),默認(rèn)編碼是UTF-8
//通過(guò)源碼,也看到了這個(gè)方法public String(byte bytes[], int offset, int length, String charsetName)
}
但是解碼這并不是字節(jié)流做的,而是String的功能。查看String的源碼,構(gòu)造方法有解碼功能,并且默認(rèn)編碼是utf-8。
聊到了亂碼,我覺(jué)得有必要聊一下編碼的知識(shí)。
String(byte[] bytes, String charsetName):通過(guò)指定的字符集解碼字節(jié)數(shù)組
byte[] getBytes(String charsetName):使用指定的字符集合把字符串編碼為字節(jié)數(shù)組
編碼:把看得懂的變成看不懂的
String -- byte[]
解碼:把看不懂的變成看得懂的
byte[] -- String
因此字節(jié)流讀取的數(shù)據(jù)是編碼過(guò)的數(shù)據(jù),我們解碼就行了。
編碼問(wèn)題簡(jiǎn)單,只要編碼解碼的格式是一致的。
計(jì)算機(jī)只能識(shí)別二進(jìn)制數(shù)據(jù),早期由來(lái)是電信號(hào)。為了方便應(yīng)用計(jì)算機(jī),讓它可以識(shí)別各個(gè)國(guó)家的文字。就將各個(gè)國(guó)家的文字用數(shù)字來(lái)表示,并一一對(duì)應(yīng),形成一張表。
ASCII:美國(guó)標(biāo)準(zhǔn)信息交換碼。
用一個(gè)字節(jié)的7位可以表示。
ISO8859-1:拉丁碼表。歐洲碼表
用一個(gè)字節(jié)的8位表示。
GB2312:中國(guó)的中文編碼表。
GBK:中國(guó)的中文編碼表升級(jí),融合了更多的中文文字符號(hào)。
GB18030:GBK的取代版本
BIG-5碼 :通行于臺(tái)灣、香港地區(qū)的一個(gè)繁體字編碼方案,俗稱“大五碼”。
Unicode:國(guó)際標(biāo)準(zhǔn)碼,融合了多種文字。
所有文字都用兩個(gè)字節(jié)來(lái)表示,Java語(yǔ)言使用的就是unicode
UTF-8:最多用三個(gè)字節(jié)來(lái)表示一個(gè)字符。
UTF-8不同,它定義了一種“區(qū)間規(guī)則”,這種規(guī)則可以和ASCII編碼保持最大程度的兼容:
它將Unicode編碼為00000000-0000007F的字符,用單個(gè)字節(jié)來(lái)表示?
它將Unicode編碼為00000080-000007FF的字符用兩個(gè)字節(jié)表示
它將Unicode編碼為00000800-0000FFFF的字符用3字節(jié)表示
示例:
String s = "你好";
byte[] bytes1 = s.getBytes();
System.out.println(Arrays.toString(bytes1));//[-28, -67, -96, -27, -91, -67] 默認(rèn)編碼utf-8
byte[] bytes2 = s.getBytes("GBK");
System.out.println(Arrays.toString(bytes2));//[-60, -29, -70, -61]
byte[] bytes3 = s.getBytes("UTF-8");
System.out.println(Arrays.toString(bytes3));//[-28, -67, -96, -27, -91, -67]
String s1 = new String(bytes1);
System.out.println(s1);//你好
String s2 = new String(bytes2,"GBK");
System.out.println(s2);//你好
String s3 = new String(bytes2,"gbk");
System.out.println(s3);//你好
String s4 = new String(bytes3);
System.out.println(s4);//你好
String s5 = new String(bytes3,"gbk");
System.out.println(s5);//浣犲ソ
雖然字節(jié)流有解決亂碼的方案,但并不方便,所以java io流就設(shè)計(jì)出了轉(zhuǎn)換流,一場(chǎng)亂碼引發(fā)的變革。
(OutputStreamWriter、InputStreamReader)
OutputStreamWriter(OutputStream out):根據(jù)默認(rèn)編碼把字節(jié)流的數(shù)據(jù)轉(zhuǎn)換為字符流
OutputStreamWriter(OutputStream out,String charsetName):根據(jù)指定編碼把字節(jié)流數(shù)據(jù)轉(zhuǎn)換為字符流
把字節(jié)流轉(zhuǎn)換為字符流。
//創(chuàng)造對(duì)象
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("b.txt"));//查看源碼,java8 默認(rèn)utf-8
//寫數(shù)據(jù)
osw.write("中國(guó)");
//釋放資源
osw.close();//close包含了close和flush的作用
一般默認(rèn)編碼就夠了。
查看源碼,發(fā)現(xiàn)OutputStreamWriter有5個(gè)write方法。
/*
* OutputStreamWriter的方法:
* public void write(int c):寫一個(gè)字符
* public void write(char[] cbuf):寫一個(gè)字符數(shù)組
* public void write(char[] cbuf,int off,int len):寫一個(gè)字符數(shù)組的一部分
* public void write(String str):寫一個(gè)字符串
* public void write(String str,int off,int len):寫一個(gè)字符串的一部分
*
* 面試題:close()和flush()的區(qū)別?
* A:close()關(guān)閉流對(duì)象,但是先刷新一次緩沖區(qū)。關(guān)閉之后,流對(duì)象不可以繼續(xù)再使用了。
* B:flush()僅僅刷新緩沖區(qū),刷新之后,流對(duì)象還可以繼續(xù)使用。
*/
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("c.txt"));//查看源碼,java8 默認(rèn)utf-8
//寫一個(gè)字符
osw.write('a');
osw.write(97);
osw.write('中');
// 為什么數(shù)據(jù)沒(méi)有進(jìn)去呢?
// 原因是:字符 = 2字節(jié)
// 文件中數(shù)據(jù)存儲(chǔ)的基本單位是字節(jié)。
// void flush()
osw.flush();
//寫一個(gè)字符數(shù)組
char[] chars = {'a','b','中','國(guó)'};
osw.write(chars);
osw.flush();
//寫一個(gè)字符數(shù)組的一部分
osw.write(chars,2,2);
osw.flush();
//寫一個(gè)字符串
osw.write("\n\r中國(guó)");
//寫一個(gè)字符串的一部分
osw.write("中國(guó)你好",2,2);
osw.close();
InputStreamReader(InputStream is):用默認(rèn)的編碼讀取數(shù)據(jù),默認(rèn)utf-8
InputStreamReader(InputStream is,String charsetName):
用指定的編碼讀取數(shù)據(jù)
//創(chuàng)建對(duì)象
InputStreamReader isr = new InputStreamReader(new FileInputStream("b.txt"));//默認(rèn)編碼utf-8
InputStreamReader isr1 = new InputStreamReader(new FileInputStream("b.txt"),"gbk");//可指定編碼
//讀數(shù)據(jù)
int ch = 0;
while ((ch = isr.read()) != -1) {
System.out.print((char)ch);//中國(guó)
}
//釋放資源
isr.close();
//只有文檔的編碼和讀取的編碼一致才不會(huì)亂碼。
查看源碼知道InputStreamReader有2個(gè)read方法。
/*
* InputStreamReader的方法:
* int read():一次讀取一個(gè)字符
* int read(char[] chs):一次讀取一個(gè)字符數(shù)組
*/
InputStreamReader isr = new InputStreamReader(new FileInputStream("c.txt"));
//讀一個(gè)字符
// int ch = 0;
// while ((ch = isr.read()) != -1) {
// System.out.print((char) ch);//9797200139798200132226920013222691020013222692032022909/
// //aa中ab中國(guó)中國(guó)
// //中國(guó)你好
// }
//isr.close();
//讀一個(gè)字符數(shù)組
char[] chars =new char[1024];
int len = 0;
while ((len = isr.read(chars)) != -1) {
System.out.println(chars.length);//1024
System.out.println(new String(chars,0,len));
//aa中ab中國(guó)中國(guó)
//中國(guó)你好
}
isr.close();
現(xiàn)在我們可以通過(guò)轉(zhuǎn)換流升級(jí)字節(jié)流復(fù)制文件的方式了。
InputStreamReader isr = new InputStreamReader(new FileInputStream("a.txt"));
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("e:\\a.txt"));
char [] chars = new char[1024];
int len = 0 ;
while ((len = isr.read(chars)) != -1) {
osw.write(chars,0,len);
osw.flush();
}
osw.close();
isr.close();
字符流的誕生:
轉(zhuǎn)換流已經(jīng)是字符流了,但是他們的名字太長(zhǎng)了,Java就提供了其子類供我們使用。
FileWriter 、FileReader 繼承了其父類的所有方法。
字符流=字節(jié)流+編碼表
OutputStreamWriter = FileOutputStream + 編碼表(GBK)
FileWriter = FileOutputStream + 編碼表(GBK)
InputStreamReader = FileInputStream + 編碼表(GBK)
FileReader = FileInputStream + 編碼表(GBK)
復(fù)制改寫:
FileWriter fw = new FileWriter("e:\\b.txt");
FileReader fr = new FileReader("b.txt");
char[] chars =new char[1024];
int len = 0;
while ((len = fr.read(chars)) != -1) {
fw.write(chars,0,len);
fw.flush();
}
fw.close();
fr.close();
/**
* 總結(jié),到現(xiàn)在為止,加上字節(jié)流,復(fù)制文本的方式有8種,但是復(fù)制圖片視頻等文件只有四種字節(jié)流的方式,因?yàn)椴荒苡米址鲝?fù)制圖片視頻mp3等
*/
字符流學(xué)習(xí)前輩字節(jié)流的經(jīng)驗(yàn),也設(shè)計(jì)了字符緩沖流。
BufferedReader、
BufferedWriter
和字節(jié)緩沖流的設(shè)計(jì)基本一樣,也有兩個(gè)構(gòu)造方法,但是我們只要默認(rèn)的緩沖大小的構(gòu)造方法就可以了。
用字符緩沖流改寫復(fù)制功能:
BufferedReader br = new BufferedReader(new FileReader("c.txt"));
BufferedWriter bw = new BufferedWriter(new FileWriter("e:\\d.txt"));
char[] chars = new char[1024];
int len = 0 ;
while ((len = br.read(chars)) != -1) {
bw.write(chars,0,len);
bw.flush();
}
br.close();
bw.close();
然而字符緩沖流還有自己特殊的讀寫功能。
BufferedWriter :void newLine() 換行
BufferedReader :String readLine() 讀一行數(shù)據(jù)
public static void main(String args[]) throws IOException {
// write();
read();
}
private static void read() throws IOException {
BufferedReader br = new BufferedReader(new FileReader("bw.txt"));
String line = null;
while ((line = br.readLine()) != null) {
System.out.println(line);
//readLine不會(huì)讀取換行符
//讀到末尾返回null
}
br.close();
}
private static void write() throws IOException {
BufferedWriter bw = new BufferedWriter(new FileWriter("bw.txt"));
for (int i = 0; i <10 ; i++) {
bw.write("你好哈哈哈哈哈");
bw.newLine();//換行,并且自動(dòng)檢測(cè)不同系統(tǒng)的換行符
bw.flush();
//一般三個(gè)連用
}
bw.close();
}
用字符緩沖流的特殊方式升級(jí)復(fù)制功能:
BufferedReader br = new BufferedReader(new FileReader("bw.txt"));
BufferedWriter bw = new BufferedWriter(new FileWriter("e:\\bw.txt"));
String line = null;
while ((line = br.readLine()) != null) {
bw.write(line);
bw.newLine();
bw.flush();
}
bw.close();
br.close();
//總結(jié) 字符流復(fù)制文本有5種方式
字符流的基礎(chǔ)基本就聊完了,還有一個(gè)比較常用的類LineNumberReader
查看源碼可知LineNumberReader繼承自BufferedReader,并且增加了行號(hào)的操作。
LineNumberReader lnr = new LineNumberReader(new FileReader("a.txt"));
String line = null;
while ((line = lnr.readLine()) != null) {
System.out.println(lnr.getLineNumber()+":"+line);
}
1:A
2:S
3:F
但是LineNumberWriter。源碼很簡(jiǎn)單,更多的操作請(qǐng)看源碼。
io流總結(jié):

java 中文api :
http://tool.oschina.net/apidocs/apidoc?api=jdk-zh
查看api源碼,深入理解io設(shè)計(jì)理念。
io流面試題:
題一:復(fù)制單級(jí)文件夾
/**
* 封裝
* 新建文件夾
* 獲得源文件夾下文件列表
* 復(fù)制文件到新文件夾
*/
public class copyFolder {
public static void main(String args[]) throws IOException {
File file1 = new File("F:\\湯包\\IT時(shí)代\\java基礎(chǔ)\\day21\\code\\demo");
File file2 = new File("e:\\demo");
//判斷文件夾是否存在
if (!file2.exists()){
file2.mkdir();
}
//獲取源文件夾下文件列表
File[] files = file1.listFiles();
for (File file : files) {
File newfile = new File(file2,file.getName());
copyFun(file,newfile);
}
}
private static void copyFun(File file1,File file2) throws IOException {
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file1));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file2));
byte[] bytes = new byte[1024];
int len = 0;
while ((len = bis.read(bytes)) != -1) {
bos.write(bytes,0,len);
}
bis.close();
bos.close();
}
}
題二:復(fù)制多極文件夾
/*
* 需求:復(fù)制多極文件夾
*
* 數(shù)據(jù)源:F:\湯包\IT時(shí)代\java基礎(chǔ)\day21\code\demos
* 目的地:E:\\
*
* 分析:
* A:封裝數(shù)據(jù)源File
* B:封裝目的地File
* C:判斷該File是文件夾還是文件
* a:是文件夾
* 就在目的地目錄下創(chuàng)建該文件夾
* 獲取該File對(duì)象下的所有文件或者文件夾File對(duì)象
* 遍歷得到每一個(gè)File對(duì)象
* 回到C
* b:是文件
* 就復(fù)制(字節(jié)流)
*/
public class copyFolder2 {
public static void main(String args[]) throws IOException {
File file1 = new File("F:\\湯包\\IT時(shí)代\\java基礎(chǔ)\\day21\\code\\demos");
File file2 = new File("e:\\");
copyFolder(file1,file2);
}
private static void copyFolder(File srcFile, File destFile) throws IOException {
if (srcFile.isDirectory()){
File newFolder = new File(destFile, srcFile.getName());
newFolder.mkdir();
File[] files = srcFile.listFiles();
for (File file1 : files) {
copyFolder(file1,newFolder);
}
}else {
File newFile = new File(destFile,srcFile.getName());
copyFile(srcFile,newFile);
}
}
private static void copyFile(File file1,File file2) throws IOException {
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file1));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file2));
byte[] bytes = new byte[1024];
int len = 0;
while ((len = bis.read(bytes)) != -1) {
bos.write(bytes,0,len);
}
bis.close();
bos.close();
}
}
復(fù)制多級(jí)文件夾,構(gòu)思用到了遞歸,可知遞歸真的很重要,之后也會(huì)總結(jié)一下遞歸的知識(shí)。
題三:鍵盤錄入5個(gè)學(xué)生信息(姓名,語(yǔ)文成績(jī),數(shù)學(xué)成績(jī),英語(yǔ)成績(jī)),按照總分從高到低打印到控制臺(tái)。
Student.java
public class Student {
// 姓名
private String name;
// 語(yǔ)文成績(jī)
private int chinese;
// 數(shù)學(xué)成績(jī)
private int math;
// 英語(yǔ)成績(jī)
private int english;
public Student() {
super();
}
public Student(String name, int chinese, int math, int english) {
super();
this.name = name;
this.chinese = chinese;
this.math = math;
this.english = english;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getChinese() {
return chinese;
}
public void setChinese(int chinese) {
this.chinese = chinese;
}
public int getMath() {
return math;
}
public void setMath(int math) {
this.math = math;
}
public int getEnglish() {
return english;
}
public void setEnglish(int english) {
this.english = english;
}
public int getSum() {
return this.chinese + this.math + this.english;
}
}
StendentDemo.java
/*
* 鍵盤錄入5個(gè)學(xué)生信息(姓名,語(yǔ)文成績(jī),數(shù)學(xué)成績(jī),英語(yǔ)成績(jī)),按照總分從高到低存入文本文件
*
* 分析:
* A:創(chuàng)建學(xué)生類
* B:創(chuàng)建集合對(duì)象
* TreeSet<Student>
* C:鍵盤錄入學(xué)生信息存儲(chǔ)到集合
* D:遍歷集合,把數(shù)據(jù)寫到文本文件
*/
public class StendentDemo {
public static void main(String args[]) throws IOException {
TreeSet<Student> students = new TreeSet<>(new Comparator<Student>() {
@Override
public int compare(Student s1, Student s2) {
//s1-s2升序,s2-s1降序
int num = s2.getSum() - s1.getSum();
int num1 = num == 0 ?s2.getChinese()-s1.getChinese():num;
int num2 = num1 ==0 ?s2.getMath()-s1.getMath():num1;
int num3 = num2 ==0 ?s2.getEnglish()-s1.getEnglish():num2;
int num4 = num3 == 0?s1.getName().compareTo(s2.getName()):num3;//按字母順序
return num4;
}
});
for (int i = 0; i <5 ; i++) {
Scanner sc = new Scanner(System.in);
System.out.println("錄入學(xué)生成績(jī):");
System.out.println("姓名:");
String name = sc.nextLine();
System.out.println("語(yǔ)文成績(jī):");
int chinese = sc.nextInt();
System.out.println("數(shù)學(xué)成績(jī):");
int math = sc.nextInt();
System.out.println("英語(yǔ)成績(jī):");
int english = sc.nextInt();
Student student = new Student(name,chinese,math,english);
students.add(student);
}
BufferedWriter bw = new BufferedWriter(new FileWriter("grade.txt"));
bw.write("學(xué)生信息如下:");
bw.newLine();
bw.flush();
bw.write("姓名,語(yǔ)文成績(jī),數(shù)學(xué)成績(jī),英語(yǔ)成績(jī)");
bw.newLine();
bw.flush();
for (Student student : students) {
StringBuilder sb = new StringBuilder();
sb.append(student.getName()).append(student.getChinese()).append(student.getMath()).append(student.getEnglish());
bw.write(sb.toString());
bw.newLine();
bw.flush();
}
bw.close();
}
}
題四:已知s.txt文件中有這樣的一個(gè)字符串:“hcexfgijkamdnoqrzstuvwybpl”,請(qǐng)編寫程序讀取數(shù)據(jù)內(nèi)容,把數(shù)據(jù)排序后寫入ss.txt中。
/*
* 已知s.txt文件中有這樣的一個(gè)字符串:“hcexfgijkamdnoqrzstuvwybpl”
* 請(qǐng)編寫程序讀取數(shù)據(jù)內(nèi)容,把數(shù)據(jù)排序后寫入ss.txt中。
*
* 分析:
* A:把s.txt這個(gè)文件給做出來(lái)
* B:讀取該文件的內(nèi)容,存儲(chǔ)到一個(gè)字符串中
* C:把字符串轉(zhuǎn)換為字符數(shù)組
* D:對(duì)字符數(shù)組進(jìn)行排序
* E:把排序后的字符數(shù)組轉(zhuǎn)換為字符串
* F:把字符串再次寫入ss.txt中
*/
public class StringDemo {
public static void main(String[] args) throws IOException {
// 讀取該文件的內(nèi)容,存儲(chǔ)到一個(gè)字符串中
BufferedReader br = new BufferedReader(new FileReader("s.txt"));
String line = br.readLine();
br.close();
// 把字符串轉(zhuǎn)換為字符數(shù)組
char[] chs = line.toCharArray();
// 對(duì)字符數(shù)組進(jìn)行排序
Arrays.sort(chs);
// 把排序后的字符數(shù)組轉(zhuǎn)換為字符串
String s = new String(chs);
// 把字符串再次寫入ss.txt中
BufferedWriter bw = new BufferedWriter(new FileWriter("ss.txt"));
bw.write(s);
bw.newLine();
bw.flush();
bw.close();
}
}
題五:用Reader模擬BufferedReader的readLine()功能
/*
* 用Reader模擬BufferedReader的readLine()功能
*
* readLine():一次讀取一行,根據(jù)換行符判斷是否結(jié)束,只返回內(nèi)容,不返回?fù)Q行符
*/
public class MyBufferedReader {
private Reader reader;
public MyBufferedReader(Reader reader){
this.reader=reader;
}
public String readLine() throws IOException {
StringBuilder sb = new StringBuilder();
int ch = 0;
while((ch=reader.read())!=-1) {
if (ch=='\r'){
continue;
}
if (ch=='\n'){
return sb.toString();
}else {
sb.append((char)ch);
}
}
if (sb.length()>0){
return sb.toString();
}
return null;
}
public void close() throws IOException {
this.reader.close();
}
}
測(cè)試:
MyBufferedReader mbr = new MyBufferedReader(new FileReader("a.txt"));
String line = null;
while ((line = mbr.readLine()) != null) {
System.out.println((line));
}
mbr.close();
通過(guò)自己實(shí)現(xiàn)readLine()方法,并且查看源碼可知,字符緩沖流的readLine()也用到了StringBuilder,并且也要判斷\n和\r,最后關(guān)閉的流就是Reader。
以上是本人學(xué)習(xí)筆記整理,重溫java經(jīng)典,歡迎各位同道中人批評(píng)指正。
學(xué)習(xí)心得:
再次學(xué)習(xí)io流,看了好多源碼,體驗(yàn)了其架構(gòu)思想的強(qiáng)大,邏輯的縝密。發(fā)現(xiàn)io流的讀和寫都是線程安全的。所以線程得再次研究下,設(shè)計(jì)模式得了解下。
源碼碼云地址:
https://gitee.com/stefanpy/java
夢(mèng)回io流完整目錄:
java基礎(chǔ)io流——File告白(重溫經(jīng)典)