Java IO流

I/O 是人与机器或者机器与机器交互的手段,Java中将输入输出抽象称为流,就好像水管将两个容器连接起来。IO的核心问题是将什么样的数据(字节、字符)写到什么地方(文件(磁盘)、控制台、内存中的缓存、网络(socket)、管道(线程内部通信)。操作系统实现了协议栈、对磁盘的抽象,于是应用程序可以直接使用socket和文件。
常用的上传和下载使用的就是IO流技术。上传超过大小限制就会引发异常,要加载的图片由于网络原因也不一定成功,所以IO流会涉及到文件类和异常类。
IO流的分类
如何区分输入与输出:以Java程序自身为参照物。
1. 字节流抽象类:InputStream
和OutputStream
.
- 文件大小以字节为单位:KB是千字节,文本在这个数量级。一首歌是MB,兆字节,百万字节。一部电影是GB,千兆字节。
- 读写单位:字节或字节数组。字节就是一个比较小的整数,在 [-128, 127] 范围内。字节数组的 IO 效率更高。
- 字符串 --> byte[ ]:字符串的
getBytes()
方法 - byte[ ] --> 字符串:字符串构造器
new String(byte[])
- 利用循环实现读取多个单位。
- 一个byte == 被截断的int == 一个字节可以表示的字符(英文字符)。
- 字符串 --> byte[ ]:字符串的
- 字节流不需要 flush 也能立即写到文件里面。
- 常用实现类:FileInputStream 和 FileOutputStream 用于文本、图片等文件的读写。认识File的原因是他们的构造器的底层实现调用了File类。
- close方法关闭资源,通知操作系统释放和文件相关的资源。不关闭流,会占用资源的证明:如果不关闭,程序在运行中就会一直占用该文件,就无法手动修改或删除该文件。
2. 字符流抽象类:Reader
和Writer
.
- 除了读写文本文件之外,没有什么用。复制文本都没必要用字符流。
- Reader暗含了Input之意,所以不需要再加Input修饰。
- 读写单位:字符、字符数组或者字符串(方便)(可以写一个字符串,并不能读一个字符串。毕竟空格也是字符啊,不如读一行来的有用)。
- 具有缓冲区,用于编码和解码。所以有flush方法,可以刷新缓冲区,不用关闭数据管道。缓冲区没满,不会自动刷新。
- 字符流复制图片会丢失数据,因为不能识别的字符的转换是不可逆的。
- 转换流是字符流的实现类。
3. 转换流(属于包装流):InputStreamReader
和OutputStreamWriter
- 可用于修改字符编码。
- 名字:两个抽象流的拼接。
- 利用编码表实现字节流与字符流的转换,构造器可以传入编码表。Windows系统中默认使用GBK编码,一个汉字2个字节。国际通用的是UTF-8编码,一个汉字3个字节。
- 字符流可以读取中英文夹杂的文本,知道什么时候读1个字节,什么时候读3个字节。
- 子类:FileReader和FileWriter,并不是包装流,使用默认的编码表,不可以自己设置。
注意:1. 文件结束的判断:read()
方法返回-1时。
2. read()方法就像迭代器里的next()方法一样,会记忆位置。因此读文件的代码就像是利用迭代器遍历集合的代码,需要利用while循环。
4. 高效流(属于包装流)
Most of the examples we've seen so far use unbuffered I/O. This means each read or write request is handled directly by the underlying OS. This can make a program much less efficient, since each such request often triggers disk access, network activity, or some other operation that is relatively expensive.
To reduce this kind of overhead, the Java platform implements buffered I/O streams. BufferedInputStreams
read data from a memory area known as a buffer; the native input API is called only when the buffer is empty. Similarly, BufferedOutputStreams
write data to a buffer, and the native output API is called only when the buffer is full.
- 名字:在抽象流前加上Buffered修饰。
- 除了创建对象不一样(装饰者模式),其他基本都一样。
- 可以利用复制大文件测试效率。在高效流中使用数组仍然可以提高效率。
- 缓冲字符流具有方便的方法:readLine、newLine(写一个换行符,底层实现调用了System.lineSeperator())
- 文件结束的判断:
readLine()
方法返回null
时。
5. 序列化流(属于包装流,可以包装文件流)
- 对象序列化就是把一个对象变为二进制数据流以便于传输和持久化储存。
- 名字:字节流前加Object修饰。因为是进行字节与对象之间的转换。
- 它的write方法可以写 任意 类型。
- 自定义的类想要被序列化,需要实现Serielizable接口。
- transient 关键字修饰的属性不会被永久化。
- 序列化ID
- 序列化时,会生成一个序列化ID值,来标记类的版本。
- 反序列化时,如果没有相应的类,就会报ClassnotFoundException异常。
- 反序列化时,JVM会把传来的字节流中的serialVersionUID与本地实体类中的serialVersionUID进行比较,如果相同则认为是一致的,便可以进行反序列化,否则就会报InvalidClassException异常。
- 固定序列化ID之后,即使属性不匹配,也并不会报错,只是会读出null。
- 读多个对象判断结束。
6. 打印流(属于缓冲流,高效)
- 只有输出流。
- System.out 这个静态方法返回的就是一个PrintStream
- 功能强大,因为本质上是一个包装流。
- PrintStream的 print 方法就已经可以写任何类型,包括字符串和自定义类型的对象。
- PrintWriter的构造器可以接受 字节流 或 字符流 作为参数。
- 利用 println 可以实现自动换行,printf 可以实现格式化的输出。
IO流应用:文件复制(输入+输出)、图片加密(输入+异或+输出)、极简爬虫(输入+正则表达式+输出)。
7. Properties类
是Map的实现类,键值都是String类型(因此也就不用像HashMap那样写泛型了吧),是唯一可以与IO流结合使用的集合类,用于读写配置文件。
System类下面有一个静态方法:public static Properties getProperties()
,其返回值就是Properties类型的。返回当前系统的属性,比如java版本、操作系统名称、文件分隔符、路径分隔符、行分隔符、文件默认编码。
- 主要方法:
- 关联IO流(不需要通过构造器来关联)
把数据保存到文件:void store(输出流)
把键值对儿文件加载到Properties类型的Map中:void load(输入流)
字符流字节流都可以作为参数。但既然读写的是给人看的配置文件,那自然字符流用的多吧。 - 与Map对应的方法:put--setProperty、get--getProperty、keySet--stringPropertyNames
- 关联IO流(不需要通过构造器来关联)
例子: 文件复制
To demonstrate how byte streams work, we'll focus on the file I/O byte streams: FileInputStream
and FileOutputStream
.
FileInputStream in = null;
FileOutputStream out = null;
in = new FileInputStream("input.txt");
out = new FileOutputStream("output.txt");
while (in.read() != -1) {
out.write(c);
}
in.close();
out.close();
它的available()
方法返回文件的大小。可以据此设定字符数组的大小,然后将此字符数组传入read()
方法。
IO异常处理代码
//写文件の4步全都会报错,因此都会放在try里面。
FileWriter fw = null; //放在try里面,finally就用不了了。
try {
fw = new FileWriter("d.txt"); //这里一报错,不仅文件写不了,还会引发空指针。
fw.write("hello");
fw.flush();
} catch (IOException e) {
e.printStackTrace();
} finally {
// 文件路径可能会错,导致文件没有创建成功,导致fw并没有被赋值
if (fw != null) {
try {
fw.close(); //确保在上面代码抛异常的情况下,也能关闭资源
} catch (IOException e) {
e.printStackTrace();
}
}
}
JDK7之后,finally里面的代码全部可以省略,因为会自动关闭:
//有多个流对象的话就用分号隔开。
try (FileWriter fw = new FileWriter("d.txt")) {
fw.write("hello");
fw.flush();
} catch (IOException e) {
e.printStackTrace();
}
会自动关闭的原因:实现了AutoCloseable接口,它有一个close方法。只有实现了这个接口的对象才能放到try后面,最后会自动调用close方法。代码验证:
public class Student implements AutoCloseable {
@Override
public void close() throws Exception {
System.out.println("亲,已为您自动关闭 Student 对象!");
}
}
Scanner类
The scanner API breaks input into individual tokens, and translating individual tokens according to their data type.
By default, a scanner uses white space to separate tokens(White space characters include blanks, tabs, and line terminators),因此如果想读一整行,就需要用nextLine方法。 既然要parse,所以处理的肯定是字符流。字节流像图片视频没有处理的必要。
Scanner类也可以用来读文件,读取的单位(字节、字符、数字、对象)、高效方便的方法是由选取的流来决定的。
Scanner最方便的就是可以自动把数字字符串识别成数字。
Scanner s = null;
s = new Scanner(new BufferedReader(new FileReader("input.txt")));
// 和迭代器一样,迭代器里面就有hasNext方法和Next方法。
// 所以读取字符串就是写一个迭代器的过程。
while (s.hasNext()) {
System.out.println(s.next());
}
s.close();
The above example treats all input tokens as simple String values. Scanner also supports tokens for all of the Java language's primitive types (except for char).
可以很方便的读数字:
Scanner s = null;
double sum = 0;
s = new Scanner(new BufferedReader(new FileReader("input.txt")));
//可以把想要的类型选择出来
while (s.hasNext()) {
if (s.hasNextDouble()) {
sum += s.nextDouble();
}
else {
s.next();
}
}
s.close();
从键盘读取:Scanner input = new Scanner(System.in)
input
就有nextInt()
、nextLine()
方法
从文件读取:Scanner input = new Scanner(file path)
从键盘读数据写到文件。
标准输入输出
Scanner类是在JDK1.5之后才有的,之前的标准输入输出用的是System类。
java.lang.System
public final class System extends Object{
static PrintStream err; // 标准错误流
static InputStream in; // 标准输入(从键盘输入)
static PrintStream out; // 标准输出流(向显示器输出)
}
输出重定向:
// 此时直接输出到屏幕
System.out.println( "hello" );
File file = new File( "d:" + File.separator + "hello.txt" );
try{
System.setOut( new PrintStream( new FileOutputStream(file) ) );
}catch( FileNotFoundException e ){
e.printStackTrace();
}
System.out.println( "这些内容在文件中才能看到哦!" );
输入重定向:
System.setIn( newFileInputStream(file) );
Command-line
命令行的标准输入是命令行参数,标准输出是terminal window。
重定向命令行的标准输入输出:
java Average 4 < input.txt > out.txt