Java基礎系列:IO流

小夥伴們,咱們認識一下。html

俗世遊子:專一技術研究的程序猿java

在前面的幾篇文章中,咱們分別介紹了api

LinkedHashMap繼承自HashMap,咱們在這裏不作過多介紹數組

根據以前的結構圖,咱們還缺乏Set沒有介紹。緩存

說實在的,這裏我不打算和詳細的來介紹Set集合,介紹Set也只是調用其api方法。咱們先快速過一下。網絡

Set

Set是不包含重複元素的集合,底層基於Map實現oracle

這也是爲何不介紹Set的緣由app

基礎

Set集合中,其源碼實現直接依賴Map來實現,Set添加的元素,經過MapKey來存儲,Value部分是經過Object對象來填充,實現方式以下:ide

HashSet爲例post

private static final Object PRESENT = new Object();

public HashSet() {
    map = new HashMap<>();
}

public boolean add(E e) {
    return map.put(e, PRESENT)==null;
}

能夠看到上面代碼的方式

其分類關係以下

Set子集 實現
HashSet HashMap
TreeSet TreeMap
LinkedHashSet LinkedHashMap

根據上面簡單的分析,咱們也能夠總結出Set的一些特色:

  • Set是基於Map來實現的,將元素存儲在Map的Key中,Value部分採用Object對象進行填充
public boolean add(E e) {
    return map.put(e, PRESENT)==null;
}
  • Set元素不可重複,惟一,且無序

  • TreeSet中能夠經過設置排序器來對容器數據進行排序
public TreeSet(Comparator<? super E> comparator) {
    this(new TreeMap<>(comparator));
}
  • HashSet在設置初始大小的時候其實也就是在對HashMap設置,因此這裏推薦設置2的N次冪的數
public HashSet(int initialCapacity) {
    map = new HashMap<>(initialCapacity);
}

這裏咱們引出一個方式叫作:組合方式

組合方式

在java中,基於基礎類而後再其類中進行創新操做的方式,有兩種方式:

  • 繼承基礎類(父類):在子類中重寫其父類的方法。
  • 組合基礎類:也就是說,在子類中,經過調用基礎類的方法,來達到複用基礎類的方式。在Set中,就是使用的組合基礎類的方式

對比以上兩種方式,組合基礎類的優勢:

  • 繼承表示父子類是同一個類型,而 Set 和 Map 原本就是想表達兩種類型,因此採用繼承不合適,並且 Java 語法限制,子類只能繼承一個父類,後續難以擴展。
  • 組合更加靈活,能夠任意的組合現有的基礎類,而且能夠在基礎類方法的基礎上進行擴展、編排等,並且方法命名能夠任意命名,無需和基礎類的方法名稱保持一致。

IO

本文章主要仍是要介紹一下Java中的IO。IO也就是輸入/輸出;Java中的IO操做本質上是對磁盤文件的操做

這種方式屬於磁盤IO,固然,還存在一種叫作:網絡IO

能夠說咱們開發的程序,系統瓶頸主要就出如今這兩塊IO上,咱們在開發中也是針對這兩塊在代碼層面來進行優化的

從總體劃分上,能夠分爲三大類

  • BIO
  • NIO
  • AIO

BIO也就是咱們接下來要了解的IO類型,稱爲阻塞式IO

NIO能夠叫作New IO,是一種非阻塞式的IO

NIO的執行效率比BIO要高一點

咱們先聊基礎的,在Netty系列章節中,咱們再介紹NIO的相關使用

AIO暫時不作介紹,後面介紹到網絡再說

那麼咱們就要先來了解下什麼是文件

文件

文件能夠認爲是相關記錄或放在一塊兒的數據的集合。像咱們電腦中的圖片,視頻等都是屬於文件,須要採用相對應的軟件才能打開,若是咱們都是使用純文本打開的話那麼就是一堆亂碼,其實咱們也知道這是屬於二進制數據

也就是說二進制數據經過必定的編碼規則進行排序,而後經過相對應的軟件打開,咱們就能夠很明白的查看當前文件

那麼這些數據是存儲存儲在哪裏呢?對應到硬件上能夠存儲在U盤,硬盤等硬件系統中。

你們確定都見過的

那麼再聊的深刻一點,如何從存儲盤中讀取文件內容

咱們能夠這麼來理解:

在電腦硬盤中存在一圈圈的磁道,這些磁道中存儲的就是文件對應的數據,磁盤經過磁頭來讀寫磁道,而磁頭鏈接到一個傳動臂,而傳動臂將磁頭定位到磁道的過程稱之爲尋址

盤片視圖

該圖來源:《深刻理解計算機系統》第6章存儲技術

可是咱們要注意一點:硬件在讀取數據的時候是有讀取單位的

如今打個比方:

好比如今咱們已經有了1G的數據,咱們要尋找其中1個字節的數據,這個磁壁是怎麼讀取的?

咱們應該明白一點,確定不是1個字節1個字節的找,操做系統在讀取的時候是已爲讀取單位(4K大小)

因此哪怕咱們要讀取1個字節的數據,可是磁盤讀取的時候也會讀取一頁大小(4K),而後會將從磁盤讀取到的數據讀到內存,可是讀到內存的時候又會有一個問題:

從磁盤讀取的時候是4K,可是讀到內存的時候不必定是4K,這和硬件和操做系統是相關的,有多是4K或者是4K的整數倍,

可是能肯定的是:讀數據的時候必定是按照 爲單位來讀取

咱們經過具體文件來看上述的說明:

文件大小

所以在硬件設備中存在一個概念:磁盤預讀

就是說:每次無論須要多少字節的數據,都會將頁的整數倍的數據讀取進來

在這裏同時又隱含一個著名的原理:局部性原理

程序數據的訪問都有彙集成羣的,也就是說全部數據都是彙集放在一塊兒的。所以磁盤預讀能夠將整塊的數據讀取進來,下次再要找相似數據的時候,就不須要去磁盤查找(類比緩存)

局部性原理分爲兩種不一樣形式:

  • 空間局部性

在一個具備良好空間局部性的程序中, 若是一個內存位置被引用了一次,那麼程序極可能在不遠的未來引用附近的一個內存位置

  • 時間局部性

在一個具備良好時間局部性的程序中,被引用過一次的內存位置極可能在不遠的未來再被屢次引用

在《深刻理解計算機系統》中第6章6.2

File

那麼接下來咱們來看看應該如何操做文件,在Java中爲咱們提供了一個專門用來處理文件的類:File

下面咱們來看構造方法:

File構造方法

相對比而言,使用第二種和第三種方式的構造方法的更多,咱們已第二個構造方法爲例:

File file = new File("D:\\Working Directory\\project\\study\\study-java\\src\\main\\java\\zopx\\top\\study\\jav\\_file\\FileDemo.java");
// 判斷當前文件是否存在:true
System.out.println(file.exists());

一樣,該類還有不少其餘的api方法,這裏給你們演示幾個經常使用的,其他的方法你們能夠去查看File的api文檔:

File file = new File("D:\\Working Directory\\project\\study\\study-java\\src\\main\\java\\zopx\\top\\study\\jav\\_file\\FileDemo.java");
System.out.printf("當前是不是文件:%s \n", file.isFile());
System.out.printf("當前是不是文件夾:%s \n", file.isDirectory());
System.out.printf("當前文件名稱:%s \n", file.getName());
System.out.printf("當前文件絕對路徑:%s \n", file.getAbsolutePath());
System.out.printf("文件長度:%s \n", file.length());

File newFile = new File("newCreateFile.txt");
newFile.createNewFile();
System.out.printf("newFile是否存在:%s \n", newFile.exists());

newFile.delete();
System.out.printf("newFile是否存在:%s \n", newFile.exists());

你們最好親自嘗試一下

還要一個比較經常使用的方法,咱們作個小例子:

列出指定文件夾下的全部文件,若是是文件夾的話,那麼繼續向下遍歷

這裏能夠用到一個方法:listFiles(),該方法會獲得指定文件夾下全部的文件且對象類型爲File[]

與其相對應的還有一個方法:list(),該方法和listFiles()相似,不過獲得的對象類型爲字符串數組

private static void _demo2() {
    File file = new File("D:\\Working Directory\\project");
    printFile(file, "|-");
}

private static void printFile(File file, String level) {
    File[] files = file.listFiles();
    for (File f : files) {
        if (f.isDirectory()) {
            System.out.println(level + f.getName());
            printFile(f, level + "-");
        } else {
            System.out.println(level + f.getName());
        }
    }
}
// 輸出不少,我就不給效果圖了,你們本身嘗試下

關於方法中的參數:FileFilter,主要是對文件進行過濾操做

這裏還須要說明一點:若是你們在測試的時候,出現異常可是程序自己無錯,那麼咱們須要想想是否是指定的目錄是受系統保護的目錄

文檔

關於File就介紹到這裏,更多其api方法查看官方文檔:

File API方法

這是關於文件方面的相關內容,下面咱們來聊一聊另一個內容

IO流

上面咱們能獲得當前文件了,可是咱們若是想要經過代碼來讀取文件中的內容,那麼咱們應該怎麼作呢?

這就是咱們下面要聊的內容:

能夠這麼理解:

從一個文件將數據返送到另外一個文件。這裏包含一個流向問題,這裏的流向咱們須要指定一個參照物。

參照物確定不用過多解釋了:就是咱們寫的程序

  • 咱們是經過程序從源文件讀取到數據,這裏是一個輸入流
  • 而後經過程序將數據寫入到目標文件中,這裏是輸出流

查看一下圖,咱們來清晰認識下流向

流

分類

如下類都是基類

按照流向分類

  • 輸入流
    • InputStream
    • Reader
  • 輸出流
    • OutputStream
    • Writer

按照內容處理方式

  • 字節流:8位通用字節流
    • InputStream
    • OutputStream
  • 字符流:16位unicode字符流
    • Reader
    • Writer

使用明白了其分類,那麼咱們來看看流究竟是如何來操做文件的

字節流

FileInputStream

正如咱們所說,InputStream是一個接口類,天然咱們須要瞭解其子類

FileInputStream使咱們最經常使用的一個實現類,讀取文件原始字節流,全部圖像,文件等讀取方式官方都推薦咱們採用該方法,可是並不僅是限定於字節文件,若是咱們想要讀取文本文件,也是可使用的

下面咱們看具體方式:

FileInputStream fileInputStream = new FileInputStream("D:\\Working Directory\\project\\study\\study-java\\src\\main\\java\\zopx\\top\\study\\jav\\_file\\FileInputStreamDemo.java");

// 獲得流中的文件大小
System.out.println(fileInputStream.available());
// 讀取流中的內容
System.out.println((char)fileInputStream.read());

官方給定三種初始化方式:

  • 傳入File對象的方式
new FileInputStream(new File("文件名稱"));
  • 傳入文件名稱的方式

上面的方式

  • FileDescriptor

這種方式不多使用, 這是一個文件描述符類

上面的是一個個的讀取,下面咱們來看看若是簡化其讀取操做

input read

FileInputStream fileInputStream = new FileInputStream("D:\\Working Directory\\project\\study\\study-java\\src\\main\\java\\zopx\\top\\study\\jav\\_file\\FileInputStreamDemo.java");

// 讀取方式
byte[] buffer = new byte[1024];
int len  = 0;
// 將內容讀取到byte數組中暫存起來
while ((len = fileInputStream.read(buffer)) != -1) {
    // 輸出到控制檯
    System.out.println(new String(buffer, 0, len));
}
fileInputStream.close();

在流中,若是讀到文件的最後一個位置,咱們經過read()方法來讀取的時候會獲得-1,因此咱們只須要經過判斷len是否爲-1就能夠知道文件是否已經讀取完成

在使用流來操做文件的時候,咱們在最後最好將流進行關閉:

  • 流操做是一個特別消耗資源的過程,若是咱們只開啓流而不關閉的話,那麼資源消耗會很是大

FileOutputStream

說實話,咱們在進行文件操做的時候,不可能說將讀取到的文件輸出到控制檯啊,這沒有任何意義

簡單點說,若是咱們須要須要將內容輸出到一個文件中,那麼又是該怎麼處理呢?

這裏就用到了咱們的輸出流:FileOutputStream

下面咱們來操做下:

FileOutputStream fileOutputStream = new FileOutputStream("D:\\Working Directory\\project\\study\\study-java\\src\\main\\java\\zopx\\top\\study\\jav\\_file\\_FileInputStreamDemo.java", true);

// 輸出到文件中
fileOutputStream.write(65);
fileOutputStream.write(66);
fileOutputStream.write(67);

// 關閉流:和FileInputStream同樣
fileOutputStream.close();

FileInputStream構造方式相似,不一樣點在於FileOutputStream中多了一個參數:boolean append

  • 默認狀況下:append=false,經過FileOutputStream進行輸出的時候會對文件中的內容進行覆蓋,
  • 若是append=true的話,那麼這裏會已追加的方式將內容輸出到文件的尾部

重點:咱們必定要搞清楚一個問題

  • 就是流的流向問題:記清楚上面那張流向圖,永遠已程序爲參照物

小案例:複製文件

FileInputStreamFileOutputStream都已經說完了,那麼咱們來作一個小案例:文件的複製,考慮一下應該如何實現

給大家3秒鐘

private static void copyFile() {
    FileInputStream fis = null;
    FileOutputStream fos = null;

    try {
        File sourceFile = new File("D:\\Working Directory\\project\\study\\study-java\\src\\main\\java\\zopx\\top\\study\\jav\\_file\\FileInputStreamDemo.java");
        File targetFile = new File("D:\\Working Directory\\project\\study\\study-java\\src\\main\\java\\zopx\\top\\study\\jav\\_file\\_FileInputStreamDemo.java");

        fis = new FileInputStream(sourceFile);
        fos = new FileOutputStream(targetFile);

        byte[] buffer = new byte[1024];
        int len = 0;
        while ((len = fis.read(buffer)) != -1) {
            fos.write(buffer, 0, len);
        }
    } catch (IOException e ) {
        e.printStackTrace();
    } finally {
        if (null != fis) {
            try {
                fis.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        if (null != fos) {
            try {
                fos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

有時候咱們會忘記關閉流,因此咱們也能夠採用下面這種方式

private static void copyFile2() {
        File sourceFile = new File("D:\\Working Directory\\project\\study\\study-java\\src\\main\\java\\zopx\\top\\study\\jav\\_file\\FileInputStreamDemo.java");
        File targetFile = new File("D:\\Working Directory\\project\\study\\study-java\\src\\main\\java\\zopx\\top\\study\\jav\\_file\\_FileInputStreamDemo.java");

    try (FileInputStream fis = new FileInputStream(sourceFile);FileOutputStream fos = new FileOutputStream(targetFile);) {
        byte[] buffer = new byte[1024];
        int len = 0;
        while ((len = fis.read(buffer)) != -1) {
            fos.write(buffer, 0, len);
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
}

該方式會幫助咱們關閉流,減小咱們的一步操做

這裏是字節流的方式,下面咱們來看看字符流的方式

字符流

上面咱們說過,若是咱們要處理文本文件之類的,推薦使用字符流的方式,官方爲咱們提供了比較方便的操做

這裏我想多一句,你們以爲字符是物理概念仍是邏輯概念?

你們要清楚:字符是邏輯概念,在計算機的世界裏,沒有什麼東西是表明字符的

FileReader

FileReaderReader的子類實現,咱們來看看具體操做

File file = new File("D:\\Working Directory\\project\\study\\study-java\\src\\main\\java\\zopx\\top\\study\\jav\\_file\\ReadDemo.java");

FileReader fileReader = new FileReader(file);
System.out.println((char)fileReader.read());

大致上和FileInputStream是同樣的方式,這裏就不過多介紹

FileWriter

FileWriter是Writer的實現,如下爲具體操做:

FileWriter fileWriter = new FileWriter("D:\\Working Directory\\project\\study\\study-java\\src\\main\\java\\zopx\\top\\study\\jav\\_file\\_ReadDemo.java");
fileWriter.write("這是我經過FileWriter寫入的");
fileWriter.append("append追加到內容後面");
fileWriter.flush();

FileOutputStream的構造方法相同,可是在api方法上和其有些區別:

  • write():提供了直接寫入字符串的方式
  • append():提供了追加的方法

最大的一個區別在於:

  • 雖然咱們在經過write()方法將內容輸出到了指定的文件,可是咱們經過打開文件發現並無內容,由於FileWriter會將內容暫存在流內存中,咱們須要手動刷新才能將內容從內存刷新到文件中:也就是調用flush()方法

  • 調用的close()方法,至關於作了兩個操做:

    • 刷新內容到文件

    • 關閉流

不過,通常狀況下,咱們建議手動調用flush()

好,咱們瞭解了這些以後,在經過上面複製文件的小案例來熟悉下二者:

小案例:複製文件

一樣,3秒鐘考慮時間:

private static void copyFile() {
    File sourceFile = new File("D:\\Working Directory\\project\\study\\study-java\\src\\main\\java\\zopx\\top\\study\\jav\\_file\\ReadDemo.java");
    File targetFile = new File("D:\\Working Directory\\project\\study\\study-java\\src\\main\\java\\zopx\\top\\study\\jav\\_file\\_AA.txt");
    try(FileReader fr = new FileReader(sourceFile); FileWriter fw = new FileWriter(targetFile)) {
        char[] buffer = new char[1024];
        int len = 0;
        while ((len = fr.read(buffer)) != -1) {
            fw.write(buffer, 0, len);
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
}

文檔

下面給出他們的具體官方文檔,更多的方法推薦查看API:

FileInputStream

FileOutputStream

FileReader

FileWriter

相關文章
相關標籤/搜索