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中的IO操作和缓冲区的更多相关文章

  1. java中的IO操作总结

    一.InputStream重用技巧(利用ByteArrayOutputStream) 对同一个InputStream对象进行使用多次. 比如,客户端从服务器获取数据 ,利用HttpURLConnect ...

  2. java中的IO操作

    IO流是用来处理设备之间的数据传输,Java对数据的操作是通过流的方式进行,而操作流的对象都封装到java.io包中.根据操作数据的种类可以把IO流分为字节流(InputStream,OutputSt ...

  3. 【转】Java中的IO操作

    在使用io操作之前,先看一下java中的文件类File如何使用.File包括文件和目录,对文件和目录的操作是新建目录mkdir,新建文件createNewFile,删除文件和目录delete,以及其他 ...

  4. java学习系列(一)Java中的IO操作

    Java的IO流是实现输入/输出的基础,它可以方便地实现数据的输入\输出操作,在Java中把不同的输入\输出源抽象为"流",通过流的方式允许Java程序使用相同的方式来访问不同的输 ...

  5. 第12讲-Java中的IO操作及对象的序列化与反序列化

    1.知识点 1.1.课程回顾 1.2.本章重点 1.2.1  io操作 1.2.2  对象的序列化与反序列化 2.具体内容 2.1.Java IO 2.1.1.什么是IO IO其实就是输入.输出 I ...

  6. java中的io系统详解 - ilibaba的专栏 - 博客频道 - CSDN.NET

    java中的io系统详解 - ilibaba的专栏 - 博客频道 - CSDN.NET 亲,“社区之星”已经一周岁了!      社区福利快来领取免费参加MDCC大会机会哦    Tag功能介绍—我们 ...

  7. Java中的IO流系统详解(转载)

    摘要: Java 流在处理上分为字符流和字节流.字符流处理的单元为 2 个字节的 Unicode 字符,分别操作字符.字符数组或字符串,而字节流处理单元为 1 个字节,操作字节和字节数组. Java ...

  8. Java中的IO流系统详解

    Java 流在处理上分为字符流和字节流.字符流处理的单元为 2 个字节的 Unicode 字符,分别操作字符.字符数组或字符串,而字节流处理单元为 1 个字节,操作字节和字节数组. Java 内用 U ...

  9. java中的IO流

    Java中的IO流 在之前的时候我已经接触过C#中的IO流,也就是说集中数据固化的方式之一,那么我们今天来说一下java中的IO流. 首先,我们学习IO流就是要对文件或目录进行一系列的操作,那么怎样操 ...

随机推荐

  1. 铁大树洞app功能演示和使用说明

    在观看这篇功能博客之前推荐看一下我们设计软件方案博客,可以更好地理解,博客连接:https://www.cnblogs.com/three3/p/12658897.html首先下载我们软件的安装包,点 ...

  2. Java 程序员生产神器 IDEA 的常用快捷键、插件及设置

    对于 Java 程序员来说,使用 IDEA 集成环境是最称手的.优点不多讲,用过的人都知道.IDEA 虽好,但为了充分利用 IDEA 的优势,我分享一下我常用快捷键.插件和设置. 常用快捷键 Ctrl ...

  3. java验证工具类(待验证)

    /** * <判断对象是否为null或者空> * * @param obj * 需要判断的对象 * @return 如果对象为null或者空则返回true */ public static ...

  4. 深入探究JVM之方法调用及Lambda表达式实现原理

    @ 目录 前言 正文 解析 分派 静态分派 动态分派 单分派和多分派 动态分派的实现 Lambda表达式的实现原理 MethodHandle 总结 前言 在最开始讲解JVM内存结构的时候有简单分析过方 ...

  5. 1、Java 开发环境配置

    Java 开发环境配置 在本章节中我们将为大家介绍如何搭建Java开发环境. Windows 上安装开发环境 Linux 上安装开发环境 安装 Eclipse 运行 Java window系统安装ja ...

  6. Python 切分数组,将一个数组均匀切分成多个数组

    Python 切分数组 将一个数组,均分为多个数组 代码 # -*- coding:utf-8 -*- # py3 def list_split(items, n): return [items[i: ...

  7. Bytom DAPP 开发流程

    从目前已经发布的DAPP来看,DAPP架构大致可以分成3种类型:插件钱包模式.全节点钱包模式和兼容模式. 插件钱包模式是借助封装了钱包的浏览器插件通过RPC协议与区块链节点通信,插件在运行时会将Web ...

  8. csapp第六章笔记-存储器结构

    目录 随机访问存储器(Random-Access-Memory) 静态RAM 动态RAM 增强的DRAM 非易失性存储器 磁盘存储 磁盘构成 磁盘容量 磁盘操作 逻辑磁盘块 访问磁盘和连接I/O设备 ...

  9. Windows下,配置VS Code的Java开发环境

    Windows下,配置VS Code的Java开发环境 前言 最近痴迷于VS Code的开发环境配置,原因就在于它的轻巧和免费,还能当一个非常棒的文本编辑器.如果之前你配置过VS Code并且失败了, ...

  10. 初识ABP vNext(1):开篇计划&基础知识

    目录 前言 开始 审计(Audit) 本地化(Localization) 事件总线(Event Bus) 多租户(multi-tenancy technology) DDD分层 实体(Entity) ...