Java IO流
文章目录
一、IO简介
(1)什么是IO
程序运行需要数据,数据的获取往往需要跟外部系统(文件、数据库、其他程序、网络、IO设备等)进行通信。外部系统比较复杂多变,那么我们有必要通过某种手段进行抽象、屏蔽外部的差异,从而实现更加便捷的编程。
- input:可以让程序从外部系统获得数据(读取外部数据)
- output:程序输出数据给外部系统从而可以操作外部系统
- java.io 包提供了相关API,实现了对所有外部系统的输入输出操作
(2)数据源
数据源DataSource
,提供数据的原始没接。常见的数据源:数据库、文件、其他程序、内存、网络连接、IO设备
数据源分为:源设备和目标设备
- 源设备:为程序提供数据,一般对应输入流
- 目标设备:程序数据的目的地,一般对应输出流
(3)流的概念
- 流是一个抽象、动态的概念,是一连串连续动态的数据集合
- 对于输入流而言,数据源就像水箱,流就像水管里流动的水流,程序就是我们最终的用户,我们通过流将数据源中的数据输送到程序中
- 对于输出流而言,目标数据源就是目的地,我们通过流将程序中的数据输送到目的数据源中
- 输入/输出流的划分是相对于程序而言的,并不是相对于数据源
(4)Java中四大IO抽象类
InputStream/OutputStream
和 Reader/Writer
类是所有 IO流类的抽象父类
1. InputStream
- 此抽象类是表示字节输入流的所有类的父类。InputSteam 是一个抽象类,它不可以实例化。数据的读取需要由它的子类来实现。根据节点的不同,它派生了不同的节点流子类。继承自 InputSteam 的流都是用于向程序中输入数据,且数据的单位为字节(8 bit)。
- 常用方法:
int read()
:读取一个字节的数据,并将字节的值作为int 类型返回(0-255 之间的一个值)。
如果未读出字节则返回-1 (返回值为-1 表示读取结束)。void close()
:关闭输入流对象,释放相关系统资源。
2. OutputStream
-
此抽象类是表示字节输出流的所有类的父类。输出流接收输出字节并将这些字节发送到某个目的地
-
常用方法:
void write(int n)
:向目的地中写入一个字节。void close()
:关闭输出流对象,释放相关系统资源。
3.Reader
-
Reader 用于读取的字符流抽象类,数据单位为字符。
-
int read()
: 读取一个字符的数据,并将字符的值作为 int 类型返回(0-65535 之间的一个 值,即 Unicode 值)。如果未读出字符则返回-1(返回值为-1 表示读取结束)。
-
void close()
: 关闭流对象,释放相关系统资源。
4.Writer
- Writer 用于输出的字符流抽象类,数据单位为字符。
void write(int n)
: 向输出流中写入一个字符。void close()
: 关闭输出流对象,释放相关系统资源。
(5)Java中流的概念细分
1. 按流的方向分类
- 输入流:数据流从数据源到程序(以 InputStream、Reader 结尾的流)
- 输出流:数据流从程序到目的地(以 OutputStream、Writer 结尾的流)
2. 按处理的数据单元分类
- 字节流:以字节为单位获取数据,命名上以 Stream 结尾的流一般是字节流, 如 FileInputStream、FileOutputStream。
- 字符流:以字符为单位获取数据,命名上以 Reader/Writer 结尾的流一般是字符流,如 FileReader、FileWriter。
3. 按处理对象不同分类
- 节点流:可以直接从数据源或目的地读写数据,如 FileInputStream、FileReader、DataInputStream 等。
- 处理流:不直接连接到数据源或目的地,是”处理流的流”。通过对其他流的处理提高程序的性能,如 BufferedInputStream、BufferedReader 等。处理流也叫包装流。
**总结:**节点流处于 IO 操作的第一线,所有操作必须通过它们进行;处理流可以对节点流进行包装,提高性能或提高程序的灵活性。
(6)Java IO流体系
-
InputStream/OutputStream
字节流的抽象类。 -
Reader/Writer
字符流的抽象类。 -
FileInputStream/FileOutputStream
节点流:以字节为单位直接操作“文件”。 -
ByteArrayInputStream/ByteArrayOutputStream
节点流:以字节为单位直接操作“字节数组对象”。 -
ObjectInputStream/ObjectOutputStream
处理流:以字节为单位直接操作“对象”。 -
DataInputStream/DataOutputStream
处理流:以字节为单位直接操作“基本数据类型与字符串类型”。 -
FileReader/FileWriter
节点流:以字符为单位直接操作“文本文件”(注意:只能读写文本文件)。 -
BufferedReader/BufferedWriter
处理流:将 Reader/Writer 对象进行包装,增加缓存功能,提高读写效率。 -
BufferedInputStream/BufferedOutputStream
处理流:将 InputStream/OutputStream 对象进行包装,增加缓存功能,提高读写效率。 -
InputStreamReader/OutputStreamWriter
处理流:将字节流对象转化成字符流对象。 -
PrintStream
处理流:将 OutputStream 进行包装,可以方便地输出字符,更加灵活。
二、IO流入门案例
第一个简单的 IO 流程序
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
@SuppressWarnings({"all"})
public class Main {
public static void main(String[] args) {
FileInputStream fileInputStream = null;
try {
// 创建文件字节输入流对象
fileInputStream = new FileInputStream("D:\\a.txt");
// 处理获得的流对象
int temp = 0;
StringBuilder stringBuilder = new StringBuilder();
while ((temp = fileInputStream.read()) != -1) {
stringBuilder.append((char) temp);
}
System.out.println(stringBuilder.toString());
} catch (Exception e) {
e.printStackTrace();
} finally {
// 关闭流对象,释放资源
if (fileInputStream != null) {
try {
fileInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
三、File类的使用
(1)File类的作用
File 类是 Java 提供的针对磁盘中的文件或目录转换对象的包装类。一个 File 对象而可以代表一个文件或目录,File 对象可以实现获取文件和目录属性等功能,可以实现对文件和目录的创建,删除等功能。
(2)针对文件操作的方法
-
createNewFile()
创建新文件。 -
delete()
直接从磁盘上删除 -
exists()
查询磁盘中的文件是否存在 -
getAbsolutePath()
获取绝对路径 -
getPath()
获取相对路径 -
getName()
获取文件名相当于调用了一个 toString 方法 -
isFile()
判断是否是文件 -
length()
查看文件中的字节数 -
isHidden()
测试文件是否被这个抽象路径名是一个隐藏文件
import java.io.File;
import java.io.IOException;
@SuppressWarnings({"all"})
public class Main {
public static void main(String[] args) throws IOException {
// 创建文件对象
File file = new File("D:\\aa.txt");
System.out.println(file.createNewFile()); // true
System.out.println(file.exists()); // true
System.out.println(file.getName()); // aa.txt
System.out.println(file.getPath()); // D:\aa.txt
System.out.println(file.getAbsolutePath()); // D:\aa.txt
System.out.println(file.isHidden()); // false
System.out.println(file.delete()); // true
}
}
(3)针对目录操作的方法
-
exists()
查询目录是否存在 -
isDirectory()
判断当前路径是否为目录 -
mkdir()
创建目录 -
getParentFile()
获取当前目录的父级目录。 -
list()
返回一个字符串数组,包含目录中的文件和目录的路径名。 -
listFiles
返回一个 File 数组,表示用此抽象路径名表示的目录中的文件。
import java.io.File;
import java.io.IOException;
@SuppressWarnings({"all"})
public class Main {
public static void main(String[] args) throws IOException {
// 创建文件对象
File file1 = new File("D:\\a");
file1.mkdir(); // 创建单级目录
File file2 = new File("D:\\b\\c");
file2.mkdirs(); // 创建多级目录
System.out.println(file1.exists());
System.out.println(file1.isDirectory());
System.out.println(file2.getParent()); // D:\b
System.out.println(file2.getParentFile().getName()); // b
File f = new File("D:\\");
// 获取D盘下直接的所有目录名
String[] files = f.list();
for (String ff : files) {
System.out.println(ff);
/*
aDriverDownload
API文档
*/
}
// 获取D盘下直接的所有目录路径
File[] files1 = f.listFiles();
for (File ff : files1) {
System.out.println(ff);
/*
D:\aDriverDownload
D:\API文档
*/
}
}
}
四、常用流对象
(1)文件字节流
-
FileInputStream 通过字节的方式读取文件,适合读取所有类型的文件(图像、视频、文本文件等)。
-
FileOutputStream 通过字节的方式写数据到文件中,适合所有类型的文件。
1. 文件字节输入流
import java.io.FileInputStream;
import java.io.IOException;
@SuppressWarnings({"all"})
public class Main {
public static void main(String[] args) {
FileInputStream fileInputStream = null;
try {
// 创建文件字节流对象
fileInputStream = new FileInputStream("D:\\Wallpaper\\img\\2.jpg");
// 处理字节数据
int tmp = 0;
while ((tmp = fileInputStream.read()) != -1) {
System.out.println(tmp);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (fileInputStream != null) {
try {
fileInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
2. 文件字节输出流
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
@SuppressWarnings({"all"})
public class Main {
public static void main(String[] args) {
FileInputStream fileInputStream = null;
FileOutputStream fileOutputStream = null;
try {
// 创建文件字节流对象
fileInputStream = new FileInputStream("D:\\Wallpaper\\img\\2.jpg");
fileOutputStream = new FileOutputStream("D:\\a.jpg");
// 处理字节数据
int tmp = 0;
while ((tmp = fileInputStream.read()) != -1) {
fileOutputStream.write(tmp);
}
// 调用flush方法,将内存中的数据写入磁盘
fileOutputStream.flush();
} catch (Exception e) {
e.printStackTrace();
} finally {
if (fileInputStream != null) {
try {
fileInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fileOutputStream != null) {
try {
fileOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
3. 通过缓冲区提高读写效率
方式一:
通过创建一个指定长度的字节数组作为缓冲区,以此来提高 IO 流的读写效率。该方式适用于读取较大图片时的缓冲区定义。注意:缓冲区的长度一定是 2 的整数幂。一般情况下1024 长度较为合适。
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
@SuppressWarnings({"all"})
public class Main {
public static void main(String[] args) {
FileInputStream fileInputStream = null;
FileOutputStream fileOutputStream = null;
try {
// 创建文件字节流对象
fileInputStream = new FileInputStream("D:\\Wallpaper\\img\\2.jpg");
fileOutputStream = new FileOutputStream("D:\\a.jpg");
// 处理字节数据
// 创建一个缓冲区,提高读写效率
byte[] buff = new byte[1024];
int len = 0;
// fileInputStream.read(buff)返回值是字节数组的长度
while ((len = fileInputStream.read(buff)) != -1) {
fileOutputStream.write(buff, 0, len);
}
// 调用flush方法,将内存中的数据写入磁盘
fileOutputStream.flush();
} catch (Exception e) {
e.printStackTrace();
} finally {
if (fileInputStream != null) {
try {
fileInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fileOutputStream != null) {
try {
fileOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
方式二:
通过创建一个字节数组作为缓冲区,数组长度是通过输入流对象的 available()返回当前文件的预估长度来定义的。在读写文件时,是在一次读写操作中完成文件读写操作的。**注意:**该方法的效率极高,但是需要占用大量内存空间,如果文件过大,那么对内存的占用也是比较大的,所以大文件不建议使用该方法。
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
@SuppressWarnings({"all"})
public class Main {
public static void main(String[] args) {
FileInputStream fileInputStream = null;
FileOutputStream fileOutputStream = null;
try {
// 创建文件字节流对象
fileInputStream = new FileInputStream("D:\\Wallpaper\\img\\2.jpg");
fileOutputStream = new FileOutputStream("D:\\a.jpg");
// 处理字节数据
// 创建一个缓冲区,提高读写效率
// fileInputStream.available()返回值是fileInputStream对象对应的文件的大小预估值
byte[] buff = new byte[fileInputStream.available()];
fileInputStream.read(buff);
fileOutputStream.write(buff);
// 调用flush方法,将内存中的数据写入磁盘
fileOutputStream.flush();
} catch (Exception e) {
e.printStackTrace();
} finally {
if (fileInputStream != null) {
try {
fileInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fileOutputStream != null) {
try {
fileOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
4. 通过字节缓冲流提高读写效率
- Java 缓冲流本身并不具有 IO 流的读取与写入功能,只是在别的流(节点流或其他处理流)上加上缓冲功能提高效率,就像是把别的流包装起来一样,因此缓冲流是一种处理流(包装流)
- 当对文件或者其他数据源进行频繁的读写操作时,效率比较低,这时如果使用缓冲流就能够更高效的读写信息。因为缓冲流是先将数据缓存起来,然后当缓存区存满后或者手动刷新时再一次性的读取到程序或写入目的地
- 因此,缓冲流还是很重要的,我们在 IO 操作时记得加上缓冲流来提升性能
- BufferedInputStream 和 BufferedOutputStream 这两个流是缓冲字节流,通过内部缓存数组来提高操作流的效率
import java.io.*;
@SuppressWarnings({"all"})
public class Main {
public static void main(String[] args) {
FileInputStream fileInputStream = null;
FileOutputStream fileOutputStream = null;
BufferedInputStream bufferedInputStream = null;
BufferedOutputStream bufferedOutputStream = null;
try {
fileInputStream = new FileInputStream("D:\\a.jpg");
bufferedInputStream = new BufferedInputStream(fileInputStream);
fileOutputStream = new FileOutputStream("D:\\b.jpg");
bufferedOutputStream = new BufferedOutputStream(fileOutputStream);
// 缓冲区中,byte数组长度默认是8192
int tmp = 0;
while ((tmp = bufferedInputStream.read()) != -1) {
bufferedOutputStream.write(tmp);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try{
// 注意:关闭流顺序是“先开的后关闭”
if (bufferedInputStream != null) bufferedInputStream.close();
if (fileInputStream != null) fileInputStream.close();
if (bufferedOutputStream != null) bufferedOutputStream.close();
if (fileOutputStream != null) fileOutputStream.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
5. 定义文件拷贝工具类
import java.io.*;
@SuppressWarnings({"all"})
public class Main {
public static void main(String[] args) {
Main.copyFile("D:\\a.jpg", "C:\\Users\\Administrator\\Desktop\\aa.jpg");
}
// 文件拷贝工具
public static void copyFile(String src, String dest) {
FileInputStream fileInputStream = null;
BufferedInputStream bufferedInputStream = null;
FileOutputStream fileOutputStream = null;
BufferedOutputStream bufferedOutputStream = null;
try {
fileInputStream = new FileInputStream(src);
bufferedInputStream = new BufferedInputStream(fileInputStream);
fileOutputStream = new FileOutputStream(dest);
bufferedOutputStream = new BufferedOutputStream(fileOutputStream);
int tmp = 0;
while ((tmp = bufferedInputStream.read()) != -1) {
bufferedOutputStream.write(tmp);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (bufferedInputStream != null) bufferedInputStream.close();
if (fileInputStream != null) fileInputStream.close();
if (bufferedOutputStream != null) bufferedOutputStream.close();
if (fileOutputStream != null) fileOutputStream.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
(2) 文件字符流
前面介绍的文件字节流可以处理所有的文件,如果我们处理的是文本文件,也可以使用文件字符流,它以字符为单位进行操作。
1. 文件字符输入流
import java.io.*;
@SuppressWarnings({"all"})
public class Main {
public static void main(String[] args) {
FileReader fileReader = null;
try {
// 创建文件字符输入流
fileReader = new FileReader("D:\\a.txt");
// 处理流
int tmp = 0;
// fileReader.read()返回的是字符的Unicode
while ((tmp = fileReader.read()) != -1) {
System.out.println((char) tmp);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (fileReader != null) {
fileReader.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
2. 文件字符输出流
import java.io.*;
@SuppressWarnings({"all"})
public class Main {
public static void main(String[] args) {
FileWriter fileWriter = null;
try {
fileWriter = new FileWriter("D:\\b.txt", true); // 追加写
fileWriter.write("Hello\r\n");
fileWriter.write("World");
fileWriter.flush();
} catch (Exception e) {
e.printStackTrace();
} finally {
if (fileWriter != null) {
try {
fileWriter.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
3. 使用文件字符流拷贝文本文件
import java.io.*;
@SuppressWarnings({"all"})
public class Main {
public static void main(String[] args) {
FileReader fileReader = null;
FileWriter fileWriter = null;
try {
fileReader = new FileReader("D:\\a.txt");
fileWriter = new FileWriter("D:\\b.txt", true);
/*
int tmp = 0;
while ((tmp = fileReader.read()) != -1) {
fileWriter.write(tmp);
}
*/
// 创建缓冲区,提高读写效率
char[] buff = new char[1024];
int tmp = 0;
while ((tmp = fileReader.read(buff)) != -1) {
fileWriter.write(buff, 0, tmp);
}
fileWriter.flush();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (fileReader != null) fileReader.close();
if (fileWriter != null) fileWriter.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
(3)字符缓冲流
BufferedReader/BufferedWriter 增加了缓存机制,大大提高了读写文本文件的效率。
1. 字符输入缓冲流
-
BufferedReader 是针对字符输入流的缓冲流对象,提供了更方便的按行读取的方法:readLine()
-
在使用字符流读取文本文件时,我们可以使用该方法以行为单位进行读取
import java.io.*;
@SuppressWarnings({"all"})
public class Main {
public static void main(String[] args) {
FileReader fileReader = null;
BufferedReader bufferedReader = null;
try {
fileReader = new FileReader("D:\\b.txt");
bufferedReader = new BufferedReader(fileReader);
String line = "";
while ((line = bufferedReader.readLine()) != null) {
System.out.println(line);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (bufferedReader != null) bufferedReader.close();
if (fileReader != null) fileReader.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
2. 字符输出缓冲流
BufferedWriter 是针对字符输出流的缓冲流对象,在字符输出缓冲流中可以使用newLine()方法实现换行处理。
import java.io.*;
@SuppressWarnings({"all"})
public class Main {
public static void main(String[] args) {
FileWriter fileWriter = null;
BufferedWriter bufferedWriter = null;
try {
fileWriter = new FileWriter("D:\\b.txt", true);
bufferedWriter = new BufferedWriter(fileWriter);
bufferedWriter.write("你好,我是Java开发工程师");
// 使用newLine()进行换行
bufferedWriter.newLine();
bufferedWriter.write("我想要学习SpringBoot框架");
bufferedWriter.flush();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (bufferedWriter != null) bufferedWriter.close();
if (fileWriter != null) fileWriter.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
3. 使用字符缓冲流实现文本文件的拷贝
import java.io.*;
@SuppressWarnings({"all"})
public class Main {
public static void main(String[] args) {
FileReader fileReader = null;
FileWriter fileWriter = null;
BufferedReader bufferedReader = null;
BufferedWriter bufferedWriter = null;
try {
fileReader = new FileReader("D:\\b.txt");
fileWriter = new FileWriter("D:\\a.txt", true);
bufferedReader = new BufferedReader(fileReader);
bufferedWriter = new BufferedWriter(fileWriter);
String temp = "";
while ((temp = bufferedReader.readLine()) != null) {
bufferedWriter.write(temp);
bufferedWriter.newLine();
}
bufferedWriter.flush();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (bufferedReader != null) bufferedReader.close();
if (fileReader != null) fileReader.close();
if (bufferedWriter != null) bufferedWriter.close();
if (fileWriter != null) fileWriter.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
(4)转换流
InputStreamReader/OutputStreamWriter 用来实现将字节流转化成字符流。比如,如下场景:
System.in 是字节流对象,代表键盘的输入,如果我们想按行接收用户的输入时,就必须用到缓冲字符流 BufferedReader 特有的方法 readLine(),但是经过观察会发现在创建BufferedReader 的构造方法的参数必须是一个Reader对象 , 这时候我们的转换流InputStreamReader 就派上用场了。
而 System.out 也是字节流对象,代表输出到显示器,按行读取用户的输入后,并且要将读取的一行字符串直接显示到控制台,就需要用到字符流的 write(String str)方法,所以我们要使用 OutputStreamWriter 将字节流转化为字符流。
1. 通过转换流实现键盘输入屏幕输出
import java.io.*;
@SuppressWarnings({"all"})
public class Main {
public static void main(String[] args) {
BufferedReader bufferedReader = null;
BufferedWriter bufferedWriter = null;
try {
bufferedReader = new BufferedReader(new InputStreamReader(System.in));
bufferedWriter = new BufferedWriter(new OutputStreamWriter(System.out));
while (true) {
String input = bufferedReader.readLine();
if ("exit".equals(input)) break;
bufferedWriter.write(input);
bufferedWriter.newLine();
bufferedWriter.flush();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (bufferedReader != null) bufferedReader.close();
if (bufferedWriter != null) bufferedWriter.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
2. 通过字节流读取文本文件并添加行号
import java.io.*;
@SuppressWarnings({"all"})
public class Main {
public static void main(String[] args) {
BufferedReader bufferedReader = null;
BufferedWriter bufferedWriter = null;
try {
bufferedReader = new BufferedReader(new InputStreamReader(new FileInputStream("D:\\a.txt")));
bufferedWriter = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("D:\\d.txt")));
String tmp = "";
int count = 0;
while ((tmp = bufferedReader.readLine()) != null) {
bufferedWriter.write((++count) + " " + tmp);
bufferedWriter.newLine();
}
bufferedWriter.flush();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (bufferedReader != null) bufferedReader.close();
if (bufferedWriter != null) bufferedWriter.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
(5)字符输出流
在 Java 的 IO 流中专门提供了用于字符输出的流对象 PrintWriter。该对象具有自动行刷新缓冲字符输出流,特点是可以按行写出字符串,并且可通过 println()
方法实现自动换行。
import java.io.*;
@SuppressWarnings({"all"})
public class Main {
public static void main(String[] args) {
BufferedReader bufferedReader = null;
PrintWriter printWriter = null;
try {
bufferedReader = new BufferedReader(new InputStreamReader(new FileInputStream("D:\\a.txt")));
printWriter = new PrintWriter("D:\\e.txt");
String tmp = "";
while ((tmp = bufferedReader.readLine()) != null) {
printWriter.println(tmp);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (bufferedReader != null) bufferedReader.close();
if (printWriter != null) printWriter.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
(6)字节数组流
ByteArrayInputStream 和 ByteArrayOutputStream 经常用在需要流和数组之间转化的情况!
1. 字节数组输入流
import java.io.*;
@SuppressWarnings({"all"})
public class Main {
public static void main(String[] args) {
byte[] arr = "abcdefg".getBytes();
ByteArrayInputStream byteArrayInputStream = null;
try {
// 该构造方法里面的参数就是一个字节数组,这个字节数组就是数据源
byteArrayInputStream = new ByteArrayInputStream(arr);
StringBuilder stringBuilder = new StringBuilder();
int tmp = 0;
while ((tmp = byteArrayInputStream.read()) != -1) {
stringBuilder.append((char) tmp);
}
System.out.println(stringBuilder.toString());
} finally {
try {
byteArrayInputStream.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
2. 字节数组输出流
ByteArrayOutputStream 流对象是将流中的数据写入到字节数组中。
import java.io.*;
@SuppressWarnings({"all"})
public class Main {
public static void main(String[] args) {
ByteArrayOutputStream byteArrayOutputStream = null;
try {
byteArrayOutputStream = new ByteArrayOutputStream();
byteArrayOutputStream.write('a');
byteArrayOutputStream.write('b');
byteArrayOutputStream.write('c');
// 使用toByteArray()方法获取对应的字节数组
byte[] arr = byteArrayOutputStream.toByteArray();
for (byte b : arr) {
System.out.println((char) b);
}
} finally {
try {
byteArrayOutputStream.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
(7)数据流
- 数据流将“基本数据类型与字符串类型”作为数据源,从而允许程序以与机器无关的方式从底层输入输出流中操作 Java 基本数据类型与字符串类型
- DataInputStream 和 DataOutputStream 提供了可以存取与机器无关的所有 Java 基础类型数据(如:int、double、String 等)的方法
1. 数据输出流
import java.io.*;
@SuppressWarnings({"all"})
public class Main {
public static void main(String[] args) {
DataOutputStream dataOutputStream = null;
try {
dataOutputStream = new DataOutputStream(new BufferedOutputStream(new FileOutputStream("D:\\e.txt")));
dataOutputStream.writeChar('a');
dataOutputStream.writeBoolean(true);
dataOutputStream.writeDouble(12.34);
dataOutputStream.writeUTF("HelloWorld");
dataOutputStream.flush();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (dataOutputStream != null) dataOutputStream.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
2. 数据输入流
import java.io.*;
@SuppressWarnings({"all"})
public class Main {
public static void main(String[] args) {
DataInputStream dataInputStream = null;
try {
dataInputStream = new DataInputStream(new BufferedInputStream(new FileInputStream("D:\\e.txt")));
// 注意:直接读取数据,读取的顺序要与写入的顺序一致,否则不能正确读取数据
// dataOutputStream.writeChar('a');
// dataOutputStream.writeBoolean(true);
// dataOutputStream.writeDouble(12.34);
// dataOutputStream.writeUTF("HelloWorld");
System.out.println(dataInputStream.readChar());
System.out.println(dataInputStream.readBoolean());
System.out.println(dataInputStream.readDouble());
System.out.println(dataInputStream.readUTF());
/*
a
true
12.34
HelloWorld
*/
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (dataInputStream != null) dataInputStream.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
(8)对象流
对象的本质是用来组织和存储数据的,对象本身也是数据。那么,能不能将对象存储到硬盘上的文件中呢?能不能将对象通过网络传输到另一个电脑呢?我们可以通过序列化和反序列化来实现这些需求。
1. Java对象的序列化和反序列化
序列化和反序列化是什么
- 当两个进程远程通信时,彼此可以发送各种类型的数据。 无论是何种类型的数据,都会以二进制序列的形式在网络上传送。比如,我们可以通过 http 协议发送字符串信息;我们也可以在网络上直接发送 Java 对象。发送方需要把这个 Java 对象转换为字节序列,才能在网络上传送;接收方则需要把字节序列再恢复为 Java 对象才能正常读取。
- 把 Java 对象转换为字节序列的过程称为对象的序列化。
- 把字节序列恢复为 Java 对象的过程称为对象的反序列化。
- 对象序列化的作用有如下两种:
- 持久化: 把对象的字节序列永久地保存到硬盘上,通常存放在一个文件中。
- **网络通信:**在网络上传送对象的字节序列。比如:服务器之间的数据通信、对象传递。
序列化涉及的类和接口
- ObjectOutputStream 代表对象输出流,它的 writeObject(Object obj)方法可对参数指定的obj 对象进行序列化,把得到的字节序列写到一个目标输出流中。
- ObjectInputStream 代表对象输入流,它的 readObject()方法从一个源输入流中读取字节序列,再把它们反序列化为一个对象,并将其返回。
- 只有实现了 Serializable 接口的类的对象才能被序列化。Serializable 接口是一个空接口,只起到标记作用。
2. 操作基本数据类型
前边学到的数据流只能实现对基本数据类型和字符串类型的读写,并不能对 Java对象进行读写操作(字符串除外),但是在对象流中除了能实现对基本数据类型进行读写操作以外,还可以对 Java 对象进行读写操作。
写出基本数据类型数据
import java.io.*;
@SuppressWarnings({"all"})
public class Main {
public static void main(String[] args) {
ObjectOutputStream objectOutputStream = null;
try {
objectOutputStream = new ObjectOutputStream(new BufferedOutputStream(new FileOutputStream("D:\\h.txt")));
objectOutputStream.writeInt(10);
objectOutputStream.writeDouble(Math.random());
objectOutputStream.writeChar('a');
objectOutputStream.writeBoolean(true);
objectOutputStream.writeUTF("HelloWorld");
objectOutputStream.flush();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (objectOutputStream != null) objectOutputStream.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
读取基本数据类型数据
import java.io.*;
@SuppressWarnings({"all"})
public class Main {
public static void main(String[] args) {
ObjectInputStream objectInputStream = null;
try {
objectInputStream = new ObjectInputStream(new BufferedInputStream(new FileInputStream("D:\\h.txt")));
/*
objectOutputStream.writeInt(10);
objectOutputStream.writeDouble(Math.random());
objectOutputStream.writeChar('a');
objectOutputStream.writeBoolean(true);
objectOutputStream.writeUTF("HelloWorld");
*/
// 必须要按照写入的顺序读取数据
System.out.println(objectInputStream.readInt());
System.out.println(objectInputStream.readDouble());
System.out.println(objectInputStream.readChar());
System.out.println(objectInputStream.readBoolean());
System.out.println(objectInputStream.readUTF());
/*
10
0.19669607546122192
a
true
HelloWorld
*/
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (objectInputStream != null) objectInputStream.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
3. 操作对象
将对象序列化到文件
ObjectOutputStream 可以将一个内存中的 Java 对象通过序列化的方式写入到磁盘的文件中。被序列化的对象必须要实现 Serializable 序列化接口,否则会抛出异常。
创建对象
import java.io.Serializable;
public class User implements Serializable {
private int userId;
private String userName;
private int userAge;
public User(int userId, String userName, int userAge) {
this.userId = userId;
this.userName = userName;
this.userAge = userAge;
}
public int getUserId() {
return userId;
}
public void setUserId(int userId) {
this.userId = userId;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public int getUserAge() {
return userAge;
}
public void setUserAge(int userAge) {
this.userAge = userAge;
}
@Override
public String toString() {
return "User{" +
"userId=" + userId +
", userName='" + userName + '\'' +
", userAge=" + userAge +
'}';
}
}
序列化对象
import java.io.*;
@SuppressWarnings({"all"})
public class Main {
public static void main(String[] args) {
ObjectOutputStream objectOutputStream = null;
try {
objectOutputStream = new ObjectOutputStream(new BufferedOutputStream(new FileOutputStream("D:\\h.txt")));
objectOutputStream.writeObject(new User(1, "小叮铛", 10));
objectOutputStream.flush();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (objectOutputStream != null) objectOutputStream.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
将对象反序列化到内存中
import java.io.*;
@SuppressWarnings({"all"})
public class Main {
public static void main(String[] args) {
ObjectInputStream objectInputStream = null;
try {
objectInputStream = new ObjectInputStream(new BufferedInputStream(new FileInputStream("D:\\h.txt")));
User o = (User) objectInputStream.readObject();
System.out.println(o);
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (objectInputStream != null) objectInputStream.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
(9)随机访问流
RandomAccessFile 可以实现两个作用:
- 实现对一个文件做读和写的操作
- 可以访问文件的任意位置,不像其他流只能按照先后顺序读取
在开发某些客户端软件时,经常用到这个功能强大的可以”任意操作文件内容”的类。比如,软件的使用次数和使用日期,可以通过本类访问文件中保存次数和日期的地方进行比对和修改。 Java 很少开发客户端软件,所以在 Java 开发中这个类用的相对较少。
掌握三个核心方法:
- RandomAccessFile(String name, String mode)
- name 用来确定文件; mode 取 r(读)或 rw(可读写)
- 通过 mode 可以确定流对文件的访问权限
- seek(long a) 用来定位流对象读写文件的位置,a 确定读写位置距离文件开头的字节个数。
- getFilePointer() 获得流的当前读写位置
import java.io.*;
@SuppressWarnings({"all"})
public class Main {
public static void main(String[] args) {
RandomAccessFile randomAccessFile = null;
try {
randomAccessFile = new RandomAccessFile("D:\\h.txt", "rw");
// 将若干数据写入到文件中
int[] arr = new int[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
for (int x : arr) {
randomAccessFile.writeInt(x);
}
// 一个int占4个字节,seek(4)是把指针定位到第4个字节,读取时从第4个字节后面开始读
randomAccessFile.seek(4);
System.out.println(randomAccessFile.readInt()); // 2
// 隔一个读一个
for (int i = 0; i < 10; i+=2) {
randomAccessFile.seek(i * 4);
System.out.print(randomAccessFile.readInt() + "\t"); // 1 3 5 7 9
}
// 在第8个字节后替换一个数字
randomAccessFile.seek(8);
randomAccessFile.writeInt(11);
for (int i = 0; i < 10; i+=2) {
randomAccessFile.seek(i * 4);
System.out.print(randomAccessFile.readInt() + "\t"); // 1 11 5 7 9
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (randomAccessFile != null) randomAccessFile.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
(10)File类在IO流中的作用
当以文件作为数据源或目标时,除了可以使用字符串作为文件以及位置的指定以外,我们也可以使用 File 类指定。
import java.io.*;
@SuppressWarnings({"all"})
public class Main {
public static void main(String[] args) {
BufferedReader bufferedReader = null;
BufferedWriter bufferedWriter = null;
try {
bufferedReader = new BufferedReader(new FileReader(new File("D:\\a.txt")));
bufferedWriter = new BufferedWriter(new FileWriter(new File("D:\\b.txt"), true));
String tmp = "";
while ((tmp = bufferedReader.readLine()) != null) {
bufferedWriter.write(tmp);
bufferedWriter.newLine();
}
bufferedWriter.flush();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (bufferedReader != null) bufferedReader.close();
if (bufferedWriter != null) bufferedWriter.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
五、Apache IO包
-
JDK 中提供的文件操作相关的类,但是功能都非常基础,进行复杂操作时需要做大量编程工作。实际开发中,往往需要你自己动手编写相关的代码,尤其在遍历目录文件时,经常用到递归,非常繁琐。 Apache-commons 工具包中提供了 IOUtils/FileUtils,可以让我们非常方便的对文件和目录进行操作。 本文就是让大家对 IOUtils/FileUtils 类有一个全面的认识,便于大家以后开发与文件和目录相关的功能。
-
Apache IOUtils 和 FileUtils 类库为我们提供了更加简单、功能更加强大的文件操作和 IO流操作功能。非常值得学习和使用。
1. FileUtils的使用
cleanDirectory
:清空目录,但不删除目录contentEquals
:比较两个文件的内容是否相同copyDirectory
:将一个目录内容拷贝到另一个目录。可以通过 FileFilter 过滤需要拷贝的文件copyFile
:将一个文件拷贝到一个新的地址copyFileToDirectory
:将一个文件拷贝到某个目录下copyInputStreamToFile
:将一个输入流中的内容拷贝到某个文件deleteDirectory
:删除目录deleteQuietly
:删除文件listFiles
:列出指定目录下的所有文件openInputSteam
:打开指定文件的输入流readFileToString
:将文件内容作为字符串返回readLines
:将文件内容按行返回到一个字符串数组中size
:返回文件或目录的大小write
:将字符串内容直接写到文件中writeByteArrayToFile
:将字节数组内容写到文件中writeLines
:将容器中的元素的 toString 方法返回的内容依次写入文件中writeStringToFile
:将字符串内容写到文件中
使用FileUtils工具类获取文件中的所有内容
import org.apache.commons.io.FileUtils;
import java.io.*;
@SuppressWarnings({"all"})
public class Main {
public static void main(String[] args) throws IOException {
String s = FileUtils.readFileToString(new File("D:\\a.txt"), "utf-8");
System.out.println(s);
}
}
使用FileUtils工具类将文件中的内容拷贝到另一个文件
import org.apache.commons.io.FileUtils;
import java.io.*;
@SuppressWarnings({"all"})
public class Main {
public static void main(String[] args) throws IOException {
FileUtils.copyDirectory(new File("D:\\a"), new File("D:\\aa"), new FileFilter() {
// 在文件拷贝时的过滤条件
@Override
public boolean accept(File pathname) {
if (pathname.isDirectory() || pathname.getName().endsWith("html"))
return true;
return false;
}
});
}
}
2. IOUtils的使用
buffer
:将传入的流进行包装,变成缓冲流。并可以通过参数指定缓冲大小closeQueitly
:关闭流contentEquals
:比较两个流中的内容是否一致copy
:将输入流中的内容拷贝到输出流中,并可以指定字符编码copyLarge
:将输入流中的内容拷贝到输出流中,适合大于 2G 内容的拷贝lineIterator
:返回可以迭代每一行内容的迭代器read
:将输入流中的部分内容读入到字节数组中readFully
:将输入流中的所有内容读入到字节数组中readLine
:读入输入流内容中的一行toBufferedInputStream,toBufferedReader
:将输入转为带缓存的输入流toByteArray,toCharArray
:将输入流的内容转为字节数组、字符数组toString
:将输入流或数组中的内容转化为字符串write
:向流里面写入内容writeLine
:向流里面写入一行内容
使用IOUtils工具类来读取文件内容
import org.apache.commons.io.IOUtils;
import java.io.*;
@SuppressWarnings({"all"})
public class Main {
public static void main(String[] args) throws IOException {
String s = IOUtils.toString(new FileInputStream("D:\\a.txt"), "utf-8");
System.out.println(s);
}
}
六、总结
按流的方向分类:
- 输入流:数据源到程序(InputStream、Reader 读进来)。
- 输出流:程序到目的地(OutPutStream、Writer 写出去)。
按流的处理数据单元分类:
- 字节流:按照字节读取数据(InputStream、OutputStream)。
- 字符流:按照字符读取数据(Reader、Writer)。
按流的功能分类:
- 节点流:可以直接从数据源或目的地读写数据。
- 处理流:不直接连接到数据源或目的地,是处理流的流。通过对其他流的处理提高程序的性能。
IO 的四个基本抽象类:InputStream、OutputStream、Reader、Writer
InputStream 的实现类:
- FileInputStream
- ByteArrayInutStream
- BufferedInputStream
- DataInputStream
- ObjectInputStream
OutputStream 的实现类:
- FileOutputStream
- ByteArrayOutputStream
- BufferedOutputStream
- DataOutputStream
- ObjectOutputStream
- PrintStream
Reader 的实现类 :
- FileReader
- BufferedReader
- InputStreamReader
Writer 的实现类:
- FileWriter
- BufferedWriter
- OutputStreamWriter