二,流的分類:
1,按照操作數據分爲字節流和字符流。
2,按照流向分類分爲輸入流和輸出流。
三,常用流的基類
1,字節流的抽象基類InputStream和OutputStream。
2,字符流的抽象基類Reader和Writer。
注意:這四種類派生出來的子類名稱都是以其父類名作爲子類名的後綴。
四,字符流的基本應用:
1,在io包中有一個Writer類,它的write(String str)方法可以把字符串寫入文件。OutputStreamWriter 是字符流通向字節流的橋樑,OutputStreamWriter是一個抽象類它的一個子類FileWriter,其構造函數FileWriter(String fileName)可以通過文件的路徑名稱
創建一個文件,如果該路徑中存在相同文件名的文件,則會覆蓋原文件。
需求:在當前目錄創建一個Demo.txt文本文件,向其中寫入字符。
import java.io.*;
public class WriterDemo
{
public static void main(String[] args) throws IOException{
//創建一個FileWriter對象,該對象一被創建就要明確該對象的文件名和路徑;默認路徑爲該public類
FileWriter f = new FileWriter("Demo.txt");
//調用其父類的父類Writer中的write(String str) 方法,向流中寫入要寫入文件的內容,爲String類型
f.write("sahfh");
//上一步只是將內容寫入了流中,而並沒有寫入目標文件中,所以還要調用Writer的flush()方法,將流
//中的類容刷入到目標文件中;
f.flush();
f.write("-----------kjlj");
f.flush();
//還有一個方法close()也可以將流中的內容刷入到目標文件中,只不過是刷入後,流關閉,不能再次寫入內容
//而flush則不同,刷入一次後還可以刷入
f.close();
//f.write("nk,j");運行時會拋出異常,Stream closed
}
}
實際開發中對異常的處理是不建議直接拋給方法調用者的,有些異常是需要在方法內不進行處理的。所以針對上面需求的異常處理的正確做法請參看下面的示例:
import java.io.*;
public class FileWriterDemo
{
public static void main(String[] args) {
FileWriter f = null; //這三步操作都會拋出異常,因爲這三部是關聯的,所以放在一起處理
//但是如果將變量和對象都放在第一個try中,則close就不知道去關閉誰了,因爲在不同代碼塊,不能引用
//所以將對象的初始化放在try中
try
{
f = new FileWriter("K:\\Demo.java");//K目錄不存在,運行時異常,後面的操作都會停止,流沒有被創建成功
//爲空
f.write("sdaghkjg");
}
catch (IOException e)
{
System.out.println("catch:" + e.toString());
}
finally {//由於流在程序使用完後必須要關閉以釋放資源,所以放到finally中,在前面出現異常後也會被執行;
try
{
if(f != null)
f.close();//只要創建了流就必須要在使用完後關閉
//但是有一個問題就是如果文件路徑不存在,則說明流也就沒有創建,那他關閉誰呢?所以要判斷流是否爲空。
//zai finally中的close也可能拋出異常,所以也要進行try。。。catch處理;
}
catch (IOException e)
{
System.out.println(e.toString());
}
}
}
}
總結:通過上述程序基本瞭解怎樣創建一個文本文件並且向裏面寫入字符,但是我們發現每次運行程序的時候總會創建一個新文件覆蓋就的文件,怎麼才能讓程序每次運行時不會覆蓋舊文件而是在舊文件後面續寫內容呢?
示例:
import java.io.*;
public class FileWriterDemo2 {
public static void main(String[] args) {
FileWriter f = null;
try
{
f = new FileWriter("Demo.txt",true);//參數true表示允許往Demo.txt文件中續寫內容
//沒有此參數,則每次運行時都會創建一個新文件覆蓋原文件
f.write("sdagskagnj+++++\r\n+++++++++sjgak\r\n");//windows系統的記事本換行是\r\n而不是\n
//但是EditPlus中是可以識別的。
}
catch (IOException e)
{
System.out.println(e.toString());
}
finally
{
try
{
if(f != null)
f.close();
}
catch (IOException e)
{
System.out.println(e.toString());
}
}
}
}
2,文件的讀取
文件的讀取方式和文件的寫入有相同之處;也有不同之處;首先必須要有要讀取的文件,創建FileReader對象,然後調用read方法,將文件的內容讀取到控制檯,讀取完之後要關閉流。還要注意異常的處理;它於FileWriter的不同在於不用刷新流;
(1)public int read()throws IOException讀取單個字符。在字符可用、發生 I/O 錯誤或者已到達流的末尾前,此方法一直阻塞。 用於支持高效的單字符輸入的子類應重寫此方法。
需求:從當前文件所在目錄讀取Demo.txt文件中的字符。這種方式讀取一次只能讀取到單個字符。
import java.io.*;
public class FileReaderDemo
{
public static void main(String[] args) {
//創建對象獲取要讀的文件
FileReader fr = null;
try
{
fr = new FileReader("Demo.txt");
//調用父類Reader的read方法,讀取單個字符,獲取到的是單個字符對應的ASCII碼,是int型,所以要裝換類型
//char ch = (char)fr.read();
int ch = 0;
while ((ch = fr.read())!=-1)
{
System.out.print((char)ch);
}
}
catch (IOException e)
{
System.out.println(e.toString());
}
finally
{
try
{
//關閉流文件
if(fr!=null)
fr.close();
}
catch (IOException e)
{
System.out.println(e.toString());
}
}
}
}
(2)文件讀取的第二種方式:
Reader的public int read(char[] cbuf)方法;
先定義一個char[]數組,數組的大小自己定義,每次調用read方法時,會根據數組的大小相應的從目標文件中讀取這麼多個字符,返回的是int型的讀取字符的個數,當讀取到文件的末尾時,會返回-1,但是char[]數組的指針還是會移動,沒這次讀取的字符存入到了char[]數組中,數組指針反覆移動,然後第二次讀取到的字符會覆蓋掉上一次的字符。
import java.io.*;
public class FileReaderDemo2
{
public static void main(String[] args) {
FileReader fr = null;
try
{
fr = new FileReader("Demo.txt");
char[] ch = new char[1024];//大小設定爲1024的好處是,每次讀取之後就存入,減少了讀取次數;
//一個char是兩個字節,一共是2k
int num= 0;
while ((num=fr.read(ch)) != -1)
{
System.out.println(new String(ch,0,num));
}
}
catch (IOException e)
{
System.out.println(e.toString());
}
finally
{
try
{
if(fr!=null)
fr.close();
}
catch (IOException e)
{
System.out.println(e.toString());
}
}
}
}
小例子:讀取java文件中的數據,並將這些數據打印在控制檯上。(爲了簡化代碼,我們暫且將異常直接throws出去)
import java.io.*;
public class FileReaderDemo3
{
public static void main(String[] args) throws IOException {
//讀取當前文件所在目錄下的FileReaderDemo.java文件
FileReader fr = new FileReader("FileReaderDemo.java");
char[] chs = new char[1024];//創建字符數組存儲每次讀取的單個字符
int num = 0;
while ((num = fr.read(chs)) != -1)
{
System.out.println(new String(chs,0,num));
}
fr.close();//關閉讀取流,釋放資源。
}
}
五,字符流讀取與寫入方法的綜合舉例。
在四中已經總結了文件的創建與寫入數據的方法和讀取文件數據的方法,實際開發中文件的處理一般都是寫入和讀取雙向操作的。下面是一個綜合應用舉例。
需求:將C盤下的一個java文件複製到D盤下。
其原理是:1,在D盤創建一個目的文件,用於存儲C盤目錄的文件;2,創建讀取流,於C盤文件關聯;3,通過不斷的讀寫,將C盤文件的內容複製到D盤的目的文件中;
import java.io.*;
public class FileCopy
{
public static void main(String[] args) throws IOException {
//copy_1();
copy_2();
}
public static void copy_2() throws IOException {
FileWriter fw = null;
FileReader fr = null;
try
{
fw = new FileWriter("D:\\System2.txt");
fr = new FileReader("C:\\WriterDemo.java");
char[] cbuf = new char[1024];
int len = 0;
while ((len = fr.read(cbuf))!= -1)
{
fw.write(cbuf);
}
}//這種方式的讀取效率比第一種方法要高,方法1每從WriterDemo.java讀取一個字符就要往System2.txt文件中寫入一個字符,
//方法2則是每從WriterDemo.java中讀取1024個字符才往System2.txt中寫入一次,寫入的是1024個字符。
catch (IOException e)
{
throw new RuntimeException("複製失敗");
}
finally
{
if(fw!=null)
{
try
{
fw.close();
}
catch (IOException e)
{
throw new RuntimeException("流不存在");
}
}
if(fr!=null)
{
try
{
fr.close();
}
catch (IOException e)
{
throw new RuntimeException("流不存在");
}
}
}
}
public static void copy_1() throws IOException {
//創建D盤的目的文件
FileWriter fr = new FileWriter("D:\\System.java");
//獲取要複製的原文件
FileReader fw = new FileReader("C:\\WriterDemo.java");
int len = 0;
//將原文件的內容(字符)讀取到流中
while ((len=fw.read()) != -1)
{
//將流中的內容寫到目的文件中
fr.write(len);
}
//關閉流文件
fr.close();
fw.close();
}//這種方法效率比較低,是一個字符一個字符讀取到流中然後從流中一個一個寫入到目標文件中。請看方法二:copy_2();
}
六,字符流的緩衝區
通過上述的知識點歸納可以看出字符流的操作,底層都是對單個字符單個字符的操作,效率是比較低的,所以java爲我們提供了另外一個工具,就是字符流的緩衝區,其目的就是提高對數據的讀寫效率。
對應的類:BufferedReader和BufferedWriter
注意:緩衝區必須要結合流纔可以使用的,它只是在流的基礎上對流的功能進行了增強。
1,BufferedWriter類的出現在Writer父類的基礎上有增加了一個可以直接寫入一個換行分隔符,如果不使用BufferedWriter類,那麼我們如果要向一個文本文件寫入字符,那麼就必須通過:\r\n來換行,使用BufferedWriter類的newLine()方法就可以直接寫入一個換行符,並且這個方法是跨平臺的,在任何平臺下都可以使用此方法換行。
需求:向Demo.txt文件中寫入多行數據。
import java.io.*;
public class BufferedWriterDemo
{
public static void main(String[] args) throws IOException {
//創建一個字符寫入流對象
FileWriter fw = new FileWriter("Demo.txt");
//爲了提高字符寫入的流效率,加入了緩衝技術;
//只需要將需要提高效率的流對象作爲參數傳遞給緩衝區的構造函數即可
BufferedWriter bf = new BufferedWriter(fw);
for (int i=0;i<5 ;i++ )
{
bf.write("sdagg" + i);//BufferedWriter也是Writer的子類,所以可以調用write子類。
bf.newLine();
bf.flush();
}
//緩衝區用完後一定要記得關閉;
// fw.close();其實關閉緩衝區就是關閉緩衝區中的流對象;
bf.close();
}
}
2,BufferedReader類是爲了提高字符流的讀取效率而出現的,它在Reader父類的基礎上出現了一個功能很強大的讀取一個文本行的方法readLine(),這就使得我們讀取文本文件時便利了很多。
基本用法:
import java.io.*;
public class BufferedReaderDemo
{
public static void main(String[] args) throws IOException {
//創建一個讀取流對象和文件關聯
FileReader fr = new FileReader("Demo.txt");
//爲了提高效率,加入了緩衝技術,將需要提高效率的流對象作爲參數傳遞給緩衝對象的構造函數
BufferedReader bufR = new BufferedReader(fr);
String line = null;
//readLine方法返回的時候只返回回車符之前的內容,不返回回車符。
while ((line=bufR.readLine()) != null)
{
System.out.println(line);
}
bufR.close();
}
}
需求:自定義一個類繼承自Reader類,該類和BufferedReader類的功能一樣。重點是寫出readLine()方法。
import java.io.*;
public class MyReadLine
{
public static void main(String[] args) throws IOException {
FileReader fr = new FileReader("Demo.txt");
MyBufReadLine mr = new MyBufReadLine(fr);
String line = null;
while ((line=mr.myLine()) != null)
{
System.out.println(line);
}
mr.myClose();
}
}
class MyBufReadLine extends Reader
{
private Reader f = null;
MyBufReadLine(Reader f) {
this.f = f;
}
//定義一個臨時容器。原BufferReader封裝的是字符數組。
//爲了演示方便。定義一個StringBuilder容器。因爲最終還是要將數據變成字符串。
//BufferedReaer的底層也是讀取單個字符,當遇到'\r\n'是會換行,此時返回讀取到的一行的字符串
public String myLine() throws IOException {
StringBuilder sb = new StringBuilder();
int ch = 0;
while ((ch = f.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 {
f.close();
}
public int read(char[] cbuf,
int off,
int len)
throws IOException {
return read(cbuf,off,len);//該抽象方法我們不知道如何去實現,所以有子類區實現,也就是我們傳入的FileReader
//趨去實現。
}
public void myClose() throws IOException {
f.close();
}
}
七,字符流緩衝區的綜合應用舉例。
在總結五中寫出了一個複製C盤的一個文本文件到D盤,使用的方法是從源讀取單個字符然後寫入到目的文件,這種效率是比較低的,學習了字符流緩衝區後就可以一行一行的讀取,然後一行一行的寫入目的文件。請看下面的示例:
需求:複製一個java文件,使用緩衝區BufferedReader和BufferedWriter。(注意異常的處理方法)
import java.io.*;
public class BufferCopy
{
public static void main(String[] args) {
BufferedReader bufR = null;
BufferedWriter bufW = null;
try
{
bufR = new BufferedReader(new FileReader("BufferedReaderDemo.java"));
bufW = new BufferedWriter(new FileWriter("Demo.txt"));
String line = null;
while ((line=bufR.readLine())!=null)
{
bufW.write(line);
bufW.newLine();
bufW.flush();
}
}
catch (IOException e)
{
throw new RuntimeException("文件讀取失敗");
}
finally
{
try
{
if(bufR != null)
bufR.close();
}
catch (IOException e)
{
throw new RuntimeException("輸入緩衝流不存在");
}
try
{
if(bufW != null)
bufW.close();
}
catch (IOException e)
{
throw new RuntimeException("輸出緩衝流不存在");
}
}
}
}
八,讀取帶行號的文本。
LineNumberReader類:
LineNumberReader(Reader in)
使用默認輸入緩衝區的大小創建新的行編號 reader。
之前我們讀取文本都是單行單行的讀取,那麼怎樣在讀取單行文本時給每個文本行添加一個行號呢?
需求:從一個java文件中讀取文本數據,打印到控制檯,是每行都有一個行號。將行號的其實行設置爲100.
import java.io.*;
public class LineNumberReaderDemo
{
public static void main(String[] args) throws IOException {
FileReader fr = new FileReader("Demo.txt");
LineNumberReader lb = new LineNumberReader(fr);
String line = null;
lb.setLineNumber(100);//設置起始行號爲100
while ((line=lb.readLine())!=null)
{
System.out.println(lb.getLineNumber() + ":" + line);
}
lb.close();
}
}
總結:字符緩衝區BufferedReader和BufferedWriter,以及帶行號的讀取流LineNumberReader類內的實現原理都用到了裝飾設計模式的原理,關於裝飾設計模式,請參看裝飾設計模式。