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

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

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

2.2.2.关闭流

  • I/O流在进行数据读写操作时会出现异常,为了保证I/O流的close()方法一定执行来释放占用的系统资源,通常会将关闭流的操作写在finally代码块
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.文件的拷贝

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.字节流的缓冲区

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

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

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

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();
  • 注意:使用字符流向文件追加写入数据,需要调用重载的构造方法
1
FileWriter fw = new FileWriter("test.txt", true);

3.3.字符流的缓冲区

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.字符缓冲流

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的子类,它可以将一个字节输出流转换成字符输出流,方便直接写入字符
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数组
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(): 遍历某个指定目录下的所有文件的名称
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的所有子目录或文件进行迭代
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() 递归遍历目录文件
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.删除文件及目录

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.示例

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内部支持,易实现,只需实现该接口即可,不需要其他代码支持接口中提供了两个空方法,实现该接口必须为两个方法提供实现
性能较差性能较好
容易实现,实际开发使用较多编程复杂度大
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
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.示例

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地址
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());
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)将文本行写入文件,并传入指定的写入模式
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);
1
2
3
4
5
6
7
/**
* 打印结果
*
* 目录创建成功!
* 文件的大小为:26
* 文件中的内容为:[这是一个测试文件]
*/