java I/O知识点总结
1、I/O操作相关类和接口
| 类 | 说明2 |
|---|---|
| File | 文件类,代表一个特定文件信息,或代表一个目录下的一组文件 |
| InputStream | 字节输入流接口,表示从不同数据源产生输入的类 |
| OutputStream | 字节输出流接口,表示数据输出的目标 |
| Read | 字符输入流接口 |
| Writer | 字符输出流接口 |
Read和Writer提供兼容Unicode与面向字符的I/O功能。
1.1、File类简介
** File类是对文件系统中文件以及文件夹进行封装的对象,可以通过对象的思想来操作文件和文件夹。 File类保存文件或目录的各种元数据信息,包括文件名getName()、文件长度length()、最后修改时间lastModified()、是否可读写canRead()、canWrite()、获取文件的路径getPath(),判断文件是否存在exists()、获得当前目录中的文件列表list()、创建mkdirs()、删除delete()等方法。**
package io.iosystem;
import java.io.File;
import java.io.FilenameFilter;
public class DirList {
public static void main(String[] args) {
File path = new File(".");
System.out.println("绝对路径: " + path.getAbsolutePath());
System.out.println("可读写: " + path.canRead() + " " + path.canWrite());
System.out.println("文件名: " + path.getName());
System.out.println("最后修改时间: " + path.lastModified());
// list()方法可接受一个FilenameFilter对象,
// 作为策略模式,list会回调FilenameFilter.accept()方法,允许我们对文件夹中的子文件名称做额外的处理
// 如下,只会返回文件名小于10字符的文件名组
String[] list = path.list(new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
return name.length() < 5 ? true : false;
}
});
for (String s : list) {
System.out.println(s);
}
}
}
/*output:
绝对路径: D:\IdeaProjects\Test\thinkingInJava\.
可读写: true true
文件名: .
最后修改时间: 1548920209829
file
out
src
*/
2、I/O流类结构图
3、流的概念和作用
java流是一组有顺序的,有起点和终点的字节集合,是对数据传输的总称或抽象。即数据在两设备间的传输称为流,==流的本质是数据传输,根据数据传输特性将流抽象为各种类,方便更直观的进行数据操作。
4、IO流的分类
根据处理数据类型的不同分为:字符流和字节流根据数据流向不同分为:输入流和输出流
4.1、字符流和字节流
字符流的由来: 因为数据编码的不同,而有了对字符进行高效操作的流对象。本质其实就是基于字节流读取时,去查了指定的码表。 字节流和字符流的区别:
读写单位不同:字节流以字节(8bit)为单位,字符流以字符为单位,根据码表映射字符,一次可能读多个字节。处理对象不同:字节流能处理所有类型的数据(如图片、视频等),而字符流只能处理字符类型的数据。
所以只要是处理纯文本数据,就优先考虑使用字符流。 除此之外都使用字节流。
4.2、输入流和输出流
对输入流只能进行读操作,对输出流只能进行写操作,程序中需要根据待传输数据的不同特性而使用不同的流。
5、Java IO流对象简介
代表任何有能力生产数据的数据源对象或者又能力接受数据端的对象。
5.1、字节流的输入和输出的结构
图中蓝色为输入输出对应部分,红色为不对应的部分,绿色部分已被弃用Deprecated不推荐使用,紫色虚线部分代表这些流一般需要搭配使用。
5.1.1、输入字节流
InputStream: 抽象类,是所有的输入字节流的父类。ByteArrayInputStream:作为一种数据源,用于从byte数组中读取数据(构造器参数->byte数组)。FileInputStream:作为一种数据源,用于从本地文件中读取数据(构造器参数->文件对象File,文件名字符串,或FileDescriptor对象)。ObjectInputStream: 和所有FilterInputStream 的子类都是装饰流(装饰器模式的主角),用于java序列化支持(构造器参数->InputStream对象)。PipedInputStream:作为多线程的数据源,用于从与其它线程共用的管道中读取数据(构造器参数->PipedOutputStream对象)。FilterInputStream:抽象类,作为“装饰器”的接口,为InputStream类提供更丰富的功能。SequenceInputStream:可以认为是一个工具类,将两个或者多个输入流当成一个输入流依次读取。StringBufferInputStream:[已弃用Deprecated]作为一种数据源,用于从字符串中读取数据,底层实现使用StringBuffer(构造器参数->字符串)。BufferedInputStream:FilterInputStream子类,使用缓冲区进行读取(构造器参数->InputStream,可指定缓冲区大小,可选值)。DataInputStream:FilterInputStream子类,与DataOutputStream搭配使用,可以从流中取出基本的数据类型(int、char、long等)(构造器参数->InputStream)。LineNumberInputStream:FilterInputStream子类,[已弃用Deprecated]可以跟踪流中的行号,getLineNumber()、setLineNumber(int)方法(构造器参数->InputStream)。PushbackInputStream:FilterInputStream子类,查看最后一个字节,不满意就放入缓冲区。主要用在编译器的语法、词法分析部分(构造器参数->InputStream)。
5.1.2、输出字节流
OutputStream:抽象类,是所有的输出字节流的父类。ByteArrayOutputStream:向byte 数组中写入数据(构造器参数->初始化byte数组大小,可选值)FileOutputStream :用于将信息写入文件(构造器参数->文件对象File,文件名字符串,或FileDescriptor对象)。ObjectOutputStream:和所有FilterInputStream 的子类都是装饰流(装饰器模式的主角),用于java序列化支持(构造器参数->OutputStream对象)。PipedOutputStream:任何写入其中的信息自动作为相关PipedInputStream的输出,实现管道概念。(构造器参数->PipedInputStream对象)。FilterOutputStream:抽象类,作为“装饰器”的接口,为OutputStream类提供更丰富的功能。BufferedOutputStream:FilterOutputStream子类,使用缓冲区进行写入,避免每次发送数据时都要进行实际的写操作(只有缓冲区满时才进行实际的写入操作),flush()清空缓冲区(构造器参数->OutputStream,可指定缓冲区大小,可选值)。DataOutputStream:FilterOutputStream子类,与DataInputStream搭配使用,可以向流中写入基本的类型数据(int、char、long等)(构造器参数->OutputStream)。PrintStream:用于产生格式化的输出,其中DataOutputStream处理数据存储,PrintStream处理显示。主要可以向其他输出流,或者FileInputStream 写入数据,本身内部实现还是带缓冲的。本质上是对其它流的综合运用的一个工具而已。一样可以踢出IO 包!System.out 和System.out 就是PrintStream 的实例。
流操作可以与FilterInputStream、FilterOutputStream子类(装饰器)相连可提供更有用的接口形式
5.1.3、字节流简单的例子
详细IO流对象使用请参考jdk文档
// An highlighted block
package io.iosystem;
import java.io.*;
public class StoringAndRecoveringData {
public static void main(String[] args)
throws IOException {
DataOutputStream out = new DataOutputStream( // 搭配装饰器,支持基本类型的操作
new BufferedOutputStream( // 使用缓存装饰器
new FileOutputStream( // 以文件流为目标写入文件
new File("data.txt").getAbsoluteFile()))); // getAbsoluteFile()的作用是在报错时,可显示报错文件的绝对路径
out.writeDouble(3.14159);
out.writeUTF("That was pi");
out.close();
DataInputStream in = new DataInputStream( // 与DataOutputStream搭配,支持基本类型的操作
new BufferedInputStream( // 使用缓存装饰器
new FileInputStream( // 从本地文件数据源读取数据
new File("data.txt").getAbsoluteFile())));
System.out.println(in.readDouble());
System.out.println(in.readUTF());
in.close();
}
}
/* output:
3.14159
That was pi
*/
5.2、字符流的输入和输出的结构
5.2.1、输入字符流
Reader 是所有的输入字符流的父类,它是一个抽象类。CharArrayReader、StringReader 是两种基本的介质流,它们分别将char数组、String中读取数据。PipedReader 是从与其它线程共用的管道中读取数据。BufferedReader 很明显就是一个装饰器,它和其子类负责装饰其它Reader对象,使用BufferedReader.readLine()是有效的读取行操作的方法。FilterReader 是所有自定义具体装饰流的父类,其子类PushbackReader 对Reader对象进行装饰,会增加一个行号。InputStreamReader 是一个连接字节流和字符流的桥梁,它将字节流转变为字符流。FileReader 可以说是一个达到此功能、常用的工具类,在其源代码中明显使用了将FileInputStream 转变为Reader 的方法。我们可以从这个类中得到一定的技巧。Reader 中各个类的用途和使用方法基本和InputStream 中的类使用一致。后面会有Reader 与InputStream 的对应关系。
5.2.2、字符输出流Writer
Writer 是所有的输出字符流的父类,它是一个抽象类。CharArrayWriter、StringWriter 是两种基本的介质流,它们分别向Char 数组、String 中写入数据。PipedWriter 是向与其它线程共用的管道中写入数据。BufferedWriter 是一个装饰器为Writer 提供缓冲功能。PrintWriter和PrintStream 极其类似,功能和使用也非常相似。OutputStreamWriter 是OutputStream到Writer转换的桥梁,它的子类FileWriter 其实就是一个实现此功能的具体类。功能和使用和OutputStream 比较相似。
转换流:有时我们必须把来自“字节”层次结构的类和“字符”层次结构中的类结合起来使用,为了实现,我们用到“设配器”类
InputStreamReader可以把InputStream转换为Reader而OutputStreamWriter可以把OutputStream转换为Writer
5.2.3、字符流简单的例子
详细IO流对象使用请参考jdk文档
package io.iosystem;
import java.io.*;
public class BasicFileOutput {
static String file = "BasicFileOutput.out";
public static void main(String[] args)
throws IOException {
BufferedReader in = new BufferedReader(
new StringReader("hello world"));
PrintWriter out = new PrintWriter(
new BufferedWriter(new FileWriter(file)));
int lineCout = 1;
String s;
while ((s = in.readLine()) != null) {
out.println(lineCout++ + ": " + s);
}
out.close();
System.out.println(BufferedInputFile.read(file));
}
}
/*output:
1: hello world
* */
5.2.4、RandomAccessFile类
该对象并不是流体系中的一员,其封装了字节流,同时还封装了一个缓冲区(字符数组),可以认为他是DataInputStream和DataOutputStream的整合,通过内部的指针来操作字符数组中的数据。 该对象特点:
该对象只能操作文件,所以构造函数接收两种类型的参数:a.字符串文件路径;b.File对象。该对象既可以对文件进行读操作,也能进行写操作,在进行对象实例化时可指定操作模式(r,rw)。
注意:该对象在实例化时,如果要操作的文件不存在,会自动创建;如果文件存在,写数据未指定位置,会从头开始写,即覆盖原有的内容。 可以用于多线程下载或多个线程同时写数据到文件。
5.2.5、RandomAccessFile例子
package io.nio;
import java.io.*;
import java.nio.IntBuffer;
import java.nio.channels.FileChannel;
// 比较nio与普通io的速度
public class MapperIO {
private static int numOfInts = 400000;
private static int numOfUbuffInts = 200000;
private abstract static class Tester {
private String name;
public Tester(String name) {
this.name = name;
}
public void runTest() {
System.out.print(name + ": ");
try {
long start = System.nanoTime();
test();
double duration = System.nanoTime() - start;
System.out.format("%.2f\n", duration/1.0e9);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public abstract void test() throws IOException;
}
private static Tester[] tests = {
new Tester("Stream Write") {
@Override
public void test() throws IOException {
DataOutputStream dos = new DataOutputStream(
new BufferedOutputStream(
new FileOutputStream(new File("temp.tmp"))));
for(int i = 0; i < numOfInts; i++) {
dos.writeInt(i);
}
dos.close();
}
} , new Tester("Mapped Write") {
@Override
public void test() throws IOException {
FileChannel fc =
new RandomAccessFile("temp.tmp", "rw").getChannel();
IntBuffer ib = fc.map(FileChannel.MapMode.READ_WRITE, 0, fc.size()).asIntBuffer();
for(int i = 0; i < numOfInts; i++) {
ib.put(i);
}
fc.close();
}
},
new Tester("Stream Read") {
@Override
public void test() throws IOException {
DataInputStream dis = new DataInputStream(
new BufferedInputStream(new FileInputStream("temp.tmp")));
for(int i = 0; i < numOfInts; i++) {
dis.readInt();
}
dis.close();
}
}, new Tester("Mapped Read") {
@Override
public void test() throws IOException {
FileChannel fc =
new RandomAccessFile("temp.tmp", "rw").getChannel();
IntBuffer ib = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size()).asIntBuffer();
while (ib.hasRemaining()){
ib.get();
}
fc.close();
}
}, new Tester("System Read/Write") {
@Override
public void test() throws IOException {
RandomAccessFile fc =
new RandomAccessFile("temp.tmp", "rw");
fc.writeInt('a');
for(int i = 0; i < numOfUbuffInts; i++) {
fc.seek(i*4);
fc.writeInt(fc.readInt());
}
fc.close();
}
},
new Tester("Mapped Read/Write") {
@Override
public void test() throws IOException {
FileChannel fc =
new RandomAccessFile("temp.tmp", "rw").getChannel();
IntBuffer ib = fc.map(FileChannel.MapMode.READ_WRITE, 0, fc.size()).asIntBuffer();
ib.put('b');
for(int i = 1; i < numOfUbuffInts; i++) {
ib.put(ib.get(i-1));
}
fc.close();
}
}
};
public static void main(String[] args) {
// 测试文件映射和IO操作性能
for (Tester test : tests) {
test.runTest();
}
}
}
/*output:
Stream Write: 0.03
Mapped Write: 0.01
Stream Read: 0.04
Mapped Read: 0.01
System Read/Write: 2.46
Mapped Read/Write: 0.01
* */
5.3、序列化例子
package io.serializable;
import java.io.*;
import java.util.Date;
import java.util.concurrent.TimeUnit;
public class Logon implements Serializable {
private Date date = new Date();
private String username;
private transient String password; // transient关键之表示序列化时不保存该字段信息
public Logon(String name, String pwd) {
username = name;
password = pwd;
}
@Override
public String toString() {
return "logon info: \n username: " + username +
"\n date: " + date + "\n password: " + password;
}
public static void main(String[] args) throws Exception {
Logon a = new Logon("Hulk", "myLittlePony");
System.out.println("logon a = " + a) ;
ObjectOutputStream o = new ObjectOutputStream(
new FileOutputStream("Logon.out"));
o.writeObject(a);
o.close();
TimeUnit.SECONDS.sleep(3);
ObjectInputStream in = new ObjectInputStream(
new FileInputStream("Logon.out"));
System.out.println("Recovering object at " + new Date());
a = (Logon) in.readObject();
System.out.println("logon a = " + a) ;
}
}
/*output:
logon a = logon info:
username: Hulk
date: Thu Jan 31 17:02:54 CST 2019
password: myLittlePony
Recovering object at Thu Jan 31 17:02:57 CST 2019
logon a = logon info:
username: Hulk
date: Thu Jan 31 17:02:54 CST 2019
password: null
*/
如果需要特定的序列化规则,可以实现Externalizable接口,根据自定义的规则重写writeExternal(ObjectOutput)和 readExternal(ObjectInput)方法。
参考博客:https://www.cnblogs.com/oubo/archive/2012/01/06/2394638.html
nio博客地址 https://www.iteye.com/magazines/132-Java-NIO