1. 概念
Input
,从硬盘读取数据到内存
Output
,将内存中的数据输出到硬盘
- 代表数据源对象或者接收数据的接收端对象
- 本质是数据传输,根据传输的特性将流抽象为各种类,方便直观的进行数据操作
- 作用是建立数据源与目的地的数据输送通道
2. 分类
根据数据流向分为输入流、输出流
按照数据单位分为字节流 8bit(非文本)、字符流 16bit
根据角色分为节点流(直接作用于文件上也称为文件流)、处理流(作用在已有流的基础上,对已存在的流进行包装)
- 四个抽象基类,子类根据具体情况分派角色
- 处理数据单位划分
名称 | 特性 | 实例 |
---|---|---|
字节流 | 每次读写一个字节,有中文时会出现乱码 | InputStream/OutputStream |
字符流 | 每次读写两个字节(一个字符),可正确显示中文 | Reader/Write |
- 功能划分
名称 | 特性 | 实例 |
---|---|---|
节点流 | 从特定节点读写数据 | FileInputStream |
处理流 | 对已存在的流的连接与封装,通过封装的流的功能进行读写 | BufferReader |
3. 应用实例
流的操作一般分为4步
- File类的实例化
- 相应流的实例化
- 读入/写出操作
- 资源关闭
3.1 文件流
- File类
- FileInputStream/FileOutputStream以字节方式读写文件
- FileReader/FileWrite以字符方式读写文件
- 将硬盘文件以字符流形式读入内存
File file = new File("hello.txt");
try {
if (!file.exists()){
file.createNewFile();
}
} catch (IOException e) {
e.printStackTrace();
}
//创建字符读取流
FileReader reader = null;
try {
reader = new FileReader(file);
//数据读入,若到文件末尾返回-1
int data = reader.read();
while (data!=-1){
System.out.print((char) data);
//类似迭代器循环
data = reader.read();
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
//关闭流,必须手动关闭,未关闭会导致内存泄漏等问题
try {
if (reader!=null) {
reader.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
FileReader fileReader = null;
File file = new File("hello.txt");
fileReader = new FileReader(file);
char[] ch = new char[5];
int len;
while ((len = fileReader.read(ch)) != -1) {
//这里注意 i<len 否则数组五个空间覆盖时只会覆盖前三个,后面输出还是五个,会带着上一个数组中的后两个元素
for (int i = 0; i < len; i++) {
System.out.print(ch[i]);
}
}
if (fileReader!=null){
fileReader.close();
}
- 将内存文件写出到硬盘,纯文本文件使用字符流,非文本文件使用字节流
/*
* 写出的话文件不存在会自动帮我们创建
* 存在的话会对原有文件进行覆盖,因为默认追加append是false
*/
FileWriter fileWriter = null;
File file = new File("hello.txt");
// fileWriter = new FileWriter(file);
//表示是否在原有文件内容后进行追加写入
fileWriter = new FileWriter(file,true);
String str = "abcdefg";
fileWriter.write(str + "\n");
fileWriter.write(str);
if (fileWriter != null) {
fileWriter.close();
}
//图片视频是字节数据,需要使用字节流处理,字符流转为字节流即将char 改为 byte
//复制图片
//使用字节流复制文本文件是可以的,但也只是单纯的复制,在内存中读的时候会出现乱码
File img1 = new File("1.jpg");
File img2 = new File("2.jpg");
FileInputStream inputStream = new FileInputStream(img1);
FileOutputStream outputStream = new FileOutputStream(img2);
byte[] b = new byte[10];
int len;
while ((len=inputStream.read(b))!=-1){
for (int i = 0; i < len; i++) {
System.out.println(b[i]);
outputStream.write(b[i]);
}
}
inputStream.close();
outputStream.close();
3.2 缓冲流
- BufferedInputStream/BufferedOutputStream字节缓冲流
- BufferedReader/BufferedWrite字符缓冲流
- flush()方法,缓冲区数据写入流,先调用flush再调用close
- 处理流的一种是缓冲流Buffered…
真实开发时不会采用基本的文件节点流,因为效率较差,往往采用缓冲流进行处理,提高流的读取写入速度
原因是内部提供了一个8192字节缓冲区常量
File file1 = new File("1.jpg");
File file2 = new File("2.jpg");
FileInputStream inputStream = new FileInputStream(file1);
FileOutputStream outputStream = new FileOutputStream(file2);
//不可直接作用于文件上,只可以作用于已存在的流上
BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream);
BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(outputStream);
//具体读取写入过程
byte[] b = new byte[10];
int len;
while ((len=bufferedInputStream.read(b))!=-1){
for (int i = 0; i < len; i++) {
bufferedOutputStream.write(b[i]);
}
}
//关闭时先关闭外层流,再关闭内层的流,因为处理流是包着节点流的
bufferedInputStream.close();
bufferedOutputStream.close();
//其实在关闭外层流的同时,内层流也会自动关闭,所以可以省略不写
// inputStream.close();
// outputStream.close();
3.3 转换流
- InputStreamReader字节流中读取字符
- OutputStreamWrite字符写入字节流
- 转换流(处理流的一种)
提供字节流和字符流之间的一个转换
解码:字节—>字符(看不懂—>看得懂)
/*
* 转换流使用,属于字符流,操作 char 型数组
* InputStreamReader 将字节的输入流转换为字符的输入流
* OutputStreamWriter 将字符的输出流转换为字节的输出流
*/
File file = new File("hello.txt");
File outhello = new File("outhello.txt");
FileInputStream fileInputStream = new FileInputStream(file);
FileOutputStream fileOutputStream = new FileOutputStream(outhello);
//系统默认字符集看当前IDE设定,实际编码应该按照文件存储时的字符集编码来设定参数
InputStreamReader inputStreamReader = new InputStreamReader(fileInputStream, "UTF-8");
OutputStreamWriter outputStreamWriter = new OutputStreamWriter(fileOutputStream,"gbk");
char[] ch = new char[20];
int len;
while ((len = inputStreamReader.read(ch)) != -1) {
// for (int i = 0; i <len ; i++) {
// System.out.println(ch[i]);
// }
String str = new String(ch, 0, len);
System.out.print(str);
outputStreamWriter.write(str);
}
inputStreamReader.close();
outputStreamWriter.close();
3.4 标准输入输出流
System.in//键盘输入
System.out//控制台输出
3.5 打印流
打印只能是输出
PrintStream
PrintWriter
3.6 数据流
用于读写基本数据类型的变量或字符串
DataInputStream
DataOutputStream
3.7 对象流
对象序列化机制:允许把内存中的Java对象转换成与平台无关的二进制流,从而允许把这种二进制流持久地保存在磁盘上,或通过网络将这种二进制流传输到另一个网络节点。当其它程序获取了这种二进制流,就可以恢复成原来的Java对象
序列化的好处在于可将任何实现了 Serializable接口的对象转化为字节数据,使其在保存和传输时可被还原
序列化是RMI(Remote Method Invoke-远程方法调用)过程的参数和返回值都必须实现的机制,而RMI是 JavaEE的基础。因此序列化机制是JavaEE平台的基础
如果需要让某个对象支持序列化机制,则必须让对象所属的类及其属性是可序列化的 (基本数据类型都是可序列化的),为了让某个类是可序列化的,该类必须实现如下两个接口之一。否则,会抛出 NotSerializableEXception异常
Serializable
Externalizable
凡是实现 Serializable接口的类都有一个表示序列化版本标识符的静态变量serialVersionUID用来表明类的不同版本之间的兼容性。简言之,其目的是以序列化对象进行版本控制,有关各版本反序列化时是否兼容。
如果类没有显示定义这个静态变量,它的值是Java运行时环境根据类的内部细节自动生成的,一旦类原始类进行调整,则无法再反序列化。
若类的实例变量做了修改,serialVersionUID可能发生变化。故建议,进行显式声明。
简单来说,Java的序列化机制是通过在运行时判断类的serialVersionUID来验证版本一致性的。在进行反序列化时,JVM会把传来的字节流中的serialVersionUID与本地相应实体类的 serialVersionUID进行比较,如果相同就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异常。(InvalidCastException)
/*
* 序列化时一定要先实现Serializable接口(标识接口)
* 必须实现进入的一个全局常量属性,序列版本号,设定唯一标识防止还原识别时混乱,详情见Serializable源码说明
*/
public class Person implements Serializable {
//序列版本号,自定义一个long类型值
public static final long serialVersionUID = 4752525455642L;
private String name;
private int age;
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Person{" + "name='" + name + '\'' + ", age=" + age + '}';
}
}
/*
* 将对象读出写入到数据源中
* 序列化:ObjectOutputStream保存内存中的Java对象或基本数据类型到磁盘中
* 反序列化:ObjectInputStream从磁盘中读取Java对象或基本数据类型到内存中
* 对于声明为 static 和 transient无效
*/
//序列化操作
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("data.dat"));
// objectOutputStream.writeObject("你好世界");
// //刷新操作
// objectOutputStream.flush();
objectOutputStream.writeObject(new Person("tylt", 18));
objectOutputStream.flush();
objectOutputStream.close();
//反序列化操作
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("data.dat"));
Object readObject = objectInputStream.readObject();
// String str = (String) readObject;
// System.out.println(str);
Person p = (Person) readObject;
System.out.println(p);
objectInputStream.close();
3.8 随机存取文件流
RandomAccessFile
直接继承Object,既可作为输入流也可输出流
/*
* r:只读(不会创建文件,只会读取已经存在的文件)
* rw:读写
* rwd:读写+同步内容更新
* rws:读写+同步内容更新+元数据更新
*/
File img = new File("1.jpg");
File outimg = new File("3.jpg");
RandomAccessFile accessFile = new RandomAccessFile(img,"r");
RandomAccessFile accessOutFile = new RandomAccessFile(outimg,"rw");
byte[] b = new byte[1024];
int len;
while ((len=accessFile.read(b))!=-1){
for (int i = 0; i < len; i++) {
System.out.println(b[i]);
accessOutFile.write(b[i]);
}
}
accessFile.close();
accessOutFile.close();
4. Java NIO
Java NIO(New IO,Non-Blocking IO) NIO将以更加高效的方式进行文件的读写操作,因为IO是面向流的,而NIO是面向缓冲区的
Java API中提供了两套NIO,一套是针对标准输入输出NIO,另一套就是网络编程NIO
早期的Java只提供了一个File类来访问文件系统,但File类的功能比较有限,所提供的方法性能也不高。大多数方法在出错时仅返回失败,并不会提供异常信息。
NIO.2为了弥补这种不足,引入了Path接口,代表一个平台无关的平台路径,描述了目录结构中文件的位置。Path可以看成是File类的升级版本,实际引用的资源也可以不存在。
/*
* Files、Paths工具类
* k
*/
Path path = Paths.get("hello.txt");
System.out.println(path);