Java基礎系列:緩衝流

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

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

前言

前面一章咱們對文件(File)和IO流進行了瞭解,分別介紹了:api

上面這些是重點,咱們必定要掌握數組

還重點強調了關於IO流的流向問題: 已程序爲參照物網絡

  • 從文件到程序是輸入流
  • 從程序到文件是輸出流

若是不清楚的話,建議去上一節看看oracle

這裏我先給你們看一張圖,上面羅列了一些可能會用到的一些流app

IO流彙總

下面咱們來一個個的介紹其餘的流less

處理流

在IO流中,存在一些流,是對基礎輸入流/輸出流進行一層包裝,經過這些流,咱們在經過基礎流來處理文件的時候能夠提升讀取/寫入的效率,關於這種流將其稱之爲處理流ide

流都是成對出現的,像以前的:post

  • InputStream - OutputStream
  • Reader - Writer

因此我在下面介紹的時候也就成對介紹了

字節流轉字符流

前面,咱們說過輸入流處理須要數據源,流數據源能夠來自文件,網絡等等任意的存在。

雖然任意一種數據源咱們均可以採用字節流來進行處理,不過咱們在追求功能能用的同時也能夠適當的追求下效率嘛,對不對^_^

並且在某一種場景下,只能獲取到字節流,而沒法獲取字符流

好比後面會聊到的:Socket,在Socket中只能獲得字節流

若是咱們在經過流進行處理的時候,若是咱們可以肯定數據源過來的是字符集的話,那麼咱們在處理的時候就能夠經過該處理類進行包裝,提升處理效率,這就是咱們如下要介紹的包裝類:

  • InputStreamReader
  • OutputStreamWriter

先看一張圖:

處理流

這兩個類是專門用來轉換字節流的類,下面咱們來看看具體的實現方式

File file = new File(System.getProperty("user.dir") + "/study-java/src/main/java/zopx/top/study/jav/_file/InputStreamReaderDemo.java");

/**
         * 常規寫法:
         */
FileInputStream inputStream = new FileInputStream(file);
FileOutputStream fileOutputStream = new FileOutputStream("b.txt");

/**
 * 由於讀取是讀取文本文件,是字符集數據,因此咱們能夠經過處理流進行轉換
 */

InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
OutputStreamWriter outputStreamWriter = new OutputStreamWriter(fileOutputStream);

System.out.println(inputStreamReader.read());

int len = 0;
//        byte[] buffer = new byte[1024];
char[] buffer = new char[1024];
while ((len = inputStreamReader.read(buffer)) > 0) {
    outputStreamWriter.write(new String(buffer, 0, len));
}

outputStreamWriter.flush();
outputStreamWriter.close();
inputStreamReader.close();
// 最後關閉基礎流,其實也能夠不用管,在處理流關閉的時候會關閉基礎流
fileOutputStream.close();
inputStream.close();

System.getProperty("user.dir"):能夠獲得當前工做目錄,若是咱們想要輸出java的系統參數,能夠經過當前方法來處理:

System.getProperties().list(System.out);

這裏在經過InputStreamReader來轉換流的時候,能夠指定字符編碼,若是不指定的話是這樣的:

  • Java虛擬機的默認字符集,若是Java虛擬機的默認字符集是null的話,那麼就採用UTF-8
public static Charset defaultCharset() {
    if (defaultCharset == null) {
        synchronized (Charset.class) {
            String csn = AccessController.doPrivileged(
                new GetPropertyAction("file.encoding"));
            Charset cs = lookup(csn);
            if (cs != null)
                defaultCharset = cs;
            else
                defaultCharset = forName("UTF-8");
        }
    }
    return defaultCharset;
}

上面就是經過處理流將字節流轉換成字符流的處理過程,其實只要將字節流轉換過來以後,一系列的處理操做也就和字符流的處理方法同樣了

緩衝字節流

緩衝流是咱們在實際操做中爲了提高性能的另外一種處理流,在基礎流中,讀寫文件會直接調用底層讀寫方法,頻繁調用底層方法也會形成性能的消耗,因此java在此基礎上實現了緩衝流,經過對底層讀寫方法的擴展,提升性能上的提高

咱們來看看基礎IO流的讀寫方法

// FileInputStream

private native int readBytes(byte b[], int off, int len) throws IOException;

public int read(byte b[]) throws IOException {
    return readBytes(b, 0, b.length);
}

// FileOutputStream
public void write(byte b[], int off, int len) throws IOException {
    writeBytes(b, off, len, append);
}
private native void writeBytes(byte b[], int off, int len, boolean append)
        throws IOException;

緩衝流的讀寫方法

// BufferedInputStream
public synchronized int read(byte b[], int off, int len)
        throws IOException
{
    getBufIfOpen(); // Check for closed stream
    if ((off | len | (off + len) | (b.length - (off + len))) < 0) {
        throw new IndexOutOfBoundsException();
    } else if (len == 0) {
        return 0;
    }

    int n = 0;
    for (;;) {
        int nread = read1(b, off + n, len - n);
        if (nread <= 0)
            return (n == 0) ? nread : n;
        n += nread;
        if (n >= len)
            return n;
        // if not closed but no bytes available, return
        InputStream input = in;
        if (input != null && input.available() <= 0)
            return n;
    }
}

// BufferedOutputStream
public synchronized void write(byte b[], int off, int len) throws IOException {
    if (len >= buf.length) {
        /* If the request length exceeds the size of the output buffer,
               flush the output buffer and then write the data directly.
               In this way buffered streams will cascade harmlessly. */
        flushBuffer();
        out.write(b, off, len);
        return;
    }
    if (len > buf.length - count) {
        flushBuffer();
    }
    System.arraycopy(b, off, buf, count, len);
    count += len;
}

爲何說緩衝流能夠提升性能?

  • 咱們經過查看BufferedOutputStream::write方法查看,在write()方法內部會進行判斷,若是不知足條件,那麼會將咱們外層寫入的數據存儲在protected byte buf[];中,對比這種數據咱們能夠經過flush()方法刷新而後調用底層write()方法將數據寫入到指定文件中
  • 這樣就能夠減小調用底層方法的次數,從而提升性能

下面咱們來看看具體實現方式

private static void bufferIO() throws Exception {
    BufferedInputStream bufis = new BufferedInputStream(new FileInputStream(System.getProperty("user.dir") + "/study-java/src/main/java/zopx/top/study/jav/_file/InputStreamReaderDemo.java"));
    BufferedOutputStream bufos = new BufferedOutputStream(new FileOutputStream("c.txt"));

    int len = 0;
    byte[] buffer = new byte[1024];

    while ((len = bufis.read(buffer)) != -1) {
        bufos.write(buffer, 0, len);
    }
    bufis.close();
    // 這也就是爲何在這裏須要調用flush()的緣由
    bufos.flush();
    bufos.close();
}

若是看源碼的話,咱們能夠從註釋中獲得他們的相關信息

  • 在建立BufferedInputStream的時候,會建立一個內部緩衝區數組,當讀取或跳過流中的字節時,根據須要從包含的輸入流中從新填充內部緩衝區,一次填充許多字節
  • 而經過BufferedOutputStream,應用程序能夠將字節寫入底層輸出流,而沒必要爲寫入的每一個字節引發對底層系統的調用。

緩衝讀寫流

緩衝讀寫流和緩衝字節流效果是同樣的,咱們來具體看實現:

private static void bufferRead() throws Exception {
    BufferedReader br = new BufferedReader(new FileReader(FILE_NAME));
    BufferedWriter bw = new BufferedWriter(new FileWriter("d.txt"));

    // 讀取整行內容
    String line = "";
    while ((line = br.readLine()) != null) {
        bw.write(line);
        // 換行
        bw.newLine();
    }

    br.close();
    bw.flush();
    bw.close();
}

和以前對比的不一樣點在於

  • BufferedReader除了能夠經過char[]來讀取內容外,還容許讀取整行內容:readLine()
  • 若是咱們經過調用readLine()讀取內容的時候,在經過BufferedWriter寫入的時候,須要調用newLine()來換行,若是缺乏這步的話,那麼總體數據會寫在一行上

這樣就方便了不少

打印流

Java中還存在這麼一類流,咱們常常用,可是不少時候咱們沒有很注意過,那就是打印流,對應類也就是:

  • System.out
  • System.in

若是這樣子來看的話可能會更直觀一點:

PrintStream out = System.out;
InputStream in = System.in;

這種方式的體現相信你們都明白:全部內容都是輸出在控制檯上

PrintStream中爲咱們提供了方便地打印各類數據值的表示形式的功能

相信還有這種場景,在控制檯進行交互的時候咱們也會使用到該類,咱們來看一個小例子:

// 從控制檯輸入
Scanner scanner = new Scanner(System.in);
while(scanner.hasNext()) {
    // 獲得輸入內容
    String next = scanner.next();
    System.out.println(next);
}

其餘類型的流

結合上面最開始的圖,目前還有三組流沒有介紹,下面咱們分別來介紹一下:

ByteArrayInputStream/ByteArrayOutputStream

ByteArrayInputStream是一個包含內部緩衝區的流,該緩衝區中包含能夠從流中讀取到的字節。

ByteArrayOutputStream的數據被寫入字節數組。 緩衝區隨着數據寫入而自動增加

咱們來看下主要構造

// 核心點
protected byte buf[];
protected int pos;

下面是具體的使用方式:

使用方式和以前講到的流差很少

ByteArrayInputStream inputStream = new ByteArrayInputStream("mr.sanq學習記錄資料完善".getBytes());
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();

int len = 0;
byte[] buffer = new byte[1024];
while ((len = inputStream.read(buffer)) != -1) {
    outputStream.write(buffer, 0, len);
}
inputStream.close();
outputStream.close();

System.out.println(outputStream.toString());
outputStream.writeTo(System.out);

和以前流的不一樣點在於:

  • ByteArrayInputStream的數據來源只能是byte[]
  • ByteArrayOutputStream經過調用write()方法,會將數據存入其內部屬性protected byte buf[],而且會自動增加數據長度,而且能夠經過writeTo()方法來指定輸出流
  • 並且ByteArrayOutputStreamclose()以後也會能夠經過toString()或者toByteArray()來輸出數據內容

說白了,就是在操做字節數組

ObjectInputStream/ObjectOutputStream

對基本數據類型和對象進行反序列化/序列化

在文件存儲或者是網絡傳輸過程當中,能夠傳遞文本、圖片、視頻、音頻等文件,這些文件都是經過二進制數組來傳遞的,那麼若是咱們想要傳遞對象數據呢,能不能實現?

答案固然是確定的,這裏就要先聊一下序列化和反序列化

首先先介紹一下什麼序列化和反序列化?

  • 序列化: 是將對象的狀態信息轉換爲能夠存儲或傳輸的形式的過程
  • 反序列化:程序從文件或者網絡獲得序列化以後的數據以後對內容重組獲得對象狀態的過程

能夠說,咱們尋常定義的Java對象,只能存儲在JVM內存中,當JVM停機的時候,內存被清空,當前對象就不會存在。若是經過序列化並結合ObjectOutputStream就能夠將對象轉換成流的形式存儲在指定的文件中,這樣就可讓對象永久保存,只須要經過ObjectInputStream將存儲的流對象反序列化成Java對象

基於這種特性,咱們能夠經過該方式實現:

  • 永久保存對象
  • 將對象進行網絡傳輸

在Java中若是想要實現序列化,只須要實現一個接口:

  • Serializable

這裏先來看個案例:

public class Student implements Serializable {
    public Long id;
    public String name;

    public Student() {
    }

    public Student(Long id, String name) {
        this.id = id;
        this.name = name;
    }
}

這樣就實現了對象的序列化。

咱們看看有什麼用

上面的實現方式很簡單,可是其關鍵API在於:ObjectInputStream和ObjectOutputStream

咱們看具體使用:

private static void io() throws Exception {
    // 寫入到指定的文件中
    // 寫入成功不用打開看,這不是人能看懂的
    ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("a.tmp"));
    outputStream.writeObject(new Student(1L, "張三"));
    outputStream.writeUTF("里斯");
    outputStream.writeBoolean(true);
    outputStream.writeInt(1);
    outputStream.close();

    // 從文件中讀取對應數據
    ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("a.tmp"));
    Object o = inputStream.readObject();
    String name = inputStream.readUTF();
    boolean b = inputStream.readBoolean();
    int i = inputStream.readInt();
    inputStream.close();
    System.out.println(o);
    System.out.println(name);
    System.out.println(b);
    System.out.println(i);
}

ObjectIO

ObjectInputStreamObjectOutputStream爲咱們提供了不少相對應的方法:

Object Write

Object read

咱們能夠經過這些方法將咱們想要存儲的類型數據都存儲起來。 可是這裏要注意一點的是:

  • writeXX()和readXX()的順序必須一致,不然會報錯

下面咱們來總結一下該流對象:

  • 若是須要將對象經過IO流進行傳輸,那麼就必需要是實現序列化接口
  • 若是在序列化的時候有個別字段不須要實例化,那麼咱們能夠經過transient來進行修飾,好比:密碼

在後面聊到網絡的時候,咱們能夠嘗試經過當前流在網絡來傳遞對象

DataInputStream/DataOutputStream

數據輸入流容許應用程序以與機器無關的方式從基礎輸入流中讀取原始Java數據類型。 應用程序使用數據輸出流來寫入數據,之後能夠由數據輸入流讀取

該組流和上面介紹的ObjectInputStream的使用方式差很少,API方法也很相近。下面咱們來看看實際操做:

static String str = "ddsad";
private static void io() throws Exception {

    DataOutputStream outputStream = new DataOutputStream(new FileOutputStream("b.tmp"));
    outputStream.writeUTF("卡卡卡");
    outputStream.writeBoolean(true);
    outputStream.writeBytes(str);
    outputStream.writeLong(2);

    outputStream.flush();
    outputStream.close();

    DataInputStream inputStream = new DataInputStream(new FileInputStream("b.tmp"));
    System.out.println(inputStream.readUTF());
    System.out.println(inputStream.readBoolean());
    byte[] buff = new byte[str.length()];
    inputStream.read(buff);
    System.out.println(new String(buff));
    System.out.println(inputStream.readLong());

    inputStream.close();
}

幾乎涵蓋了ObjectInputStream和ObjectOutputStream所支持的方法,這裏就不過多介紹了

文檔

上面方法只列出了一點點,若是要查看更多的方法的話推薦查看官方文檔:

我就不一一列了,你們經過如下彙總部分,用到那個就去搜一下

IO流文檔集

相關文章
相關標籤/搜索