IO流總結(基礎知識)
IO流總結
IO這章的知識在上面一篇部落格也說過一點,主要要體會一下裝飾者設計模式和介面卡設計模式,這樣更利於我們理解複雜的IO體系結構。今天就讓我們看一看。不過在講IO
之前,我們先把檔案(File)的知識簡單過一下。
一、檔案File
檔案大家都不陌生,檢視JDK幫助文件,我們知道File的定義——檔案和目錄(資料夾)路徑名的抽象表示形式。1、構造方法
File(String pathname):根據一個路徑得到File物件File(String parent, String child):根據一個目錄和一個子檔案/目錄得到File物件
File(File parent, String child):根據一個父File物件和一個子檔案/目錄得到File物件
2、建立功能
public boolean createNewFile():建立檔案 如果存在這樣的檔案,就不建立了public boolean mkdir():建立資料夾 如果存在這樣的資料夾,就不建立了
public boolean mkdirs():建立資料夾,如果父資料夾不存在,會幫你創建出來
舉例:
//需求1:我要在e盤目錄下建立一個檔案demo
File file = new File("e:\\a.txt");
大家可能會認為這裡用了關鍵字new,那麼檔案File就會出來了,其實不是,因為File的定義是:檔案和目錄(資料夾)路徑名的抽象表示形式。它僅僅是檔案的表示
形式,不是真實的檔案。 所以需要呼叫這樣一句程式碼file.createNewFile()
//需求2:我要在e盤目錄test下建立一個檔案b.txt
File file = new File("e:\\test\\b.txt");System.out.println("createNewFile:" + file3.createNewFile());
執行之後可以看到控制檯打印出:
Exception in thread "main" java.io.IOException: 系統找不到指定的路徑。
所以注意:要想在某個目錄下建立內容,該目錄首先必須存在。我們可以這麼做:File file1=new File("e:\\test);file1.mkdir();//先把資料夾(目錄)創建出來然後在:
File file2=new File(e:\\test\\b.txt);file2.createNewFile();//在建立檔案
//看下面這行程式碼File file = new File("e:\\liuyi\\a.txt");
System.out.println("mkdirs:" + file8.mkdirs());
你會看到a.txt是個目錄,不要認為指定了.txt字尾就是檔案了,要看它呼叫的是creatNewFile()還是mkdir();
3、刪除功能
public boolean delete()這個方法比較簡單,注意:
A:如果你建立檔案或者資料夾忘了寫碟符路徑,那麼,預設在專案路徑下。
B:Java中的刪除不走回收站。
C:要刪除一個資料夾,請注意該資料夾內不能包含檔案或者資料夾,所以delete一次只能刪除一個檔案,如果想刪除一個資料夾,那必須要迴圈遍歷了。
4、重新命名功能
public boolean renameTo(File dest)如果路徑名相同,就是改名。
如果路徑名不同,就是改名並剪下。
需求:將G盤所有視訊檔案該名成“0?-網路程式設計(概述).avi”,原來視訊檔名稱為:黑馬程式設計師_畢向東_Java基礎視訊教程第23天-0?-網路程式設計(概述).avi
<span style="font-size:18px;">public class FileDemo {
private static File dest;
public static void main(String[] args) {
//封裝目錄
File file=new File("G:\\黑馬入學\\網路程式設計1");
File[] sorcFolder = file.listFiles();
for (File f : sorcFolder) {
String name = f.getName();
//黑馬程式設計師_畢向東_Java基礎視訊教程第23天-01-網路程式設計(概述).avi
//System.out.println(name);
int index = name.indexOf('-');
String string = name.substring(index+1);
//再次封裝新的檔案目錄
dest = new File(file,string);
f.renameTo(dest);
}
}
}</span>
執行結果:一看全部改名成功,哈哈。。。
5、判斷功能
public boolean isDirectory():判斷是否是目錄public boolean isFile():判斷是否是檔案
public boolean exists():判斷是否存在
public boolean canRead():判斷是否可讀
public boolean canWrite():判斷是否可寫
public boolean isHidden():判斷是否隱藏
6、簡單獲取功能
public String getAbsolutePath():獲取絕對路徑public String getPath():獲取相對路徑
public String getName():獲取名稱
public long length():獲取長度。位元組數
public long lastModified():獲取最後一次的修改時間,毫秒值
示例程式碼:
<span style="font-size:18px;"> public class FileDemo {
public static void main(String[] args) {
// 建立檔案物件
File file = new File("demo\\test.txt");
System.out.println("getAbsolutePath:" + file.getAbsolutePath());
System.out.println("getPath:" + file.getPath());
System.out.println("getName:" + file.getName());
System.out.println("length:" + file.length());
System.out.println("lastModified:" + file.lastModified());
// 1432518063391
Date d = new Date(1432518063391L);
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String s = sdf.format(d);
System.out.println(s);
}
}</span>
執行結果是:
7、高階獲取功能
public String[] list():獲取指定目錄下的所有檔案或者資料夾的名稱陣列public File[] listFiles():獲取指定目錄下的所有檔案或者資料夾的File陣列
後者功能更為強大一點,因為返回的是File物件,所以可以得到File的各種屬性。
8.檔名稱過濾器——FilenameFilter
需求:判斷E盤目錄下是否有後綴名為.jpg的檔案,如果有,就輸出此檔名稱public String[] list(FilenameFilter filter)
public File[] listFiles(FilenameFilter filter)
示例程式碼:
<span style="font-size:18px;">public class FileDemo {
public static void main(String[] args) {
//封裝e盤
File file=new File("e:\\");
// 獲取該目錄下所有檔案或者資料夾的String陣列
String[] list = file.list(new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
//如果是檔案並且是以.jpg結尾的,返回true,一旦返回true,name就會被放進String陣列中,
return new File(dir, name).isFile() && name.endsWith(".jpg");
}
});
for (String string : list) {
System.out.println(string);
}
}
}</span>
雖然普通的方式也能實現,這樣看起來,程式碼更加優雅,現在我們來看看list(FilenameFilter filter)的原始碼<span style="font-size:18px;">public String[] list(FilenameFilter filter) {
String names[] = list();
if ((names == null) || (filter == null)) {
return names;
}
List<String> v = new ArrayList<>();
for (int i = 0 ; i < names.length ; i++) {
if (filter.accept(this, names[i])) {
v.add(names[i]);
}
}
return v.toArray(new String[v.size()]);
}</span>
二、IO相關操作
IO因為體系過於龐大,這裡並不面面俱到,撿其重點來說。上面簡單說了一下File的使用,File的讀取,寫入,複製。相信都是我們經常使用的功能。談起IO流,首先我們要對它有個大致的認識,也就是IO的分類,請看下面:A:按流向分
輸入流讀取資料
輸出流寫出資料
B:按資料型別
位元組流
位元組輸入流
位元組輸出流
字元流
字元輸入流
字元輸出流
如果你不知道什麼是位元組流,什麼是字元流,我告訴你,如果電腦記事本可以開啟的並且可以看得懂的,那就是可以用字元流操作的檔案,當然也可以用位元組流來操作,如果電腦記事本能開啟,但是我們看不懂的,那就是用位元組流來操作的檔案,比如MP3檔案、JPG檔案等,現在我們來看看IO是怎麼來操作File的。
1、位元組流操作檔案
1.1、FileOutputStream寫出資料
A:操作步驟a:建立位元組輸出流物件
b:呼叫write()方法
c:釋放資源
B:示例程式碼:
FileOutputStream fos = new FileOutputStream("fos.txt");
fos.write("hello".getBytes());
fos.close();
1.2、FileInputStream讀取資料
FileInputStream讀取資料A:操作步驟
a:建立位元組輸入流物件
b:呼叫read()方法
c:釋放資源
B:程式碼體現:
FileInputStream fis = new FileInputStream("fos.txt");
//方式1,我們一次讀取一個位元組
int by = 0;
while((by=fis.read())!=-1) {
System.out.print((char)by);
}
//方式2 我們一次讀取一個位元組陣列
byte[] bys = new byte[1024];
int len = 0;
while((len=fis.read(bys))!=-1) {
System.out.print(new String(bys,0,len));
}
fis.close();
1.3、四種方式複製檔案效率比較
FileInputStream、FileOutputStream這兩個類我們稱之為基本位元組流,相對於基本位元組流,IO中還有高效位元組流BufferedInputStream、BufferedOutputStream看完了這個,那麼問題來,哪種方式好呢?
下面我們來看一個例子:
需求:把G:\IO流(IO流小結圖解).avi複製到當前專案目錄下的copy.avi中
位元組流四種方式複製檔案:
* 基本位元組流一次讀寫一個位元組
* 基本位元組流一次讀寫一個位元組陣列
* 高效位元組流一次讀寫一個位元組
* 高效位元組流一次讀寫一個位元組陣列
G:\IO流(IO流小結圖解).av的資訊如下:
示例程式碼:
<span style="font-size:18px;">public class CopyAVI {
public static void main(String[] args) throws IOException {
long start = System.currentTimeMillis();
method1("G:\\IO流(IO流小結圖解).avi", "copy1.avi");
//method2("G:\\IO流(IO流小結圖解).avi", "copy2.avi");
//method3("G:\\IO流(IO流小結圖解).avi", "copy3.avi");
//method4("G:\\IO流(IO流小結圖解).avi", "copy4.avi");
long end = System.currentTimeMillis();
System.out.println("共耗時:" + (end - start) + "毫秒");
}
// 高效位元組流一次讀寫一個位元組陣列:
public static void method4(String srcString, String destString)throws IOException {
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(srcString));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(destString));
byte[] bys = new byte[1024];
int len = 0;
while ((len = bis.read(bys)) != -1) {
bos.write(bys, 0, len);
}
bos.close();
bis.close();
}
// 高效位元組流一次讀寫一個位元組:
public static void method3(String srcString, String destString)throws IOException {
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(srcString));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(destString));
int by = 0;
while ((by = bis.read()) != -1) {
bos.write(by);
}
bos.close();
bis.close();
}
// 基本位元組流一次讀寫一個位元組陣列
public static void method2(String srcString, String destString)throws IOException {
FileInputStream fis = new FileInputStream(srcString);
FileOutputStream fos = new FileOutputStream(destString);
byte[] bys = new byte[1024];
int len = 0;
while ((len = fis.read(bys)) != -1) {
fos.write(bys, 0, len);
}
fos.close();
fis.close();
}
// 基本位元組流一次讀寫一個位元組
public static void method1(String srcString, String destString)throws IOException {
FileInputStream fis = new FileInputStream(srcString);
FileOutputStream fos = new FileOutputStream(destString);
int by = 0;
while ((by = fis.read()) != -1) {
fos.write(by);
}
fos.close();
fis.close();
}
}</span>
執行結果:方法一執行:
方法二執行:
方法三執行:
方法四執行:
四種方式的效能差異,通過這個執行結果,想必大家也知道了 ,所以在實際開發中選擇哪一個來操作檔案,相信大家已經有了選擇了,反正我是喜歡用方法四!!
2、字元流操作檔案
大家可能會問,操作檔案已經有了位元組流,為什麼還要說字元流呢,哎,誰叫咱們是中國人呢,漢字比較複雜,一個漢字站了兩個位元組,所以直接用字元流操作不夠方便所以就有了字元流。我們來看看字元流的體系結構。2.1字元流體系結構
|--字元流|--字元輸入流
Reader
int read():一次讀取一個字元
int read(char[] chs):一次讀取一個字元陣列
|--InputStreamReader
|--FileReader
|--BufferedReader
String readLine():一次讀取一個字串
|--字元輸出流
Writer
void write(int ch):一次寫一個字元
void write(char[] chs,int index,int len):一次寫一個字元陣列的一部分
|--OutputStreamWriter
|--FileWriter
|--BufferedWriter
void newLine():寫一個換行符
void write(String line):一次寫一個字串
字元流的讀寫是由基類Reader和Writer來完成的,它們各有兩個子類來幫助我們來操作檔案。Reader類的子類BufferedReader與Writer的子類BufferedWriter,我們一定可以猜到這是高效字元流操作檔案使用的!那麼,Reader類的子類InputStreamReader與Writer的子類OutputStreamWriter是幹什麼的呢?從類的名稱來來看,前面是位元組的,後面是字元的,我告訴你,這兩個類就是轉換流,把位元組流轉換成字元流,那麼這有什麼用呢?-----鍵盤錄入!!!一會你們就知道了。。。。。注意OutputStreamWriter還有子類FileWriter,InputStreamReader還有子類FileReader。
2.1、字元流操作檔案
我們先看一個字元流操作檔案的例子。需求:把當前專案目錄下的a.txt內容複製到當前專案目錄下的b.txt中
資料來源:
a.txt -- 讀取資料 -- 字元轉換流 -- InputStreamReader -- FileReader -- BufferedReader
目的地:
b.txt -- 寫出資料 -- 字元轉換流 -- OutputStreamWriter -- FileWriter -- BufferedWriter
示例程式碼:
<span style="font-size:18px;">public class CopyFileDemo {
public static void main(String[] args) throws IOException {
// 封裝資料來源
BufferedReader br = new BufferedReader(new FileReader("a.txt"));
// 封裝目的地
BufferedWriter bw = new BufferedWriter(new FileWriter("b.txt"));
// 兩種方式其中的一種一次讀寫一個字元陣列,當然此處你也可以一次讀取一個位元組的方法
// char[] chs = new char[1024];
// int len = 0;
// while ((len = br.read(chs)) != -1) {
// bw.write(chs, 0, len);
// bw.flush();
// }
// 還有一種方式讀寫資料,這就是BufferedWriter、BufferedReader的強大之處。。。。
String line = null;
//下面三句話,我習慣連著寫,寫入、換行、重新整理,動作相當連貫有木有啊!!!!!!
while ((line = br.readLine()) != null) {
bw.write(line);
bw.newLine();
bw.flush();
}
// 釋放資源
bw.close();
br.close();
}
}</span>
程式碼解讀: bw.newLine();這句話的意思是換行,呼叫的效果和我們寫“\r\n”是一樣的,但是newLine可以誇平臺的,這就是區別!readLine()這個方法有點厲害,因為它一次讀一行!!!
部落格寫到這裡,我們發現,複製檔案檔案起碼有5種方式,前面所講的1.基本位元組流一次讀寫一個位元組、2.基本位元組流一次讀寫一個位元組陣列、3.高效位元組流一次讀寫一個位元組、4.高效位元組流一次讀寫一個位元組陣列共4種,再加上上面的一種,共5種,而且第五種是最好的,個人感覺。。。。
3、IO程式設計實戰
3.1、複製單級資料夾
/** 需求:複製指定目錄下的指定檔案,並修改後綴名。
* 指定的檔案是:.java檔案。
* 指定的字尾名是:.txt
* 指定的目錄是:txt
* 資料來源:G:\designModel
* 目的地:G:\txt
*
* 分析:
* A:封裝目錄
* B:獲取該目錄下的java檔案的File陣列
* C:遍歷該File陣列,得到每一個File物件
* D:把該File進行復制
* E:在目的地目錄下改名
*/
* 資料來源:G:\designModel如下圖所示:
示例程式碼:
<span style="font-size:18px;">public class CopyFolder {
public static void main(String[] args) throws IOException {
//A:封裝目錄
File srcFolder=new File("G:\\designModel");
//封裝目的地
File destFolder=new File("G:\\myNote");
//如果目的地檔案不存在,就建立一個
if(!destFolder.exists()){
destFolder.mkdir();
}
//B:獲取該目錄下的java檔案的File陣列
File[] srcFiles = srcFolder.listFiles(new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
// 檔案過濾器,得到.java檔案,去除其他型別的檔案
return new File(dir,name).isFile()&&name.endsWith(".java");
}
});
// C:遍歷該File陣列,得到每一個File物件
for (File file : srcFiles) {
String name = file.getName();
File newFile=new File(destFolder,name);
copyFile(file,newFile);
}
//E:在目的地目錄下改名
for (File file :destFolder.listFiles()) {
String name = file.getName();
String newName = name.replace(".java", ".txt");
File newFile=new File(destFolder,newName);
file.renameTo(newFile);
}
}
// D:把該File進行復制
private static void copyFile(File file, File newFile) throws IOException {
BufferedInputStream bis=new BufferedInputStream(new FileInputStream(file));
BufferedOutputStream bos=new BufferedOutputStream(new FileOutputStream(newFile));
byte [] bys=new byte[1024];
int len=0;
while((len=bis.read(bys))!=-1){
bos.write(bys,0,len);
}
//關閉資源
bis.close();
bos.close();
}
}</span>
執行結果:
3.2、複製多級資料夾
思路分析
資料來源:G:\\
目的地:G:\\
A:封裝資料來源File
B:封裝目的地File
C:判斷該File是資料夾還是檔案
a:是資料夾
就在目的地目錄下建立該資料夾
獲取該File物件下的所有檔案或者資料夾File物件
遍歷得到每一個File物件
回到C
b:是檔案
複製(位元組流)
示例程式碼:
<span style="font-size:18px;">public class CopyFolders{
public static void main(String[] args) throws IOException {
// 封裝資料來源File
File srcFile = new File("G:\\");
// 封裝目的地File
File destFile = new File("E:\\");
// 複製資料夾的功能
copyFolder(srcFile, destFile);
}
private static void copyFolder(File srcFile, File destFile)throws IOException {
// 判斷該File是資料夾還是檔案
if (srcFile.isDirectory()) {
// 資料夾
File newFolder = new File(destFile, srcFile.getName());
newFolder.mkdir();
// 獲取該File物件下的所有檔案或者資料夾File物件
File[] fileArray = srcFile.listFiles();
for (File file : fileArray) {
copyFolder(file, newFolder);
}
} else {
// 檔案
File newFile = new File(destFile, srcFile.getName());
copyFile(srcFile, newFile);
}
}
//複製檔案
private static void copyFile(File srcFile, File newFile) throws IOException {
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(srcFile));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(newFile));
byte[] bys = new byte[1024];
int len = 0;
while ((len = bis.read(bys)) != -1) {
bos.write(bys, 0, len);
}
bos.close();
bis.close();
}
}</span>
3.3 鍵盤錄入5個學生資訊(姓名,語文成績,數學成績,英語成績),按照總分從高到低存入文字檔案
思路分析:A:建立學生類
B:建立集合物件
因為要排序,所以用TreeSet<Student>
C:鍵盤錄入學生資訊儲存到集合
D:遍歷集合,把資料寫到文字檔案
<span style="font-size:18px;">public class Student {
// 姓名
private String name;
// 語文成績
private int chinese;
// 數學成績
private int math;
// 英語成績
private int english;
//構造方法和setter/getter方法省去了。。。。。。。。
}</span>
<span style="font-size:18px;">public class StudentIO {
public static void main(String[] args) throws IOException {
// 建立集合物件(採用匿名內部類的方式,建立帶有比較器的集合物件)
TreeSet<Student> ts = new TreeSet<Student>(new Comparator<Student>() {
@Override
public int compare(Student s1, Student s2) {
int num = s2.getSum() - s1.getSum();
int num2 = num == 0 ? s1.getChinese() - s2.getChinese() : num;
int num3 = num2 == 0 ? s1.getMath() - s2.getMath() : num2;
int num4 = num3 == 0 ? s1.getName().compareTo(s2.getName()): num3;
return num4;
}
});
// 鍵盤錄入學生資訊儲存到集合
for (int x = 1; x <= 5; x++) {
Scanner sc = new Scanner(System.in);
System.out.println("請錄入第" + x + "個的學習資訊");
System.out.println("姓名:");
String name = sc.nextLine();
System.out.println("語文成績:");
int chinese = sc.nextInt();
System.out.println("數學成績:");
int math = sc.nextInt();
System.out.println("英語成績:");
int english = sc.nextInt();
// 建立學生物件
Student s = new Student();
s.setName(name);
s.setChinese(chinese);
s.setMath(math);
s.setEnglish(english);
// 把學生資訊新增到集合
ts.add(s);
}
// 遍歷集合,把資料寫到文字檔案
BufferedWriter bw = new BufferedWriter(new FileWriter("students.txt"));
//以下三行程式碼連寫
bw.write("學生資訊如下:");
bw.newLine();
bw.flush();
bw.write("姓名\t語文成績\t數學成績\t英語成績");
bw.newLine();
bw.flush();
for (Student s : ts) {
StringBuilder sb = new StringBuilder();
sb.append(s.getName()).append("\t").append(s.getChinese())
.append("\t").append(s.getMath()).append("\t")
.append(s.getEnglish());
bw.write(sb.toString());
bw.newLine();
bw.flush();
}
// 釋放資源
bw.close();
System.out.println("學習資訊儲存完畢");
}
}</span>
輸入:
執行結果:
好啦,如果上面的例子可以弄懂,IO就基本沒有什麼問題啦,哈哈。。。