【Java IO流】字节流详解
创始人
2024-05-13 11:36:21
0

在这里插入图片描述

文章目录

  • 1. IO 流概述
  • 2. IO 流分类
  • 3. 字节输出流
  • 4. 字节输入流
  • 5. 文件拷贝
  • 6. IO 流中的异常处理
  • 7. 总结
  • Java编程基础教程系列

1. IO 流概述

什么是 IO 流?

IO 流是存取数据的解决方案,在计算机中数据存放在硬盘的文件中,如果程序需要使用这些数据时,就会从文件中把数据读取到内存中,内存中数据的特点是不能永久化存储,程序停止,数据丢失。那么如何持久的保存程序中的数据呢?

程序中的数据会通过写入的方式存储到硬盘的文件中,特点是可以长期的存储,不会随着程序的终止而丢失,那么 Java 语言是怎样读取和写入数据的呢?

这里就引出了流的概念,流是一个抽象的概念,我们把数据在两设备的传输抽象为流的方式,流的本质是数据传输,根据数据传输特性将流抽象为各种类,方便更直观的进行数据操作。

2. IO 流分类

Java中的流可以从不同的角度进行分类,按照流动方向可以分为输入流和输出流,输入流用于数据的读取,输出流用于数据的写出。按照操作对象的不同可以分为字节流和字符流,字节流可以操作所有类型的文件,例如:文本,图像,音频等,字符流用于操作纯文本文件。

image-20230114213143003

Java中有四种顶层的流 InputStreamOutputStreamReaderWriter ,这四种流是抽象类,不能用来实例化对象,其又分别有更具体的子类,分为文件流,缓冲流,数据流,转换流,Print流,Object流等,都分别有特定的功能或用来操作特定的数据。

我们一般不会使用字节流来操作文本文件,因为会出现乱码的情况,相信学完今天的内容,你就会明白其中的原理。纯文本文件是指使用记事本的形式创建的文件,例如 txt 文件,md 文件,而 Word 文件就不是纯文本文件。

在学习时,为了逻辑清晰,一般通过字节流和字符流两类来学习,每一类又包括输出流和输入流。

总结

  • IO流是存储和读取数据的解决方案
  • I 表示 input,O表示 output,流则是抽象的一种概念,表示数据的传输
  • IO 流用于读取数据,既可以读取本地文件,也可以是网络文件
  • 按照流的方向,IO 流分为输出流(程序到文件)和输入流(文件到程序)
  • 按照文件类型,IO 流分为字节流(操作所有类型)和字符流(操作纯文本文件)

3. 字节输出流

上面说到的四种基本的流类都是抽象类,不能用来实例化对象,我们要使用其子类创建对象用来传输数据。

例如,往本地文件中写出数据时,可以使用 FileOutputStream ,该类被称为字节输出流。使用该类往本地文件中写出数据可以分为三步:

  1. 创建流对象
  2. 写出数据
  3. 释放资源

image-20230117125637749

示例:

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;public class Test {public static void main(String[] args) throws IOException {/*使用 FileOutputStream 往本地文件中写入数据FileOutputStream 构造方法的参数既可以使用String类型也可以使用File类程序需要进行异常处理,直接抛出异常即可*///1. 创建流对象FileOutputStream fos=new FileOutputStream("test.txt");//2. 写出数据fos.write(97);//3. 释放资源fos.close();}
}

在程序创建流对象时,程序和文件之间就会建立一个通道,此时我们就可以通过调用 write 方法往文件中写出数据,写出数据完成之后,进行资源释放,相当于打断了这个通道。

image-20230115183723684

细节:

在创建 FileOutputStream 流对象时,构造方法中既可以传入 String 类型也可以传入 File 类对象,如果传入的是 String 类型,其底层会自动创建 File 类对象。如果目标文件不存在,则会创建一个新的文件,并且把数据写出到新创建的文件中,但是要保证父级目录存在。如果文件存在,则会默认清空文件,并且写出数据。

write() 方法传入的参数是一个整数,实际写出到文件中的是参数在字符集表中对应的字符。几乎所有的流操作都要进行释放资源的操作。释放资源实际就是打断了程序和文件之间的流通道,如果不释放资源,文件则一直被程序占用。

上面的方式每次在只能传输一个字节的数据,那么如何一次传输多个字节数据呢?

FileOutputStream 中一共有三种方法进行数据的写出:

方法说明
void write(int b)一次写一个字节数据
void write(byte[] b)一次写一个字节数组的数据
void write(byte[] b,int off,int len)一次写一个字节数组的部分数据

如果要一次性的写多个数据,那么你可以先把数据放到 byte 类型的数组中,然后写入文件中。

示例,在写出数据时:

byte[] bytes={97,98,99,100};
fos.write(bytes);

或者:

//2. 写出数据
String s="abcd";
byte[] bytes = s.getBytes();
fos.write(bytes);

两种方法效果相同,运行结果:

abcd

前面说到,创建流对象时,如果文件存在,则会默认清空文件。那么,我们如何把数据追加或者写入到文件中呢?

其实,在 FileOutputStream 类中的构造方法中,有一个 boolean 类型的参数,这个参数控制写出数据时是否追加在文件末尾,默认传入的是 false ,我们只需要在创建对象时传入 true 即可把数据追加写出到文件末尾。

示例,假设文件中已有数据 Hello:

//2. 写出数据
String s1="abcd";
byte[] bytes1 = s1.getBytes();
fos.write(bytes1);
String s2="\r\n";
byte[] bytes2 = s2.getBytes();
fos.write(bytes2);
String s3="Hello";
byte[] bytes3 = s3.getBytes();
fos.write(bytes3);

运行结果:

Helloabcd
Hello

接下来我们查看一下 JDK 源码中的 FileOutputStream 类,这个问题就不难理解了。

  public FileOutputStream(String name, boolean append)throws FileNotFoundException{this(name != null ? new File(name) : null, append);}public FileOutputStream(File file, boolean append)throws FileNotFoundException{...}

在不同的操作系统中,换行符的定义是不同的,Windows系统中,换行符是\r\n,表示回车换行,回车是指把光标移动到一行的开始,换行指光标移动到下一行,Java语言对其进行了优化,只需要使用\r或者\n来实现换行,实际上Java在底层会进行补全操作。MacOS 中换行使用 \r,而 Linux 中使用 \n 表示换行。

4. 字节输入流

我们可以使用 FileInputStream 类把本地文件中的数据读取到程序中,该类称为字节输入流类,和字节输出流类似,使用字节输入流读取本地文件可以分为三个步骤:

  1. 创建流对象
  2. 读取数据
  3. 释放资源

示例,假设文件中以后数据abcd:

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;public class Test {public static void main(String[] args) throws IOException {/*使用 FileInputStream 把本地文件中的数据读取到程序中FileInputStream 构造方法的参数既可以使用String类型也可以使用File类对象程序需要进行异常处理,直接抛出异常即可*///1. 创建流对象FileInputStream fis=new FileInputStream("test.txt");//2. 读取数据int b1 = fis.read();System.out.println((char)b1);int b2 = fis.read();System.out.println((char)b2);int b3 = fis.read();System.out.println((char)b3);int b4 = fis.read();System.out.println((char)b4);int b5 = fis.read();System.out.println(b5);//3. 释放资源fis.close();}
}

同样的,在程序创建流对象时,程序和文件之间就会建立一个通道,此时我们就可以通过调用 read() 方法读取本地文件中的数据,读取完数据以后,需要释放资源,相当于打断了这个通道,否则文件将一直被程序占用。

细节:

在创建流对象时,传入的参数既可以是 String 类型,也可以是 File 类的对象。不同的是,如果目标文件不存在则会报错,如果文件存在,则会读取数据。read() 方法的返回值是文件中字符在字符集中对应的十进制值,如果读取到文件末尾,则会返回 -1 。

image-20230115202933750

如果文件中存放的数据恰好是 -1 ,其实它是分负号和 1 两次读取的。如果读取的数据很多时,这样的方法显然是不可取的,此时就要使用循环来读取文件中的数据。

示例:

//2. 读取数据
int b;
while((b=fis.read())!=-1){System.out.println((char)b);
}

这里定义一个临时变量是十分重要的,而不是多此一举。否则将无法实现循环打印读取到的数据的效果。

使用 FileInputStream 读取数据时,一次只能读取一个字节的数据,显然这样的方式效率是非常低的,那么怎样解决这个问题呢?此时我们可以使用 read() 方法的重载方法一次读取多个数据,往 read() 方法中传入一个字节类型的数组,read() 方法一次读取多少个数据是由数组的大小决定。

示例,假设文件中存放数据 abc:

import java.io.FileInputStream;
import java.io.IOException;public class Test {public static void main(String[] args) throws IOException {//使用字节数组来一次读取多个字节数据FileInputStream fis=new FileInputStream("test.txt");byte[] bytes=new byte[2];int len1 = fis.read(bytes);System.out.println(new String(bytes,0,len1));int len2= fis.read(bytes);System.out.println(new String(bytes,0,len2));}
}

上面的例子中,read() 方法每次读取两个字节的数据,并且返回读取到的数据的个数,读取到文件末尾返回 -1 。为了防止 read() 方法读取到最后时获取残留数据,如下图。可以往 String 类构造方法中加入两个参数,表示从某个索引开始,读取 len 个字符。

image-20230115212819664

5. 文件拷贝

前面已经学习了数据的读取和写入,那么我们就可以实现文件拷贝了。之前说过,字节流可以操作所有类型的文件,那么,我们今天使用图片文件作为示例来演示文件拷贝。

示例:

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;public class Test {public static void main(String[] args) throws IOException {//1. 创建流对象FileInputStream fis=new FileInputStream("C:\\Users\\24091\\Desktop\\java.png");FileOutputStream fos=new FileOutputStream("copy.png");//2. 拷贝文件int b;while((b=fis.read())!=-1){fos.write(b);}//3. 释放资源fos.close();fis.close();}
}

在创建多个流对象的程序中释放资源时,先创建的后释放。此时,桌面的 java.png 文件已经被拷贝到了项目中的 copy.png 文件中。

前面说到,FileInputStream 每次读取一个字节的效率是非常低的,那么我们可以改写上面的程序,每次读取多个字节来实现文件的拷贝。

修改示例:

 		//1. 创建流对象FileInputStream fis=new FileInputStream("C:\\Users\\24091\\Desktop\\java.png");FileOutputStream fos=new FileOutputStream("copy.png");//2. 拷贝文件int len;byte[] bytes = new byte[5 * 1024];while((len=fis.read())!=-1){fos.write(bytes,0,len);}//3. 释放资源fos.close();fis.close();

6. IO 流中的异常处理

在 JDK 1.7 中,Java 提供了一个 autoCloseable 接口,用于在特定情况下进行异常处理。在 Java 7 中,可以把定义流对象的代码写在 try 后面的括号中,表示当 try…catch 语句执行完成后,会自动释放资源,前提是写在括号中的类必须是实现了 autoCloseable 接口的类。

但是这样在括号中定义流对象的代码是难以阅读的,所以在Java 9 中,我们可以把定义流对象的代码放在 try 语句前面,括号中只需要写流的引用变量名,执行逻辑与前面相同。

例如,拷贝文件时使用 try…catch 捕获异常:

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;public class Test {public static void main(String[] args) throws FileNotFoundException {//1. 创建流对象FileInputStream fis = new FileInputStream("C:\\Users\\24091\\Desktop\\java.png");FileOutputStream fos = new FileOutputStream("copy.png");try (fis; fos) {//2. 拷贝文件int len;byte[] bytes = new byte[5 * 1024];while ((len = fis.read()) != -1) {fos.write(bytes, 0, len);}} catch (IOException e) {e.printStackTrace();}}
}

在学习 Java 编程基础时,对于 IO 流中出现的异常我们抛出即可,后面在学习 Spring框架时,再做探讨。

7. 总结

在 File 类中,我们可以使用类的对象来操作文件和目录,包括增删查等。不同的是,IO 流用于文件的读写操作,这些操作是 File 无法实现的。在创建流对象时,相当于在文件和程序之间建立了一个流的通道,方便对数据进行操作。

流是个抽象的概念,是对输入输出设备的抽象,无论采取什么样的形式输出输入数据,只是针对流做处理,无关与输入输出的设备,可以说这个思想是很优秀的。


Java编程基础教程系列

【Java集合】Collection 体系集合

【Java基础】泛型详解

相关内容

热门资讯

【NI Multisim 14...   目录 序言 一、工具栏 🍊1.“标准”工具栏 🍊 2.视图工具...
银河麒麟V10SP1高级服务器... 银河麒麟高级服务器操作系统简介: 银河麒麟高级服务器操作系统V10是针对企业级关键业务...
不能访问光猫的的管理页面 光猫是现代家庭宽带网络的重要组成部分,它可以提供高速稳定的网络连接。但是,有时候我们会遇到不能访问光...
AWSECS:访问外部网络时出... 如果您在AWS ECS中部署了应用程序,并且该应用程序需要访问外部网络,但是无法正常访问,可能是因为...
Android|无法访问或保存... 这个问题可能是由于权限设置不正确导致的。您需要在应用程序清单文件中添加以下代码来请求适当的权限:此外...
北信源内网安全管理卸载 北信源内网安全管理是一款网络安全管理软件,主要用于保护内网安全。在日常使用过程中,卸载该软件是一种常...
AWSElasticBeans... 在Dockerfile中手动配置nginx反向代理。例如,在Dockerfile中添加以下代码:FR...
AsusVivobook无法开... 首先,我们可以尝试重置BIOS(Basic Input/Output System)来解决这个问题。...
ASM贪吃蛇游戏-解决错误的问... 要解决ASM贪吃蛇游戏中的错误问题,你可以按照以下步骤进行:首先,确定错误的具体表现和问题所在。在贪...
月入8000+的steam搬砖... 大家好,我是阿阳 今天要给大家介绍的是 steam 游戏搬砖项目,目前...