主要內容
- File類
- 遞歸
- 字節輸出流
教學目標
- 能夠說出File對象的創建方式
- 能夠說出File類獲取名稱的方法名稱
- 能夠說出File類獲取絕對路徑的方法名稱
- 能夠說出File類獲取文件大小的方法名稱
- 能夠說出File類判斷是否是文件的方法名稱
- 能夠說出File類判斷是否是文件夾的方法名稱
- 能夠辨別相對路徑和絕對路徑
- 能夠遍歷文件夾
- 能夠解釋遞歸的含義
- 能夠使用遞歸的方式計算5的階乘
- 能夠說出使用遞歸會內存溢出隱患的原因
第一章 字符流
當使用字節流讀取文本文件時,可能會有一個小問題。就是遇到中文字符時,可能不會顯示完整的字符,那是因爲一箇中文字符可能佔用多個字節存儲。所以Java提供一些字符流類,以字符爲單位讀寫數據,專門用於處理文本文件。
1.1 字節流讀取字符的問題
數據在持久設備上一定是以二進制形式保存,是以字節形式保存。但是有些字節數據合在一起才表示的是字符數據。
比如:記事本中保存的數據,最後肯定是以字節形式保存在硬盤上,但是其中需要2個字節表示一個漢字。我們真正要使用流讀取記事本中的數據時,不應該一個一個字節讀取,而是應該把表示漢字的那幾個字節一起讀取,然後把這些字節合在一起表示一個漢字。
需求:使用字節流讀取D:\out.txt記事本以下的內容:
abc你好
一次讀一個字節數組代碼演示如下所示:
分析和步驟:
1)創建FileInputStream類的對象fis,D:\out.txt作爲參數;
2)定義一個字節byte數組b,先讓數組長度是1024然後讀取一遍後在將數組長度變爲4;
3)定義一個變量len=0記錄讀取字節的個數;
4)使用while循環來讀取數據,並使用輸出語句輸出String類構造函數轉換後的數據
5)關閉資源流;
//一次讀取一個字節數組
public static void method_2() throws IOException {
// 創建讀取字符數據的輸入流對象
FileInputStream fis = new FileInputStream("D:\\out.txt");
//定義一個字節數組
// byte[] b=new byte[1024];//數組大小是1024的整數倍
byte[] b=new byte[4];//abc
//定義一個變量記錄讀取字節的個數
int len=0;
while((len=fis.read(b))!=-1)
{
System.out.print(new String(b,0,len));
}
//釋放資源
fis.close();
}
結果:
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-s85yzpWW-1593434888587)(img/字節流讀取文本文件亂碼.jpg)]
說明:會出現上述結果的原因是字母abc各佔一個字節,而漢字佔兩個或者三個字節。上述定義的字節數組一次只能存放4個字節,所以會將你字進行字節的拆分,這樣會導致後面的漢字字符對應的字節都發生了變化,所以會出現亂碼。
字節流讀取字符數據的問題:
漢字等字符,往往由多個字節組成。使用字節流讀取由多個字節組成字符數據,發現讀取到的每個字符的對應的字節數據,而不是真正的字符內容。而我們更希望看到讀取的具體的內容是什麼字符數據。
我們需要把讀取到的某2個或者3個字節合併在一起,拼成一個漢字。這時我們在程序中並不知道應該把哪2個或者3個字節合併成一個漢字。由於在程序中,有時一個字節就表示一個字符數據,比如英文字母,有時必須是2個字節或者3個字節表示一個漢字,那麼到底應該把一個字節轉成字母,還是把2個字節或者3個字節轉成漢字,這時我們無法對讀取的數據進行控制。
例如:記事本中的數據:abc你好
我們如果使用字節流來讀取上述數據,那麼字節流不知道什麼時候是幾個字節組成字母,幾個字節組成漢字,這樣會出現我們不想要的結果。
但是Java提供的字符流就可以解決上述讀取字符數據的問題
1.2 字符輸入流【Reader】
java.io.Reader
抽象類是表示用於讀取字符流的所有類的超類,可以讀取字符信息到內存中。它定義了字符輸入流的基本共性功能方法。
-
public void close()
:關閉此流並釋放與此流相關聯的任何系統資源。 -
public int read()
: 調用一次讀取一個字符,返回字符的編碼值。即讀取的內容存放到返回值中。如果讀取到文件末尾返回-1; -
public int read(char[] cbuf)
: 調用一次讀取多個字符,把這些字符保存在cbuf中,返回給字符數組中存儲的字符個數,如果讀取到文件末尾返回-1;
1.3 FileReader類
java.io.FileReader
類是讀取字符文件的便利類。構造時使用當前環境默認的字符編碼和默認字節緩衝區。
小貼士:
- 字符編碼:字節與字符的對應規則。Windows系統的中文編碼默認是GBK編碼表。idea中UTF-8
- 字節緩衝區:一個字節數組,用來臨時存儲字節數據。
注意:在計算機中所有數據在底層都是以字節數據存在的,即使是字符數據在最底層也是以字節數據存在的,因爲計算機只識別字節數據。所以在字符流底層使用的是字節緩衝區,其實就是一個字節數組。
構造方法
FileReader(File file)
: 創建一個新的 FileReader ,給定要讀取的File對象。FileReader(String fileName)
: 創建一個新的 FileReader ,給定要讀取的文件的名稱。
當你創建一個流對象時,必須傳入一個文件路徑。類似於FileInputStream 。
- 構造舉例,代碼如下:
public class FileReaderConstructor throws IOException{
public static void main(String[] args) {
// 使用File對象創建流對象
File file = new File("day09\\a.txt");
FileReader fr = new FileReader(file);
// 使用文件名稱創建流對象
FileReader fr = new FileReader("day09\\b.txt");
}
}
讀取字符數據
-
讀取字符:
read
方法,調用一次讀取一個字符,返回字符的編碼值。提升爲int類型。即讀取的內容存放到返回值中。如果讀取到文件末尾返回-1;循環讀取,代碼使用演示:案例:使用字符輸入流讀取D:\out.txt上的文本文件。並將數據顯示到控制檯中。
讀數據–輸入流–FileReader
FileReader:
FileReader(String fileName):傳遞文件名稱
輸入流讀文件的步驟:
A:創建輸入流對象 B:調用輸入流對象的讀數據方法 C:釋放資源
package com.itheima_02; import java.io.FileReader; import java.io.IOException; public class FileReaderDemo { public static void main(String[] args) throws IOException { //創建輸入流對象 //指定文件不存在就會報異常:java.io.FileNotFoundException: D:\\test\\1111.txt (系統找不到指定的文件。) // FileReader fr = new FileReader("D:\\test\\1111.txt"); FileReader fr = new FileReader("D:\\out.txt"); //調用輸入流對象的讀數據方法 //int read():一次讀取一個字符 int ch=0; while((ch=fr.read())!=-1) { System.out.print((char)ch); } //釋放資源 fr.close(); } }
小貼士:雖然讀取了一個字符,但是會自動提升爲int類型。
- 使用字符數組讀取:
read(char[] cbuf)
,調用一次讀取多個字符,把這些字符保存在cbuf數組中,返回給字符數組中存儲的字符個數,如果讀取到文件末尾返回-1.代碼使用演示:
public class FISRead {
public static void main(String[] args) throws IOException {
// 使用文件名稱創建流對象
FileReader fr = new FileReader("D:\\out.txt");
// 定義變量,保存有效字符個數
int len =0;
// 定義字符數組,作爲裝字符數據的容器
char[] cbuf = new char[1024];
// 循環讀取
while ((len = fr.read(cbuf))!=-1) {
System.out.println(new String(cbuf,0,len));
}
// 關閉資源
fr.close();
}
}
1.4 字符輸出流【Writer】
java.io.Writer
抽象類是表示用於寫出字符流的所有類的超類,將指定的字符信息寫出到目的地。它定義了字符輸出流的基本共性功能方法。
-
public abstract void close()
:關閉此輸出流並釋放與此流相關聯的任何系統資源。 -
public abstract void flush()
:刷新此輸出流並強制任何緩衝的輸出字符被寫出。說明:字符輸入和字符輸出流都是自帶緩衝區的。緩衝區就是在類的底層封裝了一個數組,如果我們使用字符輸出流向目的地文件中寫數據的時候,數據不會立刻寫到目的地文件中,而是寫到自帶的數組中,數據還在內存中,所以我們必須調用FileWriter類中的刷新方法flush將數組中的數據刷新到目的地硬盤文件中。
-
public void write(int b)
:寫出一個字符。 -
public void write(char[] cbuf)
:將 b.length字符從指定的字符數組寫出此輸出流。 -
public abstract void write(char[] b, int off, int len)
:從指定的字符數組寫出 len字符,從偏移量 off開始輸出到此輸出流。 -
public void write(String str)
:寫出一個字符串。
1.5 FileWriter類
java.io.FileWriter
類是寫出字符到文件的便利類。構造時使用系統默認的字符編碼和默認字節緩衝區。
構造方法
-
FileWriter(File file)
: 創建一個新的 FileWriter,給定要讀取的File對象。 -
FileWriter(String fileName)
: 創建一個新的 FileWriter,給定要讀取的文件的名稱。當你創建一個流對象時,必須傳入一個文件路徑。該路徑下,如果沒有這個文件,會創建該文件。如果有這個文件,會清空這個文件的數據。 類似於FileOutputStream。
-
構造舉例,代碼如下:
public class FileWriterConstructor {
public static void main(String[] args) throws IOException {
// 使用File對象創建流對象
File file = new File("day09\\a.txt");
FileWriter fw = new FileWriter(file);
// 使用文件名稱創建流對象
FileWriter fw = new FileWriter("day09\\b.txt");
}
}
基本寫出數據
寫出字符:write(int b)
方法,每次可以寫出一個字符數據,代碼使用演示:
public class FWWrite {
public static void main(String[] args) throws IOException {
// 使用文件名稱創建流對象
FileWriter fw = new FileWriter("day09\\fw.txt");
// 寫出數據
fw.write(97); // 寫出第1個字符
fw.write('b'); // 寫出第2個字符
fw.write('C'); // 寫出第3個字符
/*
【注意】關閉資源時,與FileOutputStream不同。
如果不關閉,數據只是保存到緩衝區,並未保存到文件。
*/
// fw.close();
}
}
輸出結果:
abC
小貼士:
- 雖然參數爲int類型四個字節,但是隻會保留一個字符的信息寫出。
- 未調用close方法,數據只是保存到了緩衝區,並未寫出到文件中。
關閉和刷新
因爲內置緩衝區的原因,如果不關閉輸出流,無法寫出字符到文件中。但是關閉的流對象,是無法繼續寫出數據的。如果我們既想寫出數據,又想繼續使用流,就需要flush
方法了。
flush
:刷新緩衝區,流對象可以繼續使用。close
:關閉流,釋放系統資源。關閉前會刷新緩衝區。
代碼使用演示:
public class FWWrite {
public static void main(String[] args) throws IOException {
// 使用文件名稱創建流對象
FileWriter fw = new FileWriter("day09\\fw.txt");
// 寫出數據,通過flush
fw.write('刷'); // 寫出第1個字符
fw.flush();
fw.write('新'); // 繼續寫出第2個字符,寫出成功
fw.flush();
// 寫出數據,通過close
fw.write('關'); // 寫出第1個字符
fw.close();
fw.write('閉'); // 繼續寫出第2個字符,【報錯】java.io.IOException: Stream closed
fw.close();
}
}
小貼士:
1)即便是flush方法寫出了數據,操作的最後還是要調用close方法,釋放系統資源。
2)注意:
close()和flush()方法的區別:
flush():刷新緩衝區。流對象還可以繼續使用。
close():先刷新緩衝區,然後通知系統釋放資源。流對象不可以再被使用了。
寫出其他數據
void write(String str):寫一個字符串數據
void write(String str,int index,int len):寫一個字符串中的一部分數據
說明:
str 表示要寫的字符串
index 表示從字符串哪個下標開始寫
len 表示寫的字符個數。
注意:字符串可以看做是一個由多個字符組成的字符數組。
void write(int ch):寫一個字符數據,這裏寫int類型的好處是既可以寫char類型的數據,也可以寫char對應的int類型的值。‘a’,97
void write(char[] chs):寫一個字符數組數據
void write(char[] chs,int index,int len):寫一個字符數組的一部分數據
參數:
chs 表示寫的字符數組
index 表示從數組哪個下標開始寫
len寫的字符個數
代碼使用演示:
package com.itheima_01;
import java.io.FileWriter;
import java.io.IOException;
/*
* void write(String str):寫一個字符串數據
* void write(String str,int index,int len):寫一個字符串中的一部分數據
* void write(int ch):寫一個字符數據,這裏寫int類型的好處是既可以寫char類型的數據,也可以寫char對應的int類型的值。'a',97
* void write(char[] chs):寫一個字符數組數據
* void write(char[] chs,int index,int len):寫一個字符數組的一部分數據
*/
public class FileWriterDemo3 {
public static void main(String[] args) throws IOException {
//創建輸出流對象
FileWriter fw = new FileWriter("day09\\b.txt");
//void write(String str):寫一個字符串數據
//fw.write("abcde");
//void write(String str,int index,int len):寫一個字符串中的一部分數據
//fw.write("abcde",0,5);
//fw.write("abcde",1,3);
//void write(int ch):寫一個字符數據,這裏寫int類型的好處是既可以寫char類型的數據,也可以寫char對應的int類型的值。'a',97
//fw.write('a');
//fw.write(97);
//void write(char[] chs):寫一個字符數組數據
char[] chs = {'a','b','c','d','e'};
//fw.write(chs);
//void write(char[] chs,int index,int len):寫一個字符數組的一部分數據
//fw.write(chs,0,5);
fw.write(chs,2,3);
//釋放資源
fw.close();
}
}
FileWriter寫入換行以及向文本末尾追加
說明:操作類似於FileOutputStream。
操作代碼演示如下:
package com.itheima_01;
import java.io.FileWriter;
import java.io.IOException;
public class FileWriterDemo4 {
public static void main(String[] args) throws IOException {
//創建輸出流對象
//FileWriter fw = new FileWriter("day09\\4.txt");
FileWriter fw = new FileWriter("day09\\4.txt",true); //表示追加寫入,默認是false
for(int x=0; x<10; x++) {
fw.write("hello"+x+"\r\n");
}
//釋放資源
fw.close();
}
}
小貼士:字符流,只能操作文本文件,不能操作圖片,視頻等非文本文件。
第二章 IO異常的處理
JDK7前處理
我們使用Java程序,操作的是Java程序以外的其他設備上的數據,都有可能發生異常問題。
我們在書寫的Java程序讀寫其他設備上的數據時,都要考慮異常問題。這些異常一般開發中都要開發者自己處理掉,不能直接往聲明。
建議使用try...catch...finally
代碼塊,處理異常部分,代碼使用演示:
說明:這裏拿字符輸出流舉例。
public class HandleException1 {
public static void main(String[] args) {
// 聲明變量
FileWriter fw = null;
try {
//創建流對象
fw = new FileWriter("day09\\fw.txt");
// 寫出數據
fw.write("黑馬程序員"); //黑馬程序員
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (fw != null) {
fw.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
JDK7的處理(掌握)
如果涉及到關閉多個流的時候,我們使用上述jdk7之前的技術雖然也可以關閉資源,但是顯得比較麻煩,在finally中代碼顯得比較冗餘,所以在jdk7對於釋放資源進行了優化,即以後關閉資源不用在finally中書寫釋放資源的代碼了。這樣對於程序員關於釋放資源的代碼寫法更爲簡單了。
注意:此處只是優化釋放資源的代碼。
那麼接下來我們來學習下jdk7是如何實現的。
首先在jdk7中出現了一個接口叫做:AutoCloseable
無論是字節流還是字符流都實現了這個接口。
舉例:FileWriter
AutoCloseable 接口說明:
說明:
- 只要IO流實現了這個接口,都可以完成自動釋放資源.
- jdk7提供了新的方式來處理釋放資源的問題.
使用JDK7優化後的try-with-resource
語句,該語句確保了每個資源在語句結束時關閉。所謂的資源(resource)是指在程序完成後,必須關閉的對象。
格式:
try (創建流對象語句,如果多個流對象語句,使用';'隔開) {
// 讀寫數據
} catch (IOException e) {
e.printStackTrace();
}
說明:
1)try後面書寫了小括號(),括號裏書寫了流對象的創建。
2)try後面的大括號中書寫讀取或者書寫數據的邏輯代碼。
3)只要使用上述格式,資源就會被自動釋放。
代碼使用演示:
寫數據模板代碼:
public class HandleException2 {
public static void main(String[] args) {
// 創建流對象
try ( FileWriter fw = new FileWriter("fw.txt"); ) {
// 寫出數據
fw.write("黑馬程序員"); //黑馬程序員
} catch (IOException e) {
e.printStackTrace();
}
}
}
讀寫數據模板代碼:
public class Demo {
public static void main(String[] args) {
try (FileWriter fw = new FileWriter("day09\\fw1.txt"); FileReader fr = new FileReader("day09\\fw.txt")) {
//讀數據
int len=0;
char[] ch=new char[1024];
while ((len=fr.read(ch))!=-1) {
fw.write(ch,0,len);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
第三章 屬性集(掌握)
3.1 概述
java.util.Properties
繼承於Hashtable
,來表示一個持久的屬性集。它使用鍵值結構存儲數據,每個鍵及其對應值都是一個字符串。
3.2 Properties類
Properties類:在之前講解Map集合時,提過一個集合類:Hashtable集合類,該類現已經被HashMap代替。而Hashtable集合類有一個子類對象:Properties。
1)該類是一個Map集合。之前學習的集合都是將數據存儲到內存中,但是這個集合類它可以和IO流結合,直接把集合中的數據保存在硬盤的文件中,或者直接從硬盤的文件中加載數據保存在集合中;
2)這個集合中的鍵和值都是String類型的字符串;
3)這個集合類沒有泛型;
構造方法
public Properties()
:創建一個空的屬性列表。
基本的存儲方法
-
public Object setProperty(String key, String value)
: 保存一對屬性。 等同於Map中的put功能 -
public String getProperty(String key)
:使用此屬性列表中指定的鍵搜索屬性值。等同於Map中的 get(Object key) -
public Set<String> stringPropertyNames()
:所有鍵的名稱的集合。等同於Map中的keySet方法
public class ProDemo {
public static void main(String[] args) throws FileNotFoundException {
//創建集合對象
Properties prop = new Properties();
//向集合中添加數據
prop.setProperty("張三","北京");
prop.setProperty("李四","上海");
prop.setProperty("王五","南京");
prop.setProperty("田七","杭州");
//使用對象調用stringPropertyNames()函數獲取所有的鍵集合
Set<String> keys = prop.stringPropertyNames();
//遍歷集合
for (Iterator<String> it = keys.iterator(); it.hasNext();) {
String key = it.next();
//根據key獲得value
String value = prop.getProperty(key);
System.out.println(key+"---"+value);
}
}
}
與流相關的方法
public void load(InputStream inStream)
: 從字節輸入流中讀取鍵值對。
參數中使用了字節輸入流,通過流對象,可以關聯到某文件上,這樣就能夠加載文本中的數據了。文本數據格式要求必須是如下格式: key=value
張三=北京
李四=上海
王五=南京
田七=杭州
加載代碼演示:
public class ProDemo2 {
public static void main(String[] args) throws FileNotFoundException {
//創建集合對象
Properties prop = new Properties();
//使用集合對象prop調用load()函數從硬盤上加載數據
// prop.load(new FileInputStream("D:\\person.txt"));
prop.load(new FileReader("D:\\person.txt"));
//遍歷集合
Set<String> keys = prop.stringPropertyNames();
for (Iterator<String> it = keys.iterator(); it.hasNext();) {
String key = it.next();
//根據key鍵獲得value
String value = prop.getProperty(key);
System.out.println(key+"---"+value);
}
}
}
小貼士:文本中的數據,必須是鍵值對形式。
第四章 ResourceBundle工具類
前面我們學習了Properties工具類,它能夠讀取資源文件,當資源文件是以.properties結尾的文件時,我們可以使用JDK提供的另外一個工具類java.util.ResourceBundle來對文件進行讀取,使得操作更加簡單。它是一個抽象類,我們可以使用它的子類PropertyResourceBundle來讀取以.properties結尾的配置文件。
1.作用
能夠更方便的讀取文件。代替Properties的load方法的。
但是使用這個工具類有前提條件:
1.文件需要是properties類型
2.文件需要保存在src路徑下
2.創建對象
在ResourceBundle類中提供了一個靜態方法,用於獲得它的子類對象(抽象類不能創建對象!)。
// 使用指定的基本名稱,默認語言環境和調用者的類加載器獲取資源包。
static ResourceBundle getBundle(String baseName);
參數:baseName:給定參數只需要配置文件的名稱,不要擴展名。
3.獲取方法
ResourceBundle類提供了一個getString(String key)方法用於讀取配置文件中指定key的值
String getString(String key) //根據鍵獲取值
-
需求:使用ResourceBundle讀取如下配置文件中的value值
配置文件如下所示:
代碼如下:
public class Test01 {
public static void main(String[] args) throws Exception {
//獲取對象
//static ResourceBundle getBundle(String baseName);
//注意:getBundle()方法的參數只數寫文件名,不要書寫後綴名
/*
帶後綴名會報錯:
Exception in thread "main" java.util.MissingResourceException:
Can't find bundle for base name 123.properties, locale zh_CN
*/
ResourceBundle bundle = ResourceBundle.getBundle("123");
//根據鍵獲取值
//String getString(String key) //根據鍵獲取值
String s = bundle.getString("username");
System.out.println(s);//root
//如果鍵不存在 就報錯了
/*
Exception in thread "main" java.util.MissingResourceException:
Can't find resource for
bundle java.util.PropertyResourceBundle, key pwd
*/
// String s2 = bundle.getString("pwd");
// System.out.println(s2);
}
}
-
中文亂碼問題
如果在使用ResourceBundle讀取配置文件中的中文時,出現亂碼問題,可以按照如下方式去做。但是注意我們在開發中不會在配置文件中書寫中文,所以這裏如果課下遇到了則按照下面做法做就可以了。
1.修改idea中的編碼,我們統一修改idea中的編碼爲UTF-8.
改完編碼之後刪除創建好的配置文件,重新創建配置文件。
2.如果按照如上方式還會出現亂碼,則可以按照如下做法:
public class Demo06 {
public static void main(String[] args) throws UnsupportedEncodingException {
//獲取對象
ResourceBundle bundle = ResourceBundle.getBundle("123");
//根據鍵獲取值
String s = bundle.getString("abc");
//把s從字符串變回到字節
byte[] bytes = s.getBytes("ISO8859-1");
//把字節數組通過正確的編碼轉回漢字
String ss = new String(bytes,"UTF-8");
System.out.println(ss);
}
}