zoukankan      html  css  js  c++  java
  • 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 字符串 | 菜鸟教程
    ------------恢复内容结束------------

  • 相关阅读:
    JAVA总结--正则表达式
    JAVA总结--java数据类型
    数据结构与算法
    JAVA总结--JDK版本区别
    JAVA总结--代码规范
    .net core 学习笔记(4)-ViewComponent
    .net core学习笔记(3)-依赖注入
    .net core 学习笔记(2)-中间件
    .net core 学习笔记(1)-分页控件的使用
    taginput ,complete使用笔记
  • 原文地址:https://www.cnblogs.com/linzworld/p/13048539.html
Copyright © 2011-2022 走看看