天天看點

Java 基礎開發Word總結《 十三》Java IO總結

1.File類

1.1.File類說明

存儲在變量,數組和對象中的資料是暫時的,當程式終止時他們就會丢失.為了能夠永

久的儲存程式中建立的資料,需要将他們存儲到硬碟或CD光牒的檔案中.這些檔案可以移動,傳送,亦可以被其他程式使用.由于資料存儲在檔案中,是以我們需要學習一個和檔案有密切關系的類,叫做File類,将要掌握擷取檔案的屬性以及删除和重命名檔案.最終如何向檔案中寫入資料和從檔案中讀取資料.

那麼File類關心的是在磁盤上檔案的存儲.

File類描述的是一個檔案或檔案夾。(檔案夾也可以稱為目錄)

該類的出現是對檔案系統的中的檔案以及檔案夾進行對象的封裝。可以通過對象的思想來操作檔案以及檔案夾。

可以用面向對象的處理問題,通過該對象的方法,可以得到檔案或檔案夾的資訊友善了對檔案與檔案夾的屬性資訊進行操作。

檔案包含很多的資訊:如檔案名、建立修改時間、大小、可讀可寫屬性等。

1.2.體驗File類

檢驗指定路徑下是否存在指定的目錄或者檔案.

// 檢驗指定路徑下是否存在指定的目錄或者檔案.

File file = new File(“c:\a.txt”);

System.out.println(file.exists());

// File對象是否是目錄

System.out.println(file.isDirectory());

// 對象是否是檔案

System.out.println(file.isFile());

結論:File對象也可以表示不存在的檔案。其實代表了一個抽象路徑

建構一個File類的執行個體并不會在機器上建立一個檔案.不管檔案是否存在都可以建立任意檔案名的File執行個體,可以調用File執行個體的exists方法判斷檔案或目錄是否存在

1.3.構造一個File類執行個體:

new File(String pathname);

通過将給定路徑來建立一個新File執行個體。

new File(String parent, String child);

根據parent路徑名字元串和child路徑名建立一個新File執行個體。

parent是指上級目錄的路徑,完整的路徑為parent+child.

new File(File parent, String child);

根據parent抽象路徑名和child路徑名建立一個新File執行個體。

parent是指上級目錄的路徑,完整的路徑為parent.getPath()+child.

說明:

如果指定的路徑不存在(沒有這個檔案或是檔案夾),不會抛異常,這時file.exists()傳回false。

建立File對象File file=new File();
           

public static void main(String[] args) {

File file = new File();

}

1:建立File對象需要導包, import java.io.File

2:File對象沒有無參數構造.建立對象需要傳參.

根據API文檔提示,傳入一個檔案的字元串路徑. String path=”c:/a.txt”;

(a.txt 檔案在c盤下已經存在)

//file 是一個檔案對象

String path = “c:/a.txt”;

File file = new File(path);

File類的對象,既可以代表檔案也可以代表檔案夾。

public static void main(String[] args) {

//file 是一個檔案夾

String path = “c:/test”;

File file = new File(path);

}

1.4.路徑:

路徑就是檔案或檔案夾所在的位置。

1.4.1.路徑分割符:

上下級檔案夾之間使用分隔符分開:

在Windows中分隔符為’\’,在Unix/Linux中分隔符為’/’。

跨平台的目錄分隔符

更專業的做法是使用File.separatorChar,這個值就會根據系統得到的相應的分割符。

例:new File(“c:” + File.separatorChar + “a.txt”);

注意,如果是使用”\”,則需要進行轉義,寫為”\”才可以,如果是兩個”\”,則寫為”\\”。

實驗:

在以下代碼的path處寫不同的路徑試一下,并觀察輸出結果。

File file = new File(path);

System.out.println(file.getAbsolutePath());

1.4.2.絕對路徑與相對路徑:

對于UNIX平台,絕對路徑名的字首是”/”。相對路徑名沒有字首。

對于Windows平台,絕對路徑名的字首由驅動器号和一個”:”組成,例”c:\…”。相對路徑沒有盤符字首。

相對路徑:

相對路徑是指相對于某位置的路徑,是指相對于目前目錄。

在執行Java程式時,相對路徑為執行java指令時目前所在的目錄。

實驗:

在不同的路徑下執行java指令運作以下程式,并觀察輸出結果。

File file = new File(“a.txt”);

System.out.println(file.getAbsolutePath());

一般在使用時,建議用絕對路徑,因為相對路徑容易出問題,不好确定到底在什麼地方。

//相對路徑

//File file = new File(“src/a.txt”);

1.5.File類中常用的方法:

建立:

createNewFile() 在指定位置建立一個空檔案,成功就傳回true,如果已存在就不建立然後傳回false

mkdir() 在指定位置建立目錄,這隻會建立最後一級目錄,如果上級目錄不存在就抛異常。

mkdirs() 在指定位置建立目錄,這會建立路徑中所有不存在的目錄。

renameTo(File dest) 重命名檔案或檔案夾,也可以操作非空的檔案夾,檔案不同時相當于檔案的剪切,剪切時候不能操作非空的檔案夾。移動/重命名成功則傳回true,失敗則傳回false。

删除:
           

delete() 删除檔案或一個空檔案夾,如果是檔案夾且不為空,則不能删除,成功傳回true,失敗傳回false。

deleteOnExit() 在虛拟機終止時,請求删除此抽象路徑名表示的檔案或目錄,保證程式異常時建立的臨時檔案也可以被删除

判斷:
           

exists() 檔案或檔案夾是否存在。

isFile() 是否是一個檔案,如果不存在,則始終為false。

isDirectory() 是否是一個目錄,如果不存在,則始終為false。

isHidden() 是否是一個隐藏的檔案或是否是隐藏的目錄。

isAbsolute() 測試此抽象路徑名是否為絕對路徑名。

擷取:
           

getName() 擷取檔案或檔案夾的名稱,不包含上級路徑。

getPath() 傳回絕對路徑,可以是相對路徑,但是目錄要指定

getAbsolutePath() 擷取檔案的絕對路徑,與檔案是否存在沒關系

length() 擷取檔案的大小(位元組數),如果檔案不存在則傳回0L,如果是檔案夾也傳回0L。

getParent() 傳回此抽象路徑名父目錄的路徑名字元串;如果此路徑名沒有指定父目錄,則傳回null。

lastModified() 擷取最後一次被修改的時間。

檔案夾相關:

staic File[] listRoots() 列出所有的根目錄(Window中就是所有系統的盤符)

list() 傳回目錄下的檔案或者目錄名,包含隐藏檔案。對于檔案這樣操作會傳回null。

list(FilenameFilter filter) 傳回指定目前目錄中符合過濾條件的子檔案或子目錄。對于檔案這樣操作會傳回null。

listFiles() 傳回目錄下的檔案或者目錄對象(File類執行個體),包含隐藏檔案。對于檔案這樣操作會傳回null。

listFiles(FilenameFilter filter) 傳回指定目前目錄中符合過濾條件的子檔案或子目錄。對于檔案這樣操作會傳回null。

1.6.案例:

1,列出指定目錄中所有的子檔案名與所有的子目錄名。

2,列出指定目錄中所有的子檔案名與所有的子目錄名,要求目錄名與檔案名分開列出,格式如下:

子目錄:

子檔案:

3,列出指定目錄中所有擴充名為.java的檔案。

4,列出指定目錄中所有擴充名為.class的檔案。

5,思考第3與第4題,代碼是不是重複呢,如果想要列出其中的所有.txt檔案,是不是要再寫一個類呢?

a, 請自行設計一個工具方法,可以傳遞指定的擴充名,工具方法會過列出指定目錄中指定擴充名的所有子檔案與子檔案夾。

b, 請利用FilenameFilter接口寫一個工具類,可以傳遞指定的過濾規則。

6,列出指定目錄中所有的子孫檔案與子孫目錄名,隻需要列出名稱即可。

解題: 列出指定目錄中所有的子檔案名與所有的子目錄名。

需求1:擷取所有的c:/test 即test目錄下的所有檔案和檔案夾

解題思路:

代碼需要封裝,就需要建立方法,并在main方法中調用和測試. 方法名要有意義: listAllFilesAndDirs

第一步:建立File對象

第二步:查找File類中可用的方法,想要擷取該目錄下的所有子檔案和子目錄

第三步:顯示這些檔案和檔案夾的名稱

實作:

public static void listAllFilesAndDirs(String path) {

// 1.建立File對象,表示這個目錄

File dir = new File(path);

// 2.通過list方法得到所包含的所有子目錄與子檔案名稱

String[] names = dir.list();

// 3顯示這些名稱

for (int i = 0; i < names.length; i++) {

System.out.println(names[i]);

}

}

解題: 列出指定目錄中所有的子檔案名與所有的子目錄名,要求目錄名與檔案名分開列出

案例1把檔案和檔案夾都列了出來,但是無法區分檔案和檔案夾.File類有判斷檔案和檔案夾的方法,但是list方法傳回的是String數組,這個list() 方法無法滿足我們的需求.繼續查找File的方法.檢視api找到 File[] listFiles() 發現該方法傳回的是一個File數組。

思路:

第一步:建立listAllFilesAndDirs(String path)方法,接受路徑

第二步: 建立File對象表示這個目錄

第三步: 通過listFiles方法得到所包含的所有子目錄與子檔案名稱

第四步: 得到所有的檔案名集合,與所有的檔案夾名集合

第五步: 分别顯示檔案名與檔案夾名

實作

public static void listAllFilesAndDirs2(String path) {

// 1.建立File對象,表示這個目錄

File dir = new File(path);

// 2通過listFiles方法得到所包含的所有子目錄與子檔案名稱

File[] names = dir.listFiles();

// 3,分别顯示檔案名與檔案夾名

for (int i = 0; i < names.length; i++) {

File file = names[i];

if (file.isFile()) {

System.out.println((“子檔案:”));

System.out.println(“\t” + file.getName());

} else if (file.isDirectory()) {

System.out.println((“子目錄:”));

System.out.println(“\t” + file.getName());

}

}

}

實作二:

public static void listAllFilesAndDirs(String path) {

//1建立File對象表示這個目錄

File dir = new File(path);

//2通過listFiles方法得到所包含的所有子目錄與子檔案名稱
    File[] names = dir.listFiles();

    //3,得到所有的檔案名集合,與所有的檔案夾名集合
    List<File> filesList = new ArrayList<File>();
    List<File> dirsList = new ArrayList<File>();
    for (int i = 0; i < names.length; i++) {
        File file = names[i];
        if (file.isFile()) {
            filesList.add(file);
        } else if (file.isDirectory()) {
            dirsList.add(file);
        }
    }

    //4,分别顯示檔案名與檔案夾名
    System.out.println("子檔案:");
    for (int i = 0; i < filesList.size(); i++) {
        System.out.println("\t" + filesList.get(i).getName());
    }
    System.out.println("子目錄:");
    for (int i = 0; i < dirsList.size(); i++) {
        System.out.println("\t" + dirsList.get(i).getName());
    }
}
           

練習3,列出指定目錄中所有擴充名為.java的檔案。

需求: 從指定目錄中找到指定擴充名的檔案,并列出來

思路

第一步: 建立lsitAllFiles方法,接受路徑和檔案字尾名

第二步: 擷取所有的子檔案和子檔案夾

第三步: 從中找出符合條件的檔案并顯示出來

注意:不同系統對于路徑的 windows系統使用斜線作為路徑分隔符 “\” linux 系統使用反斜線作為路徑分隔符”/”java是跨平台的語言,java程式如果部署到linux系統上,如果程式中有File對象, 可以使用File類separatorChar (字段)

public class FileTest2 {

public static void main(String[] args) {

String path = “c:” + File.separatorChar + “test”;

File file = new File(path);

listtAllFiles(file, “java”);

}

/**
 * 從指定目錄中找到指定擴充名的檔案,并列出來
 * 
 */
public static void listtAllFiles(File dir, String extension) {
    // 1.擷取所有的子檔案和子檔案夾
    File[] files = dir.listFiles();

    // 2.從中找出符合條件的檔案并顯示出來
    for (int i = 0; i < files.length; i++) {
        File file = files[i];
        // 3.需要以指定檔案字尾結尾才算符合條件
        if (file.getName().endsWith(extension)) {
            System.out.println(file.getName());
        }
    }
}
           

}

練習4:

public class FileTest2 {

public static void main(String[] args) {

String path = “c:” + File.separatorChar + “test”;

File file = new File(path);

listtAllFiles2(file, “txt”);

}

/**
 * FilenameFilter接口寫一個工具類,可以傳遞指定的過濾規則。
 *  從指定目錄中找到指定擴充名的檔案,并列出來
 *
 * */
public static void listtAllFiles2(File dir, String name) {
    // 1.擷取所有的子檔案和子檔案夾
    String[] files = dir.list(new DirFilter("txt"));

    // 2顯示名稱
    for (int i = 0; i < files.length; i++) {
        System.out.println(files[i]);
    }
}
           

}

class DirFilter implements FilenameFilter {

private String extension;

public DirFilter() {

}

public DirFilter(String extension) {
    this.extension = extension;
}

@Override
public boolean accept(File dir, String name) {
    return name.endsWith(extension);
}
           

}

注意: DirFilter 就是實作了accept 方法.提供給File類的list方法使用.

2.IO流體驗與簡介

File對象可以表示存在的檔案或檔案夾,也可以表示不存在的。

我們想要得到檔案的内容怎麼辦,File隻是操作檔案,檔案的内容如何處理就需要使用io流技術了。

例如在C槽下有一個名稱為a.txt的文本檔案.想要通過Java程式讀出來檔案中的内容,需要使用IO流技術.同樣想要将程式中的資料,儲存到硬碟的檔案中,也需要IO流技術.

讀和寫檔案檔案示例:

public class IoTest {

public static void main(String[] args) throws FileNotFoundException,

IOException {

writFileTest();

readFileTest();
}

private static void writFileTest() throws FileNotFoundException,
        IOException {
    // 建立檔案對象
    File file = new File("c:\\a.txt");
    // 建立檔案輸出流
    FileOutputStream fos = new FileOutputStream(file);
    fos.write('g');
    fos.write('z');
    fos.write('i');
    fos.write('t');
    fos.write('c');
    fos.write('a');
    fos.write('s');
    fos.write('t');
    fos.close();
}

private static void readFileTest() throws FileNotFoundException,
        IOException {
    // 建立檔案對象
    File file = new File("c:\\a.txt");
    // 建立檔案輸入流
    FileInputStream fis = new FileInputStream(file);
    // 有對多長,就讀多少位元組。
    for (int i = 0; i < file.length(); i++) {
        System.out.print((char) fis.read());
    }
    fis.close();
}
           

}

當完成流的讀寫時,應該通過調用close方法來關閉它,這個方法會釋放掉十分有限的作業系統資源.如果一個應用程式打開了過多的流而沒有關閉它們,那麼系統資源将被耗盡.

IO流簡介:(Input/Output)

I/O類庫中使用“流”這個抽象概念。Java對裝置中資料的操作是通過流的方式。

表示任何有能力産出資料的資料源對象,或者是有能力接受資料的接收端對象。“流”屏蔽了實際的I/O裝置中處理資料的細節。IO流用來處理裝置之間的資料傳輸。裝置是指硬碟、記憶體、鍵盤錄入、網絡等。

Java用于操作流的對象都在IO包中。IO流技術主要用來處理裝置之間的資料傳輸。

由于Java用于操作流的對象都在IO包中。是以使用IO流需要導包如:import java.io.*;

IO流的分類

流按操作資料類型的不同分為兩種:位元組流與字元流。

流按流向分為:輸入流,輸出流(以程式為參照物,輸入到程式,或是從程式輸出)

3.位元組流

什麼是位元組流

計算機中都是二進制資料,一個位元組是8個2進制位.位元組可以表示所有的資料,比如文本,音頻,視訊.圖檔,都是作為位元組存在的.也就是說位元組流處理的資料非常多。

在文本檔案中存儲的資料是以我們能讀懂的方式表示的。而在二進制檔案中存儲的資料是用二進制形式表示的。我們是讀不懂二進制檔案的,因為二進制檔案是為了讓程式來讀取而設計的。例如,Java的源程式(.java源檔案)存儲在文本檔案中,可以使用文本編輯器閱讀,但是Java的類(位元組碼檔案)存儲在二進制檔案中,可以被Java虛拟機閱讀。二進制檔案的優勢在于它的處理效率比文本檔案高。

我們已經知道File對象封裝的是檔案或者路徑屬性,但是不包含向(從)檔案讀(寫)資料的方法。為了實作對檔案的讀和寫操作需要學會正确的使用Java的IO建立對象。

位元組流的抽象基類:

輸入流:java.io.InputStream

輸出流:java.io.OutputStream

特點:

位元組流的抽象基類派生出來的子類名稱都是以其父類名作為子類名的字尾。

如:FileInputStream, ByteArrayInputStream等。

說明:

位元組流處理的單元是一個位元組,用于操作二進制檔案(計算機中所有檔案都是二進制檔案)

3.1.InputStream

案例:讀取”c:/a.txt”檔案中的所有内容并在控制台顯示出來。

注意:事先準備一個a.txt并放到c:/下,不要儲存中文。

a, 使用read()方法實作。

b, 使用int read(byte[] b)方法實作。

寫代碼讀取”c:/a.txt”檔案中的所有的内容并在控制台顯示出來

實作:

檢視api文檔(自己一定要動手)

InputStream 有read方法,一次讀取一個位元組,OutputStream的write方法一次寫一個int。發現這兩個類都是抽象類。意味着不能建立對象,那麼需要找到具體的子類來使用。

通過檢視api文檔,找到了FileInputStream類,該類正是我們體驗Io流的一個輸入流。

實作;顯示指定檔案内容。

明确使用流,使用哪一類流?使用輸入流,FileInputStream

第一步:

1:打開流(即建立流)

第二步:

2:通過流讀取内容

第三步:

3:用完後,關閉流資源

顯然流是Java中的一類對象,要打開流其實就是建立具體流的對象,由于是讀取硬碟上的檔案,應該使用輸入流。是以找到了InputStream類,但是InputStream是抽象類,需要使用它的具體實作類來建立對象就是FileInputStream。通過new 調用FileInputStream 的構造方法來建立對象。發現FileInputStream的構造方法需要指定檔案的來源。檢視構造方法,可以接受字元串也可以接受File對象。我們通過建構File對象指定檔案路徑。

使用流就像使用水管一樣,要打開就要關閉。是以打開流和關閉流的動作是比不可少的。如何關閉流?使用close方法即可,當完成流的讀寫時,應該通過調用close方法來關閉它,這個方法會釋放掉十分有限的作業系統資源.如果一個應用程式打開了過多的流而沒有關閉它們,那麼系統資源将被耗盡.

如何通過流讀取内容?

查找api文檔通過read方法,檢視該方法,發現有傳回值,并且是int類型的,該方法一次讀取一個位元組(byte)

3.1.1.輸入流讀取方式1:

read方法()

一次讀取一個位元組,讀到檔案末尾傳回-1.

仔細檢視api文檔發現read方法如果讀到檔案的末尾會傳回-1。那麼就可以通過read方法的傳回值是否是-1來控制我們的循環讀取。

private static void showContent(String path) throws IOException {

// 打開流

FileInputStream fis = new FileInputStream(path);

int len = fis.read();
    while (len != -1) {
        System.out.print((char)len);
        len = fis.read();

    }
    // 使用完關閉流
    fis.close();
}
           

我們習慣這樣寫:

private static void showContent(String path) throws IOException {

// 打開流

FileInputStream fis = new FileInputStream(path);

int len;
    while ((len = fis.read()) != -1) {
        System.out.print((char) len);
    }
    // 使用完關閉流
    fis.close();
}
           

3.1.2.輸入流讀取方式2:

使用read(byte[] b) 方法。使用緩沖區(關鍵是緩沖區大小的确定)

使用read方法的時候,流需要讀一次就處理一次,可以将讀到的資料裝入到位元組數組中,一次性的操作數組,可以提高效率。

問題1:緩沖區大小

那麼位元組數組如何定義?定義多大?

可以嘗試初始化長度為5的byte數組。通過read方法,往byte數組中存内容

那麼該read方法傳回的是往數組中存了多少位元組。

private static void showContent2(String path) throws IOException {

// 打開流

FileInputStream fis = new FileInputStream(path);

// 通過流讀取内容
    byte[] byt = new byte[5];
    int len = fis.read(byt);
    for (int i = 0; i < byt.length; i++) {
        System.out.print((char) byt[i]);
    }

    // 使用完關閉流
    fis.close();
}
           

問題1: 緩沖區太小:

資料讀取不完.

測試發現問題,由于數組太小,隻裝了5個位元組。而文本的位元組大于數組的長度。那麼很顯然可以将數組的長度定義大一些。例如1024個。

private static void showContent2(String path) throws IOException {

// 打開流

FileInputStream fis = new FileInputStream(path);

// 通過流讀取内容
    byte[] byt = new byte[1024];
    int len = fis.read(byt);
    for (int i = 0; i < byt.length; i++) {
        System.out.print(byt[i]);
    }

    // 使用完關閉流
    fis.close();
}
           

問題三:緩沖區有預設值.

測試,列印的效果列印出了很多0,因為數組數組有預設初始化值,是以,我們将數組的資料全部都周遊和出來.現在需要的是取出數組中的部分資料.需要将循環條件修改仔細檢視api文檔。發現該方法read(byte[] b)傳回的是往數組中存入了多少個位元組。就是數組實際存儲的資料個數。

private static void showContent2(String path) throws IOException {

// 打開流

FileInputStream fis = new FileInputStream(path);

// 通過流讀取内容
    byte[] byt = new byte[1024];
    int len = fis.read(byt);
    for (int i = 0; i <len; i++) {
        System.out.print(byt[i]);
}

    // 使用完關閉流
    fis.close();
}
           

總結:

問題一:為什麼列印的不是字母而是數字,

是字母對應的碼值。

如何顯示字元,強轉為char即可

問題二:注意:回車和換行的問題。

windows的換車和換行是”\r\n” 對應碼表是13和10 。

3.1.3.輸入流讀取方式3:

使用read(byte[] b,int off,int len)

檢視api文檔,

b顯然是一個byte類型數組,當做容器來使用

off,是指定從數組的什麼位置開始存位元組

len,希望讀多少個

其實就是把數組的一部分當做流的容器來使用。告訴容器,從什麼地方開始裝要裝多少。

private static void showContent3(String path) throws IOException {

// 打開流

FileInputStream fis = new FileInputStream(path);

// 通過流讀取内容
    byte[] byt = new byte[1024];
    // 從什麼地方開始存讀到的資料
    int start = 5;

    // 希望最多讀多少個(如果是流的末尾,流中沒有足夠資料)
    int maxLen = 6;

    // 實際存放了多少個
    int len = fis.read(byt, start, maxLen);

    for (int i = start; i < start + maxLen; i++) {
        System.out.print((char) byt[i]);
    }

    // 使用完關閉流
    fis.close();
}
           

需求2:測試skip方法

通過Io流,讀取”c:/a.txt”檔案中的第9個位元組到最後所有的内容并在控制台顯示出來。

分析:其實就是要跳過檔案中的一部分位元組,需要查找API文檔。可以使用skip方法skip(long n),參數跟的是要跳過的位元組數。

我們要從第9個開始讀,那麼要跳過前8個即可。

private static void showContent4(String path) throws IOException {

// 打開流

FileInputStream fis = new FileInputStream(path);

// 通過流讀取内容
    byte[] byt = new byte[1024];
    fis.skip(8);
    int len = fis.read(byt);
    System.out.println(len);
    System.out.println("**********");
    for (int i = 0; i < len; i++) {
        System.out.println((char) byt[i]);
    }
    // 使用完關閉流
    fis.close();
           

}

3.1.4.輸入流讀取方式4:

使用緩沖(提高效率),并循環讀取(讀完所有内容).

總結:讀完檔案的所有内容。很顯然可以使用普通的read方法,一次讀一個位元組直到讀到檔案末尾。為了提高效率可以使用read(byte[] byt);方法就是所謂的使用緩沖提高效率。我們可以讀取大文本資料測試(大于1K的文本檔案.)

private static void showContent5(String path) throws IOException {

FileInputStream fis = new FileInputStream(path);

byte[] byt = new byte[1024];

int len = fis.read(byt);

System.out.println(len);

String buffer = new String(byt, 0, len);

System.out.print(buffer);

}

注意:如何将位元組數組轉成字元串? 可以通過建立字元串對象即可。

發現:一旦資料超過1024個位元組,數組就存儲不下。

如何将檔案的剩餘内容讀完?

我們可以通過通過循環保證檔案讀取完。

private static void showContent7(String path) throws IOException {

FileInputStream fis = new FileInputStream(path);

byte[] byt = new byte[1024];

int len = 0;

while ((len = fis.read(byt)) != -1) {

System.out.println(new String(byt, 0, len));

}

}

3.2.OutputStream

位元組輸出流

案例:

1,寫代碼實作把”Hello World!”寫到”c:/a.txt”檔案中。

a, c:/a.txt不存在時,測試一下。

b, c:/a.txt存在時,也測試一下。

要寫兩個版本:

a, 使用write(int b) 實作。

b, 使用write(byte[] b) 實作。

2,在已存在的c:/a.txt文本檔案中追加内容:“Java IO”。

顯然此時需要向指定檔案中寫入資料。

使用的就是可以操作檔案的位元組流對象。OutputStream。該類是抽象類,需要使用具體的實作類來建立對象檢視API文檔,找到了OutputStream的實作類FileOutputStream 建立FileOutputStream 流對象,必須指定資料要存放的目的地。通過構造函數的形式。建立流對象時,調用了系統底層的資源。在指定位置建立了資料存放的目的檔案。

流程:

1:打開檔案輸出流,流的目的地是指定的檔案

2:通過流向檔案寫資料

3: 用完流後關閉流

3.2.1.輸出流寫出方式1:

使用write(int b)方法,一次寫出一個位元組.

在C槽下建立a.txt文本檔案

import java.io.FileOutputStream;

import java.io.IOException;

public class IoTest2 {

public static void main(String[] args) throws IOException {

String path = “c:\a.txt”;

writeTxtFile(path);

}

private static void writeTxtFile(String path) throws IOException {
    // 1:打開檔案輸出流,流的目的地是指定的檔案
    FileOutputStream fos = new FileOutputStream(path);

    // 2:通過流向檔案寫資料
    fos.write('j');
    fos.write('a');
    fos.write('v');
    fos.write('a');
    // 3:用完流後關閉流
    fos.close();

}
           

}

當c盤下的a.txt不存在會怎麼樣?

測試:将c盤下的a.txt檔案删除,發現當檔案不存在時,會自動建立一個,但是建立不了多級目錄。

注意:使用write(int b)方法,雖然接收的是int類型參數,但是write 的正常協定是:向輸出流寫入一個位元組。要寫入的位元組是參數 b 的八個低位。b 的 24 個高位将被忽略。

3.2.2.輸出流寫出方式2:

使用write(byte[] b),就是使用緩沖.提高效率.

上述案例中的使用了OutputStram 的write方法,一次隻能寫一個位元組。成功的向檔案中寫入了内容。但是并不高效,如和提高效率呢?是否應該使用緩沖,根據位元組輸入流的緩沖原理,是否可以将資料儲存中位元組數組中。通過操作位元組數組來提高效率。查找API文檔,在OutputStram類中找到了write(byte[] b)方法,将 b.length 個位元組從指定的 byte 數組寫入此輸出流中。

如何将位元組資料儲存在位元組數組中,以字元串為例,”hello , world” 如何轉為位元組數組。顯然通過字元串的getBytes方法即可。

public class IoTest2 {

public static void main(String[] args) throws IOException {

String path = “c:\a.txt”;

writeTxtFile(path);

}

private static void writeTxtFile(String path) throws IOException {
    // 1:打開檔案輸出流,流的目的地是指定的檔案
    FileOutputStream fos = new FileOutputStream(path);

    // 2:通過流向檔案寫資料
    byte[] byt = "java".getBytes();
    fos.write(byt);
    // 3:用完流後關閉流
    fos.close();
}
           

}

仔細檢視a.txt文本檔案發現上述程式每運作一次,老的内容就會被覆寫掉。,那麼如何不覆寫已有資訊,能夠往a.txt裡追加資訊呢。檢視API文檔,發現FileOutputStream類中的構造方法中有一個構造可以實作追加的功能FileOutputStream(File file, boolean append) 第二個參數,append - 如果為 true,則将位元組寫入檔案末尾處,而不是寫入檔案開始處

private static void writeTxtFile(String path) throws IOException {

// 1:打開檔案輸出流,流的目的地是指定的檔案

FileOutputStream fos = new FileOutputStream(path,true);

// 2:通過流向檔案寫資料
    byte[] byt = "java".getBytes();
    fos.write(byt);
    // 3:用完流後關閉流
    fos.close();
}
           

3.3.位元組流檔案拷貝

3.3.1.位元組輸入輸出流綜合使用

通過位元組輸出流向檔案中寫入一些資訊,并使用位元組輸入流把檔案中的資訊顯示到控制台上。

public class IoTest3 {

public static void main(String[] args) throws IOException {

String path = “c:\b.txt”;

String content = “hello java”;

writeFile(path, content);

    readFile(path);
}

public static void writeFile(String path, String content)
        throws IOException {
    // 打開檔案輸出流
    FileOutputStream fos = new FileOutputStream(path);
    byte[] buffer = content.getBytes();
    // 向檔案中寫入内容
    fos.write(buffer);
    // 關閉流
    fos.close();

}

public static void readFile(String path) throws IOException {
    FileInputStream fis = new FileInputStream(path);
    byte[] byt = new byte[1024];
    int len = 0;
    while ((len = fis.read(byt)) != -1) {
        System.out.println(new String(byt, 0, len));
    }
    // 關閉流
    fos.close();

}
           

}

注意輸出流的細節:

這個輸出流顯然隻适合小資料的寫入,如果有大資料想要寫入,我們的byte數組,該如何定義?

上述案例中我們将輸入流和輸出流進行和綜合使用,如果嘗試進輸出流換成文本檔案就可以實作檔案的拷貝了.

什麼是檔案拷貝?很顯然,先開一個輸入流,将檔案加載到流中,再開一個輸出流,将流中資料寫到檔案中。就實作了檔案的拷貝。

分析:

第一步:需要打開輸入流和輸出流

第二步:讀取資料并寫出資料

第三步:關閉流

public class IoTest3 {

public static void main(String[] args) throws IOException {

    String srcPath = "c:\\a.txt";
    String destPath = "d:\\a.txt";
    copyFile(srcPath, destPath);
}

public static void copyFile(String srcPath, String destPath)
        throws IOException {

}
           

}

3.3.2.位元組流拷貝檔案實作1

讀一個位元組寫一個位元組read 和write

public class IoTest3 {

public static void main(String[] args) throws IOException {

    String srcPath = "c:\\a.txt";
    String destPath = "d:\\a.txt";
    copyFile(srcPath, destPath);
}

public static void copyFile(String srcPath, String destPath)
        throws IOException {
    // 打開輸入流,輸出流
    FileInputStream fis = new FileInputStream(srcPath);
    FileOutputStream fos = new FileOutputStream(destPath);

    // 讀取和寫入資訊
    int len = 0;
    while ((len = fis.read()) != -1) {
        fos.write(len);
    }

    // 關閉流
    fis.close();
    fos.close();
}
           

}

文本檔案在計算機中是以二進制形式存在的,可以通過io流來拷貝,那麼圖檔能不能拷貝呢?視訊呢?音頻呢?

public class IoTest3 {

public static void main(String[] args) throws IOException {

    String srcPath = "c:\\秋.jpg";
    String destPath = "d:\\秋.jpg";
    copyFile(srcPath, destPath);
}

public static void copyFile(String srcPath, String destPath)
        throws IOException {
    // 打開輸入流,輸出流
    FileInputStream fis = new FileInputStream(srcPath);
    FileOutputStream fos = new FileOutputStream(destPath);

    // 讀取和寫入資訊
    int len = 0;
    while ((len = fis.read()) != -1) {
        fos.write(len);
    }

    // 關閉流
    fis.close();
    fos.close();
}
           

}

測試統統通過,是以位元組流可以操作所有的檔案。隻是發現程式很慢,需要很長時間。特别是拷貝音頻和視訊檔案時。

為什麼?因為每次讀一個位元組再寫一個位元組效率很低。很顯然這樣效率低下的操作不是我們想要的。有沒有更快更好的方法呢,是否可以使用緩沖區來提高程式的效率呢。

3.3.3.位元組流拷貝檔案實作2;

使用位元組數組作為緩沖區

public static void copyFile2(String srcPath, String destPath)

throws IOException {

// 打開輸入流,輸出流

FileInputStream fis = new FileInputStream(srcPath);

FileOutputStream fos = new FileOutputStream(destPath);

// 讀取和寫入資訊
    int len = 0;

    // 使用位元組數組,當做緩沖區
    byte[] byt = new byte[1024];
    while ((len = fis.read(byt)) != -1) {
        fos.write(byt);
    }

    // 關閉流
    fis.close();
    fos.close();
}
           

問題1: 使用緩沖(位元組數組)拷貝資料,拷貝後的檔案大于源檔案.

測試該方法,拷貝文本檔案,仔細觀察發現和源檔案不太一緻。

打開檔案發現拷貝後的檔案和拷貝前的源檔案不同,拷貝後的檔案要比源檔案多一些内容問題就在于我們使用的容器,這個容器我們是重複使用的,新的資料會覆寫掉老的資料,顯然最後一次讀檔案的時候,容器并沒有裝滿,出現了新老資料并存的情況。

是以最後一次把容器中資料寫入到檔案中就出現了問題。

如何避免?使用FileOutputStream 的write(byte[] b, int off, int len)

b 是容器,off是從數組的什麼位置開始,len是擷取的個數,容器用了多少就寫出多少。

public static void copyFile2(String srcPath, String destPath)

throws IOException {

// 打開輸入流,輸出流

FileInputStream fis = new FileInputStream(srcPath);

FileOutputStream fos = new FileOutputStream(destPath);

// 讀取和寫入資訊
    int len = 0;

    // 使用位元組數組,當做緩沖區
    byte[] byt = new byte[1024];
    while ((len = fis.read(byt)) != -1) {
        fos.write(byt, 0, len);
    }

    // 關閉流
    fis.close();
    fos.close();
}
           

使用緩沖拷貝視訊,可以根據拷貝的需求調整數組的大小,一般是1024的整數倍。發現使用緩沖後效率大大提高。

3.4.位元組流的異常處理

上述案例中所有的異常都隻是進行了抛出處理,這樣是不合理的。是以上述代碼并不完善,因為異常沒有處理。

當我們打開流,讀和寫,關閉流的時候都會出現異常,異常出現後,後面的代碼都不會執行了。假設打開和關閉流出現了異常,那麼顯然close方法就不會再執行。那麼會對程式有什麼影響?

案例:

public class IoTest4 {

public static void main(String[] args) throws IOException,

InterruptedException {

String path = “c:\b.txt”;

readFile(path);

}

private static void readFile(String path) throws IOException,
        InterruptedException {
    FileInputStream fis = new FileInputStream(path);
    byte[] byt = new byte[1024];
    int len = fis.read(byt);
    System.out.println(new String(byt, 0, len));
    // 讓程式睡眠,無法執行到close方法。
    Thread.sleep(1000 * 10);
    fis.close();
}
           

}

在執行該程式的同時我們嘗試去删除b.txt檔案。如果在該程式沒有睡醒的話,我們是無法删除b.txt 檔案的。因為b.txt還被該程式占用着,這是很嚴重的問題,是以一定要關閉流。

目前我們是抛出處理,一旦出現了異常,close就沒有執行,也就沒有釋放資源。那麼為了保證close的執行該如何處理呢。

那麼就需要使用try{} catch(){}finally{}語句。try中放入可能出現異常的語句,catch是捕獲異常對象,fianlly是一定要執行的代碼

public class IoTest4 {

public static void main(String[] args) throws IOException,

InterruptedException {

String path = “c:\b.txt”;

readFile(path);

}

private static void readFile(String path) {
    FileInputStream fis = null;
    try {
        fis = new FileInputStream(path);
        byte[] byt = new byte[1024];
        int len = fis.read(byt);
        System.out.println(new String(byt, 0, len));
    } catch (IOException e) {
        // 抛出運作時異常
        throw new RuntimeException(e);
    } finally {
        // 把close方法放入finally中保證一定會執行
        // 先判斷是否空指針
        if (fis != null) {
            try {
                fis.close();
            } catch (Exception e) {
                throw new RuntimeException(e);
            }

        }

    }

}
           

}

檔案拷貝的異常處理:

public static void copyFile(String srcPath, String destPath) {

FileInputStream fis = null;
    FileOutputStream fos = null;
    try {
        fis = new FileInputStream(srcPath);
        fos = new FileOutputStream(destPath);

        byte[] byt = new byte[1024 * 1024];
        int len = 0;
        while ((len = fis.read(byt)) != -1) {

            fos.write(byt, 0, len);
        }
    } catch (IOException e) {
        throw new RuntimeException(e);
    } finally {
        if (fis != null) {
            try {
                fis.close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
        if (fos != null) {
            try {
                fos.close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }

    }

}
           

注意:

在最後的close代碼中可能會有問題,兩個close,如果第一個close方法出現了異常,并抛出了運作時異常,那麼程式還是停止了。下面的close方法就沒有執行到。

那麼為了保證close的執行,将第二個放到fianlly中即可。

public static void copyFile(String srcPath, String destPath) {

FileInputStream fis = null;
    FileOutputStream fos = null;
    try {
        fis = new FileInputStream(srcPath);
        fos = new FileOutputStream(destPath);

        byte[] byt = new byte[1024 * 1024];
        int len = 0;
        while ((len = fis.read(byt)) != -1) {

            fos.write(byt, 0, len);
        }
    } catch (IOException e) {
        throw new RuntimeException(e);
    } finally {

        try {
            if (fis != null) {
                fis.close();
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            if (fos != null) {
                try {
                    fos.close();
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }

        }
    }

}
           

3.5.位元組緩沖流

3.5.1.緩沖流

上述程式中我們為了提高流的使用效率,自定義了位元組數組,作為緩沖區.Java其實提供了專門的位元組流緩沖來提高效率.

BufferedInputStream和BufferedOutputStream

BufferedOutputStream和BufferedOutputStream類可以通過減少讀寫次數來提高輸入和輸出的速度。它們内部有一個緩沖區,用來提高處理效率。檢視API文檔,發現可以指定緩沖區的大小。其實内部也是封裝了位元組數組。沒有指定緩沖區大小,預設的位元組是8192。

顯然緩沖區輸入流和緩沖區輸出流要配合使用。首先緩沖區輸入流會将讀取到的資料讀入緩沖區,當緩沖區滿時,或者調用flush方法,緩沖輸出流會将資料寫出。

注意:當然使用緩沖流來進行提高效率時,對于小檔案可能看不到性能的提升。但是檔案稍微大一些的話,就可以看到實質的性能提升了。

public class IoTest5 {

public static void main(String[] args) throws IOException {

String srcPath = “c:\a.mp3”;

String destPath = “d:\copy.mp3”;

copyFile(srcPath, destPath);

}

public static void copyFile(String srcPath, String destPath)
        throws IOException {
    // 打開輸入流,輸出流
    FileInputStream fis = new FileInputStream(srcPath);
    FileOutputStream fos = new FileOutputStream(destPath);

    // 使用緩沖流
    BufferedInputStream bis = new BufferedInputStream(fis);
    BufferedOutputStream bos = new BufferedOutputStream(fos);

    // 讀取和寫入資訊
    int len = 0;

    while ((len = bis.read()) != -1) {
        bos.write(len);
    }

    // 關閉流
    bis.close();
    bos.close();    
           

}

}

4.字元流

計算機并不區分二進制檔案與文本檔案。所有的檔案都是以二進制形式來存儲的,是以,從本質上說,所有的檔案都是二進制檔案。是以字元流是建立在位元組流之上的,它能夠提供字元層次的編碼和解碼。例如,在寫入一個字元時,Java虛拟機會将字元轉為檔案指定的編碼(預設是系統預設編碼),在讀取字元時,再将檔案指定的編碼轉化為字元。

常見的碼表如下:

ASCII: 美國标準資訊交換碼。用一個位元組的7位可以表示。

ISO8859-1: 拉丁碼表。歐洲碼表,用一個位元組的8位表示。又稱Latin-1(拉丁編碼)或“西歐語言”。ASCII碼是包含的僅僅是英文字母,并且沒有完全占滿256個編碼位置,是以它以ASCII為基礎,在空置的0xA0-0xFF的範圍内,加入192個字母及符号,

藉以供使用變音符号的拉丁字母語言使用。進而支援德文,法文等。因而它依然是一個單位元組編碼,隻是比ASCII更全面。

GB2312: 英文占一個位元組,中文占兩個位元組.中國的中文編碼表。

GBK: 中國的中文編碼表更新,融合了更多的中文文字元号。

Unicode: 國際标準碼規範,融合了多種文字。所有文字都用兩個位元組來表示,Java語言使用的就是unicode。

UTF-8: 最多用三個位元組來表示一個字元。

(我們以後接觸最多的是iso8859-1、gbk、utf-8)

檢視上述碼表後,很顯然中文的‘中’在iso8859-1中是沒有對映的編碼的。或者一個字元在2中碼表中對應的編碼不同,例如有一些字在不同的編碼中是有交集的,例如bjg5 和gbk 中的漢字簡體和繁體可能是一樣的,就是有交集,但是在各自碼表中的數字不一樣。

例如

使用gbk 将中文儲存在計算機中,

中 國

對映 100 200 如果使用big5 打開

可能 ? …

不同的編碼對映的是不一樣的。

很顯然,我們使用什麼樣的編碼寫資料,就需要使用什麼樣的編碼來對資料。

ISO8859-1:一個位元組

GBK: 兩個位元組包含了英文字元和擴充的中文 ISO8859-1+中文字元

UTF-8 萬國碼,推行的。是1~3個位元組不等長。英文存的是1個位元組,中文存的是3個位元組,是為了節省空間。

那麼我們之前學習的流稱之為位元組流,以位元組為機關進行操作之情的操作全是英文,如果想要操作中文呢?

測試:将指定位置的檔案通過位元組流讀取到控制台

public class TestIo {

public static void main(String[] args) throws IOException {

String path = “c:\a.txt”;

writFileTest();

readFileByInputStream(path);

}

private static void readFileByInputStream(String path) throws IOException {
    FileInputStream fis = new FileInputStream(path);

    int len = 0;
    while ((len = fis.read()) != -1) {
        System.out.print((char) len);
    }
}

private static void writFileTest() throws FileNotFoundException,
        IOException {
    // 建立檔案對象
    File file = new File("c:\\a.txt");
    // 建立檔案輸出流
    FileOutputStream fos = new FileOutputStream(file);
    fos.write("中國".getBytes());
    fos.close();
}
           

}

發現控制台輸出的資訊:

???ú 是這樣的東西,打開a.txt 文本發現漢字”中國”确實寫入成功。

那麼說明使用位元組流進行中文有問題。

仔細分析,我們的FileInputStream輸入流的read() 一次是讀一個位元組的,傳回的是一個int顯然進行了自動類型提升。那麼我們來驗證一下“中國”對應的位元組是什麼

使用:”中國”.getBytes() 即可得到字元串對應的位元組數組。是[-42, -48, -71, -6]

同樣,将read方法傳回值直接強轉為byte ,發現結果也是-42, -48, -71, -6 。

代碼:

public class TestIo {

public static void main(String[] args) throws IOException {

String path = “c:\a.txt”;

writFileTest();

readFileByInputStream(path);

//檢視中國對應的編碼

System.out.println(Arrays.toString(“中國”.getBytes()));

}

private static void readFileByInputStream(String path) throws IOException {
    FileInputStream fis = new FileInputStream(path);
    int len = 0;
    while ((len = fis.read()) != -1) {
        System.out.println((byte)len);
    }
}

private static void writFileTest() throws FileNotFoundException,
        IOException {
    // 建立檔案對象
    File file = new File("c:\\a.txt");
    // 建立檔案輸出流
    FileOutputStream fos = new FileOutputStream(file);
    fos.write("中國\r\n".getBytes());
    fos.close();
}
           

}

那麼中國 對應的是-42, -48, -71, -6是4個位元組。 那就是一個中文占2個位元組,(這個和編碼是有關系的)

很顯然,我們的中文就不能夠再一個位元組一個位元組的讀了。是以位元組流處理字元資訊時并不友善那麼就出現了字元流。

位元組流是 字元流是以字元為機關。

體驗字元流:

public static void main(String[] args) throws IOException {

String path = "c:\\a.txt";
    readFileByReader(path);
}
           

private static void readFileByReader(String path) throws IOException {

FileReader fr = new FileReader(path);

int len = 0;

while ((len = fr.read()) != -1) {

System.out.print((char) len);

}

}

總結:字元流就是:位元組流 + 編碼表,為了更便于操作文字資料。字元流的抽象基類:

Reader , Writer。

由這些類派生出來的子類名稱都是以其父類名作為子類名的字尾,如FileReader、FileWriter。

4.1.Reader

方法:

1,int read():

讀取一個字元。傳回的是讀到的那個字元。如果讀到流的末尾,傳回-1.

2,int read(char[]):

将讀到的字元存入指定的數組中,傳回的是讀到的字元個數,也就是往數組裡裝的元素的個數。如果讀到流的末尾,傳回-1.

3,close()

讀取字元其實用的是window系統的功能,就希望使用完畢後,進行資源的釋放

由于Reader也是抽象類,是以想要使用字元輸入流需要使用Reader的實作類。檢視API文檔。找到了FileReader。

1,用于讀取文本檔案的流對象。

2,用于關聯文本檔案。

構造函數:在讀取流對象初始化的時候,必須要指定一個被讀取的檔案。

如果該檔案不存在會發生FileNotFoundException.

public class IoTest1_Reader {

public static void main(String[] args) throws Exception {
    String path = "c:/a.txt";
    // readFileByInputStream(path);
    readFileByReader(path);
}

/**
 * 使用位元組流讀取檔案内容
 * 
 * @param path
 */
public static void readFileByInputStream(String path) throws Exception {
    InputStream in = new FileInputStream(path);

    int len = 0;
    while ((len = in.read()) != -1) {
        System.out.print((char) len);
    }

    in.close();
}

/**
 * 使用字元流讀取檔案内容
 */
public static void readFileByReader(String path) throws Exception {
    Reader reader = new FileReader(path);
    int len = 0;
    while ((len = reader.read()) != -1) {
        System.out.print((char) len);
    }

    reader.close();
}
           

}

4.2.Writer

Writer中的常見的方法:

1,write(ch): 将一個字元寫入到流中。

2,write(char[]): 将一個字元數組寫入到流中。

3,write(String): 将一個字元串寫入到流中。

4,flush():重新整理流,将流中的資料重新整理到目的地中,流還存在。

5,close():關閉資源:在關閉前會先調用flush(),重新整理流中的資料去目的地。然流關閉。

發現基本方法和OutputStream 類似,有write方法,功能更多一些。可以接收字元串。

同樣道理Writer是抽象類無法建立對象。查閱API文檔,找到了Writer的子類FileWriter

1:将文本資料存儲到一個檔案中。

public class IoTest2_Writer {

public static void main(String[] args) throws Exception {
    String path = "c:/ab.txt";

    writeToFile(path);
}

/**
 * 寫指定資料到指定檔案中
 * 
 */
public static void writeToFile(String path) throws Exception {
    Writer writer = new FileWriter(path);
    writer.write('中');
    writer.write("世界".toCharArray());
    writer.write("中國");

    writer.close();
}
           

}

2:追加檔案:

預設的FileWriter方法新值會覆寫舊值,想要實作追加功能需要

使用如下構造函數建立輸出流 append值為true即可。

FileWriter(String fileName, boolean append)

FileWriter(File file, boolean append)

3:flush方法

如果使用字元輸出流,沒有調用close方法,會發生什麼?

private static void writeFileByWriter(File file) throws IOException {

FileWriter fw = new FileWriter(file);

fw.write(‘新’);

fw.flush();

fw.write(“中國”.toCharArray());

fw.write(“世界你好!!!”.toCharArray());

fw.write(“明天”);

// 關閉流資源

//fw.close();

}

程式執行完畢打開檔案,發現沒有内容寫入.原來需要使用flush方法. 重新整理該流的緩沖。

為什麼隻要指定claose方法就不用再flush方法,因為close也調用了flush方法.

4.3.字元流拷貝檔案

一個文本檔案中有中文有英文字母,有數字。想要把這個檔案拷貝到别的目錄中。

我們可以使用位元組流進行拷貝,使用字元流呢?肯定也是可以的。

4.3.1.字元流拷貝檔案實作1

public static void main(String[] args) throws Exception {

String path1 = “c:/a.txt”;

String path2 = “c:/b.txt”;

copyFile(path1, path2);
}
           

public static void copyFile(String path1, String path2) throws Exception {

Reader reader = new FileReader(path1);

Writer writer = new FileWriter(path2);

int ch = -1;
    while ((ch = reader.read()) != -1) {
        writer.write(ch);
    }

    reader.close();
    writer.close();
}
           

但是這個一次讀一個字元就寫一個字元,效率不高。把讀到的字元放到字元數組中,再一次性的寫出。

4.3.2.字元流拷貝檔案實作2

public static void main(String[] args) throws Exception {

String path1 = “c:/a.txt”;

String path2 = “c:/b.txt”;

copyFile(path1, path2);
}
           

public static void copyFile3(String path1, String path2) throws Exception {

Reader reader = new FileReader(path1);

Writer writer = new FileWriter(path2);

int ch = -1;
    char [] arr=new char[1024];
    while ((ch = reader.read(arr)) != -1) {
        writer.write(arr,0,ch);
    }

    reader.close();
    writer.close();
}
           

位元組流可以拷貝視訊和音頻等檔案,那麼字元流可以拷貝這些嗎?

經過驗證拷貝圖檔是不行的。發現丢失了資訊,為什麼呢?

計算機中的所有資訊都是以二進制形式進行的存儲(1010)圖檔中的也都是二進制

在讀取檔案的時候字元流自動對這些二進制按照碼表進行了編碼處理,但是圖檔本來就是二進制檔案,不需要進行編碼。有一些巧合在碼表中有對應,就可以處理,并不是所有的二進制都可以找到對應的。資訊就會丢失。是以字元流隻能拷貝以字元為機關的文本檔案

(以ASCII碼為例是127個,并不是所有的二進制都可以找到對應的ASCII,有些對不上的,就會丢失資訊。)

4.4.字元流的異常處理

public static void main(String[] args) throws Exception {

String path1 = “c:/a.txt”;

String path2 = “c:/b.txt”;

copyFile2(path1, path2);
}
           

public static void copyFile2(String path1, String path2) {

Reader reader = null;

Writer writer = null;

try {

// 打開流

reader = new FileReader(path1);

writer = new FileWriter(path2);

// 進行拷貝
        int ch = -1;
        while ((ch = reader.read()) != -1) {
            writer.write(ch);
        }
    } catch (Exception e) {
        throw new RuntimeException(e);
    } finally {
        // 關閉流,注意一定要能執行到close()方法,是以都要放到finally代碼塊中
        try {
            if (reader != null) {
                reader.close();
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            try {
                if (writer != null) {
                    writer.close();
                }
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    }
}
           

4.5.字元流的緩沖區

檢視Reader 發現Reader,操作的是字元,我們就不需要進行編碼解碼操作,由字元流讀到二進制,自動進行解碼得到字元,寫入字元自動編碼成二進制.

Reader有一個子類BufferedReader。子類繼承父類顯然子類可以重寫父類的方法,也可以增加自己的新方法。例如一次讀一行就是常用的操作.那麼BufferedReader 類就提供了這個方法,可以檢視readLine()方法具備 一次讀取一個文本行的功能。很顯然,該子類可以對功能進行增強。

體驗BufferedReader

public class IoTest_BufferedReader {

public static void main(String[] args) throws IOException {

readFile(“c:\a.txt”);

}

private static void readFile(String path) throws IOException {
    Reader read = new FileReader(path);

    BufferedReader br = new BufferedReader(read);

    String line = null;
    while ((line = br.readLine()) != null) {
        System.out.println(line);
    }

}
           

}

注意:

在使用緩沖區對象時,要明确,緩沖的存在是為了增強流的功能而存在,是以在建立緩沖區對象時,要先有流對象存在.

緩沖區的出現提高了對流的操作效率。原理:其實就是将數組進行封裝。

使用字元流緩沖區拷貝文本檔案.

public class Demo7 {

public static void main(String[] args) throws IOException {

// 關聯源檔案

File srcFile = new File(“c:\linux大綱.txt”);

// 關聯目标檔案

File destFile = new File(“d:\linux大綱.txt”);

// 實作拷貝

copyFile(srcFile, destFile);

}

private static void copyFile(File srcFile, File destFile)
        throws IOException {
    // 建立字元輸入流
    FileReader fr = new FileReader(srcFile);
    // 建立字元輸出流
    FileWriter fw = new FileWriter(destFile);

    // 字元輸入流的緩沖流
    BufferedReader br = new BufferedReader(fr);
    // 字元輸出流的緩沖流
    BufferedWriter bw = new BufferedWriter(fw);

    String line = null;
    // 一次讀取一行
    while ((line = br.readLine()) != null) {
        // 一次寫出一行.
        bw.write(line);
        // 重新整理緩沖
        bw.flush();
        // 進行換行,由于readLine方法預設沒有換行.需要手動換行
        bw.newLine();
    }
    // 關閉流
    br.close();
    bw.close();
}
           

}

4.6.裝飾器模式

需求:想要在讀取的檔案的每一行添加行号。

public class IoTest7_BufferedReader {

public static void main(String[] args) throws IOException {
    readFile("c:\\a.txt");
}

private static void readFile(String path) throws IOException {
    Reader read = new FileReader(path);

    BufferedReader br = new BufferedReader(read);
    int count = 0;
    String line = null;
    while ((line = br.readLine()) != null) {
        count++;
        System.out.println(count+":"+line);     
    }

}
           

}

很容易的就可以實作。如果每次使用BufferedReader 輸出時都需要顯示行号呢? 每次都加? 很顯然,我們的BufferedReader繼承了Reader 對父類進行了功能的增強,那麼我們也可以繼承BufferedReader 重寫該類的readLine方法,進行功能的增強.

public class IoTest_BufferedReader {

public static void main(String[] args) throws IOException {

readFile(“c:\a.txt”);

}

private static void readFile(String path) throws IOException {
    Reader read = new FileReader(path);

    BufferedReader br = new MyBufferedReader(read);
    String line = null;
    while ((line = br.readLine()) != null) {
        System.out.println(line);
    }

}
           

}

class MyBufferedReader extends BufferedReader {

public MyBufferedReader(Reader read) {

super(read);

}

int count;

@Override
public String readLine() throws IOException {
    String line = super.readLine();
    if (line != null) {
        count++;
        return count + ":" + line;

    } else {
        return null;
    }

}
           

}

需求:

要在輸出的一行前加上引号

可以再定義一個BufferedReader的子類,繼承BufferedReader增強功能.

public class IoTest_BufferedReader {

public static void main(String[] args) throws IOException {

readFile(“c:\a.txt”);

}

private static void readFile(String path) throws IOException {
    Reader read = new FileReader(path);
    BufferedReader br = new MyQutoBufferedReader(read);
    int count = 0;
    String line = null;
    while ((line = br.readLine()) != null) {
        System.out.println(line);
        count++;
    }

}
           

}

// quotation 引号

class MyQutoBufferedReader extends BufferedReader {

public MyQutoBufferedReader(Reader reader) {
    super(reader);
}

public String readLine() throws IOException {
    String line = super.readLine();
    if (line != null) {

        return "\"" + line + "\"";

    } else {
        return null;
    }

}
           

}

需求三:

既想要顯示行号又想要顯示引号

發現,就需要再定義子類,發現這樣比較麻煩,代碼臃腫.而且代碼重複.

可以換一種方式.如下:

其實就是一個新類要對原有類進行功能增強.

1. 在增強類中維護一個被增強的父類引用變量

2. 在增強類的構造函數中初始化1中的變量

3. 建立需要增強的方法,在剛方法中調用被被增強類的方法,并加以增強。

public class IoTest_BufferedReader {

public static void main(String[] args) throws IOException {

readFile(“c:\a.txt”);

}

private static void readFile(String path) throws IOException {
    Reader read = new FileReader(path);
    BufferedReader bufferedReader = new BufferedReader(read);
    BufferedReader br = new MyQutoBufferedReader2(bufferedReader);
    br = new MyLineBufferedReader2(br);
    String line = null;
    while ((line = br.readLine()) != null) {
        System.out.println(line);
    }
}
           

}

// quotation 引号

class MyQutoBufferedReader2 extends BufferedReader {

private BufferedReader bufferedReader;

public MyQutoBufferedReader2(BufferedReader bufferedReader) {
    super(bufferedReader);
    this.bufferedReader = bufferedReader;
}

public String readLine() throws IOException {
    String line = super.readLine();
    if (line != null) {

        return "\"" + line + "\"";

    } else {
        return null;
    }

}
           

}

class MyLineBufferedReader2 extends BufferedReader {

private BufferedReader bufferedReader;

public MyLineBufferedReader2(BufferedReader bufferedReader) {
    super(bufferedReader);
    this.bufferedReader = bufferedReader;
}

int count;

@Override
public String readLine() throws IOException {
    String line = super.readLine();
    if (line != null) {
        count++;
        return count + ":" + line;

    } else {
        return null;
    }

}
           

}

這就是裝飾器模式

裝飾器模式:

使用分層對象來動态透明的向單個對象中添加責任(功能)。

裝飾器指定包裝在最初的對象周圍的所有對象都具有相同的基本接口。

某些對象是可裝飾的,可以通過将其他類包裝在這個可裝飾對象的四周,來将功能分層。

裝飾器必須具有和他所裝飾的對象相同的接口。

JavaIO中的應用:

Java I/O類庫需要多種不同的功能組合,是以使用了裝飾器模式。

FilterXxx類是JavaIO提供的裝飾器基類,即我們要想實作一個新的裝飾器,就要繼承這些類。

裝飾器與繼承:

問題:

修飾模式做的增強功能按照繼承的特點也是可以實作的,為什麼還要提出修飾設計模式呢?

繼承實作的增強類和修飾模式實作的增強類有何差別?

繼承實作的增強類:

優點:代碼結構清晰,而且實作簡單

缺點:對于每一個的需要增強的類都要建立具體的子類來幫助其增強,這樣會導緻繼承體系過于龐大。

修飾模式實作的增強類:

優點:内部可以通過多态技術對多個需要增強的類進行增強

缺點:需要内部通過多态技術維護需要增強的類的執行個體。進而使得代碼稍微複雜。

5.其他流

5.1.序列流

也稱為合并流。

5.1.1.SequenceInputStream

序列流,對多個流進行合并。

SequenceInputStream 表示其他輸入流的邏輯串聯。它從輸入流的有序集合開始,并從第一個輸入流開始讀取,直到到達檔案末尾,接着從第二個輸入流讀取,依次類推,直到到達包含的最後一個輸入流的檔案末尾為止。

注意:

構造函數

SequenceInputStream(InputStream s1, InputStream s2)

SequenceInputStream(InputStream s1, InputStream s2)

合并兩個流

使用構造函數SequenceInputStream(InputStream s1, InputStream s2)

private static void testSequenceInputStream() throws IOException {

FileInputStream fis1 = new FileInputStream(“c:\a.txt”);

FileInputStream fis2 = new FileInputStream(“c:\b.txt”);

SequenceInputStream s1 = new SequenceInputStream(fis1, fis2);
    int len = 0;
    byte[] byt = new byte[1024];

    FileOutputStream fos = new FileOutputStream("c:\\z.txt");

    while ((len = s1.read(byt)) != -1) {
        fos.write(byt, 0, len);
    }
    s1.close();
}
           

合并多個流:

public static void testSequenceInputStream() throws Exception {

InputStream in1 = new FileInputStream(“c:/a.txt”);

InputStream in2 = new FileInputStream(“c:/b.txt”);

InputStream in3 = new FileInputStream(“c:/c.txt”);

LinkedHashSet<InputStream> set = new LinkedHashSet<InputStream>();
    set.add(in1);
    set.add(in2);
    set.add(in3);
    final Iterator<InputStream> iter = set.iterator();

    SequenceInputStream sin = new SequenceInputStream(
            new Enumeration<InputStream>() {
                @Override
                public boolean hasMoreElements() {
                    return iter.hasNext();
                }

                @Override
                public InputStream nextElement() {
                    return iter.next();
                }
            });

    FileOutputStream out = new FileOutputStream("c:/z.txt");

    for (int b = -1; (b = sin.read()) != -1;) {
        out.write(b);
    }
    sin.close();
    out.close();
}
           

案例:将map3歌曲檔案進行切割拷貝,并合并.

public class Demo2 {

public static void main(String[] args) throws IOException {

split(new File("c:\\a.mp3"), 10, new File("c:\\"));
    System.out.println("切割完畢");

    LinkedHashSet<InputStream> hs = new LinkedHashSet<InputStream>();   
    hs.add(new FileInputStream(new File("c:\\part.1.mp3")));
    hs.add(new FileInputStream(new File("c:\\part.2.mp3")));
    hs.add(new FileInputStream(new File("c:\\part.3.mp3")));
    hs.add(new FileInputStream(new File("c:\\part.4.mp3")));
    merage(hs, new File("c:\\merage.mp3"));
    System.out.println("合并完畢");
}

private static void merage(LinkedHashSet<InputStream> hs, File dest)
        throws IOException {

    final Iterator<InputStream> it = hs.iterator();
    FileOutputStream fos = new FileOutputStream(dest);
    SequenceInputStream seq = new SequenceInputStream(
            new Enumeration<InputStream>() {

                @Override
                public boolean hasMoreElements() {

                    return it.hasNext();
                }

                @Override
                public InputStream nextElement() {
                    return it.next();
                }
            });
    byte[] byt = new byte[1024 * 1024];
    int len = 0;
    while ((len = seq.read(byt)) != -1) {
        fos.write(byt, 0, len);
    }
    seq.close();
    fos.close();
}

// 1. 切割檔案
/*
 * 切割檔案,切割份數, 切割後儲存路徑
 */
private static void split(File src, int count, File dir) throws IOException {
    FileInputStream fis = new FileInputStream(src);
    FileOutputStream fos = null;
    byte[] byt = new byte[1024 * 1024];
    int len = 0;
    for (int i = 1; i <= count; i++) {
        len = fis.read(byt);
        if (len != -1) {
            fos = new FileOutputStream(dir + "part." + i + ".mp3");
            fos.write(byt, 0, len);
        }

        // fos.close();

    }
    fis.close();

}
           

}

5.2.對象的序列化

當建立對象時,程式運作時它就會存在,但是程式停止時,對象也就消失了.但是如果希望對象在程式不運作的情況下仍能存在并儲存其資訊,将會非常有用,對象将被重建并且擁有與程式上次運作時擁有的資訊相同。可以使用對象的序列化。

對象的序列化: 将記憶體中的對象直接寫入到檔案裝置中

對象的反序列化: 将檔案裝置中持久化的資料轉換為記憶體對象

基本的序列化由兩個方法産生:一個方法用于序列化對象并将它們寫入一個流,另一個方法用于讀取流并反序列化對象。

ObjectOutput

writeObject(Object obj)

将對象寫入底層存儲或流。

ObjectInput

readObject()

讀取并傳回對象。

5.2.1.ObjectOutputStream

5.2.2.ObjectInputStream

由于上述ObjectOutput和ObjectInput是接口,是以需要使用具體實作類。

ObjectOutput

ObjectOutputStream被寫入的對象必須實作一個接口:Serializable

否則會抛出:NotSerializableException

ObjectInput

ObjectInputStream 該方法抛出異常:ClassNotFountException

ObjectOutputStream和ObjectInputStream 對象分别需要位元組輸出流和位元組輸入流對象來建構對象。也就是這兩個流對象需要操作已有對象将對象進行本地持久化存儲。

案例:

序列化和反序列化Cat對象。

public class Demo3 {

public static void main(String[] args) throws IOException,

ClassNotFoundException {

Cat cat = new Cat(“tom”, 3);

FileOutputStream fos = new FileOutputStream(new File(“c:\Cat.txt”));

ObjectOutputStream oos = new ObjectOutputStream(fos);

oos.writeObject(cat);

System.out.println(cat);

oos.close();

// 反序列化

FileInputStream fis = new FileInputStream(new File(“c:\Cat.txt”));

ObjectInputStream ois = new ObjectInputStream(fis);

Object readObject = ois.readObject();

Cat cat2 = (Cat) readObject;

System.out.println(cat2);

fis.close();

}

class Cat implements Serializable {

public String name;

public int age;

public Cat() {

}

public Cat(String name, int age) {

    this.name = name;
    this.age = age;
}

@Override
public String toString() {
    return "Cat [name=" + name + ", age=" + age + "]";
}
           

}

例子關鍵點:

1.聲明Cat類實作了Serializable接口。是一個标示器,沒有要實作的方法。

2.建立Cat對象。

3.建立位元組流對象(FileOutputStream)進序列化對象儲存在本地檔案中。

4.建立ObjectOutputStream對象,調用writeObject方法序列化Cat對象。

5.writeObject方法會執行兩個工作:序列化對象,然後将序列化的對象寫入檔案中。

6.反序列化就是調用ObjectInputStream的readObject()方法。

7.異常處理和流的關閉動作要執行。

5.2.3.Serializable:

類通過實作 java.io.Serializable 接口以啟用其序列化功能。未實作此接口的類将無法使其任何狀态序列化或反序列化。可序列化類的所有子類型本身都是可序列化的。序列化接口沒有方法或字段,僅用于辨別可序列化的語義。

是以需要被序列化的類必須是實作Serializable接口,該接口中沒有描述任何的屬性和方法,稱之為标記接口。

如果對象沒有實作接口Serializable,在進行序列化時會抛出:NotSerializableException 異常。

注意:

儲存一個對象的真正含義是什麼?如果對象的執行個體變量都是基本資料類型,那麼就非常簡單。但是如果執行個體變量是包含對象的引用,會怎麼樣?儲存的會是什麼?很顯然在Java中儲存引用變量的實際值沒有任何意義,因為Java引用的值是通過JVM的單一執行個體的上下文中才有意義。通過序列化後,嘗試在JVM的另一個執行個體中恢複對象,是沒有用處的。

如下:

首先建立一個Dog對象,也建立了一個Collar對象。Dog中包含了一個Collar(項圈)

現在想要儲存Dog對象,但是Dog中有一個Collar,意味着儲存Dog時也應該儲存Collar。假如Collar也包含了其他對象的引用,那麼會發生什麼?意味着儲存一個Dog對象需要清楚的知道Dog對象的内部結構。會是一件很麻煩的事情。

Java的序列化機制可以解決該類問題,當序列化一個對象時,Java的序列化機制會負責儲存對象的所有關聯的對象(就是對象圖),反序列化時,也會恢複所有的相關内容。本例中:如果序列化Dog會自動序列化Collar。但是,隻有實作了Serializable接口的類才可以序列化。如果隻是Dog實作了該接口,而Collar沒有實作該接口。會發生什麼?

Dog類和Collar類

import java.io.Serializable;

public class Dog implements Serializable {

private Collar collar;

private String name;

public Dog(Collar collar, String name) {

    this.collar = collar;
    this.name = name;
}

public Collar getCollar() {
    return collar;
}
           

}

class Collar {

private int size;

public int getSize() {
    return size;
}

public Collar(int size) {
    this.size = size;
}
           

}

序列化

import java.io.File;

import java.io.FileOutputStream;

import java.io.IOException;

import java.io.ObjectOutputStream;

public class Demo4 {

public static void main(String[] args) throws IOException {

Collar coll = new Collar(10);

Dog dog = new Dog(coll, “旺财”);

FileOutputStream fis = new FileOutputStream(new File("c:\\dog.txt"));
    ObjectOutputStream os = new ObjectOutputStream(fis);
    os.writeObject(dog);
}
           

}

執行程式,出現了運作時異常。

Exception in thread “main” java.io.NotSerializableException: Collar

是以我們也必須将Dog中使用的Collar序列化。但是如果我們無法通路Collar的源代碼,或者無法使Collar可序列化,如何處理?

兩種解決方法:

一:繼承Collar類,使子類可序列化

但是:如果Collar是final類,就無法繼承了。并且,如果Collar引用了其他非序列化對象,也無法解決該問題。

transient

此時就可以使用transient修飾符,可以将Dog類中的成員變量辨別為transient

那麼在序列化Dog對象時,序列化就會跳過Collar。

public class Demo4 {

public static void main(String[] args) throws IOException,

ClassNotFoundException {

Collar coll = new Collar(10);

Dog dog = new Dog(coll, “旺财”);

System.out.println(dog.getCollar().getSize());

FileOutputStream fis = new FileOutputStream(new File("c:\\dog.txt"));
    ObjectOutputStream os = new ObjectOutputStream(fis);
    os.writeObject(dog);

    // 反序列化
    FileInputStream fos = new FileInputStream(new File("c:\\dog.txt"));
    ObjectInputStream ois = new ObjectInputStream(fos);
    Object readObject = ois.readObject();
    Dog dog2 = (Dog) readObject;
    // Collar未序列化。
    dog2.getCollar().getSize();
}
           

}

這樣我們具有一個序列化的Dog和非序列化的Collar。

此時反序列化Dog後,通路Collar,就會出現運作時異常

10

Exception in thread “main” java.lang.NullPointerException

注意:序列化不适用于靜态變量,因為靜态變量并不屬于對象的執行個體變量的一部分。靜态變量随着類的加載而加載,是類變量。由于序列化隻适用于對象。

基本資料類型可以被序列化

public class Demo5 {

public static void main(String[] args) throws IOException {

// 建立序列化流對象

FileOutputStream fis = new FileOutputStream(new File(“c:\basic.txt”));

ObjectOutputStream os = new ObjectOutputStream(fis);

// 序列化基本資料類型

os.writeDouble(3.14);

os.writeBoolean(true);

os.writeInt(100);

os.writeInt(200);

// 關閉流
    os.close();

    // 反序列化
    FileInputStream fos = new FileInputStream(new File("c:\\basic.txt"));
    ObjectInputStream ois = new ObjectInputStream(fos);

    System.out.println(ois.readDouble());
    System.out.println(ois.readBoolean());
    System.out.println(ois.readInt());
    System.out.println(ois.readInt());

    fos.close();
}
           

}

serialVersionUID

用于給類指定一個UID。該UID是通過類中的可序列化成員的數字簽名運算出來的一個long型的值。

隻要是這些成員沒有變化,那麼該值每次運算都一樣。

該值用于判斷被序列化的對象和類檔案是否相容。

如果被序列化的對象需要被不同的類版本所相容。可以在類中自定義UID。

定義方式:static final long serialVersionUID = 42L;

5.3.Properties.

可以和流相關聯的集合對象Properties.

Map

|–Hashtable

|–Properties

Properties:該集合不需要泛型,因為該集合中的鍵值對都是String類型。

1,存入鍵值對:setProperty(key,value);

2,擷取指定鍵對應的值:value getProperty(key);

3,擷取集合中所有鍵元素:

Enumeration propertyNames();

在jdk1.6版本給該類提供一個新的方法。

Set stringPropertyNames();

4,列出該集合中的所有鍵值對,可以通過參數列印流指定列出到的目的地。

list(PrintStream);

list(PrintWriter);

例:list(System.out):将集合中的鍵值對列印到控制台。

list(new PrintStream(“prop.txt”)):将集合中的鍵值對存儲到prop.txt檔案中。

5,可以将流中的規則資料加載進行集合,并稱為鍵值對。

load(InputStream):

jdk1.6版本。提供了新的方法。

load(Reader):

注意:流中的資料要是”鍵=值” 的規則資料。

6,可以将集合中的資料進行指定目的的存儲。

store(OutputStram,String comment)方法。

jdk1.6版本。提供了新的方法。

store(Writer ,String comment):

使用該方法存儲時,會帶着當時存儲的時間。

注意:

Properties隻加載key=value這樣的鍵值對,與檔案名無關,注釋使用#

練習:記錄一個程式運作的次數,當滿足指定次數時,該程式就不可以再繼續運作了。

通常可用于軟體使用次數的限定。

public static void sysPropList() throws IOException {

Properties prop = System.getProperties();

// prop.list(System.out);// 目的是控制台。
    // 需求是:将jvm的屬性資訊存儲到一個檔案中。
    prop.list(new PrintStream("java.txt"));
}

public static void sysProp() {
    Properties prop = System.getProperties();

    Set<String> keys = prop.stringPropertyNames();

    for (String key : keys) {
        System.out.println(key + ":" + prop.getProperty(key));
    }
}
           

Properties類與配置檔案

Map

|–Hashtable

|–Properties

注意:是一個Map集合,該集合中的鍵值對都是字元串。該集合通常用于對鍵值對形式的配置檔案進行操作.

配置檔案:将軟體中可變的部分資料可以定義到一個檔案中,友善以後更改,該檔案稱之為配置檔案。

優勢: 提高代碼的維護性。

Properties: 該類是一個Map的子類,提供了可以快速操作配置檔案的方法

load() : 将檔案裝置資料裝載為Map集合資料

get(key): 擷取Map中的資料

getProperty()擷取Map中的資料特有方法

案例:

public static void loadFile() throws IOException {

// 1,建立Properties(Map)對象

Properties prop = new Properties();

// 2.使用流加載配置檔案。
    FileInputStream fis = new FileInputStream("c:\\qq.txt");

    // 3。使用Properties 對象的load方法将流中資料加載到集合中。
    prop.load(fis);

    // 周遊該集合
    Set<Entry<Object, Object>> entrySet = prop.entrySet();
    Iterator<Entry<Object, Object>> it = entrySet.iterator();
    while (it.hasNext()) {
        Entry<Object, Object> next = it.next();
        Object key = next.getKey();
        Object value = next.getValue();
    }
    // 通過鍵擷取指定的值
    Object object = prop.get("jack");
    System.out.println(object);

    // 通過鍵修改值
    prop.setProperty("jack", "888888");

    // 将集合中的資料寫入到配置檔案中。
    FileOutputStream fos = new FileOutputStream("c:\\qq.txt");

    // 注釋:
    prop.store(fos, "yes,qq");

    fos.close();
    fis.close();

}
           

擷取記錄程式運作次數:

public class Demo6 {

public static void main(String[] args) throws IOException {

int count = 0;

Properties pro = new Properties();

File file = new File("c:\\count.ini");
    FileInputStream fis = null;
    if (!file.exists()) {
        file.createNewFile();
    }
    fis = new FileInputStream(file);
    pro.load(fis);
    String str = pro.getProperty("count");
    if (str != null) {
        count = Integer.parseInt(str);
    }
    if (count == 3) {
        System.out.println("使用次數已到,請付費");
        System.exit(0);
    }

    count++;
    System.out.println("歡迎使用本軟體" + "你已經使用了:" + count + " 次");

    pro.setProperty("count", count + "");
    FileOutputStream fos = new FileOutputStream(new File("c:\\count.ini"));
    pro.store(fos, "請保護知識産權");

    fis.close();
    fos.close();

}
           

}

5.4.列印流

PrintStream可以接受檔案和其他位元組輸出流,是以列印流是對普通位元組輸出流的增強,其中定義了很多的重載的print()和println(),友善輸出各種類型的資料。

5.4.1.PrintStream

PrintWriter

1,列印流。

PrintStream:

是一個位元組列印流,System.out對應的類型就是PrintStream。

它的構造函數可以接收三種資料類型的值。

1,字元串路徑。

2,File對象。

3,OutputStream。

public static void main(String[] args) throws IOException {

PrintStream ps = System.out;

// 普通write方法需要調用flush或者close方法才會在控制台顯示
    // ps.write(100);
    // ps.close();

    // 不換行列印
    ps.print(100);
    ps.print('a');
    ps.print(100.5);
    ps.print("世界");
    ps.print(new Object());
    System.out.println("--------------");
    // 換行
    ps.println(100);
    ps.println('a');
    ps.println(100.5);
    ps.println("世界");
    ps.println(new Object());

    // 重定向列印流
    PrintStream ps2 = new PrintStream(new File("c:\\a.txt"));
    System.setOut(ps2);
    // 換行
    ps2.println(100);
    ps2.println('a');
    ps2.println(100.5);
    ps2.println("世界");
    ps2.println(new Object());

    // printf(); 格式化
    ps2.printf("%d,%f,%c,%s", 100, 3.14, '中', "世界你好!!!");
    ps2.printf("%4s和%8s 打價格戰", "京東", "蘇甯");


}   }
           

注意: 列印流的三種方法

void print(資料類型 變量)

println(資料類型 變量)

printf(String format, Object… args)

可以自定資料格式

print 和println方法的差別在于,一個換行一個不換行

print 方法和write方法的卻别在于,print提供自動重新整理.

普通的write方法需要調用flush或者close方法才可以看到資料.

JDK1.5之後Java對PrintStream進行了擴充,增加了格式化輸出方式,可以使用printf()重載方法直接格式化輸出。但是在格式化輸出的時候需要指定輸出的資料類型格式。

5.4.2.PrintWriter

是一個字元列印流。構造函數可以接收四種類型的值。

1,字元串路徑。

2,File對象。

對于1,2類型的資料,還可以指定編碼表。也就是字元集。

3,OutputStream

4,Writer

對于3,4類型的資料,可以指定自動重新整理。

注意:該自動重新整理值為true時,隻有三個方法可以用:println,printf,format.

如果想要既有自動重新整理,又可執行編碼。如何完成流對象的包裝?

PrintWrter pw =

new PrintWriter(new OutputSteamWriter(new FileOutputStream(“a.txt”),”utf-8”),true);

如果想要提高效率。還要使用列印方法。

PrintWrter pw =

newPrintWriter(new BufferdWriter(new OutputSteamWriter(

newFileOutputStream(“a.txt”),”utf-8”)),true);

public static void testPrintWriter() throws Exception {

PrintWriter pw = new PrintWriter(“c:/b.txt”, “gbk”);

// pw.append("xxx");
    // pw.println(55);
    // pw.println('c');
    // pw.printf("%.1s與%4s打價格戰, %c", "京東","蘇甯", 'a');

    pw.close();

}
           

Scanner

public static void testScanner() throws Exception {

// Scanner scanner = new Scanner(new File(“c:/test.txt”));

Scanner scanner = new Scanner(System.in);

System.out.println(scanner.nextInt());
    System.out.println(scanner.nextBoolean());

    scanner.close();
}
           

5.5.操作數組的流對象

5.5.1.操作位元組數組

ByteArrayInputStream

以及ByteArrayOutputStream

toByteArray();

toString();

writeTo(OutputStream);

public static void testByteArrayInputStream() throws Exception {

InputStream in = new ByteArrayInputStream(new byte[] { 65, 66, 67 });

ByteArrayOutputStream out = new ByteArrayOutputStream();

for (int b = -1; (b = in.read()) != -1;) {
        out.write(b);
    }

    in.close();
    out.close();

    System.out.println(Arrays.toString(out.toByteArray()));
    System.out.println(out);
}
           

5.5.2.操作字元數組

CharArrayReader

CharArrayWriter

對于這些流,源是記憶體。目的也是記憶體。

而且這些流并未調用系統資源。使用的就是記憶體中的數組。

是以這些在使用的時候不需要close。

操作數組的讀取流在構造時,必須要明确一個資料源。是以要傳入相對應的數組。

對于操作數組的寫入流,在構造函數可以使用空參數。因為它内置了一個可變長度數組作為緩沖區。

public static void testCharArrayReader() throws Exception {

CharArrayReader reader = new CharArrayReader(new char[] { ‘A’, ‘b’, ‘c’ });

CharArrayWriter writer = new CharArrayWriter();

for (int b = -1; (b = reader.read()) != -1;) {
        writer.write(b);
    }

    reader.close();
    writer.close();

    System.out.println(writer.toCharArray());
}
           

這幾個流的出現其實就是通過流的讀寫思想在操作數組。

類似的對象同理:

StringReader

StringWriter。

public static void testStringReader() throws Exception {

StringReader reader = new StringReader(“test 中國”);

StringWriter writer = new StringWriter();

for (int b = -1; (b = reader.read()) != -1;) {
        writer.write(b);
    }

    reader.close();
    writer.close();

    System.out.println(writer.toString());
}
           

5.6.操作基本資料類型的流對象

5.6.1.DataInputStream

以及DataOutputStream

檢視API文檔DataInputStream的資訊。發現從底層輸入流中讀取基本 Java 資料類型。檢視方法,有讀一個位元組,讀一個char讀一個double 的方法,

DataInputStream 從資料流讀取位元組,并将它們轉換為正确的基本資料類型值或字元串。

該流有操作基本資料類型的方法.

有讀的,那麼必定有對應的寫的就是DataOutputStream 将基本類型的值或字元串轉換為位元組,并且将位元組輸出到資料流。

DataInputStream類繼承FilterInputStream類,并實作了DataInput接口。DataOutputStream

類繼承FilterOutputStream 并實作了DataOutput 接口。

例如:

DataInputStream

操作基本資料類型的方法:

int readInt():一次讀取四個位元組,并将其轉成int值。

boolean readBoolean():一次讀取一個位元組。

short readShort();

long readLong();

剩下的資料類型一樣。

String readUTF():按照utf-8修改版讀取字元。注意,它隻能讀writeUTF()寫入的字元資料。

DataOutputStream

DataOutputStream(OutputStream):

操作基本資料類型的方法:

writeInt(int):一次寫入四個位元組。

注意和write(int)不同。write(int)隻将該整數的最低一個8位寫入。剩餘三個8位丢棄。

writeBoolean(boolean);

writeShort(short);

writeLong(long);

剩下是資料類型也也一樣。

writeUTF(String):按照utf-8修改版将字元資料進行存儲。隻能通過readUTF讀取。

測試: DataOutputStream

使用DataOutputStream寫資料檔案。

public static void testDataInputStream() throws Exception {

DataOutputStream out = new DataOutputStream(new FileOutputStream(

“c:/a.txt”));

out.writeBoolean(true);
    out.writeByte(15); // 0x05 1 個位元組
    out.writeBytes("abc"); // 0x 0041 2個位元組
    out.writeChar('X'); // ??
    out.writeChars("xyz");
    out.writeLong(111);
    out.writeUTF("中國");

    out.close();

    DataInputStream in = new DataInputStream(
            new FileInputStream("c:/a.txt"));
    System.out.println(in.readBoolean());
    System.out.println(in.readByte());

    System.out.println(in.readByte());
    System.out.println(in.readByte());
    System.out.println(in.readByte());

    System.out.println(in.readChar());

    System.out.println(in.readChar());
    System.out.println(in.readChar());
    System.out.println(in.readChar());

    System.out.println(in.readLong());

    System.out.println(in.readUTF());
    in.close();
}
           

6.編碼

什麼是編碼?

計算機中存儲的都是二進制,但是要顯示的時候,就是我們看到的卻可以有中國 ,a 1 等字元

計算機中是沒有存儲字元的,但是我們卻看到了。計算機在存儲這些資訊的時候,根據一個有規則的編号,當使用者輸入a 有a對映的編号,就将這個編号存進計算機中這就是編碼。

計算機隻能識别二進制資料。

為了友善應用計算機,讓它可以識别各個國家的文字。就将各個國家的文字用數字來表示,并一一對應,形成一張表,這就是編碼表。

例如:漢字 中

有一種編碼:

中字在utf 8中對映的編碼

utf-8 –>100

在gbk中呢?有可能就不是100了

gbk –> 150

很顯然同一個資訊在不同的編碼中對映的數字也不同,

不同的國家和地區使用的碼表是不同的,

gbk 是中國大陸

bjg5 是台灣同胞中的繁體字。是以如果給big5一個簡體字是不認識的。

還有ASCII 美國标準資訊交換碼

6.1.碼表

常見的碼表如下:

ASCII: 美國标準資訊交換碼。用一個位元組的7位可以表示。

ISO8859-1: 拉丁碼表。歐洲碼表,用一個位元組的8位表示。又稱Latin-1(拉丁編碼)或“西歐語言”。ASCII碼是包含的僅僅是英文字母,并且沒有完全占滿256個編碼位置,是以它以ASCII為基礎,在空置的0xA0-0xFF的範圍内,加入192個字母及符号,

藉以供使用變音符号的拉丁字母語言使用。進而支援德文,法文等。因而它依然是一個單位元組編碼,隻是比ASCII更全面。

GB2312: 中國的中文編碼表。

GBK: 中國的中文編碼表更新,融合了更多的中文文字元号。

Unicode: 國際标準碼,融合了多種文字。所有文字都用兩個位元組來表示,Java語言使用的就是unicode。

UTF-8: 最多用三個位元組來表示一個字元。

(我們以後接觸最多的是iso8859-1、gbk、utf-8)

檢視上述碼表後,很顯然中文的‘中’在iso8859-1中是沒有對映的編碼的。或者一個字元在2中碼表中對應的編碼不同,例如有一些字在不同的編碼中是有交集的,例如bjg5 和gbk 中的漢字簡體和繁體可能是一樣的,就是有交集,但是在各自碼表中的數字不一樣。

例如

使用gbk 将中文儲存在計算機中,

中 國

對映 100 200 如果使用big5 打開

可能 ? …

不同的編碼對映的是不一樣的。

很顯然,我們使用什麼樣的編碼寫資料,就需要使用什麼樣的編碼來對資料。

ISO8859-1:一個位元組

GBK: 兩個位元組包含了英文字元和擴充的中文 ISO8859-1+中文字元

UTF-8 萬國碼,推行的。是1~3個位元組不等長。英文存的是1個位元組,中文存的是3個位元組,是為了節省空間。

6.2.編碼:

字元串—》位元組數組

String類的getBytes() 方法進行編碼,将字元串,轉為對映的二進制,并且這個方法可以指定編碼表。如果沒有指定碼表,該方法會使用作業系統預設碼表。

注意:中國大陸的Windows系統上預設的編碼一般為GBK。在Java程式中可以使用System.getProperty(“file.encoding”)方式得到目前的預設編碼。

6.3.解碼:

位元組數組—》字元串

String類的構造函數完成。

String(byte[] bytes) 使用系統預設碼表

String(byte[],charset)指定碼表

注意:我們使用什麼字元集(碼表)進行編碼,就應該使用什麼字元集進行解碼,否則很有可能出現亂碼(相容字元集不會)。

// 編碼操作與解碼操作。
public static void main(String[] args) throws Exception {
    String value = System.getProperty("file.encoding");
    System.out.println("系統預設的編碼為 " + value);

    String str = "中";

    // 編碼操作
    byte[] bytes = str.getBytes();
    byte[] bytes2 = str.getBytes("gbk");// d6d0
    byte[] bytes3 = str.getBytes("utf-8");// e4b8ad

    System.out.println(Arrays.toString(bytes)); // [-42, -48]
    System.out.println(Arrays.toString(bytes2));// [-42, -48]
    System.out.println(Arrays.toString(bytes3));// [-28, -72, -83]

    // 解碼操作
    // 編碼gbk,解碼utf-8亂碼。
    String str2 = new String(bytes2, "utf-8");
    System.out.println(str2);

    // 編碼utf-8 解碼gbk,亂碼
    str2 = new String(bytes3, "gbk");
    System.out.println(str2);
    // gbk相容gb2312是以,沒有問題。
    str = new String("中國".getBytes("gb2312"), "gbk");
    System.out.println(str);
}
           

存檔案時可以使用各種編碼,但是解碼的時候要對映的采用相同的解碼方式。

我們的字元流自動的做了編碼和解碼的工作,寫一個中文,字元流進行了編碼,存到了計算機中讀到了一個字元,字元流進行了解碼,我們可以看到字元。因為檔案存的都是二進制。

但是拷貝圖檔時,是純二進制,不是有意義的字元,是以碼表無法轉換。

字元流的弊端:

一:無法拷貝圖檔和視訊。

二:拷貝檔案使用位元組流而不使用字元流,因為字元流讀檔案涉及到解碼,會先解碼,寫檔案的時候又涉及到編碼,這些操作多餘,而且讀和寫的碼表不對應還容易引發問題。

例如FileReader 讀檔案,我們沒有指定編碼時,預設是按照系統編碼gbk進行操作,如果讀到utf-8的檔案也是按照gbk編碼進行解碼,那就會出現問題。

6.4.位元組流讀取中文

public class TestIo {

public static void main(String[] args) throws IOException {

readFileByInputStream2(“c:\a.txt”);

}

private static void readFileByInputStream2(String path) throws IOException {
    FileInputStream fis = new FileInputStream(path);
    int len = 0;

    while ((len = fis.read()) != -1) {
        System.out.print((char) len);
    }

}
           

}

這個方法讀取文本檔案,中文是無法正确顯示的。

很顯然這些位元組需要解碼,可以将位元組輸入流讀取的資訊儲存在位元組數組中,指定對應的碼表進行解碼即可。

public class TestIo {

public static void main(String[] args) throws IOException {

readFileByInputStream(“c:\a.txt”);

}

private static void readFileByInputStream(String path) throws IOException {
    FileInputStream fis = new FileInputStream(path);
    int len = 0;
    byte[] buffer = new byte[1024];
    while ((len = fis.read(buffer)) != -1) {
        System.out.println(new String(buffer, 0, len, "gbk"));
    }

}
           

}

注意:如果指定的編碼表和解碼表不對應就會出現問題

public class TestIo {

public static void main(String[] args) throws IOException {

// 該檔案預設是gbk編碼

readFileByInputStream(“c:\a.txt”);

}

private static void readFileByInputStream(String path) throws IOException {
    FileInputStream fis = new FileInputStream(path);
    int len = 0;
    byte[] buffer = new byte[1024];
    while ((len = fis.read(buffer)) != -1) {
        // 使用utf-8 解碼,解錯。
        System.out.println(new String(buffer, 0, len, "utf-8"));
    }

}
           

}

6.5.位元組流寫出中文

需要編碼,可以指定碼表。就需要自己把字元串進行編碼操作後,把得到的二進制内容通過位元組流寫入到檔案中

使用String的getBytes方法,無參數的會使用系統預設的碼表進行編碼,也可以指定碼表

系統預設編碼

public class TestIo {

public static void main(String[] args) throws IOException {

String path = "c:\\test.txt";
    writeFileByOutputStream(path, "世界你好");
    readFileByInputStream(path);
}

private static void writeFileByOutputStream(String path, String content)
        throws IOException {
    FileOutputStream fos = new FileOutputStream(path);

    // 把字元串進行編碼操作,系統預設編碼
    byte[] bytes = content.getBytes();
    // 内容通過位元組流寫入到檔案中。
    fos.write(bytes);
    fos.close();
}

private static void readFileByInputStream(String path) throws IOException {
    FileInputStream fis = new FileInputStream(path);
    int len = 0;
    byte[] buffer = new byte[1024];

    while ((len = fis.read(buffer)) != -1) {
        // 二進制解碼,使用系統預設編碼
        System.out.println(new String(buffer, 0, len));
    }

}
           

}

使用utf-8進行編碼

public class TestIo {

public static void main(String[] args) throws IOException {

String path = "c:\\test.txt";
    writeFileByOutputStream(path, "世界你好");
    readFileByInputStream(path);
}

private static void writeFileByOutputStream(String path, String content)
        throws IOException {
    FileOutputStream fos = new FileOutputStream(path);

    // 把字元串進行編碼操作
    byte[] bytes = content.getBytes("utf-8");
    // 内容通過位元組流寫入到檔案中。
    fos.write(bytes);
    fos.close();
}

private static void readFileByInputStream(String path) throws IOException {
    FileInputStream fis = new FileInputStream(path);
    int len = 0;
    byte[] buffer = new byte[1024];

    while ((len = fis.read(buffer)) != -1) {
        // 二進制解碼,使用系統預設編碼
        System.out.println(new String(buffer, 0, len,"utf-8"));
    }

}
           

}

在明白了位元組流也可以正确的進行中文字元之後,就應該明白字元流其實就是位元組流在加上系統預設的碼表。自動進行了編碼和解碼的操作。底層還是使用位元組流讀取檔案。通過轉換流的學習就可以明白這些道理。

6.6.轉換流

InputStreamReader

檢視API文檔,發現是位元組流通向字元流的橋梁。檢視構造,可以傳遞位元組流,可以指定編碼,該流可以實作什麼功能?很顯然可以包裝我們的位元組流,自動的完成節流編碼和解碼的工作。該流是一個Reader的子類,是字元流的體系。是以将轉換流稱之為位元組流和字元流之間的橋梁。

InputStreamReader 是位元組流通向字元流的橋梁

測試InputStreamReader:

第一步: 需要專門建立以GBK編碼的文本檔案。為了便于辨別,我們命名為gbk.txt

和以UFT-8編碼的文本檔案,命名為utf.txt

第二步: 分别寫入漢字”中國”

第三步:編寫測試方法,用InputStreamReader 分别使用系統預設編碼,GBK,UTF-8編碼讀取檔案.

public class Demo4 {

public static void main(String[] args) throws IOException {

File file = new File(“c:\a.txt”);

File fileGBK = new File(“c:\gbk.txt”);

File fileUTF = new File(“c:\utf.txt”);

// 預設編碼

testReadFile(file);

// 傳入gbk編碼檔案,使用gbk解碼

testReadFile(fileGBK, “gbk”);

// 傳入utf-8檔案,使用utf-8解碼

testReadFile(fileUTF, “utf-8”);

}

// 該方法中nputStreamReader使用系統預設編碼讀取檔案.
private static void testReadFile(File file) throws 
        IOException {
    FileInputStream fis = new FileInputStream(file);
    InputStreamReader ins = new InputStreamReader(fis);
    int len = 0;
    while ((len = ins.read()) != -1) {
        System.out.print((char) len);
    }
    ins.close();
    fis.close();
}

// 該方法使用指定編碼讀取檔案
private static void testReadFile(File file, String encod)
        throws IOException {
    FileInputStream fis = new FileInputStream(file);
    InputStreamReader ins = new InputStreamReader(fis, encod);
    int len = 0;
    while ((len = ins.read()) != -1) {
        System.out.print((char) len);
    }
    ins.close();
    }
           

}

注意:碼表不對應

分别測試:

使用系統預設編碼讀取utf-8編碼檔案

使用utf-8編碼讀取gbk編碼檔案

使用”gbk”編碼讀取utf-8檔案.

發現都會出現亂碼的問題.

// 使用系統預設編碼讀取utf-8

testReadFile(fileUTF);

// 傳入gbk編碼檔案,使用utf-8解碼

testReadFile(fileGBK, “utf-8”);

// 傳入utf-8檔案,使用”gbk解碼

testReadFile(fileUTF, “gbk”);

類 OutputStreamWriter

OutputStreamWriter

有了InputStreamReader 可以轉換InputStream

那麼其實還有OutputStreamWriter 可以轉換OutputStream

OutputStreamWriter 是字元流通向位元組流的橋梁

測試OutputStreamWriter

一: 分别使用OutputStreamWriter使用系統預設編碼,GBK,UTF-8相對應的預設編碼檔案,GBK編碼檔案,UTF-8編碼檔案中寫出漢字”中國”.

二: 在使用上述案例中的readFile方法傳入相對應碼表讀取.

public class TestIo {

public class Demo4 {

public static void main(String[] args) throws IOException {

File file = new File(“c:\a.txt”);

File fileGBK = new File(“c:\gbk.txt”);

File fileUTF = new File(“c:\utf.txt”);

// 寫入
    // 使用系統預設碼表寫入
    testWriteFile(file);
    // 使用gbk編碼向gbk檔案寫入資訊
    testWriteFile(fileGBK, "gbk");
    // 使用utf-8向utf-8檔案中寫入資訊
    testWriteFile(fileUTF, "utf-8");

    // 讀取
    // 預設編碼
    testReadFile(file);
    // 傳入gbk編碼檔案,使用gbk解碼
    testReadFile(fileGBK, "gbk");
    // 傳入utf-8檔案,使用utf-8解碼
    testReadFile(fileUTF, "utf-8");

}

// 使用系統碼表将資訊寫入到檔案中
private static void testWriteFile(File file) throws IOException {
    FileOutputStream fos = new FileOutputStream(file);
    OutputStreamWriter ops = new OutputStreamWriter(fos);
    ops.write("中國");
    ops.close();
}

// 使用指定碼表,将資訊寫入到檔案中
private static void testWriteFile(File file, String encod)
        throws IOException {
    FileOutputStream fos = new FileOutputStream(file);
    OutputStreamWriter ops = new OutputStreamWriter(fos, encod);
    ops.write("中國");
    ops.close();
}

// 該方法中nputStreamReader使用系統預設編碼讀取檔案.
private static void testReadFile(File file) throws IOException {
    FileInputStream fis = new FileInputStream(file);
    InputStreamReader ins = new InputStreamReader(fis);
    int len = 0;
    while ((len = ins.read()) != -1) {
        System.out.print((char) len);
    }
    ins.close();

}

// 該方法适合用指定編碼讀取檔案
private static void testReadFile(File file, String encod)
        throws IOException {
    FileInputStream fis = new FileInputStream(file);
    InputStreamReader ins = new InputStreamReader(fis, encod);
    int len = 0;
    while ((len = ins.read()) != -1) {
        System.out.print((char) len);
    }

    ins.close();
}
           

}

注意: 碼表不對應的問題

分别測試:

向GBK檔案中寫入utf-8編碼的資訊

向utf檔案中寫入gbk編碼的資訊

發現檔案都有問題,無法正常的讀取了.

public static void main(String[] args) throws IOException {

File file = new File(“c:\a.txt”);

File fileGBK = new File(“c:\gbk.txt”);

File fileUTF = new File(“c:\utf.txt”);

// 寫入
    // // 使用系統預設碼表寫入
    // testWriteFile(file);
    // // 使用gbk編碼向gbk檔案寫入資訊
    // testWriteFile(fileGBK, "gbk");
    // // 使用utf-8向utf-8檔案中寫入資訊
    // testWriteFile(fileUTF, "utf-8");

    testWriteFile(fileGBK);
    // 向GBK檔案中寫入utf-8編碼的資訊
    testWriteFile(fileGBK, "utf-8");
    // 向utf檔案中寫入gbk編碼的資訊
    testWriteFile(fileUTF, "gbk");

    // 讀取
    // 預設編碼
    testReadFile(file);
    // 傳入gbk編碼檔案,使用gbk解碼
    testReadFile(fileGBK, "gbk");
    // 傳入utf-8檔案,使用utf-8解碼
    testReadFile(fileUTF, "utf-8");

}
           

InputStreamReader:位元組到字元的橋梁。

OutputStreamWriter:字元到位元組的橋梁。

它們有轉換作用,而本身又是字元流。是以在構造的時候,需要傳入位元組流對象進來。

構造函數:

InputStreamReader(InputStream)

通過該構造函數初始化,使用的是本系統預設的編碼表GBK。

InputStreamReader(InputStream,String charSet)

通過該構造函數初始化,可以指定編碼表。

OutputStreamWriter(OutputStream)

通過該構造函數初始化,使用的是本系統預設的編碼表GBK。

OutputStreamWriter(OutputStream,String charSet)

通過該構造函數初始化,可以指定編碼表。

注意:

操作檔案的字元流對象是轉換流的子類。

Reader

|–InputStreamReader

|–FileReader

Writer

|–OutputStreamWriter

|–FileWriter

注意:

在使用FileReader操作文本資料時,該對象使用的是預設的編碼表。

如果要使用指定編碼表時,必須使用轉換流。

如果系統預設編碼是GBK的:

FileReader fr = new FileReader(“a.txt”);//操作a.txt的中的資料使用的本系統預設的GBK。

操作a.txt中的資料使用的也是本系統預設的GBK。

InputStreamReader isr = new InputStreamReader(new FileInputStream(“a.txt”));

這兩句的代碼的意義相同。

但是:如果a.txt中的檔案中的字元資料是通過utf-8的形式編碼。使用FileReader就無能為力,那麼在讀取時,就必須指定編碼表。那麼轉換流必須使用。

InputStreamReader isr =

new InputStreamReader(new FileInputStream(“a.txt”),”utf-8”);

7.遞歸

遞歸做為一種算法在程式設計語言中廣泛應用。是指函數/過程/子程式在運作過程中直接或間接調用自身而産生的重入現象。

(自己調用自己,有結束條件)

注意:遞歸時一定要明确結束條件。

數學中遞歸運算.

對于任何正整數N ,N! (讀作N的階乘)的值定義為1-N(包括N)的所有的整數的成績.是以3! 就是 3!=3*2*1 =6;

5! 定義為5!=5*4*3*2*1=120

那麼整數N 的階乘 N! 可以表示為

1!=1

N!=N*(N-1)! for N>1

若果N 等于1 那麼1的繼承就是1,其他所有N! =N*(N-1)!,例如:50!=50*49!

49!=49*48! 48!=48*47! 一直持續到1出現.

如何使用Java程式計算階乘?

public static long recursion(int n) {

if (n == 1) {

return 1;

} else {

return n * recursion(n - 1);

}

}

7.1.案例:

1,列出指定目錄中所有的子孫檔案與子孫目錄名,隻需要列出名稱即可。

2,列出指定目錄中所有的子孫檔案與子孫目錄名,要求名稱前面要有相應數量的空格:

第一級前面有0個,第二級前面有1個,第三級前面有2個…,以此類推。

3,列出指定目錄中所有的子孫檔案與子孫目錄名,要求要是樹狀結構,效果如下所示:

|–src

| |–cn

| | |–itcast

| | | |–a_helloworld

| | | | |–HelloWorld.java

| | | |–b_for

| | | | |–ForTest.java

| | | |–c_api

| | | | |–Student.java

|–bin

| |–cn

| | |–itcast

| | | |–i_exception

| | | | |–ExceptionTest.class

| | | |–h_linecount

| | | | |–LineCounter3.class

| | | | |–LineCounter2.class

| | | | |–LineCounter.class

|–lib

| |–commons-io.jar

答案:

案例一:

// 1,列出指定目錄中所有的子孫檔案與子孫目錄名,隻需要列出名稱即可。

private static void listFile(File file) {

File[] listFiles = file.listFiles();

    for (File f : listFiles) {
        if (f.isFile()) {
            System.out.println(f.getName());
        } else if (f.isDirectory()) {
            System.out.println(f.getName());
            listFile(f);
        }

    }
}
           

public static void main(String[] args) {

File file = new File(“c:\abc”);

listFile(file);

}

案例二

// 2,列出指定目錄中所有的子孫檔案與子孫目錄名,要求名稱前面要有相應數量的空格:

private static void listFile2(File file, String str) {

File[] listFiles = file.listFiles();

    for (int i = 0; i < listFiles.length; i++) {
        File f = listFiles[i];
        System.out.println(str + f.getName());

        if (f.isDirectory()) {
            listFile2(f, str + "-");
        }
    }
}
           

public static void main(String[] args) {

File file = new File(“c:\abc”);

String str = “-“;

listFile2(file, str);

案例三:

// 列出指定目錄中所有的子孫檔案與子孫目錄名,要求要是樹狀結構

private static void listFile3(File file, String str) {

File[] listFiles = file.listFiles();

    for (File f : listFiles) {
        System.out.println(str + f.getName());
        if (f.isDirectory()) {
            listFile3(f, "|  " + str);
        }

    }
}
           

public static void main(String[] args) {

File file = new File(“c:\abc”);

file = new File(“c:\day18ide”);

file = new File(“c:\MyIo”);

str = “|-“;

listFile3(file, str);

7.2.練習:

1,删除一個非空的目錄。

2,移動一個非空的目錄到另一個地方(剪切)。

3,把File類中的重要方法設計代碼測試一遍。

// 1,删除一個非空的目錄。并加強健壯性

private static void deleteFile(File file) {

if (!file.exists()) {

System.out.println(“路徑不存在”);

return;

}

if (!file.isDirectory()) {

System.out.println(“不是目錄”);

return;

}

// 如果目前目錄中有子目錄和檔案,先删除子目錄和檔案

File[] listFiles = file.listFiles();

for (File f : listFiles) {

if (f.isFile()) {

f.delete();

} else if (f.isDirectory()) {

deleteFile(f);

}

}

// 删除目前目錄

file.delete();

}
           

0o

練習2:

使用File類的renameTo 方法和遞歸實作非空目錄的剪切.

public static void main(String[] args) throws IOException {

// 重命名檔案(成功)

// File src = new File(“c:\aaa.txt”);

// File dest = new File(“c:\bbb.txt”);

// src.renameTo(dest);

// //移動檔案(成功)
    // File src = new File("c:\\aaa.txt");
    // File dest = new File("d:\\aaa.txt");
    // src.renameTo(dest);

    // 移動一個空目錄(失敗)
    // File src = new File("c:\\aaa");
    // File dest = new File("d:\\aaa");
    // System.out.println(src.renameTo(dest));

    // 使用File類和遞歸實作檔案的剪切.
    File src = new File("c:\\abc");
    File dest = new File("d:\\");
    cutFile(src, dest);

}

// 移動一個非空的目錄到另一個地方(剪切)。
private static void cutFile(File srcDir, File dest) throws IOException {
    if (!srcDir.exists() || !dest.exists()) {
        System.out.println("指定的源目錄或者目标目錄不存在");
        return;
    }
    if (!srcDir.isDirectory() || !dest.isDirectory()) {
        System.out.println("指定的源目錄或者目标目錄不是目錄");
        return;
    }

    // 得到源目錄名
    String srcDirName = srcDir.getName(); // abc
    // 根據源目錄名建立新目錄名
    File destDir = new File(dest + srcDirName); // d:\\abc dest 為父路徑
                                                // srcDirName 為子路徑
    // 建立目标目錄
    destDir.mkdir();

    // 周遊源目錄
    File[] listFiles = srcDir.listFiles();

    for (File f : listFiles) {
        // 如果是子源檔案,使用renameTo方法,移動至目标目錄中(該方法同時會删除源目錄中的檔案)
        if (f.isFile()) {
            f.renameTo(new File(destDir, f.getName())); // 指定目标檔案的父目錄,檔案名(根據源檔案名生成).
        } else if (f.isDirectory()) {
            // 如果是子目錄,執行重複動作. 将源子目錄 , 目标目錄(父目錄+//)
            cutFile(f, new File(destDir, File.separator)); // 指定源目錄,指定目的路徑d:\\abc\\
        }
    }
    // 删除源目錄
    srcDir.delete();

}
           

以上文檔來自傳智播客