day21【字符流、Properties】

主要內容

  • 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類是讀取字符文件的便利類。構造時使用當前環境默認的字符編碼和默認字節緩衝區。

小貼士:

  1. 字符編碼:字節與字符的對應規則。Windows系統的中文編碼默認是GBK編碼表。idea中UTF-8
  2. 字節緩衝區:一個字節數組,用來臨時存儲字節數據。

注意:在計算機中所有數據在底層都是以字節數據存在的,即使是字符數據在最底層也是以字節數據存在的,因爲計算機只識別字節數據。所以在字符流底層使用的是字節緩衝區,其實就是一個字節數組。

構造方法

  • 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");
    }
}

讀取字符數據

  1. 讀取字符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類型。

  1. 使用字符數組讀取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

小貼士:

  1. 雖然參數爲int類型四個字節,但是隻會保留一個字符的信息寫出。
  2. 未調用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);

    }
}

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章