Java中的IO操作和緩沖區


Java中的IO操作和緩沖區

一、簡述

Java.io 包幾乎包含了所有操作輸入、輸出需要的類。所有這些流類代表了輸入源和輸出目標。

Java.io 包中的流支持很多種格式,比如:基本類型、對象、本地化字符集等等。

一個流可以理解為一個數據的序列。輸入流表示從一個源讀取數據,輸出流表示向一個目標寫數據。

根據數據的單位划分: 字節流和字符流。

Java 為 I/O 提供了強大的而靈活的支持,使其更廣泛地應用到文件傳輸和網絡編程中。

本文主要介紹Java中的字符流和字節流,以及緩沖區的區別

二、IO流的介紹

什么是流

​ Java中的流是對字節序列的抽象,我們可以想象有一個水管,只不過現在流動在水管中的不再是水,而是字節序列。和水流一樣,Java中的流也具有一個“流動的方向”,通常可以從中讀入一個字節序列的對象被稱為輸入流;能夠向其寫入一個字節序列的對象被稱為輸出流。

輸入輸出流的作用范圍

三、Java中的字節流和字符流

字節流

Java中的字節流處理的最基本單位為單個字節,它通常用來處理二進制數據。Java中最基本的兩個字節流類是InputStream和OutputStream,它們分別代表了組基本的輸入字節流和輸出字節流。InputStream類與OutputStream類均為抽象類,我們在實際使用中通常使用Java類庫中提供的它們的一系列子類。

字符流

Java中的字符流處理的最基本的單元是Unicode碼元(大小統一為2字節),它通常用來處理文本數據。所謂Unicode碼元,也就是一個Unicode代碼單元,范圍是0x0000~0xFFFF。在以上范圍內的每個數字都與一個字符相對應,Java中的String類型默認就把字符以Unicode規則編碼而后存儲在內存中。然而與存儲在內存中不同,存儲在磁盤上的數據通常有着各種各樣的編碼方式。使用不同的編碼方式,相同的字符會有不同的二進制表示。

Java中最基本的兩個字符流類是Reader和Writer

二者的聯系

Java提供了以下兩種IO類型的相互轉化

InputStreamReader(字節到字符)和 OutputStreamReader(字符到字節):

1.InputStreamReader

從字節流到字符流的橋梁:它讀入字節,並根據指定的編碼方式,將之轉換為字符流。

​ 使用的編碼方式可能由名稱指定,或平台可接受的缺省編碼方式
​ InputStreamReader的read()方法之一的每次調用,可能促使從基本字節輸入流中讀取一個或多個字節。

2.OutputStreamWriter

將多個字符寫入到一個輸出流,根據指定的字符編碼將多個字符轉換為字節
每個 OutputStreamWriter 合並它自己的 CharToByteConverter, 因而是從字符流到字節流的橋梁

字符流=字節流+Unicode編碼

字節流變為字符流=基本單位從一個字節變為兩個字節(Unicode編碼)

字節流和字符流的區別

讀取單位不同

  1. 字節流:用於讀取一個一個的數據字節(8位),每8位當成一個單元
  2. 字符流:用於讀取一個一個的數據字符(16位),每16位當成一個單元

執行效率不同

從執行效率來說,字符流相對於字節流的速度是要快的。因為字符流每次處理是可以處理一個緩沖區的,而字節只能一個一個字節的處理。

使用對象不同

     1. 字節流通常用於處理二進制數據,實際上它可以**處理任意類型**的數據,但它不支持直接寫入或讀取Unicode碼元;
     2. 字符流通常處理文本數據,它支持寫入及讀取**Unicode碼元**。

緩沖區的使用

  1. 字節流默認不使用緩沖區;
  2. 字符流使用緩沖區。

四、效率測試

代碼測試

輸入測試

package IOOperation;

import java.io.*;

/**
 * @Description 輸入流的測試
 * @Author 林澤鴻
 * @Date 2020/5/31 11:27
 */
public class InputTest {


    /**
     * 用於輸入的對象,大小為2.58MB
     */
    public static final File inputFile = new File("E:\\RDC\\學習資料\\2.pdf");


    // 字節流
    public void bytes() {
        try {
            System.out.println("1.字節流輸入");
            // 新建文件命名
            // 創建輸入輸出流對象
            long s1 = System.currentTimeMillis();// 測試開始,計時
            FileInputStream fileInputStream = new FileInputStream(inputFile);
            // 讀寫數據
            // 讀寫數據
            while (fileInputStream.read() != -1) {
            }
            fileInputStream.close();
            long s2 = System.currentTimeMillis();// 測試結束,計時
            System.out.println("輸入文件耗時:" + (s2 - s1) + "ms");
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    //字符流
    public void chars() {
        try {
            System.out.println("2.字符流輸入");
            // 新建文件命名
            long s1 = System.currentTimeMillis();// 測試開始,計時
            // 創建輸入輸出流對象
            FileReader fileReader = new FileReader(inputFile);
            // 讀寫數據
            while (fileReader.read() != -1) {
            }
            fileReader.close();
            long s2 = System.currentTimeMillis();// 測試結束,計時
            System.out.println("輸入文件耗時:" + (s2 - s1) + "ms");
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    // 字節緩沖流
    public void bytesBuffer() {
        try {
            System.out.println("3.字節緩沖流輸入");
            // 新建文件命名
            long s1 = System.currentTimeMillis();// 測試開始,計時
            BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream(inputFile));
            // 創建輸入輸出流對象
            // 讀寫數據
            while (bufferedInputStream.read() != -1) {
            }
            bufferedInputStream.close();
            long s2 = System.currentTimeMillis();// 測試結束,計時
            System.out.println("輸入文件耗時:" + (s2 - s1) + "ms");
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }


    // 字符緩沖流
    public void charsBuffer() {
        try {
            System.out.println("4.字符緩沖流輸入");
            // 新建文件命名
            long s1 = System.currentTimeMillis();// 測試開始,計時
            // 創建輸入輸出流對象
            BufferedReader bufferedReader = new BufferedReader(new FileReader(inputFile));
            // 讀寫數據
            while (bufferedReader.read() != -1) {
            }
            bufferedReader.close();
            long s2 = System.currentTimeMillis();// 測試結束,計時
            System.out.println("輸入文件耗時:" + (s2 - s1) + "ms");
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

}

輸出測試

package IOOperation;

import java.io.*;

/**
 * @Description 輸出流的測試
 * @Author 林澤鴻
 * @Date 2020/5/31 11:28
 */
public class OutPutTest {
    public static final String PATH =
            "E:\\codingEnvironment\\GitRepository\\JAVA\\src\\fileCatalog\\";
    int count = 1;

    /**
     * 用於輸出的對象
     */
    public static byte[] outputBytes = null;

    public static char[] outputChars = null;

    public static void before() {
        StringBuilder stringBuilder = new StringBuilder("");
        for (int i = 0; i < 300000; i++) {
            stringBuilder.append("testtesttesttesttesttest");
        }
        outputBytes = stringBuilder.toString().getBytes();
        outputChars = stringBuilder.toString().toCharArray();
    }




    // 字節流
    public void bytes() {
        try {
            System.out.println("1.字節流輸出");
            // 新建文件命名
            String name = PATH + "字節流輸出文件.txt";
            File file = new File(name);
            // 創建輸入輸出流對象
            FileOutputStream fos = new FileOutputStream(file);
            // 讀寫數據
            long s1 = System.currentTimeMillis();// 測試開始,計時
            writeBytes(fos);
            long s2 = System.currentTimeMillis();// 測試結束,計時
            fos.close();
            System.out.println("輸出文件耗時:" + (s2 - s1) + "ms");
            System.out.println("文件大小:" + file.length() / 1024 + "KB");
            file.delete();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }


    // 字節流
    public void chars() {
        try {
            System.out.println("2.字符流輸出");
            // 新建文件命名
            String name = PATH + "字符流輸出文件.txt";
            File file = new File(name);
            // 創建輸入輸出流對象
            FileWriter fileWriter = new FileWriter(file);
            // 讀寫數據
            long s1 = System.currentTimeMillis();// 測試開始,計時
            writeChars(fileWriter);
            long s2 = System.currentTimeMillis();// 測試結束,計時
            fileWriter.close();
            System.out.println("輸出文件耗時:" + (s2 - s1) + "ms");
            System.out.println("文件大小:" + file.length() / 1024 + "KB");
            file.delete();

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }


    // 字節緩沖流
    public void bytesBuffer() {
        try {
            System.out.println("3.字節緩沖流輸出");
            // 新建文件命名
            String name = PATH + "字節緩沖流輸出文件.txt";
            File file = new File(name);
            // 創建輸入輸出流對象
            FileOutputStream fileOutputStream = new FileOutputStream(file);
            BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(fileOutputStream);
            // 讀寫數據
            long s1 = System.currentTimeMillis();// 測試開始,計時
            writeBytes(bufferedOutputStream);
            long s2 = System.currentTimeMillis();// 測試結束,計時
            bufferedOutputStream.close();
            System.out.println("輸出文件耗時:" + (s2 - s1) + "ms");
            System.out.println("文件大小:" + file.length() / 1024 + "KB");
            file.delete();

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }


    // 字符緩沖流
    public void charsBuffer() {
        try {
            System.out.println("4.字符緩沖流輸出");
            // 新建文件命名
            String name = PATH + "字符緩沖流輸出文件.txt";
            File file = new File(name);
            // 創建輸入輸出流對象
            BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(file));
            // 讀寫數據
            long s1 = System.currentTimeMillis();// 測試開始,計時
            for (int i = 0; i < count; i++) {
                bufferedWriter.write(outputChars);
            }
            long s2 = System.currentTimeMillis();// 測試結束,計時
            bufferedWriter.close();

            System.out.println("輸出文件耗時:" + (s2 - s1) + "ms");
            System.out.println("文件大小:" + file.length() / 1024 + "KB");
            file.delete();

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }



    /**
     * 字節輸出
     */
    private void writeBytes(OutputStream fos) throws IOException {
        for (int i = 0; i < count; i++) {
            for (int j = 0; j < outputBytes.length; j++) {
                fos.write(outputBytes[j]);
            }
        }
    }

    /**
     * 字符輸出
     */
    private void writeChars(Writer writer) throws IOException {
        for (int i = 0; i < count; i++) {
            for (int j = 0; j < outputChars.length; j++) {
                writer.write(outputChars[j]);
            }
        }
    }
}

主測試類

package IOOperation;

/**
 * @Description Java中IO效率的比較
 * @Author 林澤鴻
 * @Date 2020/5/31 10:48
 */
public class Main {
    public static void main(String[] args) {
        OutPutTest.before();
        InputTest inputTest = new InputTest();
        System.out.println("========================");
        System.out.println("測試輸入流(文件大小:2.58MB)");
        System.out.println("========================");

        inputTest.bytes();
        inputTest.chars();
        inputTest.bytesBuffer();
        inputTest.charsBuffer();

        OutPutTest outPutTest = new OutPutTest();
        System.out.println("========================");
        System.out.println("測試輸出流");
        System.out.println("========================");

        outPutTest.bytes();
        outPutTest.chars();
        outPutTest.bytesBuffer();
        outPutTest.charsBuffer();

    }
}

測試結果

========================
測試輸入流(文件大小:2.58MB)
========================
1.字節流輸入
輸入文件耗時:6723ms
2.字符流輸入
輸入文件耗時:522ms
3.字節緩沖流輸入
輸入文件耗時:14ms
4.字符緩沖流輸入
輸入文件耗時:98ms
========================
測試輸出流
========================
1.字節流輸出
輸出文件耗時:19272ms
文件大小:7031KB
2.字符流輸出
輸出文件耗時:405ms
文件大小:7031KB
3.字節緩沖流輸出
輸出文件耗時:38ms
文件大小:7031KB
4.字符緩沖流輸出
輸出文件耗時:45ms
文件大小:7031KB

Process finished with exit code 0

結果分析

  1. 有緩沖區的操作都比沒有緩沖區的效率高

  2. 輸入流中,由於輸入流的文件對象是.pdf對象,不是文本文件,從而帶緩沖區的字節流比帶緩沖區的字符流效率更高

  3. 查看了StreamDecoder的源碼,從而知道FileReader類(字符輸入流)默認會有一個DEFAULT_BYTE_BUFFER_SIZE 默認緩沖區大小8192,所以會比字節輸入流快。

  4. BufferReader中是將數據放到緩沖區。

    FileReader是用來讀文件的類,而BufferReader是將IO流轉換為Buffer以提高程序的處理速度

  5. BufferedInputStream(帶緩沖區的字節輸入流)在實現的時候是在自身read方法中提供緩存,是一次取1024或更多字節然后再慢慢讀,一個個的返回,它並沒有實現讀一行的方法

    BufferedReader(字符緩沖流輸入)在實現時通過提供一個readLine方法,使用數組或者stringBuilder存儲一行數據,並一次性返回

五、字節序

字節序,顧名思義字節的順序,再多說兩句就是大於一個字節類型的數據在內存中的存放順序(一個字節的數據當然就無需談順序的問題了)。其實大部分人在實際的開 發中都很少會直接和字節序打交道。唯有在跨平台以及網絡程序中字節序才是一個應該被考慮的問題。

類型

小端格式和大端格式(Little-Endian&Big-Endian)

不同的CPU有不同的字節序類型,這些字節序是指整數在內存中保存的順序。

最常見的有兩種:

1. Little-endian:將低序字節存儲在起始地址(低位編址)

2. Big-endian:將高序字節存儲在起始地址(高位編址)

C語言中的二進制和文本文件的讀取效率比較

測試代碼

#include <time.h>
#include <stdio.h>
#include <stdlib.h> 

double txtRead(char* filePath){
  FILE* fpRead = fopen(filePath, "r");
  	clock_t start, finish;
	int a=0;
	if (fpRead == NULL)
	{
		printf("文件打開失敗");
		return 0;
	}

	start = clock();
	while (!feof(fpRead))
	{
		a = fgetc(fpRead);
	}
	finish = clock();
	double text_duration = (double)(finish - start) / CLOCKS_PER_SEC;
	fclose(fpRead);
    return text_duration;
}

double binaryRead(char* filePath){
	FILE*  fbRead = fopen(filePath,"rb");
	clock_t start, finish;
    int a=0;
	if (fbRead == NULL)
	{
		printf("文件打開失敗");
		return 0;
	}
	start = clock();
	while (!feof(fbRead))
	{
		a = fgetc(fbRead);
	}
	finish = clock();
    fclose(fbRead);
	double binary_duration = (double)(finish - start) / CLOCKS_PER_SEC;
    return binary_duration;
}
int main()
{
    char filePath[10] = "D:\\1.txt";
	printf("文本格式總耗時:%f 秒\n", txtRead(filePath));
    printf("二進制格式總耗時:%f 秒\n", binaryRead(filePath));
	return 1;
}

測試結果

[Running] cd "e:\VSCodeConfiguration\Java\" && gcc clock_test.c -o clock_test && "e:\VSCodeConfiguration\Java\"clock_test
文本格式總耗時:3.678000 秒
二進制格式總耗時:3.048000 秒

[Done] exited with code=1 in 7.1 seconds

結果分析

二進制格式的讀取要比文本格式的讀取快一點

六、思考總結

  1. 源碼中很多都有寫明作用,直接看源碼效率更高
  2. 字符流默認有一個緩沖區
  3. 學會看官方文檔,Java的官方文檔對IO類都有說明

參考鏈接

Java 流(Stream)、文件(File)和IO | 菜鳥教程

輸入和輸出(IO)概述_java_dreamzuora的博客-CSDN博客

Java中字符流與字節流的區別 - 那啥快看 - 博客園

(2條消息)java字節流與字符流的區別_java_呼卓宇的博客-CSDN博客

(2條消息)JAVA基礎知識之StreamDecoder流_java_咕嚕是個胖子-CSDN博客

(2條消息)FileReader和BufferedReader的區別_java_meihuai7538的博客-CSDN博客

(2條消息)Endian_vickey -CSDN博客

C 字符串 | 菜鳥教程
------------恢復內容結束------------


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM