主要內容
- File類
- 遞歸
- 字節輸出流
教學目標
- 能夠說出File對象的創建方式
- 能夠說出File類獲取名稱的方法名稱
- 能夠說出File類獲取絕對路徑的方法名稱
- 能夠說出File類獲取文件大小的方法名稱
- 能夠說出File類判斷是否是文件的方法名稱
- 能夠說出File類判斷是否是文件夾的方法名稱
- 能夠辨別相對路徑和絕對路徑
- 能夠遍歷文件夾
- 能夠解釋遞歸的含義
- 能夠使用遞歸的方式計算5的階乘
- 能夠說出使用遞歸會內存溢出隱患的原因
第一章 File類
1.1 概述
據最終保存在硬盤上,在硬盤上是以1和0 保存的。而給我們展示出來的並不是二進制數據,而是一個一個的文件,換句話說數據最終都保存在這些文件中。而這些文件又被文件夾管理。
通過上述描述我們發現在計算機硬盤中就存在兩種事物:文件和文件夾事物。而在Java中使用class類來描述生活中存在的事物,那麼Java中是如何描述文件和文件夾這兩類事物呢?
使用File類來描述文件和文件夾事物。
java.io.File
類是文件和目錄路徑名的抽象表示,主要用於文件和目錄的創建、查找和刪除等操作。
說明:
1)Java中的定義的io技術的類基本都在java.io包下;
2)使用File類來描述文件和文件夾事物的;
3)File類它主要是對持久設備上的文件和文件夾進行操作。它不能去操作文件中的數據。將來我們只要需要操作持久設備上的文件或文件夾直接找File類完成,如果要操作文件中的數據只能使用IO流技術搞定;
結論:File類就是描述文件或文件夾的,只要我們在Java中要操作文件或文件夾,就找File類。
1.2 構造方法
-
public File(String pathname)
:表示根據文件或文件夾的路徑名創建一個File對象。
使用File類的構造函數可以把指定的字符串封裝成File類的對象,但是這個字符串表示的路徑或者文件到底是否存在,File類是不進行判斷的。
注意:File類表示文件和目錄路徑名的抽象表示形式。那麼該路徑代表的文件或文件夾不一定存在。
-
public File(String parent, String child)
:從父路徑名字符串和子路徑名字符串創建新的 File實例。 -
public File(File parent, String child)
:從父抽象路徑名和子路徑名字符串創建新的 File實例。 -
構造舉例,代碼如下:
// 文件路徑名
String pathname = "D:\\aaa.txt";
File file1 = new File(pathname);
// 文件路徑名
String pathname2 = "D:\\aaa\\bbb.txt";
File file2 = new File(pathname2);
// 通過父路徑和子路徑字符串
String parent = "D:\\aaa";
String child = "bbb.txt";
File file3 = new File(parent, child);
// 通過父級File對象和子路徑字符串
File parentDir = new File("D:\\aaa");
String child = "bbb.txt";
File file4 = new File(parentDir, child);
小貼士:
- 一個File對象代表硬盤中實際存在的一個文件或者目錄。
- 無論該路徑下是否存在文件或者目錄,都不影響File對象的創建。
1.3 常用方法
File類是描述文件和文件夾的。使用File類的對象就可以獲取當前File類描述的那個文件或文件夾的信息(信息包括文件或者文件夾的名稱、大小、類型、日期等)。
獲取功能的方法
-
public String getAbsolutePath()
:獲取的當前調用這個方法的File對象的全路徑(絕對路徑或者真實路徑) 返回的是路徑的字符串; 說明:在操作文件時,需要指定文件的路徑,而路徑在程序中有兩種表現形式:
1、 絕對路徑(真實路徑):是從盤符開始的路徑,帶有根目錄的路
徑 例: D:\abc\test\Demo.java D就是根目錄。
2、 相對路徑:是從當前路徑開始的路徑,或者可以理解不帶根目錄的路徑
例: 當前路徑爲D:\abc,要描述相對路徑,只需輸入test\Demo.java
-
public String getPath()
:獲取到當前File類的對象中封裝的內容。 -
public String getName()
:獲取的是File類的對象中封裝的文件或目錄的最後一級名稱。 -
public long length()
:獲取文件大小long size = file.length();方法演示,代碼如下:
/* * File類中的獲取方法演示 * 絕對路徑:帶根路徑 * 相對路徑:不帶根路徑 test\\haha表示相對路徑,這裏表示相對於項目day19a來說 */ public class FileGetFunctionDemo { public static void main(String[] args) { // 創建File類的對象 File file=new File("D:\\test\\123.txt"); //getAbsolutePath()獲取絕對路徑演示 返回的是路徑的字符串 D:\test\123.txt String absolutePath = file.getAbsolutePath(); System.out.println("absolutePath="+absolutePath); //getName()函數 獲取的是封裝在File類中的文件或者文件夾的最後一級名稱 123.txt System.out.println("getName()="+file.getName()); //getPath()表示獲得File類中封裝的所有內容 D:\test\123.txt System.out.println("getPath()="+file.getPath()); //獲取文件大小 long size = file.length(); System.out.println("size="+size); } }
API中說明:length(),表示文件的長度。但是File對象表示目錄,則返回值未指定。
絕對路徑和相對路徑
- 絕對路徑:從盤符開始的路徑,這是一個完整的路徑。
- 相對路徑:相對於項目目錄的路徑,這是一個便捷的路徑,開發中經常使用。
public class FilePath {
public static void main(String[] args) {
// D盤下的bbb.java文件
File f = new File("D:\\bbb.java");
System.out.println(f.getAbsolutePath());
// 項目下的bbb.java文件
File f2 = new File("bbb.java");
System.out.println(f2.getAbsolutePath());
}
}
輸出結果:
D:\bbb.java
D:\idea_project_test4\bbb.java
判斷功能的方法
public boolean exists()
:是否存在 如果File類對象中的文件或者文件夾在硬盤上存在 返回 true ,否則返回false;public boolean isDirectory()
:是否是文件夾 如果是文件夾 返回 true 否則返回false;public boolean isFile()
:是否是文件 如果是文件 返回 true , 否則返回false;
方法演示,代碼如下:
public class FileIs {
public static void main(String[] args) {
File f = new File("D:\\aaa\\bbb.java");
File f2 = new File("D:\\aaa");
// 判斷是否存在
System.out.println("D:\\aaa\\bbb.java 是否存在:"+f.exists());
System.out.println("D:\\aaa 是否存在:"+f2.exists());
// 判斷是文件還是目錄
System.out.println("D:\\aaa 文件?:"+f2.isFile());
System.out.println("D:\\aaa 目錄?:"+f2.isDirectory());
}
}
輸出結果:
d:\aaa\bbb.java 是否存在:true
d:\aaa 是否存在:true
d:\aaa 文件?:false
d:\aaa 目錄?:true
創建刪除功能的方法
-
public boolean createNewFile()
:當且僅當具有該名稱的文件尚不存在時,創建一個新的空文件。 A:true :表示創建文件成功; false :表示創建文件失敗,失敗原因基本是因爲當前的目錄有了相同的文件;
B:如果指定的路徑不存在,這時就會拋異常 Java.io.IOException:系統找不到指定的路徑。這時是不會去創建文件的;
需求:創建:D:\abc\1.txt文件。在D盤的abc文件夾下新創建一個1.txt文件。
分析和步驟:
1)創建File類的對象file,指定目錄和文件名;
2)使用file對象調用createNewFile()函數創建文件,並打印返回值;
-
public boolean delete()
:刪除由此File表示的文件或目錄。 -
public boolean mkdir()
:創建由此File表示的目錄。 -
public boolean mkdirs()
:創建由此File表示的目錄,包括任何必需但不存在的父目錄。
方法演示,代碼如下:
public class FileCreateDelete {
public static void main(String[] args) throws IOException {
// 目錄的創建
File f2= new File("D:\\aaa");
System.out.println("是否存在:"+f2.exists());// false
System.out.println("是否創建:"+f2.mkdir()); // true
System.out.println("是否存在:"+f2.exists());// true
// 創建多級目錄
File f3= new File("D:\\aaa\\bbb\\ccc");
System.out.println(f3.mkdir());// false
File f4= new File("D:\\aaa\\bbb\\ccc");
System.out.println(f4.mkdirs());// true
// 目錄的刪除
System.out.println(f2.delete());// true
System.out.println(f4.delete());// false
}
}
API中說明:
1)delete方法,如果此File表示目錄,則目錄必須爲空才能刪除。
2)函數的刪除不走回收站。謹慎使用。
1.4 列舉方法
-
public String[] list()
:返回一個String數組,表示該File目錄中的所有子文件或目錄。 -
public File[] listFiles()
:返回一個File數組,表示該File目錄中的所有的子文件或目錄。
public class FileFor {
public static void main(String[] args) {
File dir = new File("D:\\test");
//獲取當前目錄下的文件以及文件夾的名稱。
String[] names = dir.list();
for(String name : names){
System.out.println(name);
}
//獲取當前目錄下的文件以及文件夾對象,只要拿到了文件對象,那麼就可以獲取更多信息
File[] files = dir.listFiles();
for (File file : files) {
System.out.println(file);
}
}
}
小貼士:
1)調用listFiles方法的File對象,表示的必須是實際存在的目錄,否則返回null,無法進行遍歷。
-
練習
-
統計文件夾下java文件的個數
public class Demo08_練習 { public static void main(String[] args) { //判斷裏面有幾個java文件 //創建對象 File f = new File("D:\\test"); //獲取所有子內容的文件名 File[] strs = f.listFiles(); //定義計數器 int count = 0; //遍歷數組 for (File file : strs) { //如果是以.java結尾就是java文件 //調用獲取名稱 String name = file.getName(); if(name.endsWith(".java") && file.isFile()){ System.out.println(name); count++; } } System.out.println("一共有幾個:" + count); } }
-
第二章 遞歸
2.1 概述
需求:掃描D:\test所有子文件夾及子子文件夾下的.jpg文件。
我們如果用循環來做這件事,我們不知道循環的結束條件,也不知道到底有多少層,所以比較麻煩。
我們可以用一種新的思想:遞歸。
遞歸舉例:
從前有一座山,山裏有座廟,廟裏有個老和尚,老和尚在給小和尚講故事:
從前有一座山,山裏有座廟,廟裏有個老和尚,老和尚在給小和尚講故事:
從前有一座山,山裏有座廟,廟裏有個老和尚,老和尚在給小和尚講故事:
。。。。。。。
故事如何才能結束:小和尚還俗了。廟塌了。山崩了。
Java中的遞歸:
在方法的函數體中又調用了方法自己本身。
遞歸調用的細節:必須要求遞歸中有可以讓函數調用的結束條件。否則函數一直調用,就會導致內存溢出。
2.2 遞歸累和
計算1 ~ 5的和
練習1:需求:計算1~5的和值,不許使用循環。
實現代碼:
public class DiGuiDemo {
public static void main(String[] args) {
//計算1~5的和,使用遞歸完成
int n = 5;
// 調用求和的方法
int sum = getSum(n);
// 輸出結果
System.out.println(sum);
}
/*
通過遞歸算法實現.
參數列表:int
返回值類型: int
*/
public static int getSum(int n) {
/*
n爲1時,方法返回1,
相當於是方法的出口,n總有是1的情況
*/
if(n == 1){
return 1;
}
/*
n不爲1時,方法返回 n +(n-1)的累和
遞歸調用getSum方法
*/
return n + getSum(n-1);
}
}
代碼執行圖解
小貼士:遞歸一定要有條件限定,保證遞歸能夠停止下來,次數不要太多,否則會發生棧內存溢出。
2.3 遞歸求階乘
練習2:需求:求5的階乘!!
分析:
普及一下數學知識:
使用遞歸思想來完成
5! = 5 * 4!
4! = 4 * 3!
3! = 3 * 2!
2! = 2 * 1!
1! = 1*0!
找規律:n! = n * (n-1)!
補充:0!等於1
結束條件:if(n <= 1) return 1;
遞歸方式:
代碼如下所示:
步驟:
1)定義一個DiGuiDemo3測試類;
2)在這個類中的main函數中調用自定義函數jc2(),5作爲函數的參數,使用一個變量result來接收返回的階乘的值,並輸出結果result;
3)自定義jc2()函數接收傳遞的參數5;
4)在自定義函數中書寫if語句判斷n是否小於等於1,如果小於等於1,則使用return返回1;
5)否則n>1時,使用return返回n * jc2(n - 1);
package cn.itcast.sh.digui.demo;
/*
* 方式2:使用遞歸思想來解決5的階乘
*方式一: 5!=5*4*3*2*1;
*方式二:5!=5*4!
* 4!=4*3!
* 3!=3*2!
* 2!=2*1!
* 1!=1*0!
*找規律:n!=n*(n-1)!
*找結束條件:
* if(n<=1) return 1;
*/
public class DiGuiDemo3 {
public static void main(String[] args) {
// 調用函數求5的階乘
int result=jc2(5);
System.out.println(result);//120
}
//自定義函數求5的階乘
public static int jc2(int n) {
// 結束條件
if(n<=1)
{
return 1;
}
return n*jc2(n-1);
}
}
上述代碼內存圖解如下所示:
2.4 遞歸注意事項
1)遞歸必須有結束條件,否則棧內存會溢出,稱爲死遞歸!棧炸了。
棧內存溢出報的異常如下所示:
2)遞歸次數不能太多,否則棧溢出。炸了
棧內存溢出報的異常如下所示:
總結:遞歸容器容易導致內存溢出。即使遞歸調用中有結束條件,但是如果遞歸的次數太多,也會發生內存溢出。
所以在開發中使用函數的遞歸調用時需謹慎。
2.5 遞歸打印所有子目錄中的文件路徑
需求:掃描D:\test所有子文件夾及子子文件夾下的.jpg文件,輸出其絕對路徑。
分析:
首先我們可以拿到D:\\test下的所有兒子,我們判斷兒子是不是文件夾,如果是,再次掃描兒子的文件夾,然後獲取兒子下面的所有子文件和子文件夾,即就是D:\\test的孫子,然後我們再判斷孫子是不是文件夾等等,以此類推,最後我們輸出其絕對路徑;
思路:
A:封裝父目錄的File對象;
B:獲取父目錄下的所有兒子的File數組;
C:循環遍歷,獲取每個兒子;
D:判斷是否是文件夾
是:回到步驟B 繼續獲取孫子
否:判斷是否是.jpg 結束遞歸的條件
是:打印
否:不管
我們發現:B到D的過程是不斷重複。我們可以封裝成遞歸的函數。
步驟:
1)創建測試類FileTest1;
2)在FileTest1類的main函數中封裝父目錄D:\test的對象parent;
3)調用遞歸掃描的函數scanFolders(parent);
4)自定義函數scanFolders(),使用父目錄對象parent調用listFiles()函數獲得父目錄下所有兒子的File數組files;
5)循環遍歷,獲取每個兒子對象file;
6)使用file對象調用isDirectory()函數判斷是否是文件夾;
7)如果是文件夾,則遞歸調用scanFolders(file)函數;
8)如果不是文件夾,肯定是文件,使用file對象調用getName()函數獲得文件的全名,調用endsWith()函數判斷後綴名是否是.jpg,如果是輸出文件所屬的絕對路徑;
package cn.itcast.sh.file.test;
import java.io.File;
/*
* 需求:掃描D:\\test所有子文件及子子文件下的.jpg文件,輸出其絕對路徑。
* 思路:
* A:創建父目錄
* B:調用函數查找文件或者文件夾
* C:通過父目錄對象調用函數獲取所有兒子的File數組
* D:循環遍歷所有的兒子,dir表示父目錄D:\\test的每一個兒子
* a:判斷獲取的兒子dir是否是文件夾 如果是 執行步驟B
* b:不是,判斷後綴名是否是.jpg,是,輸出絕對路徑
*
* 我們發現:B到D的過程是不斷重複。我們可以封裝成遞歸的函數。
*/
public class FileTest1 {
public static void main(String[] args) {
//封裝父目錄的對象
File parent = new File("D:\\test");
//調用函數查找文件或者文件夾
scanFolderAndFile(parent);
}
//自定義函數掃描文件或者文件夾
public static void scanFolderAndFile(File parent) {
//通過父目錄對象調用函數獲取所有兒子的File數組
File[] dirs = parent.listFiles();
//循環遍歷所有的兒子,dir表示父目錄D:\\test的每一個兒子
for (File dir : dirs) {
/*
* 判斷獲取的兒子dir是否是文件夾
* 如果是文件夾,那麼繼續掃描或者查找兒子下面的所有文件或者文件夾
* 以此類推
* 如果不是文件夾,那麼肯定是文件,判斷後綴名是否是.jpg
* 如果是.jpg 則輸出其絕對路徑
*/
if(dir.isDirectory())
{
//說明是文件夾 繼續找兒子下面的文件或者文件夾 執行掃描函數
scanFolderAndFile(dir);
}else
{
/*
* 說明不是文件夾,是文件,我們判斷是否是.jpg
* dir.getName()表示獲取文件的名字 mm.jpg
*/
if(dir.getName().endsWith(".jpg"))
{
//說明文件的後綴名是.jpg 輸出其絕對路徑
System.out.println(dir.getAbsolutePath());
}
}
}
}
}
第三章 IO概述
3.1 什麼是IO
簡單回顧之前所學的知識:
字符串String:操作文本數據的;
字符串緩衝區:容器,可以存儲很多的任意類型的數據,最後都變成字符串;
基本數據類型包裝類:解決了字符串和基本數據類型之間的轉換,同時給基本數據類型提供更多的操作;
集合框架:對象多了,便於存儲後操作;
Date ,DateFormat,SimpleDateFormat、Calendar:解決日期和時間的問題;
在前期的學習上述知識點的過程中,我們書寫的任何程序,它運行的時候都會有數據的產生,比如時間數據,而這些數據最終都保存在內存中。程序運行結束之後,數據就沒有了。當程序下次啓動的時候,如果還需要使用上次的結果,這時程序中是沒有的。
而在開發中,在我們真正書寫的程序中,往往有一些數據是需要長久保存起來的,當程序中需要這些長久保存起來的數據的時候,再從其他的地方讀取到程序中。
也就是說我們以前學習的數據都是存儲到內存中,而內存中只能暫時存儲數據,當電腦關閉之後,數據就不見了。如果在開發中需要大量數據被長久保存,下次開電腦這些數據仍然還在,那麼我們肯定不能保存到內存中,我們需要保存在能夠保存持久數據的地方或者叫持久設備。
那麼問題來了:什麼是持久設備呢?
什麼是持久設備
持久設備:可以持久保存數據的設備。硬盤、U盤、光盤、網盤、軟盤等。這些設備都可以長久的保存數據。
那麼還有一個問題:如何把程序中的數據保存到持久設備中?
Java提供了一個技術,專門來實現數據與持久設備間的交互:IO技術。
通過IO技術就可以達到程序和其他設備之間的數據交互。
什麼是IO技術
IO技術:它主要的功能就是把我們程序中的數據可以保存到程序以外的其他持久設備中(如:我們電腦的硬盤),或者從其他持久設備中(如:我們電腦的硬盤)進行讀取數據,把數據讀取到我們的Java程序中。
IO:
I:Input:輸入或者讀取,從持久設備(硬盤)的數據放到內存中;
O:Output:輸出或者寫出,從內存中的數據放到持久設備(硬盤)上;
也可以按照如下理解:
把從持久設備(硬盤)上讀取(輸入)到內存中的動作,稱爲讀操作。 I:input。
把內存中的數據輸出(寫出)到持久設備(硬盤)的動作,稱爲寫操作。O:output。
3.2 IO的分類
1)按流向分:
- 輸入流:讀取數據,把持久設備的數據讀取到內存中。
- 輸出流:寫出數據,把內存的數據寫出到持久設備。
2)按數據類型分:
計算機中一切數據都是:字節數據。
字符數據:底層還是字節數據,但是可以根據某些規則,把字節變成人們認識的文字、符號等等。
-
字節流:數據在持久設備上都是以二進制形式保存的。二進制就是字節數據。Java就給出了字節流可以直接操作字節數據。
字節輸入流:InputStream
兒子:XxxInputStream
字節輸出流:OutputStream
兒子:XxxOutputStream
-
字符流:讀取字符數據。數據在設備上是以二進制形式表示,但是有些二進制合併在一起可以表示一些字符數據。
字符輸入流:Reader
兒子:XxxReader
字符輸出流:Writer
兒子:XxxWriter
3.3 IO的流向說明圖解
說明:
1)字節流可以對任意類型的文件按照字節進行讀和寫的操作;
例如:圖片、視頻、文本文件、word文檔、mp3等。
2)字符流只能對文本類型的文件進行操作;
問題1:文本類型的文件是什麼文件?
只要可以使用記事本打開並看得懂的文件就是文本文件。
例如:.java文件、.txt等文件。
而字符流只能操作文本類型的文件,也就是說如果一個文件可以使用記事本打開並能夠看懂,那麼這個文件就可以使用字符流來操作,否則其他的文件都得使用字節流進行操作。
注意:字節流也可以操作文本文件。
3.4 頂級父類們
輸入流 | 輸出流 | |
---|---|---|
字節流 | 字節輸入流:InputStream | 字節輸出流:OutputStream |
字符流 | 字符輸入流:Reader | 字符輸出流:Writer |
第四章 字節流
4.1 一切皆爲字節
一切文件數據(文本、圖片、視頻等)在存儲時,都是以二進制數字的形式保存,都是一個一個的字節,那麼傳輸時一樣如此。所以,字節流可以傳輸任意文件數據。在操作流的時候,我們要時刻明確,無論使用什麼樣的流對象,底層傳輸的始終爲二進制數據。
4.2 字節輸出流【OutputStream】
java.io.OutputStream
抽象類是表示字節輸出流的所有類的超類,將指定的字節信息寫出到目的地。它定義了字節輸出流的基本共性功能方法。
public void close()
:關閉此輸出流並釋放與此流相關聯的任何系統資源。public void write(byte[] b)
:把這個b字節數組中的所有數據寫到關聯的設備中(設備包括文件、網絡或者其他任何地方)。public void write(byte[] b, int off, int len)
:把b字節中的數據從下標off位置開始往出寫,共計寫len個。public abstract void write(int b)
: 把這個b數據寫到關聯的設備中。
小貼士:
close方法,當完成流的操作時,必須調用此方法,釋放系統資源。
4.3 FileOutputStream類
OutputStream
有很多子類,我們從最簡單的一個子類開始。
java.io.FileOutputStream
類是文件輸出流,用於將數據寫出到文件。
構造方法
public FileOutputStream(File file)
:創建文件輸出流以寫入由指定的 File對象表示的文件。public FileOutputStream(String name)
: 創建文件輸出流以指定的名稱寫入文件。
當你創建一個流對象時,必須傳入一個文件路徑。該路徑下,如果沒有這個文件,會創建該文件。如果有這個文件,會清空這個文件的數據。
- 構造舉例,代碼如下:
public class FileOutputStreamConstructor throws IOException {
public static void main(String[] args) {
// 使用File對象創建流對象
File file = new File("day10\\a.txt");//day10表示項目名
FileOutputStream fos = new FileOutputStream(file);
// 使用文件名稱創建流對象
FileOutputStream fos = new FileOutputStream("day10\\b.txt");
}
}
寫出字節數據
- 寫出字節:
write(int b)
方法,每次可以寫出一個字節數據,代碼使用演示:
public class FOSWrite {
public static void main(String[] args) throws IOException {
// 使用文件名稱創建流對象
FileOutputStream fos = new FileOutputStream("day10\\fos.txt");
// 寫出數據
fos.write(97); // 寫出第1個字節
fos.write(98); // 寫出第2個字節
fos.write(99); // 寫出第3個字節
// 關閉資源
fos.close();
}
}
輸出結果:
abc
小貼士:
流操作完畢後,必須釋放系統資源,調用close方法,千萬記得。
雖然參數爲int類型四個字節,但是隻會保留一個字節的信息寫出。
fos.write(353);//97+ 256--->寫到文件中的是字符a
- 寫出字節數組:
write(byte[] b)
,每次可以寫出數組中的數據,代碼使用演示:
public class FOSWrite {
public static void main(String[] args) throws IOException {
// 使用文件名稱創建流對象
FileOutputStream fos = new FileOutputStream("day10\\fos.txt");
// 字符串轉換爲字節數組
byte[] b = "黑馬程序員".getBytes();
// 寫出字節數組數據
fos.write(b);
// 關閉資源
fos.close();
}
}
輸出結果:
黑馬程序員
- 寫出指定長度字節數組:
write(byte[] b, int off, int len)
,每次寫出從off索引開始,len個字節,代碼使用演示:
public class FOSWrite {
public static void main(String[] args) throws IOException {
// 使用文件名稱創建流對象
FileOutputStream fos = new FileOutputStream("day10\\fos.txt");
// 字符串轉換爲字節數組
byte[] b = "abcde".getBytes();
// 寫出從索引2開始,2個字節。索引2是c,兩個字節,也就是cd。
fos.write(b,2,2);
// 關閉資源
fos.close();
}
}
輸出結果:
cd
數據追加續寫
經過以上的演示,每次程序運行,創建輸出流對象,都會清空目標文件中的數據。如何保留目標文件中數據,還能繼續添加新數據呢?
public FileOutputStream(File file, boolean append)
: 創建文件輸出流以寫入由指定的 File對象表示的文件。public FileOutputStream(String name, boolean append)
: 創建文件輸出流以指定的名稱寫入文件。
這兩個構造方法,參數中都需要傳入一個boolean類型的值,true
表示追加數據,false
表示清空原有數據。這樣創建的輸出流對象,就可以指定是否追加續寫了,代碼使用演示:
需求:使用字節輸出流把字符串數據”你好嗎”寫到硬盤上,要求不能覆蓋文件中原有的數據;
分析和步驟:
1)使用new關鍵字調用FileOutputStream類的構造函數創建輸出流對象fos;
2)使用對象fos調用write函數向指定的文件添加數據;
3)關閉資源;
//演示向文件末尾追加數據
//需求:使用字節輸出流把字符串數據”你好嗎”寫到硬盤上,要求不能覆蓋文件中原有的數據;
public static void method_1() throws IOException {
//創建輸出流對象
FileOutputStream fos = new FileOutputStream("D:\\test1\\4.txt", true);
/*
* 對於以上構造函數進行說明:
* 如果第二個參數爲true,那麼就會在已經存在的文件中的末尾處追加數據,如果這個文件4.txt不存在
* 那麼就會創建這個文件
* 如果第二個參數爲false,那麼向文件添加數據的時候就會覆蓋原來的數據
*/
//向文件中添加數據
fos.write("你好嗎".getBytes());
fos.write("我叫黑旋風".getBytes());
//關閉資源
fos.close();
}
寫出換行
數據換行問題:
我們如果想換行,可以在數據的末尾加:\r\n
但是:\r\n是windows系統識別的換行符。不同的操作系統,換行符可能會不相同的。
Windows系統裏,換行符號是\r\n
。linux:\n mac:\r
代碼使用演示:
public static void method_2() throws IOException {
//創建輸出流對象
FileOutputStream fos = new FileOutputStream("D:\\test1\\5.txt", true);
//向文件中寫入數據
//fos.write("hello 上海傳智\r\n".getBytes());
//System.lineSeparator()表示獲取當前系統的行分隔符,可以實現跨平臺
String s = System.lineSeparator();
//向文件中寫入數據
fos.write(("hello 上海傳智"+s).getBytes());
//關閉資源
fos.close();
}
說明:爲了實現換行的跨平臺,就是獲取不同操作系統行分隔符。在jdk1.7之後,System類中添加了一個方法,可以實現在任何操作系統中獲取不同系統的行分隔符:
public static String lineSeparator()
之前學習的是輸出流對象,是用來從內存中向文件(硬盤)中寫入數據。如果想要從文件(硬盤)中向內存中讀取數據,需要使用輸入流對象:InputStream。
4.5 字節輸入流【InputStream】
java.io.InputStream
抽象類是表示字節輸入流的所有類的超類,可以讀取字節信息到內存中。它定義了字節輸入流的基本共性功能方法。
-
public void close()
:關閉此輸入流並釋放與此流相關聯的任何系統資源。 -
public abstract int read()
: 調用一次read,就可以從關聯的文件中讀取一個字節數據,並返回這個字節數據。方法可以從關聯的文件中讀取數據。所有read方法如果讀取到文件的末尾,都會返回-1。遇到-1就代表文件中的數據已經被讀取完畢。
-
public int read(byte[] b)
: 調用一次,讀取多個字節數據,把讀到的字節數據保存在傳遞的b字節數組中。返回字節數組中讀取的字節個數。注意啦:這個返回值不是數組長度。由於InputStream類是抽象類,不能創建這個類的對象,但是如果想使用這個類中的函數,那必須得創建這個類的對象,如果想要創建對象,那麼只能創建InputStream類的子類。
由於我們這裏是操作文件的,所以我們需要創建FileInputStream類的對象。
小貼士:
close方法,當完成流的操作時,必須調用此方法,釋放系統資源。
4.6 FileInputStream類
java.io.FileInputStream
類是文件輸入流,從文件中讀取字節。
構造方法
FileInputStream(File file)
: 通過打開與實際文件的連接來創建一個 FileInputStream ,該文件由文件系統中的 File對象 file命名。FileInputStream(String name)
: 通過打開與實際文件的連接來創建一個 FileInputStream ,該文件由文件系統中的路徑名 name命名。
注意:
**1)字節輸入流,構造函數執行時,如果源文件不存在,那麼拋出異常!!;**
**2)由於輸入流讀取的是文件中的字節數據,所以要求輸入流指定的一定是文件,不能是文件夾,否則會報異常;**
- 構造舉例,代碼如下:
public class FileInputStreamConstructor throws IOException{
public static void main(String[] args) {
// 使用File對象創建流對象
File file = new File("E:\\a.txt");
FileInputStream fos = new FileInputStream(file);
// 使用文件名稱創建流對象
FileInputStream fos = new FileInputStream("E:\\b.txt");
}
}
讀取字節數據
-
讀取字節:先使用輸入流對象,從文件中讀取數據,每調用一次read()方法,可以從硬盤文件中讀取一個字節數據,把這個字節數據保存在一個int類型的變量中。然後判斷讀取到的這個數據也就是這個int類型的變量是否是-1,如果不是-1,說明當前沒有讀取到文件的末尾。如果是-1,說明當前已經讀取到文件的末尾。
int類型的變量中就保存着當前讀取到的那個字節數據,後續步驟可以對這個數據進行相關處理。
輸入流的使用步驟:
A:創建輸入流,關聯源文件; B:讀取數據; C:釋放資源;
分析和步驟:
1)使用new關鍵字調用FileInputStream類的構造函數創建指定路徑D:\test1\1.txt的輸入流對象in;
2)使用輸入流對象in調用read()函數開始讀取文件,返回一個int類型的整數,並輸出最後返回值;
3)多次調用read()函數引起代碼重複,我們可以考慮使用循環來實現;
4)循環條件是返回值是-1;
代碼使用演示:
/*
* 演示:字節輸入流讀取數據,一次讀一個字節。
* 注意:由於輸入流讀取的是文件中的字節數據,所以要求輸入流指定的一定是文件,否則會報異常
* FileNotFoundException
* 讀取功能:
* int read():表示讀取下一個字節並返回
* 輸入流的使用步驟:
* 1)創建輸入流;
* 2)讀取數據;
* 3)關閉輸入流;
*/
public class FileInputStreamDemo {
public static void main(String[] args) throws IOException {
// 創建輸入流的對象 java.io.FileNotFoundException: D:\test1 (拒絕訪問。) 系統找不到指定的文件
FileInputStream fis = new FileInputStream("D:\\test1\\1.txt");
//使用輸入流對象調用read()函數一次讀一個字節數據
//第一次讀取
int i = fis.read();//i表示讀取的字節數據,如果是-1說明文件的結尾
//輸出讀取的字節數據
System.out.println((char)i);
//第二次讀取
int i2 = fis.read();
System.out.println((char)i2);
//第三次讀取
int i3 = fis.read();
System.out.println((char)i3);
//第四次讀取
int i4 = fis.read();
System.out.println((char)i4);
//第五次讀取
int i5 = fis.read();
System.out.println(i5);//-1
//第六次讀取
int i6 = fis.read();
System.out.println(i6);//-1
//關閉資源
fis.close();
}
}
通過書寫代碼發現上述代碼重複太多,我們可以考慮使用循環來解決上述代碼重複性的問題
問題來了:循環的結束條件是什麼?
讀取到文件結尾,即-1則結束,所以可以讓讀取的結果是-1結束讀取文件。
改良上述代碼,簡化代碼開發,使用循環。
循環改進讀取方式,代碼使用演示:
/*
* 通過書寫代碼發現上述代碼重複太多,我們可以考慮使用循環來解決上述代碼重複性的問題
* 問題:循環的循環條件是什麼?
* 讀取到文件結尾,即-1則結束,所以可以讓讀取的結果是-1結束讀取文件
*/
//讀取數據
int i = fis.read();
//循環控制讀取文件數據的次數
while(i!=-1)
{
//說明文件中還有數據,可以繼續讀取,輸出讀取的數據
System.out.println((char)i);
//修改循環條件 可以理解爲一個光標,讀取一次,i的值改變一次
i=fis.read();
}
通過上述代碼發現,在使用輸入流對象fis調用read()函數的時候,出現多次調用的情況,這樣也會導致代碼重複,在開發中儘量簡化代碼的書寫,所以對上述代碼還得進一步優化:
終極版代碼單個讀取數據的代碼模板如下所示:
//上述的代碼讀取的時候調用多次,代碼也重複,我們仍然可以簡化代碼
//定義一個變量接收讀取的數據
int b=0;
//fis.read()表示使用輸入流對象讀取字節數據保存到變量b中,
//如果b等於-1說明已經讀取到文件末尾,否則文件中還有數據
while((b=fis.read())!=-1)
{
//輸出字節數據
System.out.println((char)b);
}
//關閉資源
fis.close();
小貼士:
- 雖然讀取了一個字節,但是會自動提升爲int類型。
- 流操作完畢後,必須釋放系統資源,調用close方法,千萬記得。
-
使用字節數組讀取:
read(byte[] b)
, 讀取數據到數組:buf中,返回的是讀取到的字節的個數len.定義的字節數組是用來存儲從底層文件中讀取到的多個字節數據;
在把讀取的字節個數保存在len中。len中保存的是真正給字節數組中讀取的字節個數,如果讀取到文件末尾,也會返回-1;
一般這個數組的長度都會定義成1024的整數倍。
使用循環重複的從文件中讀取數據,每次最多可以從文件中讀取1024個字節數據
需求:在當前項目下新創鍵一個文件1.txt,然後在該文件中書寫幾個字符串,如下所示:
hello world Java
字樣,然後使用字節輸入流一次讀一個字節數組來讀取上述路徑中的1.txt文件中的數據,將每次讀取的數據輸出打印到控制檯。
分析和步驟:
1)創建一個輸入流對象,和D:\test\1.txt文件進行關聯;
2)定義的字節byte數組b,這個字節數組的長度是5,主要是用來存儲從底層文件中讀取到的多個字節數據;
3)用來記錄當前給byte數組中讀取的字節個數的變量,int len = 0;
4)先執行fis.read()函數從底層讀取數據,然後會把數據保存在我們傳遞的參數b數組中。返回值定義一個int類型的變量len記錄着讀取到字節數組中的字節數;
5)輸出記錄的讀取到字節數len和將字節數組轉換後的字符串數據,將字節轉換爲字符串可以使用Arrays.toString(b)或者String類的構造函數;
6)由於字節數組長度是5,所以需要多次讀取,按照上述操作多次讀取1.txt文件中剩餘的數據,將結果輸出到控制檯上面;
/*
* 演示:字節輸入流,一次讀一個字節數組。
* int read(byte[] b) 表示定義一個byte數組,每次讀取的字節都存儲到這個數組中
*/
public class FileInputStreamDemo1 {
public static void main(String[] args) throws IOException {
// 創建輸入流對象,關聯源文件
FileInputStream fis = new FileInputStream("D:\\test\\1.txt");
//第一次讀取
//定義一個數組保存字節數據
byte[] b=new byte[5];
//讀取數據 將數據保存到字節數組中
int len = fis.read(b);
//輸出len
System.out.println("len="+len);
//輸出字節數組中的內容
// System.out.println(Arrays.toString(b));
System.out.println(new String(b));//hello
//第一次讀取結果:
/*
len=5
hello
*/
//第二次讀取
len = fis.read(b);
//輸出len
System.out.println("len="+len);
//輸出字節數組中的內容
System.out.println(new String(b));
//第二次讀取結果:
/*
len=5
\r\n
wor
*/
//第三次讀取
len = fis.read(b);
//輸出len
System.out.println("len="+len);
//輸出字節數組中的內容
System.out.println(new String(b));
//第三次讀取結果:
/*
* len=5
ld\r\n
J
*/
//第四次讀取
len = fis.read(b);
//輸出len
System.out.println("len="+len);
//輸出字節數組中的內容
System.out.println(new String(b));
//第四次讀取結果:
/*
在1.txt文件中,如果最後的數據Java有回車換行,那麼會輸出如下所示數據:
len=5
ava\r\n
在1.txt文件中,如果最後的數據Java沒有回車換行,那麼會輸出如下所示數據:
len=3
ava
J
*/
}
}
說明:
1)通過上述代碼發現返回值len表示讀取到的字節數,而不是字節數組的長度。如果讀取爲5個字節數,那麼返回5,即,len等於5。如果讀取到的字節數是3,那麼返回3,即len等於3。如果文件中沒有要讀取的數據,則返回-1。
2)上述代碼中當第四次讀取的時候有問題,如果文件中最後一個數據後面沒有回車換行,那麼應該只打印ava,爲什麼會打印:
ava
J
呢?
原因如下圖所示:
爲了解決上述代碼出現的問題,我們更希望看到當我們讀取幾個字節數據,我們就輸出幾個字節數據,所以這裏我們不能在使用new String(b)構造函數,我們應該使用new String(b,int offset,int length)構造函數,這樣做就不會出現上述問題。
說明:new String(b,0,len):
創建一個字符串對象,把b數組中的數據轉成字符串,從0位置開始,共計轉len個。
從0位置開始,因爲每次調用輸入流的read方法的時候,把數據給byte數組中保存
這時真正是從byte數組的0位置開始存儲讀取的每個字節數據。
len是byte數組中的保存的真正的讀取的字節個數。
這樣做就可以做到我們讀取幾個字節數據,我們就輸出幾個字節數據的目的。
代碼如下所示:
public class FileInputStreamDemo1 {
public static void main(String[] args) throws IOException {
// 創建輸入流對象,關聯源文件
FileInputStream fis = new FileInputStream("D:\\test\\1.txt");
//第一次讀取
//定義一個數組保存字節數據
byte[] b=new byte[5];
//讀取數據 將數據保存到字節數組中
int len = fis.read(b);
// System.out.println(new String(b,0,len));//hello
System.out.print(new String(b,0,len));//hello
//第二次讀取
len = fis.read(b);
//輸出len
// System.out.println("len="+len);
//輸出字節數組中的內容
// System.out.println(new String(b,0,len));
System.out.print(new String(b,0,len));
//第三次讀取
len = fis.read(b);
//輸出len
// System.out.println("len="+len);
//輸出字節數組中的內容
// System.out.println(new String(b,0,len));
System.out.print(new String(b,0,len));
//第四次讀取
len = fis.read(b);
//輸出len
// System.out.println("len="+len);
//輸出字節數組中的內容
// System.out.println(new String(b,0,len));
System.out.print(new String(b,0,len));
//關閉資源
fis.close();
}
}
上面的代碼重複太多了,考慮用循環。
問題來了 :循環的接收條件是什麼呢?
結束條件:末尾返回-1
終極版代碼如下所示:
//定義一個數組
// byte[] b=new byte[5];
//終極版代碼模板
byte[] b=new byte[1024];//數組長度一般是1024的整數倍
//定義一個變量保存讀取字節的個數
int len=0;
//fis.read(b)表示讀取的數據都存放到byte數組中了,len表示讀取字節數
while((len=fis.read(b))!=-1)//一定要傳遞參數數組b
{
System.out.print(new String(b,0,len));
}
//關閉資源
fis.close();
小貼士:
使用數組讀取,每次讀取多個字節,減少了系統間的IO操作次數,從而提高了讀寫的效率,建議開發中使用。
4.7 字節流練習:文件複製
複製原理圖解
案例實現
需求:
演示覆制文件:把D盤下的1.mp3 拷貝到F盤下。
分析和步驟: 每次讀字節數組來完成複製
1)步驟和上述步驟大致相同,只是需要定義一個整數變量len來記錄當前給byte數組中讀取的字節個數的;
2)定義的字節數組buf是用來存儲從底層文件中讀取到的多個字節數據;
3)使用read(buf)函數讀取文件的時候需要傳入一個數組參數buf;
4)每讀取一次使用輸出流對象調用write()函數向文件中寫數據;
複製圖片文件,代碼使用演示:
//演示一次讀取一個字節數組
public static void method_2() throws IOException {
// 創建輸入流對象 關聯源文件
FileInputStream fis = new FileInputStream("D:\\1.mp3");
//創建輸出流對象 關聯目標文件
FileOutputStream fos = new FileOutputStream("F:\\1.mp3");
//獲取開始複製的時間
long start = System.currentTimeMillis();
//定義數組 讀數據
// byte[] b=new byte[8192];
byte[] b=new byte[1024];
int len=0;
while((len=fis.read(b))!=-1)
{
//寫數據
fos.write(b, 0, len);
}
//獲取結束複製的時間
long end = System.currentTimeMillis();
System.out.println("複製時間是:"+(end-start));//複製時間是:15
//關閉資源
fis.close();
fos.close();
}