I/O 流是所有程序都必需的部分——使用输入机制,允许程序读取外部数据、用户输入数据;使用输出机制,允许程序记录运行状态,将程序数据输出到磁盘、光盘等存储设备中。
Java 的 IO 通过 java.io 包下的类和接口支持,主要包括输入、输出两种 IO 流,每种输入、输出又可分为字节流和字符流两大类。通常字节流的功能比字符流的功能强大,因为计算机里所有的数据都是二进制,二字节流可处理所有二进制文件。但一般我们有这样一个规则:如果输入/输出的内容是文本内容,则应该使用字符流;如果进行输入/输出的内容是二进制内容,则应该使用字符流。
Java 输入/输出流体系中常用分类,下面会介绍其中的一些
理解 Java 的 IO 流
Java 的 IO 流是实现输入/输出的基础。下面从不同角度来对流进行分类。
输入流和输出流
按照流向来分:
- 输入流:只能从中读取数据,而不能向其写入数据
- 输出流:只能向其写入数据,而不能从中读取数据
- **例如,数据从内存到硬盘,通常称为输出流,**这里的输入、输出都是从程序运行的内存的角度来划分的。
字节流和字符流
按照操作数据单元来分:
- 字节流:操作的数据单元是 8 位的字节
- 字符流:操作的数据单元是 16 位的字符
- 字节流和字符流的用法几乎完全一样。
节点流和处理流
按照角色来分:
- 节点流:可以从/向一个特定的 IO 设备读/写数据的流,也被称为低级流
- 处理流:用于对一个已存在的流进行连接和封装,通过封装后的流来实现数据读/写功能,也被称为高级流。
介绍完各个流的分类,下面会着重进行介绍他们的使用。
File 类
File 类可使用文件路径字符串来创建 File 实例。而 File 类提供了很多方法来操作文件和目录,在这里直接进行代码演示,来看一下比较常用的方法:
public static void main(String[] args) throws IOException {
{
//以当前路径来创建一个File对象,括号里的是文件路径字符串,标识当前路径
File file = new File(".");
//直接获取文件名,输出一点
System.out.println(file.getName());
//获取相对路径的父路径可能出错,下面代码输出为null
System.out.println(file.getParent());
//获取绝对路径
System.out.println(file.getAbsoluteFile());
//获取上一级路径
System.out.println(file.getAbsoluteFile().getParent());
//在当前路径下创建一个临时文件
File tempFile = File.createTempFile("aaa",".txt",file);
//指定当JVM退出时删除该文件
tempFile.deleteOnExit();
//以系统当前时间作为新文件名来创建文件
File newFile = new File(System.currentTimeMillis() + "");
System.out.println("newFile是否存在:" + newFile.exists());
//以指定newFile来创建一个目录,因为newFile已经存在
//所以下面方法返回false,即无法创建该目录
newFile.mkdir();
//使用list()方法列出当前路径 下的所有文件和路径
String[] fileList = file.list();
System.out.println("=====当前路径下所有文件和路径如下======");
for( String fileName : fileList){
System.out.println(fileName);
}
//listRoots()静态方法列出所有的磁盘根路径
File[] roots = File.listRoots();
System.out.println("========系统所有根路径如下=========");
for(File root : roots){
System.out.println(root);
}
}
在这里,就不贴结构图咯,各位小伙伴可以直接试一下。
字节流和字符流
下面详细介绍字节流和字符流的具体使用:
InputStream 和 Reader
InputStream 和 Reader 是所有输入流的抽象基类,本身并不能创建实例执行输入,但它们将成为所有输入流的模板。
在 InputStream 里包含下面三个方法:
- int Read():从输入流中读取单个字节,返回所读取的字节数据。
- int read(byte[] b):从输入流中最多读取 b.length 个字节的数据,并将其存储在字节数组 b 中,返回实际读取的字节数。
- int read(byte[] b,int off,int len):从输入流中最多读取 len 个字节的数据,并将其存储在数组 b 中;放入数组 b 中时,并不是从数组起点开始,而是从 off 位置开始,返回实际读取的字节数。
在 Read 里包含下面三个方法:
- int Read():从输入流中读取单个字符,返回所读取的字符数据。
- int read(char[] cbuf):从输入流中最多读取 cbuf.length 个字节的数据,并将其存储在字节数组 b 中,返回实际读取的字节数。
- int read(byte[] cbuf,int off,int len):从输入流中最多读取 len 个字节的数据,并将其存储在数组 cbuf 中;放入数组 cbuf 中时,并不是从数组起点开始,而是从 off 位置开始,返回实际读取的字节数。
从上面的方法来看,字节流和字符流的方法几乎都是一样的。只不过是操作数据单元不同罢了。
OutputStream 和 Writer
OutputStream 和 Writer 也非常相似,两个流都提供了如下三个方法:
- void write(int c):将指定的字节/字符输出到输出流中,其中 c 既可以代表字节,也可代表字符。
- void write(byte[]/char[] buf):将指定的字节/字符数组的数据输出到指定输出流中。
- void write(byte[]/char[] buf,int off,int len):将字节数组/字符数组中从 off 位置开始,长度为 len 的字节/字符输出到输出流中。
- 因为字符流直接以字符作为操作单位,所以 Writer 可以用字符串来代替字符数组,即以 String 对象作为参数。Writer 里还包含如下两个方法:
- void write(String str):将 str 字符串里包含的字符输出到指定输出流中。
- void write(String str,int off,int len):将 str 字符串里从 off 位置开始,长度为 len 的字符输出到指定输出流中。
注意:使用 IO 流执行输出时,不要忘记关闭输出流。
处理流的用法
使用处理流时的思路是,使用处理流来包装节点流,程序通过处理流来执行输入/输出功能,让节点流与底层的 I/O设备、文件交互。
识别处理流非常简单:只要流的构造器参数不是一个物理节点,而是已经存在的流,那么这种流就一定是处理流;而所有节点流都是直接以物理 IO 节点作为构造器参数的。
处理流优势:
- 对开发人员来说,使用处理流进行输入/输出操作更简单
- 使用处理流的执行效率更高。
public static void main(String[] args){
try (
FileOutputStream fos = new FileOutputStream("test.txt");
PrintStream ps = new PrintStream(fos))
{
//使用PrintStream执行输出
ps.println("普通字符串");
//使用PrintStream输出对象
ps.println(new PrintStreamTest());
}
catch (IOException ioe)
{
ioe.printStackTrace();
}
}
上面的代码,先定义了一个节点输出流 FileOutPutSteam ,然后程序使用 PrintStream 包装了该节点输出流,最后使用 PrintStream 输出字符串、输出对象。
在使用处理流包装了底层节点流之后,关闭流资源时,只要关闭最上层的处理流即可。关闭最上层的处理流时,系统会自动关闭被该处理流包装的节点流。
转换流
IO 流还提供了两个转换流,用于字节流转换成字符流,其中** InputStreamReader 将字节输入流转换成字符输入流,OutputStreamWriter 将字节输出流转换成字符输出流。**
那么问题来了,为什么没有把字符流转换成字节流的转换流呢?
因为,字节流使用范围广,但字符流比字节流操作方便,如果已经有一个操作方便了的字节流,为什么要转换成字节流呢?但如果有一个内容都是文本内容的字节流,那么现在可能就需要转换为字符流方便我们操作了。
public static void main(String[] args){
try(
//将System.in 对象转换成Reader对象
InputStreamReader reader = new InputStreamReader(System.in);
//将普通的Reader包装成BufferedReader
final BufferedReader br = new BufferedReader(reader))
{
String line = null;
//循环逐行读取
while((line = br.readLine()) != null){
//如果读取的字符串“exit”,则程序退出
if(line.equals("exit")){
System.exit(1);
}
System.out.println("输入内容为:" + line);
}
}
catch (IOException ioe)
{
ioe.printStackTrace();
}
}
上面的程序**将输入的字符转换成字节,**把 System.in 包装成具有缓冲功能的 BufferedReader ,它可以依次读取一行文本——以换行符为标志;如果没有换行符,则程序阻塞,直至读到换行符位置。