來小夥伴們,咱們認識一下。html
俗世遊子:專一技術研究的程序猿java
在前面的幾篇文章中,咱們分別介紹了api
LinkedHashMap繼承自HashMap,咱們在這裏不作過多介紹數組
根據以前的結構圖,咱們還缺乏Set沒有介紹。緩存
說實在的,這裏我不打算和詳細的來介紹Set集合,介紹Set也只是調用其api方法。咱們先快速過一下。網絡
Set是不包含重複元素的集合,底層基於Map實現oracle
這也是爲何不介紹Set的緣由app
在Set集合中,其源碼實現直接依賴Map來實現,Set添加的元素,經過Map的Key來存儲,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的一些特色:
public boolean add(E e) { return map.put(e, PRESENT)==null; }
Set元素不可重複,惟一,且無序
public TreeSet(Comparator<? super E> comparator) { this(new TreeMap<>(comparator)); }
public HashSet(int initialCapacity) { map = new HashMap<>(initialCapacity); }
這裏咱們引出一個方式叫作:組合方式
在java中,基於基礎類而後再其類中進行創新操做的方式,有兩種方式:
對比以上兩種方式,組合基礎類的優勢:
本文章主要仍是要介紹一下Java中的IO。IO也就是輸入/輸出;Java中的IO操做本質上是對磁盤文件的操做
這種方式屬於磁盤IO,固然,還存在一種叫作:網絡IO
能夠說咱們開發的程序,系統瓶頸主要就出如今這兩塊IO上,咱們在開發中也是針對這兩塊在代碼層面來進行優化的
從總體劃分上,能夠分爲三大類
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
那麼接下來咱們來看看應該如何操做文件,在Java中爲咱們提供了一個專門用來處理文件的類: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方法查看官方文檔:
這是關於文件方面的相關內容,下面咱們來聊一聊另一個內容
上面咱們能獲得當前文件了,可是咱們若是想要經過代碼來讀取文件中的內容,那麼咱們應該怎麼作呢?
這就是咱們下面要聊的內容:流
能夠這麼理解:
從一個文件將數據返送到另外一個文件。這裏包含一個流向問題,這裏的流向咱們須要指定一個參照物。
參照物確定不用過多解釋了:就是咱們寫的程序
查看一下圖,咱們來清晰認識下流向
如下類都是基類
使用明白了其分類,那麼咱們來看看流究竟是如何來操做文件的
正如咱們所說,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());
官方給定三種初始化方式:
new FileInputStream(new File("文件名稱"));
上面的方式
這種方式不多使用, 這是一個文件描述符類
上面的是一個個的讀取,下面咱們來看看若是簡化其讀取操做
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 = 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
重點:咱們必定要搞清楚一個問題
FileInputStream和FileOutputStream都已經說完了,那麼咱們來作一個小案例:文件的複製,考慮一下應該如何實現
給大家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是Reader的子類實現,咱們來看看具體操做
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是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()
方法將內容輸出到了指定的文件,可是咱們經過打開文件發現並無內容,由於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: