黑馬程式員—java基礎之IO流
------- android教育訓練、java教育訓練、期待與您交流! ----------
請記住: 這個世界上沒有任何力量能夠阻礙我們走向成功。如果有,就是我們自己。
IO流概念
IO流用來處理裝置之間的資料傳輸
Java對資料的操作是通過流的方式
Java用于操作流的對象都在IO包中
流按流向分為兩種:輸入流,輸出流。
流按操作類型分為兩種:位元組流與字元流。 位元組流可以操作任何資料,字元流隻能操作純字元資料,比較友善。
IO流常用基類
位元組流的抽象基類:
InputStream ,OutputStream
字元流的抽象基類:
Reader , Writer
由這四個類派生出來的子類名稱都是以其父類名作為子類名的字尾。
如:InputStream的子類FileInputStream。
如:Reader的子類FileReader。
注意: InputStreamReader 是字元流, 可以從位元組流中讀取字元
IO流使用步驟:
使用前,導入IO包中的類
使用時,進行IO異常處理
使用後,釋放資源
IO流(IO異常處理方式)
1.凡是能和裝置上的資料發生關聯的,調用底層資源的都會發生IOException。
2."K:\\demo.txt"沒有K盤,會抛出出FileNotFoundException(系統找不到指定的路徑)異常。
FileNotFoundException是IOException的子類。
3.初始化抛出異常,說明初始化失敗,fw還為空,故不可調用對象的close()方法,
是以抛出NullPointerException異常,finally中一定要對關閉的流對象進行不等于空的判斷。
字元流讀寫檔案
讀取檔案
定義字元流關聯指定檔案
FileReader reader = new FileReader("Test.txt");
讀取一個字元,傳回int,該字元的碼表值
int ch = reader.read();
關閉流,釋放資源
reader.close();
寫出檔案
定義字元輸出流關聯指定檔案
FileWriter writer = new FileWriter("Test.txt");
寫出一個字元,接收int碼表值
writer.write(97);
關閉流,釋放資源
writer.close();
注意事項
檔案路徑
定義檔案路徑時Windows中的目錄符号為“\”,但這個符号在Java中是特殊字元,需要轉義。
可以用“\\”或“/”表示。
讀取檔案
讀取檔案時必須保證檔案存在,否則将抛出FileNotFoundException。
寫出檔案
寫出時檔案如不存在時程式會建立新檔案,如檔案已存在則會清空原檔案内容重新寫入。
如需追加内容可調用FileWriter構造函數FileWriter(String fileName, boolean append),傳入true之後則不會清空原有檔案
練習
拷貝一個檔案
字元流緩沖區讀寫
自定義緩沖區讀寫
為什麼定義緩沖區
由于單個字元讀寫需要頻繁操作檔案,是以效率非常低。
我們可以定義緩沖區将要讀取或寫出的資料緩存,減少操作檔案次數。
緩沖區讀取
先定義一個數組,然後調用FileReader讀取一個數組的方法。
int read(char[] cbuf)
緩沖區寫出
将要寫出的資料存放在數組中,調用FileWriter方法,一次寫出一個數組。
void write(char[] cbuf,int off, int len)
内置緩沖區的BufferedReader和BufferedWriter
Java提供了帶緩沖功能的Reader和Writer類:BufferedReader,BufferedWriter
這兩個類都是提供包裝功能,需要提供其他流來使用,給其他流增加緩沖功能
當我們調用BufferedReader讀取資料時,程式會從檔案中一次讀取8192個字元用來緩沖
當我們調用BufferedWriter寫出資料時,程式會先将資料寫出到緩沖數組,直到寫滿8192個才一次性刷出到檔案中,
練習:
現有一個程式是一個試用程式,
每次啟動提示剩餘次數,如果執行了10次.提示已到期.
代碼:
package com.itheima.io.exercise;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
public classExercise4{
public static void main(String[] args)throws IOException {
BufferedReader br = new BufferedReader(newFileReader("times.txt")); //定義BufferedReader
String line = br.readLine(); //從times.txt讀取一行文本
br.close(); // 關閉流, 釋放資源
int times =Integer.parseInt(line); //把String轉為int, Integer.parseInt()
if (times > 0) { //如果次數大于0
System.out.println("歡迎試用Xxx軟體,剩餘使用次數: " + --times +"次"); //列印次數,并且-1
FileWriter fw = newFileWriter("times.txt");//定義FileWriter
fw.write(times+ ""); //把次數轉為String,寫出次數.
fw.close(); // 關閉流
}else
System.out.println("軟體已到期!");
}
}
裝飾設計模式(Decorator)
當我們需要對一個類的功能進行改進、增強的時候會使用裝飾設計模式.
格式:
含有被裝飾類的引用
通過構造函數傳入被裝飾類對象
和被裝飾類含有同樣的方法,其中調用被裝飾類的方法,對其進行改進、增強
和被裝飾類繼承同一個類或實作同一個接口,可以當做被裝飾類來使用
BufferedReader、BufferedWriter都是裝飾類,他們可以裝飾一個Reader或Writer,給被裝飾的Reader和Writer提供緩沖的功能。
就像我們用BufferedReader、BufferedWriter裝飾FileReader和FileWriter,使用的讀寫功能還是FileReader和FileWriter的,但給這兩個類的讀寫添加了緩沖功能
位元組流
基本操作與字元流相同
位元組流可以操作任意類型資料
練習:拷貝一個Jpg檔案
package com.itheima.io.test
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class Demo5_CopyByArray {
public staticvoid main(String[]args)throws IOException {
FileInputStream fis = new FileInputStream("ts.jpg");
FileOutputStream fos = new FileOutputStream("F:/ts.jpg");
byte[] buffer =newbyte[1024]; // 定義一個較小的緩沖區(1KB), 提高效率又不太占記憶體
int len; // 該變量用來記錄每次拷貝的位元組個數
while ((len = fis.read(buffer)) != -1) { // 從檔案中讀取資料到數組,len記住個數, 判斷如果不是檔案末尾(-1)
fos.write(buffer,0, len); //将數組中的資料寫出,從0号索引寫出len個
}
fis.close();
fos.close();
}
}
位元組流緩沖區讀寫
自定義緩沖區讀寫
原理和字元流相同,都是為了提高效率
定義數組緩沖資料,一次讀取一個數組,一次寫出一個數組,減少操作檔案的次數
BufferedInputStream、BufferedOutputStream
和BufferedReader、BufferedWriter原理相同,都是包裝類
BufferedInputStream、BufferedOutputStream包裝InputStream和OutputStream提供緩沖功能
讀取鍵盤輸入:
1讀取鍵盤錄入:
System.out:對應的是标準輸出裝置,控制台。
System.in :對應的是标準輸入裝置:鍵盤。
2.in
public static final InputStream in
“标準”輸入流。此流已打開并準備提供輸入資料。
通常,此流對應于鍵盤輸入或者由主機環境或使用者指定的另一個輸入源。
3.需求:
通過鍵盤錄入資料。
當錄入一行資料後,就将該行資料進行列印。
如果錄入的資料是over,那麼停止錄入。
4.注意:
ctrl+c :可以強行停止程式,其原理是因為它調用封裝了回車換行标記。
10.IO流(讀取轉換流)
1.通過剛才的鍵盤錄入一行資料并列印其大寫,發現其實就是讀一行資料的原理。
也就是readLine方法。
能不能直接使用readLine方法來完成鍵盤錄入的一行資料的讀取呢?
readLine方法是字元流BufferedReader類中的方法。
而鍵盤錄入的read方法是位元組流InputStream的方法。
那麼能不能将位元組流轉成字元流,再使用字元流緩沖區的readLine方法呢?
2.轉換流
Reader ---> InputStreamReader(java.io包)
1)InputStreamReader 是位元組流通向字元流的橋梁:它使用指定的 charset 讀取位元組并将其解碼為字元。
它使用的字元集可以由名稱指定或顯式給定,或者可以接受平台預設的字元集。
每次調用 InputStreamReader 中的一個 read() 方法都會導緻從底層輸入流讀取一個或多個位元組。
要啟用從位元組到字元的有效轉換,可以提前從底層流讀取更多的位元組,使其超過滿足目前讀取操作所需的位元組。
為了達到最高效率,可要考慮在 BufferedReader 内包裝InputStreamReader。例如:
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
2)構造方法:
InputStreamReader(InputStream in)
建立一個使用預設字元集的 InputStreamReader。
3)特有方法:
<1> void close()
關閉該流并釋放與之關聯的所有資源。
<2>String getEncoding()
傳回此流使用的字元編碼的名稱。
<3>int read()
讀取單個字元。
<4>int read(char[] cbuf, int offset, int length)
将字元讀入數組中的某一部分。
<5>boolean ready()
判斷此流是否已經準備好用于讀取。
3.BufferedReader中有readLine()方法,而且BufferedReader可作用于Reader及其子類。
InputStreamReader是Reader子類,故可以使用BufferedReader對其進行緩沖區技術高效操作.
4.代碼:
//擷取鍵盤錄入對象。
InputStream in = System.in;
//将位元組流對象轉成字元流對象,使用轉換流InputStreamReader.
InputStreamReader isr = new InputStreamReader(in);
//為了提高效率,将字元串進行。使用BufferedReader.
BufferedReader bufr = new BufferedReader(isr);
String line = null;
while ((line = bufr.readLine()) != null)
{
if("over".equals(line))
break;
System.out.println(line.toUpperCase());
}
bufr.close();
标準輸入輸出流
System類中的成員變量:in,out。
它們各代表了系統标準的輸入和輸出裝置。
預設輸入裝置是鍵盤,輸出裝置是顯示器。
System.in的類型是InputStream.
System.out的類型是PrintStream是OutputStream的子類FilterOutputStream 的子類.
練習:通過修改标準輸入輸出流,使用System.in和System.out拷貝檔案
流基本應用小節
流是用來處理資料的。
處理資料時,一定要先明确資料源,或者資料目的地
資料源可以是檔案,可以是鍵盤或者其他裝置。
資料目的地可以是檔案、顯示器或者其他裝置。
而流隻是在幫助資料進行傳輸,并對傳輸的資料進行處理,比如過濾處理、轉換處理等。
IO流(寫入轉換流)
1.寫入轉換流:
Writer ---> OutputStreamWriter(java.io包)
1)public class OutputStreamWriterextends WriterOutputStreamWriter 是字元流通向位元組流的橋梁:
可使用指定的 charset 将要寫入流中的字元編碼成位元組。它使用的字元集可以由名稱指定或顯式給定,
否則将接受平台預設的字元集。
每次調用 write() 方法都會導緻在給定字元(或字元集)上調用編碼轉換器。在寫入底層輸出流之前,
得到的這些位元組将在緩沖區中累積。可以指定此緩沖區的大小,不過,預設的緩沖區對多數用途來說已足夠大。
注意,傳遞給 write() 方法的字元沒有緩沖。
為了獲得最高效率,可考慮将 OutputStreamWriter 包裝到BufferedWriter 中,
以避免頻繁調用轉換器。例如:
Writer out
=new BufferedWriter(new OutputStreamWriter(System.out));
2)構造函數:
OutputStreamWriter(OutputStream out)
建立使用預設字元編碼的 OutputStreamWriter。
3)方法摘要
<1>void close()
關閉此流,但要先重新整理它。
<2>void flush()
重新整理該流的緩沖。
<3>String getEncoding()
傳回此流使用的字元編碼的名稱。
<4>void write(char[] cbuf, int off, int len)
寫入字元數組的某一部分。
<5>void write(int c)
寫入單個字元。
<6>void write(String str, int off, int len)
寫入字元串的某一部分。
轉換流
用來将檔案或者檔案夾路徑封裝成對象
友善對檔案與檔案夾進行操作。
File對象可以作為參數傳遞給流的構造函數。
了解File類中的常用方法。
File類常見方法:
1.建立:
boolean createNewFile()
throws IOException
在指定位置建立檔案,如果該檔案已經存在,則不建立,傳回false.
和輸出流不一樣,輸出流對象一建立建立檔案,而且檔案已經存在,會覆寫。
boolean mkdir():建立檔案夾。
boolean mkdirs():建立多級檔案夾。
2.删除:
<1>boolean delete()
删除此抽象路徑名表示的檔案或目錄。删除失敗傳回false.
<2>void deleteOnExit()
在虛拟機終止時,請求删除此抽象路徑名表示的檔案或目錄。在程式退出時删除指定檔案。
注意:若前面的代碼發生異常,則此時後面的删除操作就執行不到,此時就會産生垃圾。
就算是在finally中删除有時也未必有效。因為正在被流操作的檔案會報提示無法删除。
此時用deleteOnExit()退出時删除方法就很容易解決這個問題了。
3.判斷:
<1>boolean canExecute()
測試應用程式是否可以執行此抽象路徑名表示的檔案。
<2>boolean canRead()
測試應用程式是否可以讀取此抽象路徑名表示的檔案。
<3>boolean canWrite()
測試應用程式是否可以修改此抽象路徑名表示的檔案。
<4>int compareTo(File pathname)
按字母順序比較兩個抽象路徑名。
<5>boolean exists()
測試此抽象路徑名表示的檔案或目錄是否存在。
<6>boolean isAbsolute()
測試此抽象路徑名是否為絕對路徑名。
<7>boolean isDirectory()
測試此抽象路徑名表示的檔案是否是一個目錄。
<8>boolean isFile()
測試此抽象路徑名表示的檔案是否是一個标準檔案。
<9>boolean isHidden()
測試此抽象路徑名指定的檔案是否是一個隐藏檔案。
注意:檔案夾也可能叫a.txt
4.擷取資訊:
<1>String getName()
傳回由此抽象路徑名表示的檔案或目錄的名稱。
<2>String getParent()
傳回此抽象路徑名父目錄的路徑名字元串;如果此路徑名沒有指定父目錄,則傳回 null。
<3>File getParentFile()
傳回此抽象路徑名父目錄的抽象路徑名;如果此路徑名沒有指定父目錄,則傳回 null。
<4>String getPath()
将此抽象路徑名轉換為一個路徑名字元串。
<5>String getAbsolutePath()
傳回此抽象路徑名的絕對路徑名字元串。
<6>long lastModified()
傳回此抽象路徑名表示的檔案最後一次被修改的時間。
<7>long length()
傳回由此抽象路徑名表示的檔案的長度。
<8>boolean renameTo(File dest)
重新命名此抽象路徑名表示的檔案。
5.檔案清單
檔案清單功能:
1)static File[] listRoots()
列出可用的檔案系統根。
2)String[] list()
傳回一個字元串數組,這些字元串指定此抽象路徑名表示的目錄中的檔案和目錄。
3)String[] list(FilenameFilter filter)
傳回一個字元串數組,這些字元串指定此抽象路徑名表示的目錄中滿足指定過濾器的檔案和目錄。
6.檔案清單2:
1)String[] list(FilenameFilter filter)
傳回一個字元串數組,這些字元串指定此抽象路徑名表示的目錄中滿足指定過濾器的檔案和目錄。
2)File[] listFiles()
傳回一個抽象路徑名數組,這些路徑名表示此抽象路徑名表示的目錄中的檔案。
3)File[] listFiles(FileFilter filter)
傳回抽象路徑名數組,這些路徑名表示此抽象路徑名表示的目錄中滿足指定過濾器的檔案和目錄。
4)File[] listFiles(FilenameFilter filter)
傳回抽象路徑名數組,這些路徑名表示此抽象路徑名表示的目錄中滿足指定過濾器的檔案和目錄。
遞歸
函數自己調用自己。
應用場景:
當某一功能要重複使用時。
遞歸要注意:
1)限定條件。遞歸時一定要明确結束條件。
2)要注意遞歸的次數。盡量避免記憶體溢出
遞歸示範:
package com.itheima.recursion
public classDemo4_Recursion{
private static int x;
public static void main(String[] args) {
System.out.println(count(5)); // 15
System.out.println(count(100)); // 5050
}
public static int count(int n) {
//控制循環的條件為n-1
return n == 1 ? 1 :count(n- 1) + n;
}
練習:
1.列印出一個檔案中所有帶.java的檔案
package com.itheima.io.file.exercise;
import java.io.File;
public classExercise3{
public static void main(String[] args) {
File dir = newFile("F:/Itcast/20130322_JavaSE/Code/day22");
print(dir);
}
public static void print(File dir) {//列印出該檔案夾中所有.java檔案的檔案名
File[] subFiles = dir.listFiles(); //擷取所有子檔案
for (File subFile : subFiles) { //周遊
if (subFile.isFile()&& subFile.getName().endsWith(".java")) //如果是檔案并且檔案名以.java結尾
System.out.println(subFile.getName()); //列印
if (subFile.isDirectory()) //如果是檔案夾
print(subFile); //遞歸
}
}
}
2.删除嵌套檔案夾
package com.itheima.io.file.exercise;
import java.io.File;
public classExercise4{
public static void main(String[] args) {
File file = newFile("F:/Itcast/20130322_JavaSE/Code/day22/day22");
deleteDir(file);
}
public static void deleteDir(File dir) {
File[] subFiles = dir.listFiles(); //擷取子檔案
for (File subFile : subFiles) { //循環周遊所有子檔案
if (subFile.isFile()) //如果是檔案
subFile.delete(); // 直接删除
else //是檔案夾
deleteDir(subFile); // 遞歸删除該檔案夾下所有内容
}
dir.delete(); // 所有子檔案的周遊删除了,删除目前檔案夾
}
}
IO流(Properties存取配置檔案)
1.Properties的方法:
1)voidlist(PrintStream out)
将屬性清單輸出到指定的輸出流。
2)voidlist(PrintWriter out)
将屬性清單輸出到指定的輸出流。
3)voidload(InputStream inStream)
從輸入流中讀取屬性清單(鍵和元素對)。
4)void load(Readerreader)
按簡單的面向行的格式從輸入字元流中讀取屬性清單(鍵和元素對)。
5)voidstore(OutputStream out, String comments)//comments為注釋
以适合使用load(InputStream) 方法加載到Properties 表中的格式,
将此 Properties 表中的屬性清單(鍵和元素對)寫入輸出流。
6)void store(Writerwriter, String comments)
以适合使用load(Reader) 方法的格式,将此Properties 表中的屬性清單(鍵和元素對)
寫入輸出字元。
2.示範:
如何将流中的資料存儲到集合中。
想要将info.txt中鍵值資料存到集合中進行操作。
1)用一個流和info.txt檔案關聯。
2)讀取一行資料,将該行資料用“=”進行切割。
3)等号左邊作為鍵,右邊作為值。存入到Properties集合中即可
配置檔案有兩種:
1)以“.properties”結尾的。
如:name=zhangsan
age=20;
2)以".xml"結尾的
如:
<persons>
<person id="001">
<name>zhangsan</name>
<age>30</age>
<address>bj</address>
</person>
<person>
<name>lisi</name>
</person>
</persons>
是以要想使用某個軟體不續費而繼續使用的話,可以删除配置檔案即可,
如果配置檔案放在了C:\WINDOWS\sytem32中,則删除也無用。
IO包中的其他類
序列流
SequenceInputStream
可以将多個位元組輸入流整合成一個流,在使用這個流讀取的時候,讀到第一個流的末尾時繼續讀第二個,第二個讀到末尾則繼續讀第三個,以此類推,直到讀到最後一個流的末尾傳回-1
操作對象
ObjectOutputStream
可以将實作了Serializable的接口的對象轉成位元組寫出到流中
ObjectInputStream
可以從流中讀取一個ObjectOutputStream流寫出的對象
列印流
PrintStream 、PrintWriter
相比普通的OutputStream和Writer增加了print()和println()方法,這兩個方法可以輸出實參對象的toString()方法傳回值
這兩個類還提供自動flush()的功能
操作記憶體緩沖數組
ByteArrayInputStream:可以從一個位元組數組中讀取位元組。
ByteArrayOutputStream: 寫出到位元組數組(記憶體)中,可以擷取寫出的内容裝入一個位元組數組。通常我們用這個流來緩沖資料。
CharArrayReader:可以從一個字元數組中讀取字元。
CharArrayWriter:寫出字元到字元數組(記憶體)中,可以擷取寫出的内容裝入一個字元數組。
管道流
PipedInputStream:管道輸入流,可以從管道輸出流中讀取資料
PipedOutputStream:管道輸出流,可以向管道輸入流中寫出資料
操作基本資料類型
DataInputStream、DataOutputStream
可以按照基本資料類型占用空間大小讀寫資料
随機通路檔案
RandomAccessFile
今日本帖練習:
1. 分割檔案(從鍵盤接收一個檔案的路徑,然後将檔案分為五份,存入一個和檔案同名的檔案夾内)
package com.itheima.io.exercise;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
public classExercise1{
public static void main(String[] args)throws IOException {
File file = getFile();
cut(file);
}
private static void cut(File file)throws IOException {
// 擷取檔案大小,計算每一分的大小
long length = file.length()/ 5 + 1;
// 建立一個臨時檔案夾
File dir = newFile(".temp");
dir.mkdir();
// 循環5次
BufferedInputStream bis = new BufferedInputStream(newFileInputStream(file));
for (int i = 0; i < 5; i++) {
// 每次從目标檔案中讀取資料,寫出到新檔案
File newFile = newFile(dir, i +"");
BufferedOutputStream bos =newBufferedOutputStream(newFileOutputStream(newFile));
int b;
for (int j = 0; j < length&& (b = bis.read()) != -1; j++) {
bos.write(b);
}
bos.close();
}
bis.close();
// 删除源檔案,把檔案夾改名
file.delete();
dir.renameTo(file);
}
private static File getFile() throws IOException {
System.out.println("請輸入要分割的檔案路徑:");
BufferedReader br = new BufferedReader(newInputStreamReader(System.in));
while (true) {
File file = newFile(br.readLine()); // 從鍵盤接收一個字元串,封裝成File對象
if (!file.exists()) { //如果不存在
System.out.println("您輸入的路徑不存在,請重新輸入:"); //提示
}elseif(file.isDirectory()) { //如果是檔案夾
System.out.println("您輸入的是檔案夾路徑,請輸入一個檔案路徑:"); //提示
}else{ //存在,且不是檔案夾,那麼就是一個檔案
return file;
}
}
}
}
2合并檔案(從鍵盤中接收一個檔案夾路徑,練習一中分割的,
然後将切割的檔案合并,要求還原為分割以前的檔案.)
packagecom.itheima.io.exercise;
importjava.io.BufferedReader;
importjava.io.File;
importjava.io.FileInputStream;
importjava.io.FileOutputStream;
importjava.io.IOException;
importjava.io.InputStream;
importjava.io.InputStreamReader;
importjava.io.SequenceInputStream;
importjava.util.ArrayList;
importjava.util.Collections;
public classExercise2 {
public static void main(String[] args)throws IOException {
File dir = getDir();
merge(dir);
}
private static void merge(File dir)throws IOException {
// 周遊所有子檔案, 每個子檔案建立輸入流, 裝入集合
ArrayList<InputStream>al = new ArrayList<>();
for (File subFile :dir.listFiles())
al.add(newFileInputStream(subFile));
// 從集合擷取Enumeration, 建立SequenceInputStream合并流
SequenceInputStream sis = newSequenceInputStream(Collections.enumeration(al));
// 從SequenceInputStream讀取資料寫到一個臨時檔案中
File file = newFile(".temp");
FileOutputStream fos = newFileOutputStream(file);
byte[] buffer = newbyte[1024];
int len;
while ((len =sis.read(buffer)) != -1)
fos.write(buffer, 0,len);
sis.close();
fos.close();
// 删除檔案夾, 把檔案改名
for (File subFile :dir.listFiles())
subFile.delete();
dir.delete();
file.renameTo(dir);
}
private static File getDir() throwsIOException {
System.out.println("請輸入要合并的檔案夾路徑:");
BufferedReader br = newBufferedReader(new InputStreamReader(System.in));
while (true) {
File file = newFile(br.readLine()); //從鍵盤接收一個字元串, 封裝成File對象
if (!file.exists()){ // 如果不存在
System.out.println("您輸入的路徑不存在, 請重新輸入:"); // 提示
} else if(file.isFile()) { //如果是檔案夾
System.out.println("您輸入的是檔案路徑, 請輸入一個檔案夾路徑:"); // 提示
} else { //存在, 且不是檔案夾, 那麼就是一個檔案
returnfile;
}
}
}
}
------- android教育訓練、java教育訓練、期待與您交流! ----------