Java核心技術梳理-IO

1、引言

IO(輸入/輸出),輸入是指容許程序讀取外部數據(包括來自磁盤、光盤等存儲設備的數據)、用戶輸入數據。輸出是指容許程序記錄運行狀態,將程序數據輸出到磁盤、光盤等存儲設備中。java

IO的主要內容包括輸入、輸出兩種IO流,這兩種流中又分爲字節流和字符流,字節流是以字節爲單位來處理輸入、輸出流,而字符流是以字符爲單位來處理輸入、輸出流。數組

2、File 類

File 類是用來操做文件和目錄的,File能建立、刪除、重命名文件和目錄,File不能訪問文件內容自己,File 類能夠經過文件路徑字符串來建立對象,建立完對象以後有不少方法來操做文件和目錄:網絡

2.1 構造方法

  • File(String pathname):根據一個路徑獲得File對象app

  • File(String parent, String child):根據一個目錄和一個子文件/目錄獲得File對象dom

  • File(File parent, String child):根據一個父File對象和一個子文件/目錄獲得File對函數

2.2 建立方法

//在當前路徑來建立一個File對象
File file = new File("1.txt");
//建立文件
System.out.println(file.createNewFile());
File file2 = new File("temp");
 //建立對象對應的目錄
System.out.println(file2.mkdir());

2.3 重命名和刪除功能

//把文件重命名爲指定的文件路徑
file2.renameTo(new File("temp2"));
//刪除文件或者文件夾
file2.delete();

注:重命名中若是路徑名相同,就是更名,若是路徑名不一樣,就是更名並剪切。刪除不走回收站,要刪除一個文件夾,請注意該文件夾內不能包含文件或者文件夾。
工具

2.4 判斷功能

//判斷文件或目錄是否存在
System.out.println(file.exists());
//判斷是不是文件
System.out.println(file.isFile());
//判斷是不是目錄
System.out.println(file.isDirectory());
//是否爲絕對路徑
System.out.println(file.isAbsolute());
//文件或目錄是否可讀
System.out.println(file.canRead());
//文件或目錄是否可寫
System.out.println(file.canWrite());

2.5 獲取功能

//返回文件內容長度
System.out.println(file.length());
//獲取文件或目錄名
System.out.println(file.getName());
//獲取文件或目錄相對路徑
System.out.println(file.getPath());
//獲取文件或目錄絕對路徑
System.out.println(file.getAbsolutePath());
//獲取上一級路徑
System.out.println(file.getAbsoluteFile().getParent());
//返回當前目錄的子目錄或文件的名稱
String[] list = file1.list();
for (String fileName : list) {
    System.out.println(fileName);
}
//返回當前目錄的子目錄或文件,返回的是File數組
File[] files = file1.listFiles();
//返回系統的全部根路徑
File[] listRoots = File.listRoots();
for (File root : listRoots) {
    System.out.println(root);
}

3、IO 流

實現輸入/輸出的基礎是IO流,Java把不一樣的源之間的數據交互抽象表達爲流,經過流的方式容許Java程序使用相同的方式來訪問不一樣的數據源。用於操做流的類都在IO包中。性能

3.1 流的分類

按照不一樣的分類方式,流也能夠分爲不一樣類型學習

  1. 輸入流和輸出流:根據流向來分,能夠分爲輸入流與輸出流大數據

    • 輸入流:從中讀取數據,而不能向其寫入數據

    • 輸出流:向其寫入數據,而不能讀取數據

  2. 字節流和字符流:這兩種流用法幾乎徹底同樣,區別在於所操做的數據單元不同,字節流操做的數據單元是8位的字節,而字符流是16位的字符。

3.2 InputStream與Reader

InputStream和Reader是全部輸入流的抽象基類,這是輸入流的模板,InputStream中有三個方法

  • int read() :從輸入流讀取單個字節,返回所讀取的字節數據。

  • int read(byte b[]):從輸入流中最多讀取b.length個字節的數據,並將其存儲在數組b中。

  • int read(byte b[], int off, int len):從輸入流中最多讀取len個字節的數據,並將其存儲在數組b中,放入的位置是從off中開始。

Reader中也有三個方法

  • int read() :從輸入流讀取單個字節,返回所讀取的字節數據。

  • int read(char cbuf[]):從輸入流中最多讀取cbuf.length個字符的數據,並將其存儲在數組cbuf中。

  • int read(byte cbuf[], int off, int len):從輸入流中最多讀取len個字節的數據,並將其存儲在數組cbuf中,放入的位置是從off中開始。

    兩個類的方法基本相同,用法相同,只是操做單位不同

InputStream inputStream = new FileInputStream("StreamTest.java");
byte[] bytes = new byte[1024];
int hasRead = 0;
while ((hasRead = inputStream.read(bytes)) > 0) {
System.out.println(new String(bytes, 0, hasRead));
}

inputStream.close();

3.3 OutputStream與Writer

OutputStream與Writer是全部輸出流的抽象基類,是輸出流模板,OutputStream有三個方法:

  • void write(int b):指定字節輸出到流中

  • void write(byte b[]):將指定字節數組輸出到流中

  • void write(byte b[], int off, int len):將指定字節數組從off位置到len長度輸出到流中

Writer中也有三個方法:

  • void write(int b):指定字符輸出到流中

  • void write(char buf[]):將指定字節數組輸出到流中

  • void write(char cubf[], int off, int len):將指定字節數組從off位置到len長度輸出到流中

因爲Writer是以字符爲單位進行操做,那可使用String 來代替,因而有另外的方法

  • void write(String str):將str字符串輸出到流中

  • void write(String str, int off, int len):將str從off位置開始長度爲len輸出到流中

FileWriter fileWriter = new FileWriter("test.txt");
fileWriter.write("日照香爐生紫煙\r\n");
fileWriter.write("遙看瀑布掛前川\r\n");
fileWriter.write("飛流直下三千尺\r\n");
fileWriter.write("遙看瀑布掛前川\r\n");
fileWriter.close();

注:操做流時必定要記得關閉流,由於打開的IO資源不屬於內存資源,垃圾回收沒法回收。

4、輸入/輸出流體系

Java的輸入輸出流提供了40多個類,要所有都記住很困難也沒有必要,咱們能夠按照功能進行下分類,實際上是很是有規律的

分類 字節輸入流 字節輸出流 字符輸入流 字符輸出流
抽象基類 InputStream OutputStream Reader Writer
訪問文件 FileInputStream FileOutputStream FileReader FileWriter
訪問數組 ByteArrayInputStream ByteArrayOutputStream CharArrayReader CharArrayWriter
訪問管道 PipedInputStream PipedOutputStream PipedReader PipedWriter
訪問字符串     StringReader StringWriter
緩衝流 BufferedInputStream BufferedOutputStream BufferedReader BufferedWriter
轉換流     InputStreamReader OutputStreamWriter
對象流 ObjectInputStream ObjectOutputStream    
過濾流 FilterInputStream FilterOutputStream FilterReader FilterWriter
打印流   PrintStream   PrintWriter
退回輸入流 PushbackInputStream   PushbackReader  
特殊流 DataInputStream DataOutputStream    

通常若是輸入/輸出的內容是文本內容,應該考慮使用字符流,若是輸入/輸出內容是二進制內容,則應該考慮使用字節流。

4.1 轉換流

體系中提供了兩個轉換流,實現將字節流轉換成字符流,InputStreamReader將字節輸入流轉換成字符輸入流,OutputStreamWriter將字節輸出流轉換成字符輸出流,System.in表明標準輸入,這個標準輸入是字節輸入流,可是鍵盤輸入的都是文本內容,這個時候咱們能夠InputStreamReader轉換成字符輸入流,普通的Reader讀取內容不方便,咱們可使用BufferedReader一次讀取一行數據,如:

//先將System.in轉換成Reader 對象
InputStreamReader inputStreamReader = new InputStreamReader(System.in);
//再將Reader包裝成BufferedReader
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
String line = null;
while ((line = bufferedReader.readLine()) != null) {
    if (line.equals("exit")) {
        System.exit(1);
    }
    System.out.println("輸入的內容是:" + line);
}

BufferedReader具備緩衝功能,在沒有讀到換行符則阻塞,讀到換行符再繼續。

4.2 推回輸入流

推回輸入流PushbackInputStream和PushbackReader中都提供了以下方法:

  • void unread(int b) :將一個字節/字符推回到推回緩衝區,從而容許重複讀取剛剛讀取的內容。

  • void unread(byte[] b/char[] b, int off, int len) :將一個字節/字符數組裏從off開始,長度爲len字節/字符的內容推回到推回緩衝區,從而容許重複讀取剛剛讀取的內容。

  • void unread(byte[] b/char[]):將一個字節/字符數組內容推回到推回緩衝區,從而容許重複讀取剛剛讀取的內容。

這兩個推回流都帶有一個推回緩衝區,當調用unread()方法時,系統將會把指定的內容推回到該緩衝區,而當每次調用read方法時會優先從推回緩衝區讀取,只有徹底讀取了推回緩衝區的內容後,但尚未read()所需的數組時纔會從原輸入流中讀取。

 //建立PushbackReader對象,指定推回緩衝區的長度爲64
PushbackReader pushbackReader = new PushbackReader(new FileReader("StreamTest.java"), 64);
char[] buf = new char[32];
//用以保存上次讀取的字符串內容
String lastContent = "";
int hasRead = 0;
//循環讀取文件內容
while ((hasRead = pushbackReader.read(buf)) > 0) {
    //將讀取的內容轉換成字符串
    String content = new String(buf, 0, hasRead);
    int targetIndex = 0;
    if ((targetIndex = (lastContent + content).indexOf("new PushbackReader")) > 0) {
        //將本次內容和上次的內容一塊兒推回緩衝區
        pushbackReader.unread((lastContent + content).toCharArray());
        //從新定義一個長度爲targetIndex的char數組
        if (targetIndex > 32) {
            buf = new char[targetIndex];
        }
        //再次讀取指定長度的內容
        pushbackReader.read(buf, 0, targetIndex);
        //打印讀取的內容
        System.out.print(new String(buf, 0, targetIndex));
        System.exit(0);
    } else {
        //打印上次讀取的內容
        System.out.print(lastContent);
        //將本次內容設爲上次讀取的內容
        lastContent = content;
    }
}

5、RandomAccessFile

RandomAccessFile是Java輸入/輸出流體系中最豐富的文件內容訪問類,提供了衆多的方法來訪問文件內容,既可讀取文件內容,也能夠向文件輸出數據,RandomAccessFile能夠自由訪問文件的任意位置。

RandomAccessFile包含一個記錄指針,用以標識當前讀和寫的位置,當建立新對象時,指針位置在0處,而當讀/寫了N個字節後,指針就會向後移動N個字節,而且RandomAccessFile能夠自動的移動該指針位置,固然咱們也能夠直接的獲取指針的位置。

  • getFilePointer():獲取文件記錄指針的當前位置。

  • seek(long pos):將文件記錄指針定位到pos位置。

RandomAccessFile有兩個構造函數:

  • RandomAccessFile(File file, String mode):使用File文件,指定文件自己 RandomAccessFile(String name, String mode):使用文件名稱,指定文件

其中還有一個參數mode(訪問模式),訪問模式有4個值:

  • r:以只讀方式打開文件

  • rw:以讀、寫方式打開文件,若是文件不存在,則建立

  • rws:以讀、寫方式打開文件,並要求對文件的內容或者元數據的每一個更新都同步寫入到底層存儲設備

  • rwd:以讀、寫方式打開文件,並要求對文件的內容的每一個更新都同步寫入到底層存儲設備

RandomAccessFile raf = new RandomAccessFile("StreamTest.java", "r");
System.out.println("文件指針的初始位置:" + raf.getFilePointer());
//移動指針位置
raf.seek(300);
byte[] buf = new byte[1024];
int hasRead = 0;
while ((hasRead = raf.read(buf)) > 0) {
    //讀取數據
    System.out.println(new String(buf, 0, hasRead));
}
//追加內容
RandomAccessFile randomAccessFile=new RandomAccessFile("out.txt","rw");
randomAccessFile.setLength(randomAccessFile.length());
randomAccessFile.write("追加的內容!\r\n".getBytes());

6、對象序列化

對象序列化機制是容許把內存中的java對象轉換成平臺無關的二進制流,這樣咱們能夠將這二進制流保存在磁盤上或者經過網絡將起傳輸到另外一個網絡節點,其餘程序獲取到此二進制流後,能夠將其恢復成原來的java對象。

要使一個對象是可序列化的,只須要繼承Serializable或者Externalizable接口,無需實現任何方法。全部可能在網絡上傳輸的對象的類都應該是可序列化的,如咱們JavaWeb中的輸入參數及返回結果。

6.1 使用對象流實現序列化

咱們使用一個對象流來實現序列化對象

先建一個對象類:

@Data
public class Person implements Serializable {

    private int age;

    private String name;

    public Person(String name, int age) {
        System.out.println("有參數的構造器");
        this.age = age;
        this.name = name;
    }
}

序列化對象與反序列化對象

//建立輸出流
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("object.txt"));
Person person = new Person("張三", 10);
//將person寫入文件中
objectOutputStream.writeObject(person);
//建立輸入流
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("object.txt"));
try {
    //讀出數據
    Person p = (Person) objectInputStream.readObject();
    System.out.println(p);
} catch (ClassNotFoundException e) {
    e.printStackTrace();
}

反序列化讀取的僅僅是Java對象的數據,而不java類,所以反序列化時必須提供對象所屬類的class文件,在反序列化對象時沒有調用有參數的構造器,說明反序列化時不須要經過構造器來初始化Java對象。

若是一個類中包含了引用類型,那麼引用類型也必須是可序列化的,不然該類也是不可序列化的。

若是咱們不但願某個變量被序列化,好比敏感信息,那須要使用transient來修飾此變量便可。

7、NIO

上面學習的IO都是阻塞式的,並且是底層都是經過字節的移動來處理的,這樣明顯效率不高,因而後面新增了NIO來進行改進,這些類都放在java.nio包中。

新IO 是將文件或文件的一段區域映射到內存中,這樣就能夠像訪問內存同樣來訪問文件中的內容,至關於虛擬內存概念,這種方式比傳統的IO快不少。

新IO的兩大核心對象是Channel(通道)與Buffer(緩衝),Channel與傳統的InputStream、OutputStream最大的區別在於提供了一個map()方法,這個方法是將一塊數據映射到內存中,這樣新IO就是面向塊進行處理;Buffer本質是一個數組,能夠看作一個容器,發送到Channel中的全部對象都必須首先放在Buffer中,讀取數據也是從Buffer中讀取。

7.1 Buffer

Buffer是一個抽象類,最經常使用的子類是ByteChannel和CharBuffer,Buffer類都沒有提供構造器,都是經過XXXBuffer allocate(int capacity) 來獲得對象,如

CharBuffer allocate = CharBuffer.allocate(8);

Buffer有三個重要概念:

  • 容量(capacity):緩衝區的容量,表示該buffer的最大數據容量,即最多可存儲多少數據,建立後不可改變。

  • 界限(limit):位於limit後的數據既不能夠讀,也不能夠寫。

  • 位置(position):用於指明下一個能夠被讀出或寫入的緩衝區位置索引,相似IO中的指針。

Buffer的主要做用是裝入數據,而後輸出,當建立buffer時,position在0位置,limit在capacity,當添加數據時,position向後移動。

當Buffer裝好數據時,調用flip()方法,這個方法將limit設置爲position,position設置爲0,也就是說不能繼續輸入,這就給輸出數據作好準備了,而當輸出數據結束後,調用clear()方法,這是將position設置爲0,limit設置爲capacity,這樣就爲裝入數據作好了準備。

除了上面的幾個概念,Buffer還有兩個重要方法,即put()與get()方法,就是存儲與讀取數據方法,在存儲和讀取數據時,分爲相對和絕對兩種:

  • 相對:從Buffer的position位置開始讀取或者寫入數據,這時候會改變position的數值。

  • 絕對:根據索引讀取或寫入數據,這個時候不會影響position的數值。

//建立buffer
CharBuffer buffer = CharBuffer.allocate(10);
System.out.println("capacity: " + buffer.capacity());
System.out.println("limit:" + buffer.limit());
System.out.println("position:" + buffer.position());
//加入數據
buffer.put('a');
buffer.put('b');
buffer.put('c');
System.out.println("加入元素後,position:" + buffer.position());
buffer.flip();
System.out.println("執行flip後,limit:" + buffer.limit());
System.out.println("position:" + buffer.position());
System.out.println("取出一個數據," + buffer.get());
System.out.println("取出數據後,position:" + buffer.position());
buffer.clear();
System.out.println("執行clear後,limit:" + buffer.limit());
System.out.println(",position:" + buffer.position());
System.out.println("執行clear後緩衝區未被清空:" + buffer.get(2));
System.out.println("絕對讀取後,position不會改變:" + buffer.position());

7.2 Channel

Channel相似傳統流對象,主要區別在於Channel能夠將指定文件的部分或者所有直接映射成Buffer,程序不能直接對Channel中的數據進行讀寫,只能經過Channel來進行數據讀寫。咱們用FileChannel來看看如何使用:

File file = new File("StreamTest.java");
//輸入流建立FileChannel
FileChannel inChannel = new FileInputStream(file).getChannel();
//以文件輸出流建立FileChannel,控制輸出
FileChannel outChannel = new FileOutputStream("a.txt").getChannel();
//將FileChannel映射成ByteBuffer,
MappedByteBuffer buffer = inChannel.map(FileChannel.MapMode.READ_ONLY, 0, file.length());
Charset charset = Charset.forName("GBK");
//輸出數據
outChannel.write(buffer);
buffer.clear();
CharsetDecoder charsetDecoder = charset.newDecoder();
//轉換成CharBuffer進行輸出
CharBuffer charBuffer = charsetDecoder.decode(buffer);
System.out.println(charBuffer);

7.3 字符集與Charset

咱們知道,在計算機底層文件都是二進制文件,都是字節碼,那爲何咱們還能看到字符,這裏面涉及編碼和解碼兩個概念,簡單講,將字符轉換成二進制爲編碼,而將二進制轉成字符爲解碼。

Java默認使用Unicode字符集(字符集是指二進制序列與字符之間的對應關係),但不少操做系統不使用Unicode字符集,這樣就會出錯,咱們要根據實際狀況來使用對應的字符集。

Charset包含了建立解碼器和編碼器的方法,還提供了獲取Charset所支持字符集的方法,咱們能夠經過Charset的forName()獲取對象,經過對象獲取到CharsetEncoder和CharsetDecoder對象,再經過此對象進行字符序列與字節序列的轉換。

SortedMap<String, Charset> stringCharsetSortedMap = Charset.availableCharsets();
for(String name:stringCharsetSortedMap.keySet()){
    System.out.println(name);
}
//建立簡體中文對應的Charset
Charset cn = Charset.forName("GBK");
//建立對應的編碼器及解碼器
CharsetEncoder cnEncoder = cn.newEncoder();
CharsetDecoder cnDecoder = cn.newDecoder();
CharBuffer buff = CharBuffer.allocate(8);
buff.put('李');
buff.put('白');
buff.flip();
//將buff的字符轉成字節序列
ByteBuffer bbuff = cnEncoder.encode(buff);
for (int i = 0; i <bbuff.capacity() ; i++) {
    System.out.print(bbuff.get(i)+ " ");
}
//將bbuff的數據解碼成字符
System.out.println("\n"+cnDecoder.decode(bbuff));

7.4 Path、Paths、Files

早期的Java只提供了File類來訪問文件系統,功能比較有限且性能不高,後面又提供了Path接口,Path表明一個平臺無關路徑,並提供了Paths與Files兩個工具類,提供了大量的方法來操做文件。

Path path = Paths.get(".");
System.out.println("path包含的文件數量:" + path.getNameCount());
System.out.println("path的根路徑:" + path.getRoot());
Path path1 = path.toAbsolutePath();
System.out.println("path的絕對路徑:" + path1);
//多個String構建路徑
Path path2 = Paths.get("G:", "test", "codes");
System.out.println("path2的路徑:" + path2);

System.out.println("StreamTest.java是否爲隱藏文件:" + Files.isHidden(Paths.get("StreamTest.java")));
//一次性讀取全部行
List<String> allLines = Files.readAllLines(Paths.get("StreamTest.java"), Charset.forName("gbk"));
System.out.println(allLines);
//讀取大小
System.out.println("StreamTest.java文件大小:" + Files.size(Paths.get("StreamTest.java")));
List<String> poem = new ArrayList<>();
poem.add("問君能有幾多愁");
poem.add("恰似一江春水向東流");
//一次性寫入數據
Files.write(Paths.get("poem.txt"), poem, Charset.forName("gbk"));

能夠看到Paths與Files很是的強大,提供了不少方法供咱們使用,在以前這些方法咱們本身寫的話比較麻煩,更多的方法能夠本身去看API。

7.5 文件屬性

java.nio.file.attribute包下提供了大量的屬性工具類,提供了很方便的方法去獲取文件的屬性:

BasicFileAttributeView baseView = Files.getFileAttributeView(Paths.get("poem.txt"), BasicFileAttributeView.class);
BasicFileAttributes basicFileAttributes = baseView.readAttributes();
System.out.println("建立時間:" + basicFileAttributes.creationTime().toMillis());
System.out.println("最後更新時間:" + basicFileAttributes.lastModifiedTime().toMillis());
相關文章
相關標籤/搜索