Java的IO流

1.I/O 流概述

image-20211030164314104

  • InputStreamOutPutStream是字节流,而ReaderWriter是字符流
  • InputStreamReader是输入流,而OutPutStreamWriter是输出流
  • 图中的 4 个顶级类都是抽象类,并且是所有流类型的父类

2.字节流

2.1.字节流概述

  • InputStream

image-20211030164540324

方法声明功能描述
int read()从输入流读取一个 8 位的字节,把它转换为 0~255 之间的整数,并返回这一整数。当没有可用字节时,将返回-1
int read(byte[] b)从输入流读取若干字节,把它们保存到参数 b 指定的字节数组中,返回的整数表示读取字节的数目
int read(byte[] b,int off,int len)从输入流读取若干字节,把它们保存到参数 b 指定的字节数组中,off 指定字节数组开始保存数据的起始下标,len 表示读取的字节数目
void close()关闭此输入流并释放与该流关联的所有系统资源
  • OutputStream

image-20211030164554733

方法声明功能描述
void write(int b)向输出流写入一个字节
void write(byte[] b)把参数 b 指定的字节数组的所有字节写到输出流
void write(byte[] b,int off,int len)将指定 byte 数组中从偏移量 off 开始的 len 个字节写入输出流
void flush()刷新此输出流并强制写出所有缓冲的输出字节

2.2.字节流读写文件

2.2.1.读取文件FileInputStream

java
1
2
3
4
5
6
FileInputStream in = new FileInputStream("test.txt");
int b = 0;
while((b = in.read()) != -1) {
System.out.println(b);
}
in.close();

2.2.2.写入文件FileOutputStream

java
1
2
3
4
FileOutputStream out = new FileOutputStream("test.txt");
String str = "hello";
out.write(str.getBytes());
out.close();
  • 注意:使用字节流向文件追加写入数据,需要调用重载的构造方法
java
1
FileOutputStream out = new FileOutputStream("test.txt", true);

2.2.2.关闭流

  • I/O 流在进行数据读写操作时会出现异常,为了保证 I/O 流的close()方法一定执行来释放占用的系统资源,通常会将关闭流的操作写在finally代码块
java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
finally {
try {
if(in != null) {
in.close();
}
} catch(Exception e) {
e.printStackTrace();
}
try {
if(out != null) {
out.close();
}
} catch(Exception e) {
e.printStackTrace();
}
}

2.3.文件的拷贝

java
1
2
3
4
5
6
7
8
FileInputStream in = new FileInputStream("source/origin.txt");
FileOutputStream out = new FileOutputStream("source/target.txt");
int len = 0;
while((len = in.read()) != -1) {
out.write(len);
}
in.close();
out.close();

2.4.字节流的缓冲区

java
1
2
3
4
5
6
7
8
9
FileInputStream in = new FileInputStream("source/origin.txt");
FileOutputStream out = new FileOutputStream("source/target.txt");
int len = 0;
byte[] buff = new byte[1024];
while((len = in.read(buff)) != -1) {
out.write(buff, 0, len);
}
in.close();
out.close();

2.5.字节缓冲流

  • BufferedInputStream

  • BufferedOutputStream

java
1
2
3
4
5
6
7
8
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("source/origin.tex"));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("source/target.tex"));
int len = 0;
while((len = bis.read()) != -1) {
bos.write(len);
}
bis.close();
bos.close();

3.字符流

3.1.字符流概述

image-20211030172354232

image-20211030172407959

3.2.字符流读写文件

3.2.1.读取文件FileReader

java
1
2
3
4
5
6
FileReader fr = new FileReader("test.txt");
int len = 0;
while((len = rd.read()) != -1) {
System,out.println(len);
}
fr.close();

3.2.2.写入文件FileWriter

java
1
2
3
4
5
6
FileWriter fw = new FileWriter("test.txt");
fw.write("轻轻的我来了,\r\n");
fw.write("正如我轻轻的来;\r\n");
fw.write("我轻轻的招手,\r\n");
fw.write("作别西天的云彩。\r\n");
fw.close();
  • 注意:使用字符流向文件追加写入数据,需要调用重载的构造方法
java
1
FileWriter fw = new FileWriter("test.txt", true);

3.3.字符流的缓冲区

java
1
2
3
4
5
6
7
8
9
FileReader fr = new FileReader("reader.txt");
FileWriter fw = new FileWriter("writer.txt");
int len = 0;
char[] buff = new char[1024];
while((len = fr.read(buff)) != -1) {
fw.write(buff, 0, len);
}
fr.close();
fw.close();

3.4.字符缓冲流

java
1
2
3
4
5
6
7
8
9
BufferedReader br = new BufferedReader(new FileReader("reader.txt"));
BufferedWriter bw = new BufferedWriter(new FileWriter("writer.txt"));
String str = null;
while((str = br.read()) != null) {
bw.write(str);
bw,newLine();
}
br.close();
bw.close();

3.5.转换流

image-20211030175241194

  • InputStreamReaderReader的子类,它可以将一个字节输入流转换成字符输入流,方便直接读取字符
  • OutputStreamWriterWriter的子类,它可以将一个字节输出流转换成字符输出流,方便直接写入字符
java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
FileInputStream in = new FileInputStream("reader.txt");
InputStreamReader isr = new InputStreamReader(in);
BufferedReader br = new BufferedReader(isr);

FileOutputStream out = new FileOutputSteam("writer.txt");
OutputStreamWriter osw = new OutputStreamWriter(out);
BufferedWriter bw = new BufferedWriter(osw);

String str = null;
while((str = br.read()) != -1) {
bw.write(str);
bw.newLine();
}
br.close();
bw.close();

4.File 类

4.1.构造方法

方法声明功能描述
File(String pathname)通过指定的一个字符串类型的文件路径来创建一个新的 File 对象
File(String parent,String child)根据指定的一个字符串类型的父路径和一个字符串类型的子路径(包括文件名称)创建一个 File 对象
File(File parent,String child)根据指定的 File 类的父路径和字符串类型的子路径(包括文件名称)创建一个 File 对象

4.2.常用方法

方法声明功能描述
boolean exists()判断 File 对象对应的文件或目录是否存在,若存在则返回 ture,否则返回 false
boolean delete()删除 File 对象对应的文件或目录,若成功删除则返回 true,否则返回 false
boolean createNewFile()当 File 对象对应的文件不存在时,该方法将新建一个此 File 对象所指定的新文件,若创建成功则返回 true,否则返回 false
String getName()返回 File 对象表示的文件或文件夹的名称
String getPath()返回 File 对象对应的路径
String getAbsolutePath()返回 File 对象对应的绝对路径(在 Unix/Linux 等系统上,如果路径是以正斜线/开始,则这个路径是绝对路径;在 Windows 等系统上,如果路径是从盘符开始,则这个路径是绝对路径)
String getParent()返回 File 对象对应目录的父目录(即返回的目录不包含最后一级子目录)
boolean canRead()判断 File 对象对应的文件或目录是否可读,若可读则返回 true,反之返回 false
boolean canWrite()判断 File 对象对应的文件或目录是否可写,若可写则返回 true,反之返回 false
boolean isFile()判断 File 对象对应的是否是文件(不是目录),若是文件则返回 true,反之返回 false
boolean isFile()判断 File 对象对应的是否是文件(不是目录),若是文件则返回 true,反之返回 false
boolean isDirectory()判断 File 对象对应的是否是目录(不是文件),若是目录则返回 true,反之返回 false
boolean isAbsolute()判断 File 对象对应的文件或目录是否是绝对路径
long lastModified()返回 1970 年 1 月 1 日 0 时 0 分 0 秒到文件最后修改时间的毫秒值
long length()返回文件内容的长度
String[] list()列出指定目录的全部内容,只是列出名称
String[] list(FilenameFilter filter)接收一个 FilenameFilter 参数,通过该参数可以只列出符合条件的文件
File[] listFiles()返回一个包含了 File 对象所有子文件和子目录的 File 数组
java
1
2
3
4
5
6
7
8
9
10
11
12
13
File file = new File("test.txt");
System.out.println("文件名称:" + fiel.getName());
System.out.println("文件的相对路径:" + file.getPath());
System.out.println("文件的绝对位置:" + file.AbsolutePath());
System.out.println("文件的父路径:" + file.Parent());
System.out.println(file.canRead() ? "文件可读" : "文件不可读");
System.out.println(file.canWrite() ? "文件可写" : "文件不可读");
System.out.println(file.isFile() ? "是一个文件" : "不是一个文件");
System.out.println(file.isDirectory() ? "是一个目录" : "不是一个目录");
System.out.println(file.isAbsolute() ? "是绝对路径" : "不是一个绝对路径");
System.out.println("最后修改时间为:" + file.lastModified());
System.out.peintln("文件大小为:" + file.length() + "bytes");
System.out.println("是否成功删除文件:" + file.delete());

4.3.遍历目录下的文件

  • String[] list(): 遍历某个指定目录下的所有文件的名称
java
1
2
3
4
5
File file = new File("F:\\Java\\workspace\\chapter07");
if(file.isDirectory()) {
String[] fileName = file.list();
Arrays.stream(fileName).forEach(f -> System.out.println(f));
}
  • String[] list(FilenameFilter filter): 该方法接收一个FilenameFilter接口类型的参数,其中定义了一个抽象方法accept(File dir,String name)用于依次对指定File的所有子目录或文件进行迭代
java
1
2
3
4
5
File file = new File("F:\\Java\\workspace\\chapter07");
if(file.isDirectory()) {
String[] fileName = file.list((dir, name) -> name.endsWith(".txt"));
Arrays.forEach(f -> System.out.println(f));
}
  • File[] listFiles() 递归遍历目录文件
java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Main {
public static void main(String[] args) {
File file = new File("F:\\Java\\workspace\\chapter07");
fileDir(file);
}

public static void fileDir(File files) {
File[] listFile = files.listFiles();
for(File file : listFile) {
if(file.isDirectory()) {
fileDir(file);
}
System.out.println(files);
}
}
}

4.4.删除文件及目录

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Main {
public static void main(String[] args) {
File file = new File("F:\\Java\\workspace\\chapter07");
deleteDir(file);
}

public static void deleteDir(File files) {
File[] listFiles = files.listFiles();
for(File file : listFiles) {
if(file.isDirectory()) {
delete(file);
}
file.delete();
}
}
}

5.RandomAccessFile

5.1.构造方法

  • 不属于流类,但具有读写文件数据的功能,可以随机从文件的任何位置开始并以指定的操作权限
方法声明功能描述
RandomAccessFile(File file,String mode)使用参数 file 指定被访问的文件,并使用 mode 来指定访问模式
RandomAccessFile(String name,String mode)使用参数 name 指定被访问文件的路径,并使用 mode 来指定访问模式
  • 参数mode取值
    • r:以只读的方式打开文件。如果执行写操作,会报IOException异常
    • rw:以“读写”的方式打开文件。如果文件不存在,会自动创建该文件
    • rws:以“读写”方式打开文件。与“rw”相比,它要求对文件的内容或元数据的每个更新都同步写入到底层的存储设备
    • rwd:以“读写”方式打开文件。与“rw”相比,它要求对文件的内容的每个更新都同步写入到底层的存储设备
  • 原理RandomAccessFile对象包含了一个记录指针来标识当前读写处的位置
    1. 当新建RandomAccessFile对象时,该对象的文件记录指针会在文件开始处(即标识为 0 的位置)
    2. 当读写了 n 个字节后,文件记录指针会向后移动 n 个字节
    3. 除了按顺序读写外,RandomAccessFile对象还可以自由的移动记录指针,既可以向前移动,也可以向后移动

5.2.常用方法

方法声明功能描述
long getFilePointer()返回当前读写指针所处的位置
void seek(long pos)设定读写指针的位置,与文件开头相隔 pos 个字节数
int skipBytes(int n)使读写指针从当前位置开始,跳过 n 个字节
void write(byte[] b)将指定的字节数组写入到这个文件,并从当前文件指针开始
void setLength(long newLength)设置此文件的长度
final String readLine()从指定文件当前指针读取下一行内容

5.3.示例

java
1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* @methods 模拟软件使用次数
*/
RandomAccessFile raf = new RandomAccessFile("time.txt", "rw");
int times = Integer.parseInt(raf.readLine()) - 1;
if(times > 0) {
System.out.println("您还可以试用" + times + "次!");
raf.seek(0); // 将记录指针重新指向文件开头
raf.write((times + "").getBytes()); // 将剩余次数再写入文件
} else {
System.out.println("试用次数已经用完!");
}
raf.close();

6.对象序列化

  • 对象的序列化(Serializable)将一个 Java 对象转换成一个I/O流中字节序列的过程
  • 目的: 为了将对象保存到磁盘中,或允许在网络中直接传输对象
  • 将 I/O 流中的字节序列恢复为 Java 对象的过程被称之为反序列化(Deserialize)
实现 Serializable 接口实现 Externalizable 接口
系统自动存储必要信息由程序员决定存储的信息
Java 内部支持,易实现,只需实现该接口即可,不需要其他代码支持接口中提供了两个空方法,实现该接口必须为两个方法提供实现
性能较差性能较好
容易实现,实际开发使用较多编程复杂度大
java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Person implements Serializable {
// 为该类指定一个serialVersionUID变量值
/**
* 标识Java类的序列化版本。如果不显示的定义
* serialVersionUID变量值,
* 那么将由JVM根据类的相关信息计算出一个
* serialVersionUID变量值。
*/
private static final long serialVersionUID = 1L;
//声明变量
private int id;
private String name;
private int age;
// 此处省略各属性的getter和setter方法
...
}

7.NIO

7.1.NIO 概述

  • JDK 1.4开始,Java 提供了一系列改进的用于处理输入/输出的新功能,这些新功能被称之为NIO(New I/O)
  • 三大核心
    1. **Buffer**本质是一个数组缓冲区,读入或写出到Channel中的所有对象都会先放在 Buffer 中
    2. **Channel**是对传统的输入/输出的模拟,在NIO中,所有的数据都需要通过通道流的形式传输
    3. **Selecter**用于监听多个通道的事件(例如:连接打开、数据到达等),主要用于多线程处理

7.2.Buffer 缓冲器

7.2.1.作用

  • Java NIO中的Buffer用于和NIO中的Channel进行交互,交互时数据会从Channel读取到Buffer中,或从Buffer写入到Channel

image-20211101210347778

7.2.2.使用

  • 通过子类中的static XxxBuffer allocate(int capacity)方法来实现(其中Xxx表示不同的数据类型,而capacity表示容量)
  • Buffer类似于一个数组,它可以保存多个类型相同的数据
  • Buffer是一个抽象类,其子类有ByteBufferCharBufferDoubleBufferFloatBufferIntBufferLongBufferShortBuffer
  • Buffer子类中最常用的是ByteBufferCharBuffer
java
1
charBuffer buffer = CharBuffer.allocate(6);

7.2.3.三个重要概念

  • capacity(容量): 缓冲器的容量表示该 Buffer 的最大数据容量,即最多可以存储多少数据。缓冲器的容量值不能为负数,也不能够改变
  • limit(界限): 表示Buffer容器中不可被读取的区域的第一个索引,即位于Buffer容器中索引为0limit之间的区域都可以进行读取操作。缓冲器的limit值从不为负,也从不大于其容量
  • position(位置): 用于指定下一个可以被读写的缓冲器位置索引。新创建的Buffer对象,position的默认值为0,每进行一次读取或写入操作,position的值都会自动向后移动一步

7.2.4.常用方法

方法声明功能描述
int capacity()获取缓冲区的大小
Buffer clear()清除缓冲区,将 position 设置为 0,limit 设置为 capacity
Buffer flip()反转缓冲区,先将 limit 设置为当前 position 位置,然后再将 position 设置为 0
boolean hasRemaining()判断当前位置(position)和界限(limit)之间是否还有元素
int limit获取 Buffer 的 limit 位置
Buffer limit(int newLimit)设置 limit 的值,并返回一个新的 limit 缓冲区对象
Buffer mark()设置 Buffer 的标记(mark),只能在 0 与 position 之间做标记
int position()获取 Buffer 中 position 的值
Buffer position(int newPosition)设置 Buffer 的 position,并返回位置被修改之后的 Buffer 对象
int remaining()获取当前位置和界限之间的元素个数
Buffer reset()将此缓冲区的位置重置为先前标记的位置
Buffer rewind()倒带缓冲区,将 position 设置为 0,并取消设置的标记

7.3.Channel 通道

7.3.1.概论

  • Channel可以异步的执行I/O读写操作
  • Channel的读写操作是双向的,既可以从Channel中读取数据,又可以写数据到Channel,而流的读写操作通常都是单向的
  • Channel可以直接将指定文件的部分或者全部直接映射成Buffer
  • Channel只能与Buffer进行交互,程序不能直接读写 Channel 中的数据

7.3.2.实现类

  • DatagramChannel用于支持UDP网络通信
  • FileChannel用于从文件中读写数据
  • Pipe.SinkChannelPipe.SourceChannel用于支持线程之间的通信
  • ServerSocketChannelSocketChannel用于支持TCP网络通信

7.3.3.使用

  • Channel对象并不是通过构造方法来创建的,而是通过传统I/OgetChannel()方法来获取对应的Channel
  • 不同的流所获取的Channel是不同的,例如FileInputStreamFileOutputStream获取的是FileChannel,同时还可以使用RandomAccessFile获取该对象
  • PipedInputStreamPipedOutputStream所获得的是Pipe.SinkChannelPipe.SourceChanne

7.3.4.FileChannel 常用方法

方法声明功能描述
MappedByteBuffer map(MapMode mode, long position, long size)将该通道文件的区域直接映射到内存中。其中第 1 个参数用于执行映射时的模式,包含只读、读写等模式;第 2 个参数表示映射区域开始的文件中的位置;第 3 个参数表示要映射区域的大小
long position()返回该通道的文件位置
Int read(ByteBuffer dst)从这个通道读取一个字节序列到给定的缓冲区
Int read(ByteBuffer dst, long position)从给定的文件位置开始,从这个通道读取一个字节序列到给定的缓冲区
long read(ByteBuffer[] dsts, int offset, int length)从这个通道读取一个字节序列到给定缓冲区的子序列
long size()返回该通道文件的当前大小
long transferTo(long position, long count, WritableByteChannel target)读取该通道文件中给定位置的字节数,并将它们写入目标通道
Int write(ByteBuffer src)从给定的缓冲区写入这个通道的字节序列
long write(ByteBuffer[] srcs, int offset, int length)从给定缓冲区的子序列中写入该通道的字节序列
Int write(ByteBuffer src, long position)从给定的缓冲区开始,从给定的文件位置开始向该通道写入一个字节序列

7.3.5.示例

java
1
2
3
4
5
6
7
8
9
10
11
12
13
RandomAccessFile infile = new RandomAccessFile("source/src.jpg", "rw");
FileChannel inChannel = infile.getChannel();
RandomAccessFile outfile =new RandomAccessFile("target/dest.jpg", "rw");
FileChannel outChannel = outfile.getChannel();
// 使用transferTo()方法进行整体复制
long transferTo = inChannel.transferTo(0, inChannel.size(), outChannel);
if(transferTo > 0) {
System.out.println("复制成功!");
}
infile.close();
inChannel.close();
outfile.close();
outChannel.close();

8.NIO.2

8.1.概述

  • NIO.2最大的改进是提供了全面的文件输入/输出以及文件系统的访问与支持
  • 并且新增了 java.nio.file包及其子包
  • 而且还提供基于异步 Channel 的输入/输出。

8.2.Path 接口

方法声明功能描述
boolean endsWith(String other)判断当前路径是否以指定的字符串结尾
Path getName(int index)返回此路径的名称元素作为路径对象
int getNameCount()返回路径中名称元素的数量
Path getParent()返回父路径,如果此路径没有父路径,则返回 null
Path getRoot()返回该路径的根组件作为路径对象,如果此路径没有根组件,则返回 null
Path toAbsolutePath()返回表示此路径的绝对路径的路径对象
URI toUri()返回表示此路径的 URI 地址
java
1
2
3
4
5
6
7
8
9
10
Path path = Paths.get("D:\\test\\文件夹\\test.txt");
System.out.println("path的根路径:" + path.getRoot());
System.out.println("path的父路径:" + path.getParent());
System.out.println("path中的路径名称数:" + path.getNameCount());
for(int i = 0; i < path.getNameCount(); i++) {
Path name = path.getName(i);
System.out.println("索引为" + i + " 的路径的名称为: " + name);
}
System.out.println("path的URI路径为:" + path.toUri());
System.out.println("path的绝对路径:" + path.toAbsolutePath());
java
1
2
3
4
5
6
7
8
9
10
11
12
/**
* 打印结果
*
* path的根路径:D:\
* path的父路径:D:\test\文件夹
* path中的路径名称数:3
* 索引为0 的路径的名称为:test
* 索引为1 的路径的名称为:文件夹
* 索引为2 的路径的名称为:test.txt
* path的URI路径为:file:///D:/test/文件夹/test.txt
* path的绝对路径:D:/test/文件夹/test.txt
*/

8.3.Files 类

方法声明功能描述
static Path createDirectories(Path dir, FileAttribute<?>… attrs)创建多级文件目录
static Path createFile(Path path, FileAttribute<?>… attrs)创建一个新的空文件,如果文件已经存在,则创建失败
static Path copy(Path source, Path target,CopyOption… options)该方法将一个文件复制到目标文件,并使用选项参数指定如何执行复制
static ListreadAllLines(Path path)从文件中读取所有行
static long size(Path path)返回文件的大小(以字节为单位)
static Streamlist(Path dir)将指定路径转换为 Stream 流对象
static Path write(Path path, Iterable<? extends CharSequence> lines, OpenOption… options)将文本行写入文件,并传入指定的写入模式
java
1
2
3
4
5
6
7
8
9
10
11
Path directoryPath = Paths.get("D:/test/sample");
Files.createDirectories(directoryPath);
System.out.println("目录创建成功!");
Path filePath = Paths.get("D:/test/sample/test.txt");
Files.createFile(filePath);
List<String> list = new ArrayList<String>();
list.add("这是一个测试文件");
Files.write(filePath, list, StandardOpenOption.APPEND);
List<String> lines = Files.readAllLines(filePath);
System.out.println("文件的大小为:" + Files.size(filePath));
System.out.println("文件中的内容为:" + lines);
java
1
2
3
4
5
6
7
/**
* 打印结果
*
* 目录创建成功!
* 文件的大小为:26
* 文件中的内容为:[这是一个测试文件]
*/