1. 程式人生 > >轉發:關於IO操作

轉發:關於IO操作

今日正在學習資料庫相關知識,看到有關於IO操作,因此特來查詢,看到瓷片博文,長篇大論,看上去比較專業,特來學習拜讀,但是由於文章太長了,一次看不完,轉發。原文部落格:https://blog.csdn.net/deram_boy/article/details/51191425

提及這個,就想到了各種檔案操作,這種流,什麼reader 啊, writer啊,buffer啊,file啊。但是綜合的東西與總結,只是曾經瞭解。因為工作中也沒有太做這方面的東西。逐漸被忘記了。但是找工作要會這些呀。這些也不難。下面總結下吧!

首先了解檔案操作:

檔案,也就是file是最常見的資料來源之一,在程式中經常要把資料儲存到一個檔案中,比如將圖片檔案,聲音檔案等資料檔案,也就是需要根據執行的檔案中進行資料的讀和取。當然,在實際情況下,每一個檔案都含有一個唯一的格式,這個格式需要程式設計師根據需求進行設計,讀取已有的檔案時也應當熟悉其對應的格式,才能把資料從檔案中正確的讀取下來。

檔案的儲存介質有很多,比如光碟,硬碟,U盤,外接硬碟等等。由於IO類在設計之初,已經實現了從資料來源變為流物件這個過程,所以儲存介質對於程式設計師來說就相當於一個黑盒,不用管了,也不用自己實現,我們只需要關注如下圖的I/O操作體系:



I/O操作主要指的是使用java進行輸入,輸出操作。Java所有的I/O機制都是基於資料流進行輸入輸出,這些資料流標識了字元或者位元組的流動序列。Java.io是面向資料流的輸入輸出的主要軟體包。此外java也對塊傳輸提供支援,在核心庫Java.nio 中採用的便是塊IO. 流IO的好處是簡單易用,缺點是效率比較低。塊IO的效率很高,但是程式設計上稍微複雜。Java.io 包中包含了流式I/O所需要的所有類。其中有四個基類:inputStream, outPutStream, Reader, Writer。Java的IO模型設計的特別優秀,他使用了Decoretor模式,按功能劃分為 Stream, 可以動態裝配這些Stream,以便於獲得我們所需要的功能,例如,我們需要一個具有緩衝的檔案輸入流,則應當使用FileInputStream 和 BufferedInputStream的組合來進行。


IO流的四個基本類: InputStream, OutputStream, Reader, Writer類。他們分別用於處理位元組流和字元流。


    剛剛說的東西基本可以看出,java有位元組流和字元流的區分。那麼我們來簡單的瞭解一下位元組流和字元流。

        https://blog.csdn.net/lwang_it/article/details/78886186

    位元組流和字元流在使用上非常相近,兩者除了操作程式碼上有稍微不同之外還有什麼不同呢?實際上位元組流在操作上本身不會用到緩衝區,是檔案本身直接操作的。但是字元流在操作時是要用到緩衝區的,通過緩衝區來操作檔案。

舉個栗子:

1 把流的關閉操作註釋掉!!!!!!。看看位元組流與字元流之間的變化

    1位元組流


    
  1. public static void main(String[] karg)throws Exception {
  2. File f = new File( “d:” + File.separator + “test.txt”);
  3. OutputStream out = new FileOutputStream(f);
  4. String string = “Hello World!”;
  5. byte[] b = string.getBytes();
  6. out.write(b);
  7. // out.close();
  8. }

執行,開啟檔案後發現檔案寫上了這幾個字

2 字元流


    
  1. public void testWriter(){
  2. File f = new File( "d:" + File.separator + "test.txt");
  3. Writer writer = new FileWriter(f);
  4. String string = "Hello World";
  5. Byte[] bytes = string.getBytes();
  6. writer.write(bytes);
  7. // writer.close();
  8. }
  9. }

執行程式碼,發現對應的檔案並沒有寫上這幾個字。因為字元流在操作的時候使用了緩衝區,而在關閉字元流的幾時候會強制的將緩衝區中的內容進行輸出,但是如果程式沒有0關閉的話,則緩衝區的內容是沒有辦法輸出出去的。所以得出結論,字元流使用了緩衝區,而位元組流沒有使用緩衝區是直接操作檔案的。

那麼又來了一個問題:什麼是緩衝區啊???

緩衝區可以簡單的一段記憶體區域。可以簡單的把它理解為一段特殊的記憶體。某些情況下,如果一個程式頻繁的操作一個資源,比如檔案或者資料庫,則效能可能會很低,此時為了提升效能,就可以將一部分資料暫時存到記憶體的一個區域裡面,以後直接從這個區域讀取資料即可。因為讀取記憶體的速度是比較快的這樣可以特生程式的效能。在字元流流的操作中,所有的字元都是記憶體中形成的,在輸出前會將內容暫時儲存在記憶體之中,所以使用了緩衝區暫存資料。如果想要在不關閉時可以將字元流的內容全部輸出的話,可以呼叫writer的flush方法來完成。

那麼在實際開發中我們是使用字元流好還是位元組流比較好呢?已知字元流是有緩衝的,位元組流是直接寫進去的。

答案是位元組流。所有的檔案在硬碟或者傳輸時都是以位元組的方式進行的,包括圖片等都是按照位元組的方式來儲存的,而字元只是在記憶體中才會形成,在開發中,位元組流的使用較為廣泛。那麼我又有問題了,字元和位元組具體有什麼區別?簡單說一下,ASCLL碼這個應該很清楚吧,那裡的每一個內容都是一個字元,ASCALL碼沒記錯的話,每個字元佔8位吧好像。那要是Unicode編碼的話,咱們的漢字要佔兩個位元組呢!而位元組呢,人家的單位是Byte,01010101這總明白吧,好吧每八位一個位元組。我不管你內容是啥,我就按byte單位傳!這倆的區別就是這麼明顯!

好,接下來我們看看Java中的流分類,java中的位元組流,祖宗是 inputStream,outputStream, java中的字元流是 writer和reader。 並且這樣分是有一定的歷史淵源的。 位元組流是最基本的,從上面講的東西來說,已經可以很簡單的推測出來。位元組流的代表類是 inputStream, 和 OutputStream。他們主要用來處理二進位制資料,他是按照位元組流來處理的,但是實際中很多的資料就是簡單的純文字,這些文字都有一個特定的編碼,無論是寫入和讀取都要保證編碼的對應,才能解決常見的亂碼問題,於是,又提出了字元流這個概念,他是按照虛擬機器的encoder來處理,也就是要進行字符集的轉化,在寫入和讀取的時候指定對應的(唯一的)編碼,就能轉化為正確的字元。這兩個通過InputStreamReader 和 OutputStreamWriter來關聯,實際上是 byte和string之間的關聯。在開發工作中遇到的漢字亂碼問題實際上是字元流和位元組流之間轉化不統一。

有一個byte轉化為string的方法

public string(byte bytes[], String charesName),這裡面有個很關鍵的引數,代表了編碼集,

至於java.io包中的其他流,說白了主要是為了提高效能和使使用者方便而搞出來的

好,接下來繼續講IO一些大方面架構上的。

一. Java.io的開始, 檔案


    
  1. File f = new File( "e:\\file");
  2.          if (f.isDirectory()) {
  3.             System.out.printlin(f.getPath());
  4.     }

刪除檔案

f.delete();
    

獲取子目錄:


    
  1. if(f.isDirectory()) {
  2. File[] subs = dir.listFiles();
  3. for (File sub : subs){
  4. String name = sub.getName;
  5. long length = sub.length();
  6. }
  7. }

FileFilter類:就是自己寫一個繼承自這個藉口的類,實現他的accept方法,這個方法的返回值是布林型別的。然後直接呼叫file類的 listFiles方法,那個帶fileFile的引數的,把你剛才寫的那個塞進去就行。

建立多級目錄下的一個檔案。


    
  1. public void testWriter(){
  2. File f = new File( "d:" + File.separator + "test.txt");
  3. Writer writer = new FileWriter(f);
  4. String string = "Hello World";
  5. Byte[] bytes = string.getBytes();
  6. writer.write(bytes);
  7. // writer.close();
  8. }
  9. }


    
  1. public static void main(String[] karg)throws Exception {
  2. File f = new File( "d:" + File.separator + "test.txt");
  3. OutputStream out = new FileOutputStream(f);
  4. String string = "Hello World!";
  5. byte[] b = string.getBytes();
  6. out.write(b);
  7. // out.close();
  8. }


    
  1. public static void main(String[] karg)throws Exception {
  2. File f = new File( "d:" + File.separator + "test.txt");
  3. OutputStream out = new FileOutputStream(f);
  4. String string = "Hello World!";
  5. byte[] b = string.getBytes();
  6. out.write(b);
  7. // out.close();
  8. }


    
  1. import java.io.File;
  2. import java.io.IOException;
  3. /**
  4. * 建立多級目錄下的一個檔案
  5. * @author Administrator
  6. *
  7. */
  8. public class FileDemo3 {
  9. public static void main(String[] args) throws IOException{
  10. File file = new File(
  11. "a"+File.separator+
  12. "b"+File.separator+
  13. "c"+File.separator+
  14. "d"+File.separator+
  15. "e"+File.separator+
  16. "f"+File.separator+
  17. "g"+File.separator+
  18. "h.txt"
  19. );
  20. /*
  21. * 建立檔案時,應首先判斷當前檔案所在的
  22. * 目錄是否存在,因為若不存在,會丟擲
  23. * 異常的。
  24. */
  25. /*
  26. * File getParentFile()
  27. * 獲取當前檔案或目錄所在的父目錄
  28. */
  29. File parent = file.getParentFile();
  30. if(!parent.exists()){
  31. parent.mkdirs();
  32. }
  33. if(!file.exists()){
  34. file.createNewFile();
  35. System.out.println( "檔案建立完畢");
  36. }
  37. }
  38. }

RandomAccessFile 用於讀寫檔案資料的類

看原文吧,太多不想寫了

RandomAccessFile用於讀寫檔案資料的類


    
  1. RandomAccessFile raf = new RandomAccessFile( "demo.dat", "rw");
  2. int num = 97;
  3. raf.write(num);
  4. raf.close();

    
  1. RandomAccessFile raf = new RandomAccessFile( "demo.adt", "rw");
  2. raf.read();
  3. raf.close();

使用RandomAccessFile 完成複製操作


    
  1. RandomAccessFile res = new RandomAccessFile( "src.jpg", "r");
  2. RandomAccessFile des = new RandomAccessFile( "copy.jpg", "rw");
  3. int d = - 1;
  4. while(d = res.read() != - 1) {
  5. des.write(d);
  6. }
  7. res.close();
  8. des.close();

使用RandomAccessFile批量寫出一組位元組:


    
  1. RandomAccessFile raf = new RandomAccessFile( "test.mp4", "rw");
  2. String s = "我愛你中國!";
  3. Byte [] bs = s.getBytes( "gbk");
  4. raf.write(bs);
  5. raf.close();
使用RandomAccessFile讀取一個位元組陣列的資料。
    

    
  1. RandomAccessFile raf = new RandomAccessFile( "read.mp4", "r");
  2. Byte[] bytes = new Byte[ 50];
  3. StringBuilder sb = new StringBuilder();
  4. while(raf.read(bytes) != - 1) {
  5. sb.append( new String (bytes, "gbk")) //這句我編的不太好,總是new 會出問題
  6. }
  7. System.out.println(sb.toString());
  8. raf.close();

使用RandomAccessFile 基於快取的方式複製檔案

簡單的轉化一下上面的用法就可以寫出來了。就是讀寫都運用了一個 bytes陣列。 最後不要忘記關流。算了還是寫一下吧


    
  1. RandomAccessFile res = new RandomAccessFile( "res.avm", "r");
  2. RandomAccessFile des = new RandomAccessFile( "copy.avm", "rw");
  3. Byte[] buffer = new Byte[ 60];
  4. while(res.read(buffer) != - 1) { //這裡read方法的意思是,讀出來並且給buffer賦值內容
  5. des.write(buffer);
  6. }
  7. res.close();
  8. des.close();

使用RandomAccessFile 讀取和寫入基本型別資料。


    
  1. RamdomAccessFile file = new RandomAccessFile(“test.mp4”, "rw");
  2. file.writeInt( 100);
  3. file.writeDouble( 200.0);
  4. file.writeLong( 200000000000000);
  5. file.close();
  6. RandomAccessFile reader = new RandomAccessFile( "test.mp4", "r");
  7. int inti = reader.readInt();
  8. double d = reader.readDouble();
  9. long l = reader.readLong();

檢視RandomAccessFile的指標位置


    
  1. RandomAccessFile raf = new RandomAccessFile( "raf.dat", "rw");
  2. long point = raf.getFilePointer(); //獲取當前檔案的指標位置 =0
  3. raf.write( 97);
  4. point = raf.getFilePointer(); // = 1;
  5. raf.writeInt( 1);
  6. point = raf.getFilePointer(); // = 5 因為int型別的佔四個位元組空間
  7. raf.seek( 0); //。將指標移動到最開始的位置
  8. point = raf.getfilePointer(); // = 0
  9. raf.read(); //讀取一個位元組
  10. point = raf.getFilePointer(); // = 1; 因為上次讀取的時候,往後移動了一個位元組


位元組流 InputStream, OutputStream

1 InputStream 抽象類,,抽象類抽象類!

InputStream作為位元組輸入流,他本是就是一個抽象類,必須靠其子類去實現各種功能,此抽象類是表示所有位元組輸入流的超類,所有繼承自InputStream的流都是向程式中寫入資料的,且資料單位是byte ,也就是8位 8bit

InputStream 是輸入位元組,也就是read資料的時候用到的類,所以 InputStream提供了3種過載的read    方法。InputStream中常用的方法有:


    
  1. public abstract int read();
  2. public abstract int read(byte[] b);
  3. public abstract int read(bute[] b, int off, int len); //從輸入流中最多讀取len個位元組的資料,存放到偏移量為off的b陣列中。
  4. public int avaliable();返回輸入流中可以讀取的位元組數,注意如果輸入阻塞了,當前執行緒將被掛起,如果inputStream物件呼叫這個方法的話,他只會返回 0,因為此時讀不了讀不了掛起了就是不能讀,沒毛病!這個方法只有繼承自inputStream的子類呼叫才會有用。
  5. public long skip(long n) , skip中文翻譯是跳躍的意思。方法名字將的明明白白的,忽略輸入流中n個位元組,返回值是實際忽略的位元組數,跳過一些位元組來讀取。
  6. public int close() 用完之後一定要關流!!!!關閉輸入流並且釋放這些流佔用的系統資源。

下面附上一張inputStream的族譜:

這裡寫圖片描述

無論多麼五花八門,反正你要徹徹底底記住的是他們的超類的那幾個方法。

FileInputStream: 把一個檔案放到inputStream中實現對檔案的讀寫。

PipedInputStream: 實現了Piple的概念。主要線上程中使用。

FilterInputStream:

ByteArrayInputStream: 把記憶體中的一個緩衝區作為InputStream使用

SequenceInputStream: 把多個InputStream合併為一個InputStream

StringBufferInputStream: 把一個String物件當做一個inputStream

ObjectInputStream:

流結束的判斷方式 read()返回-1   或者是readLine()返回null

2 OutputStream抽象類

感覺與InputStream基本是個對稱的關係。 比如他的write方法,也提供了三個可以過載的。

public void write(byte[] b)

public void write(byte[]b, int off, int len)將引數b從偏移量off開始的len個位元組處寫出。

public void write(int b), 先將int轉換為byte型別,把低位元組寫入到輸出流裡面。

public void flush() 將資料緩衝區中的資料全部寫出, 並清空緩衝區。

public void close() 關閉輸出流並釋放與流相關的系統資源。

這裡寫圖片描述

各類的理解按照上面那個來,一對一的不多說了。

3 檔案輸入流,FileInputStream類

 FileInputStream可以使用一個read方法一次讀入一個位元組,並且返回一個數字,或者是使用read方法讀入一個byte陣列,byte陣列的元素有多少個就有多少個位元組,在將整個檔案讀取完或者寫入的過程中這個byte陣列可以當成一個緩衝區,因為這樣的話一個byte陣列旺旺擔任的是承接資料的中間角色。


    
  1. 使用方法 1 File file1 = new File( "d:/abc.txt");
  2. FileInputStream ins = new FileInputStream(file1);
  3. 2 FileInputStream ins1 = new FileInputStream( "d:/abc.txt");

那麼關於FileInputStream的用法,基本就和RandomAccessFile 這個類差不多了。把byte【】當緩衝


    
  1. FileInputStream ins = new FileInputStream( "test.txt");
  2. Byte[] buffer = new Byte[ 80];
  3. StringBuilder sb new StringBuilder();
  4. while(ins.read(buffer) != - 1) {
  5. sb.append(