------Java培訓、Android培訓、iOS培訓、.Net培訓、期待與您交流!
一、IO概述
IO流,即InputOutput的縮寫。
IO的特點:
(1)IO流用來處理設備間的數據傳輸。
(2)Java中對數據的操作是通過流的方式。
(3)Java用於操作流的對象都在IO包中。
(4)流按操作數據分爲兩種:字節流和字符流。
按流向分爲:輸入流和輸出流。
注意,流只能操作數據,不能操作文件。
IO流的常用基類:
(1)字節流的抽象基類:InputStream和OutputStream
(2)字符流的抽象基類:Reader和Writer
這四個基類派生出來的子類名稱都是以父類名作爲子類名的後綴,以功能爲前綴。如InputStream的子類FileInputStream。
二、字符流
字符流中的對象融合了編碼表,使用的是默認的編碼,即當前系統的編碼。字符流只用於處理文字數據,而字節流可以處理媒體數據。IO流是用於操作數據的,數據的最常見體現形式就是文件,查閱API,可以找到專門用於操作文件的Writer子類對象:FileWriter後綴是父類名,前綴是功能。該流對象一被初始化,就必須有被操作的文件存在。
字符流的讀寫:
1.寫入字符流:
(1)創建一個FileWriter對象,對象一被初始化,就必須要明確被操作的文件。且該目錄下如果已有同名文件,則同名文件被覆蓋。其實就是在明確數據要存放的目的地。
(2)調用write(String s)方法,將字符串寫入到流中。
(3)調用flush()方法,刷新該流的緩衝,將數據刷新到目的地中。
(4)調用close()方法,關閉流資源。但是關閉前會刷新一次內部的緩衝數據,並將數據刷新到目的地中。
close()和flush()區別:
flush()刷新後,流可以繼續使用;close()刷新後,將會關閉流,不可再寫入字符流。
如下:
public static void main(String[] args)
{
FileWriter fw=null;
try {
//建立寫入流對象,定義要操作的文件,如果文件已經存在,不覆蓋,在其後面續寫。
fw=new FileWriter("F:\\123.txt",true);
//寫入數據
fw.write("你好,早安");
//刷新數據到指定文件
fw.flush();
} catch (IOException e) {
throw new RuntimeException("寫入失敗");
} //關閉流資源,因爲一定要執行,所以定義在finally語句中
finally{
try {
if(fw!=null)
fw.close();
} catch (Exception e) {
// TODO: handle exception
throw new RuntimeException("關閉失敗");
}
}
}
要注意幾點:1.其實Java自身不能寫入數據,而是調用系統內部方式完成數據的寫入,使用系統資源後,一定要關閉資源。2.文件的數據的續寫是通過構造函數FileWriter(String s,boolean append),在創建對象時,傳遞一個true參數,代表不覆蓋已存在的文件。並在已有文件的末尾處進行數據續寫。(Windows系統中的文件內換行用\r\n完成,而Linux中用\n完成)
3.由於在創建對象時,需要指定創建文件位置,如果指定的位置不存在,就會發生IOException異常,所以在整個步驟中,需要對IO異常進行try處理。
2.讀取字符流:
(1)創建一個文件讀取流對象,和指定名稱的文件相關聯。要保證該文件已經存在,若不存在,將會發生FileNotFoundException異常。
(2)調用讀取流對象的read方法。
第一種方式:read():一次讀一個字符
第二種方式:read(char[] c):通過字符數組進行讀取。
(3)讀取後調用close方法關閉流資源。
如下:
public static void main(String[] args)
{
FileReader fr=null;
try {
//建立讀取流對象,關聯指定文件。
fr=new FileReader("F:\\123.txt");
//第一種:一個一個讀取
int ch=0;
while((ch=fr.read())!=-1)
System.out.print((char)ch);
//第二種:通過數組進行讀取
char[]arr=new char[1024];
int len=0;
while((len=fr.read(arr))!=-1)
System.out.print(new String(arr,0,len));
} catch (IOException e) {
// TODO: handle exception
throw new RuntimeException("讀取失敗");
} //關閉流資源,因爲一定要執行,所以定義在finally語句中
finally{
try {
if(fr!=null)
fr.close();
} catch (Exception e) {
// TODO: handle exception
throw new RuntimeException("關閉失敗");
}
}
}
下面通過字符流完成一個文件的拷貝:
public static void main(String[] args)
{
FileReader fr=null;
FileWriter fw=null;
try {
//建立流對象,關聯指定文件。
fr=new FileReader("F:\\123.txt");
fw=new FileWriter("E:\\123.txt");
//讀寫操作
char[]arr=new char[1024];
int len=0;
while((len=fr.read(arr))!=-1)
fw.write(arr, 0, len);
} catch (IOException e) {
throw new RuntimeException("複製失敗");
} //關閉流資源,因爲一定要執行,所以定義在finally語句中
finally{
try {
if(fr!=null)
fr.close();
} catch (Exception e) {
// TODO: handle exception
throw new RuntimeException("讀取流關閉失敗");
}finally{
try {
if(fw!=null)
fw.close();
} catch (Exception e) {
throw new RuntimeException("寫入流關閉失敗");
}
}
}
}
字符流的緩衝區:
緩衝區的出現,提高了流的讀寫效率,在緩衝區創建前,要先創建流對象,即先將流對象初始化到構造函數中。緩衝區中封裝了數組,將數據存入,再一次性取出。
讀取流緩衝區BufferedReader中提供了按行讀取的readLine方法,方便對文本數據的讀取,當返回null時表示讀到文件末尾。readLine方法返回的是回車符之前的文本數據內容,並不返回回車符。
而寫入流緩衝區BufferedWriter中提供了一個跨平臺的換行符:newLine();可以在不同操作系統上調用,用作對數據換行。
下面通過利用加入緩衝技術的流對象,來完成對文件的拷貝。如下:
public static void main(String[] args)
{
BufferedReader fr=null;
BufferedWriter fw=null;
try {
//建立流對象,關聯指定文件,爲了提高效率,加入緩衝技術
fr=new BufferedReader(new FileReader("F:\\123.txt"));
fw=new BufferedWriter(new FileWriter("E:\\123.txt"));
//通過緩衝區的 特有操作,實現內容複製
String line=null;
while((line=fr.readLine())!=null){
fw.write(line);
//換行
fw.newLine();
//爲了數據的安全性,寫一行刷新一次。
fw.flush();
}
} catch (IOException e) {
// TODO: handle exception
throw new RuntimeException("複製失敗");
} //關閉流資源,因爲一定要執行,所以定義在finally語句中
finally{
try {
if(fr!=null)
fr.close();
} catch (Exception e) {
// TODO: handle exception
throw new RuntimeException("讀取流關閉失敗");
}finally{
try {
if(fw!=null)
fw.close();
} catch (Exception e) {
// TODO: handle exception
throw new RuntimeException("寫入流關閉失敗");
}
}
}
}
readLine的原理:無論是讀一行,或者讀取多個字符。其實最終都是在在硬盤上一個一個讀取。所以最終使用的還是read方法一個一個讀。根據這個原理,自定義一個緩衝區。如下:
//定義一個緩衝區,繼承Reader類
class MyBufferedReader extends Reader{
private Reader fr;
MyBufferedReader(Reader fr){
this.fr=fr;
}
//自定義讀一行的方法。
public String myRead()throws IOException{
//定義一個臨時容器,用來存儲數據
StringBuilder sb=new StringBuilder();
int ch=0;
while((ch=fr.read())!=-1){
if(ch=='\r')
continue;
//碰到換行標識,返回該行數據
else if(ch=='\n')
return sb.toString();
sb.append((char)ch);
}
//判斷容器中是否還有數據
if(sb.length()!=0)
return sb.toString();
return null;
}
//複寫父類Read方法
public int read(char[] cbuf, int off, int len) throws IOException {
return fr.read(cbuf,off,len);
}
//複寫關閉流方法
public void close() throws IOException{
fr.close();
}
}
這個自定義的類體現了Java設計模式中的一種常用模式:裝修設計模式。當想對已有對象進行功能增強時,可定義一個類,將已有對象傳入,基於已有對象的功能,並提供加強功能,自定義的類就是裝飾類。裝飾類通過構造方法接收被裝飾的對象,並基於被修飾的對象的功能,提供加強的功能。
裝飾和繼承相比,裝飾模式比繼承更靈活,避免了繼承體系的臃腫,降低了類與類之間的關係。裝飾類因爲增強已有對象,具備的功能和已有的是相同的,只不過提供了加強的功能,所以裝飾類和被裝飾的類通常都屬於一個體系。從繼承結構轉爲組合結構。在定義類時,不要以繼承爲主,可通過裝飾設計模式進行增強類功能。靈活性強,當裝飾類中的功能不適合時,可以再使用被裝飾類的功能。
三、字節流
字節流和字符流的基本操作是相同的,但字節流除了可以操作文本文件之外,還可以操作媒體文件。由於媒體文件的數據都是以字節存儲的,所以字節流對象可直接將媒體文件的數據寫入到指定目的文件中,而不用進行刷新動作。
字節流不用刷新的原理:字節流操作的是字節,即數據的最小單位,不需要像字符流一樣要進行轉換,所以可直接將字節數據寫入到指定目的文件中。
輸入流InputStream中有一個available()方法,返回文件中的字節個數。可以使用此方法來指定讀取方式中傳入數組的長度,減少循環判斷,但如果文件較大,虛擬機啓動分配的默認內存有限,容易造成內存溢出。所以此方法謹慎使用。
下面通過字節流拷貝一個圖片文件,如下:
public static void main(String[] args)
{
FileOutputStream fos = null;
FileInputStream fis = null;
try
{ //建立字節流對象,與文件相關聯
fos = new FileOutputStream("F:\\1.JPG");
fis = new FileInputStream("E:\\1.JPG");
byte[] buf = new byte[1024];
int len = 0;
//循環讀寫
while((len=fis.read(buf))!=-1)
{
fos.write(buf,0,len);
}
}
catch (IOException e)
{
throw new RuntimeException("複製圖片失敗");
}
//關閉流資源
finally
{
try
{
if(fis!=null)
fis.close();
}
catch (IOException e)
{
throw new RuntimeException("讀取關閉失敗");
}
try
{
if(fos!=null)
fos.close();
}
catch (IOException e)
{
throw new RuntimeException("寫入關閉失敗");
}
}
}
有個知識點要注意:在字節流中,read():會將字節byte型值提升爲int型值write(): 會將int類型轉換爲byte型,即保留二進制數的最後八位。
read() 返回值提升的原因:因爲有可能會讀到連續八個二進制1的情況,八個二進制1對應的十進制是-1,那麼數據還沒有讀完就結束,因爲-1是判斷讀取結束的結尾標記。爲了避免這種情況就對讀到的字節進行int類型的提升,在保留原字節數據的情況前面補24個0,變成int類型。可以通過將byte型數據&255獲取。最後在write()中,會把int類型轉換爲byte型,保留了最後八位。
四、轉換流
1.鍵盤錄入:
標準輸入輸出流:
System.in:對應的標準輸入設備,鍵盤。 System.in的類型是InputStream。
System.out:對應的是標準的輸出設備,控制檯。System.out的類型是PrintStream是OutputStream的子類FilterOutputStream的子類。
當使用輸入流進行鍵盤錄入時,只能一個字節一個字節進行錄入。爲了提高效率,可以自定義一個數組將一行字節進行存儲。當一行錄入完畢,再將一行數據進行顯示。這種正行錄入的方式,和字符流讀一行數據的原理是一樣的。也就是readLine方法。
那麼能不能直接使用readLine方法來完成鍵盤錄入的一行數據的讀取呢?readLine方法是字符流BufferedReader類中方法。而鍵盤錄入的read方法是字節流InputStream的方法。這時就可以利用到轉換流了。
當字節流中的數據都是字符時,通過轉換流來操作讀寫效率高。轉換流是字符流和字節流之間的橋樑方便了字符流和字節流之間的操作。下面通過一段打印鍵盤錄入的數據的程序,演示轉換流的使用。如下:
public static void main(String[] args) throws IOException
{
//獲取鍵盤錄入,利用轉換流以及緩衝技術。
BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));
//將數據打印到控制檯上。
BufferedWriter bufw = new BufferedWriter(new OutputStreamWriter(System.out));
String line = null;
while((line=bufr.readLine())!=null)
{
if("over".equals(line))
break;
bufw.write(line);
bufw.newLine();
bufw.flush();
}
bufr.close();
}
}
五、流操作規律
在要使用流對象時,要先明確三個問題:源和目的、數據是否是文本、設備是什麼。具體:
明確源和目的: 源:輸入流。InputStream Reader
目的:輸出流。OutputStream Writer
明確數據是否是文本 :是:字符流 否:字節流
明確了體系之後,這時就要明確源和目的的設備:源:內存、硬盤、鍵盤
目的:內存、硬盤、控制檯
下面通過練習進行演示:
1. 將一個文本文件中數據存儲到另一個文件中。
分析:
(1)源:因爲是源,所以使用讀取流:InputStream和Reader
明確體系:是否操作文本:是,Reader
明確設備:明確要使用該體系中的哪個對象:硬盤上的一個文件。Reader體系中可以操作文件的對象是FileReader
是否需要提高效率:是,加入Reader體系中緩衝區 BufferedReader.
(2)目的:輸出流:OutputStream和Writer
明確體系:是否操作文本:是,Writer
明確設備:明確要使用該體系中的哪個對象:硬盤上的一個文件。Writer體系中可以操作文件的對象FileWriter。
是否需要提高效率:是,加入Writer體系中緩衝區 BufferedWriter
2.將鍵盤錄入的數據保存到一個文件中。
分析:
(1)源:InputStream和Reader
是不是文本?是,Reader
設備:鍵盤。對應的對象是System.in。——爲了操作鍵盤的文本數據方便。轉成字符流按照字符串操作是最方便的。所以既然明 確了Reader,那麼就將System.in轉換成Reader。用Reader體系中轉換流,InputStreamReader
需要提高效率嗎?需要,BufferedReader
(2)目的:OutputStream Writer
是否是存文本?是,Writer。
設備:硬盤。一個文件。使用 FileWriter。
需要提高效率嗎?需要。 BufferedWriter
有時使用流對象時,涉及到指定編碼表,涉及到字符編碼轉換時就要用到轉換流,如下:
<pre name="code" class="java">/**
需求:想把鍵盤錄入的數據存儲到一個文件中。
源:鍵盤
目的:文件
把錄入的數據按照指定的編碼表(UTF-8),將數據存到文件中。
*/
class IODemo {
public static void main(String[] args)throws IOException
{
//獲取鍵盤錄入
BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
//按照指定的編碼表(UTF-8),寫入指定文件中,需要用到轉換流
BufferedWriter bw=new BufferedWriter(new OutputStreamWriter(new FileOutputStream("abc.txt"),"UTF-8"));
String line=null;
//讀寫操作
while((line=br.readLine())!=null)
{
if("over".equals(line))
break;
bw.write(line);
bw.newLine();
bw.flush();
}
bw.close();
}
}
通過System類的setIn,setOut方法可以對標準輸入輸出設備進行改變,如:
System.setIn(new FileInputStream("abc.txt")) 將標準輸入設備改爲硬盤,對應硬盤上的一個文件。
System.setOut(new PrintStream("abc.txt")) 將標準輸出設備改爲 硬盤,對應硬盤上的一個文件。
異常的日誌信息:
當程序正在執行的時候,出現問題不希望直接打印給用戶,而是需要將文件存儲其來。方便程序員查看,並即時調整、完善。
這時就要用IO,通過方法printStackTrace(PrintStream ps)來完成。在如下:
<pre name="code" class="java"> catch(Exception e)
{
//將異常輸入到一個文件上,表示異常的日誌信息
e.printStackTrace(new PrintStream("exception.log"));
}
建立日誌信息,可以通過log4j工具完成。
系統屬性信息存入文本:
之前學習過用System類獲取系統信息,現在也可以通過IO,將獲取的系統信息通過輸出流,在控制檯打印或存入到指定文件裏。
1.將信息在控制檯打印:list(System.out)
2.將信息存入到指定文件裏:list(new PrintStream("abc.txt"));
如下:
public static void main(String[] args)throws IOException
{
//獲取系統信息
Properties prop=System.getProperties();
//將信息打印在控制檯上
prop.list(System.out);
//將信息存入到文件中
prop.list(new PrintStream("F:\\123.txt"));
}
流的基本應用:
1.流是用來處理數據的。
2.處理數據時,一定要先明確數據源,與數據目的地。
3.數據源可以是文件,也可以是鍵盤。
數據目的可以是文件、控制檯、或其他設備。
4.流只是在幫助數據傳輸,並對傳輸的數據進行處理,比如過濾處理、轉換處理等。
六、File
File是文件和目錄路徑名的抽象表現形式。
File類用來將文件或文件夾封裝成對象,方便於對文件和文件夾的屬性信息進行操作。File類的實例是不可變的,也就是說,一旦創建,File對象表示抽象路徑名將不能再被改變。File對象可以作爲參數傳遞給流的構造函數。
建立File對象的三種格式:
(1)File f=new File("123.txt");將當前目錄下的123.txt封裝成對象。File類可以將存在和不存在的文件或文件夾封裝成對象。
(2)File f=nwe File("F:\\","123.txt");將文件所在目錄路徑和文件一起傳入,指定文件路徑。在以後的操作中,會應用到傳入一個目錄路徑和一個變量。
(3)File d=new File("F:\\");
File f=new File(d,"123.txt");將文件所在目錄路徑封裝成對象,再建立文件對象,降低了文件子父目錄的關聯性。
File.separator表示目錄分隔符,可跨平臺使用,相當於“\”("\\"在Windows中表示轉義分割符,而在Linux系統中不是)
File類的常見方法:
1.創建
boolean creatNewFile() ;在指定位置創建文件,如果該文件已經存在,則不創建,返回false。和輸出流不一樣,輸出流對象已建立就創建文件,而文件已經存在時,將覆蓋。
boolean mkdir() 創建文件夾,只能創建一級文件夾。
boolean mkdirs() 創建多級文件夾。
2.刪除
boolean delete() 刪除文件或目錄,文件存在,返回true 文件;文件不存在或正在被執行,返回false。
void deleteOnExit() 在程序退出時,刪除指定文件。
3.判斷
canExecute(); 是否可以是可執行文件。和Runtime類結合使用
exists();文件是否存在。
isFile();是否是文件
isDirectory();是否是文件夾
isHidden()是否是隱藏文件,例如系統卷標目錄就是隱藏文件
isAbsolute();是否是絕對路徑,未創建的話也可判斷是否是絕對路徑。
在判斷源文件對象是文件還是目錄時,必須要先判斷該文件對象封裝的內容是否存在,通過exists方法。
4.獲取信息
String getName();獲取文件名,不包含目錄名。
getPath();獲取文件的相對路徑,即創建時傳入的參數是什麼,就獲取到什麼。
getParent();獲取文件父目錄,返回的是絕對路徑的父目錄。如果傳入的是相對路徑,返回null;如果傳入的相對路徑有父目錄,返回該目錄。
getAbsolutePath();獲取文件的據對路徑。
下面通過代碼演示這種方法的返回結果:
//第一種情況:傳入絕對路徑
File f=new File("F:\\123\\456.txt");
System.out.println(f.getName());//打印”456.txt“
System.out.println(f.getPath());//打印”F:\\123\\456.txt“
System.out.println(f.getParent());//打印”F:\\123“
System.out.println(f.getAbsolutePath());//打印”F:\\123\\456.txt“
//第二種:傳入相對路徑
File f1=new File("456.txt");
System.out.println(f1.getName());//打印”456.txt“
System.out.println(f1.getPath());//打印”456.txt“
System.out.println(f1.getParent());//打印null,如果傳入的文件前有父目錄,返回父目錄。
System.out.println(f1.getAbsolutePath());//打印”D:\360Downloads\新建文件夾\TestReflect\456.txt“(當前目錄)
long length();返回文件長度。long lastModified() 返回文件最後一次被修改的時間
5.列出文件、文件過濾
static File[] listRoots() 列出可用的文件系統根目錄,即系統盤符。
String[] list() 列出當前目錄下的所在文件,包括隱藏,調用list方法的file對象必須封裝了一個目錄。該目錄必須存在。
String[] list(FilenameFilter filter) 返回一個字符串數組。獲取目錄中滿足指定過濾器的文件或目錄。FilenameFilter即文件名過濾器,它是一個接口,其中包含一個方法accept(File dir,String name),通過這個方法的boolean型返回值,對不符合條件的文件過濾掉。如下:
/**
需求:獲取一個一級目錄下的.java文件的文件名,並打印。
通過File的list方法,過濾非java文件,獲取所有java文件。
*/
class ListDemo {
public static void main(String[] args) throws IOException
{
File f=new File("F:\\123");
//對該目錄下的文件過濾,獲取所有java文件的文件名
String[]fileNames=f.list(new FilenameFilter(){
public boolean accept(File dir,String name){
return name.endsWith(".java");
}
});
//打印java文件名
for(String fileName:fileNames){
System.out.println(fileName);
}
}
}
File[] listFiles();返回一個抽象路徑名數組,獲取當前文件夾下的所有文件和目錄File[] listFiles(FilenameFileter filter):返回抽象路徑名數據,獲取目錄中滿足指定過濾器的文件目錄。
與list方法相比,listFiles方法返回的是File對象,可以進行File特有的操作。
當我們要獲取一個多級目錄下所有的文件時,就要用到一種方法:遞歸。當函數內每一次循環還可以調用本功能來實現,也就是函數自身調用自身,這種表現形式或編程手段,稱爲遞歸。使用遞歸要注意:限定條件,用來結束循環調用,否則是一個死循環;
注意遞歸的次數,以免造成內存溢出,因爲每次調用自身的時候都會先執行下一次調用自己的方法,所以會不斷在棧內存中開闢新空間,次數過多,會導致內存溢出。下面就通過遞歸,獲取一個包含很多子目錄的目錄下的所有文件。如下:
/**
列出一個目錄下的所有內容的文件名,即所有的文件和文件夾。並打印
*/
class FileDemo1
{
public static void main(String[] args)
{
File dir = new File("F:\\123");
showDir(dir);
}
//定義獲取所有文件和文件夾的方法
public static void showDir(File dir,int level)
{
System.out.println(dir.getName());
File[] files = dir.listFiles();
for(int x=0; x<files.length; x++)
{
//當遍歷到的是文件夾時,在該文件夾裏繼續遍歷
if(files[x].isDirectory())
showDir(files[x]);
//遍歷到額是文件,直接打印其文件名
else
System.out.println(files[x].getName());
}
}
}
下面是一個把獲取到的文件名或路徑存入到一個文件中的程序,通過這段程序可以將遞歸、集合、IO結合起來學習。如下:
/*
練習
將一個指定目錄下的java文件的絕對路徑,存儲到一個文本文件中。
建立一個java文件列表文件。
思路:
1,對指定的目錄進行遞歸。
2,獲取遞歸過程所以的java文件的路徑。
3,將這些路徑存儲到集合中。
4,將集合中的數據寫入到一個文件中。
*/
import java.io.*;
import java.util.*;
class JavaFileList
{
public static void main(String[] args) throws IOException
{
File dir = new File("F:\\123");
//建立集合,用於存儲文件
List<File> list = new ArrayList<File>();
//將獲取到的文件寫入集合
fileToList(dir,list);
//定義目的文件,並定義在當前目錄下
File file = new File(dir,"javalist.txt");
//從集合寫入文件
writeToFile(list,file);
}
public static void fileToList(File dir,List<File> list)
{
File[] files = dir.listFiles();
for(File file : files)
{
if(file.isDirectory())
fileToList(file,list);
else
{ //將符合條件的文件,放入集合
if(file.getName().endsWith(".java"))
list.add(file);
}
}
}
public static void writeToFile(List<File> list,File javaListFile)
{
BufferedWriter bufw = null;
try
{
bufw = new BufferedWriter(new FileWriter(javaListFile));
//遍歷集合,通過輸出流,寫入文件中
for(File f : list)
{
String path = f.getAbsolutePath();
bufw.write(path);
bufw.newLine();
bufw.flush();
}
}
catch (IOException e)
{
throw new RuntimeException("寫入失敗");
}
finally
{
try
{
if(bufw!=null)
bufw.close();
}
catch (IOException e)
{
throw new RuntimeException("關閉失敗");
}
}
}
}
-------------Java培訓、Android培訓、iOS培訓、.Net培訓、期待與您交流! -------