IO流
-
流动的是数据
- I:Input — 输入
- O: Output — 输出
-
分类
-
方向:以当前程序为基准
- 输入流:从外部流入到程序中
- 输出流:从程序内存流入到外部
-
数据单元: 字节流 & 字符流
四种流:
-
字节输入流 :InputStream & 字节输出流 :OutputStream
-
字符输入流 :Reader & 字符输出流 :Writer
-
以上四种基类都是抽象类
-
-
数据来源:硬盘、内存、网络、外部的设备
文件相关流
-
File +字符流组合:文本文件以字符形式读取
FileReader — 文件字符输入流 & FileWriter — 文件字符输出流
-
File + 字节流组合:图片、文本文档、音频、视频以字节形式读取
FileInputStream — 文件字节输入流 & FileOutputStream — 文件字节输出流
FileReader
-
构造方法:
-
FileReader(File file)
:读取指定的File文件。 -
FileReader(String fileName)
:通过给定文件路径名用来创建文件读取流
-
-
方法:
int read()
:读取单个字符 ,如果已到达流的末尾,则返回 -1int read(char[] cbuf)
:将字符读入数组 ,如果已到达流的末尾,则返回 -1;返回值代表读取的字符个数
注意:
-
默认使用
read()
读取数据时,是一个字符一个字符的读取,效率较低。为提高效率,手动创建一个字符数组(缓冲数组)提高效率 -
关于数组缓冲,为了提高效率以及内存利用率,每次读取不会清空数组,所以当我们拿数据时需要指定开始下标以及元素个数
public class FileReaderDemo1 { public static void main(String[] args) throws IOException { // 创建FileReader类 // 如果给定路径的文件不存在,则抛出java.io.FileNotFoundException异常 // FileReader reader = new FileReader("D:\\Javase\\Day18\\src\\aa\\aa.txt"); File file = new File("D:\\Javase\\Day19\\src\\aa\\a.txt"); FileReader reader = new FileReader(file); System.out.println(reader); // 没有重写,打印地址值 // read() // int i; // while ((i = reader.read()) != -1){ // System.out.println((char)i); // } // 手动指定一个缓冲数组,tigaoxiaolv char[] chars = new char[6]; // int num = reader.read(chars); // System.out.println(num+"---"+ Arrays.toString(chars)); int num; while((num = reader.read(chars))!=-1){ String str = new String(chars,0,num); // 指定开始下标以及元素个数 System.out.println(str); } } }
FileWriter
-
构造方法:
-
FileWriter(File file)
:根据给定的 File 对象构造一个 FileWriter 对象。 -
FileWriter(String fileName)
:根据给定的文件名构造一个 FileWriter 对象。 -
FileWriter(File file, boolean append)
: 如果第二个参数为 true,则追加写入内容。如果为false,则效果和没有第二个参数是一样的 -
FileWriter(String fileName, boolean append)
如果第二个参数为 true,则追加写入内容。如果为false,则效果和没有第二个参数是一样的
-
-
方法:
-
void write(int c)
:写入单个字符。 -
flush()
: 刷新该流的缓冲;将缓冲中的数据刷到文件 -
void write(String str)
:写入字符串 -
void write(char[] cbuf)
:写入字符数组。 -
close()
:关闭此流,但要先刷新它。
-
注意:
- 如果文件不存在,则创建新的文件;默认情况下如果文件已经存在,则生成新的空文件把原文件覆盖了;append表示追加写
- FileWriter类底层自定义了一个缓冲数组,当写出数据时,先将数据写入到缓冲中
- 因为流会占用较大的资源,所以每次使用完流必须通过close()方法关闭;如果后面不再需要流对象,则可置为null
- 关闭流之前,底层会自动将缓存中的数据刷新到文件中
FileInputStream
-
构造方法:
-
FileInputStream(File file)
:根据File来构建流对象 -
FileInputStream(String name)
:根据字符串路径名构造流对象
-
-
方法
int read()
:从此输入流中读取一个数据字节 ,已到达文件末尾,则返回 -1int read(byte[] b):
从此输入流中将最多 b.length 个字节的数据读入一个 byte 数组中;返回读入缓冲区的字节总数,如果读完了,返回-1
public class FileInputStreamDemo { public static void main(String[] args) throws IOException { FileInputStream in = new FileInputStream("D:\\Javase\\Day19\\src\\aa\\file.txt"); // read() --- 每次读取一个字节 // System.out.println((char)in.read()); // read(byte[] b) byte[] bytes = new byte[6]; // int num = in.read(bytes); // System.out.println(new String(bytes)); // 遍历 int num; StringBuilder builder = new StringBuilder(); while((num = in.read(bytes))!=-1){ builder.append(new String(bytes,0,num)); } System.out.println(builder); in.close(); } }
FileOutputStream
-
构造方法:
FileOutputStream(File file)
:创建一个向指定 File 对象表示的文件中写入数据的文件输出流。FileOutputStream(File file, boolean append)
: 创建一个向指定 File 对象表示的文件中写入数据的文件输出流。FileOutputStream(String name)
:创建一个向具有指定名称的文件中写入数据的输出文件流。FileOutputStream(String name, boolean append)
:创建一个向具有指定 name 的文件中写入数据的输出文件流。- 方法:
void write(int b)
:将指定字节写入此文件输出流。void write(byte[] b)
:将 b.length 个字节从指定 byte 数组写入此文件输出流中。
示例:复制图像
public class FileOutputStreamDemo { public static void main(String[] args) throws IOException { // 复制图片 File file = new File("D:\\Javase\\Day19\\src\\aa\\fig.png"); // 读取 FileInputStream in = new FileInputStream(file); // 输出 FileOutputStream out = new FileOutputStream("D:\\Javase\\Day19\\src\\aa\\bb\\"+file.getName()); byte[] bytes = new byte[6]; int num; while ((num = in.read(bytes)) != -1){ out.write(bytes,0,num); } in.close(); out.close(); } }
流的关闭异常处理
-
外置定义流对象,初始值给为null
-
try catch包裹异常代码
-
finally中关闭流,但是调用close方法之前,需要确定流对象不为null;否则会抛出空指针异常
-
因为close也有异常,也需要try catch捕获
-
finally中为了释放对象资源,将流对象设置为null
示例:
public class FileWriterDemo2 {
public static void main(String[] args) {
// 创建FileWriter对象
// 如果文件不存在,则创建新的文件
FileWriter writer = null;
try {
writer = new FileWriter("D:\\Javase\\Day19\\src\\aa\\file.txt");
// write()
writer.write('a'); // char -- int 自动类型转换
writer.write("\nhello java!\n");
char[] chars = new char[]{'e','n','d','!'};
writer.write(chars);
writer.flush(); // 刷新数据
} catch (IOException e) {
e.printStackTrace();
} finally {
if (writer != null) {
try {
writer.close();
} catch (IOException e) {
e.printStackTrace();
} finally {
writer = null;
}
}
}
}
}
IO 缓冲流
-
字节流:BufferedInputStream & BufferedOutputStream
-
字符流:BufferedReader & BufferedWriter
缓冲字节输入流: BufferedInputStream
-
构造方法:
BufferedInputStream(InputStream in)
:将参数字节输入流in存储下来,在in的基础上增加了缓冲BufferedInputStream(InputStream in, int size)
:创建具有指定缓冲区大小的 BufferedInputStream 并保存其参数,即输入流 in,以便将来使用
-
方法:
int read()
:从此输入流中读取一个数据字节 ,已到达文件末尾,则返回 -1int read(byte[] b):
从此输入流中将最多 b.length 个字节的数据读入一个 byte 数组中;返回读入缓冲区的字节总数,如果读完了,返回-1
public class BufferedInputStreamDemo { public static void main(String[] args) throws IOException { BufferedInputStream bin = new BufferedInputStream( new FileInputStream("D:\\Javase\\Day19\\src\\aa\\file.txt")); // read() int i; while((i = bin.read()) != -1){ System.out.println((char) i); } } }
缓冲字节输出流: BufferedOutputStream
-
构造方法:
BufferedOutputStream(OutputStream out):
创建一个新的缓冲输出流,以将数据写入指定的底层输出流。- 方法:同 FileOutputStream
public class BufferedOutputStreamDemo { public static void main(String[] args) throws IOException { BufferedOutputStream bout = new BufferedOutputStream( new FileOutputStream("D:\\Javase\\Day19\\src\\aa\\file2.txt")); // write() bout.write("alksdjfk".getBytes()); // 因为底层设置了缓冲区,所以需要flush()将缓冲区的数据刷新到文件中 bout.flush(); // 关流 bout.close(); } }
注意:
-
缓冲流实际上是在字节流的基础上包装了关于缓冲区的操作;但是这些操作都被隐藏了,对于用户来说,使用缓冲流其实和字节流基本是一致的;
-
缓冲流效率上要比字节流要高
缓冲字符输入流:BufferedReader
-
构造方法:
BufferedReader (Reader in)
:创建一个使用默认大小输入缓冲区的缓冲字符输入流。
-
方法 — 在FileReader上新增方法
String readLine()
:读取一个文本行。如果已到达流末尾,则返回 null
public class BufferedReaderDemo { public static void main(String[] args) throws IOException { // BufferedReader BufferedReader reader = new BufferedReader( new FileReader("D:\\Javase\\Day19\\src\\aa\\file.txt")); // 读取数据 // String line = reader.readLine(); // System.out.println(line); String line = null; // 遍历读取所有内容 // readLine方法当读取完成则返回null while((line = reader.readLine()) != null){ System.out.println(line); } } }
缓冲字符输出流:BufferedWriter
-
构造方法
BufferedWriter (Writer in)
:根据Writer实现类创建一个带有缓冲区的缓冲流
-
方法 — 在FileWriter上新增方法:
void newLine()
:写入一个行分隔符。
public class BufferedWriterDemo { public static void main(String[] args) throws IOException { // 创建缓冲输出流对象 BufferedWriter writer = new BufferedWriter( new FileWriter("D:\\Javase\\Day19\\src\\aa\\file1.txt") ); // 写出数据到文件 writer.write("hello java"); writer.newLine(); writer.write("end"); // 刷新 writer.flush(); // 关闭流 writer.close(); } }
总结:如果在使用流读取或者写入数据时,为提高效率,建议使用缓冲流
转换流
-
字符流操作文件,如果是中文可能出现中文乱码问题
-
记事本编辑的文件编码是GBK(中文占用两个字节)
-
idea用的编码是UTF-8(中文占用三个字节)
-
概述:字节和字符之间的相互转换
- 字节 — > 字符:
InputStreamReader
- 字符 — > 字节:
OutputStreamWriter
- 字节 — > 字符:
InputStreamReader
- 构造方法:
InputStreamReader (InputStream in, String charsetName)
: 创建使用指定字符集的 InputStreamReader。
OutputStreamWriter
- 构造方法:
OutputStreamWriter (OutputStream out, String charsetName)
:创建使用指定字符集的 OutputStreamWriter。
public class FileCopy {
public static void main(String[] args) throws IOException {
// 文件复制
// 定义输入流
FileInputStream in = new FileInputStream("D:\\Javase\\Day19\\src\\aa\\file.txt");
InputStreamReader reader = new InputStreamReader(in,"GBK");
// 定义输出流
FileOutputStream out = new FileOutputStream("D:\\Javase\\Day19\\src\\aa\\file1.txt");
OutputStreamWriter writer = new OutputStreamWriter(out,"GBK");
// 指定一个缓冲数组
char[] chars = new char[6];
int num;
while((num = reader.read(chars)) != -1){
writer.write(chars,0,num);
}
// in.close()
reader.close();
// out.close()
// 因为out是writer包裹的一个类,当调用writer的close()时
// 底层会默认调用out的close()
writer.close();
}
}
其他流
-
内存
-
StringReader:用来读取字符串的流
构造方法:
StringReader(String s)
:创建一个新字符串 reader。
-
StringWriter:写出字符串
构造方法:
-
StringWriter()
: 使用默认初始字符串缓冲区大小创建一个新字符串 writer。 -
StringWriter(int initialSize)
:使用指定初始字符串缓冲区大小创建一个新字符串 writer。public class StringReaderDemo { public static void main(String[] args) throws IOException { String str = "hello java"; StringReader reader = new StringReader(str); int ch; while((ch = reader.read()) != -1){ System.out.println((char) ch); } } }
-
-
-
外部设备
-
System.in : 标准输入流
-
System.out: 标准输出流
-
System.err : 标准错误流 红色字体
public class PrintContent { public static void main(String[] args) throws IOException { // 从键盘获取信息存入文档 BufferedInputStream bin = new BufferedInputStream(System.in); BufferedOutputStream bout = new BufferedOutputStream( new FileOutputStream("D:\\Javase\\Day19\\src\\aa\\fileTest.txt")); int num; while((num = bin.read()) != '#'){ bout.write((char) num); } bin.close(); bout.close(); } }
-
-
打印流
-
PrintStream:打印流:提供了多种print方法,用于对各种数据类型进行输出打印
-
PrintWriter:适用处理字符相关的内容
-
序列化和反序列化
- 序列化 —
ObjectOutputStream
:将对象当前的状态存储下来 - 反序列化 —
ObjectInputStream
:将存储对象状态恢复回来 - 常用于网络传输
ObjectOutputStream
-
将 Java 对象的基本数据类型和图形写入 OutputStream
-
前提:
-
要序列化的对象,必须实现一个接口Serializable;该接口中没有任何的方法,它的作用时用来做标识;如果没有实现该接口,抛出异常 — NotSerializableException
-
构造方法:
ObjectOutputStream(OutputStream out)
:传入参数用来指定序列化之后的内容传到什么位置
-
方法:
void writeObject(Object obj)
:将指定的对象写入 ObjectOutputStream。
ObjectInputStream
-
构造方法
ObjectInputStream(InputStream in)
:创建从指定 InputStream 读取的 ObjectInputStream。
-
方法:
Object readObject()
: 从 ObjectInputStream 读取对象。
package cn.zss.io.xuliehua;
import java.io.Serializable;
class Person implements Serializable {
private static final long serialVersionUID = 1L; // 如果没有手动给定,程序会自动生成
private String name;
private int age;
private transient char gender; // 不会被序列化
private static final int eyeNum = 2; // 不会被序列化
public Person(String name, int age){
this.name = name;
this.age = age;
}
public static int getEyeNum() {
return eyeNum;
}
public void setGender(char gender) {
this.gender = gender;
}
public char getGender() {
return gender;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
package cn.zss.io.xuliehua;
import java.io.*;
public class PersonDemo {
public static void main(String[] args) throws IOException {
Person person = new Person("Sam",20);
person.setGender('男');
System.out.println(Person.getEyeNum());
// 将当前person对象进行序列化
// 指定序列化之后将内容存储到文件中
FileOutputStream fos = new FileOutputStream("D:\\Javase\\Day19\\src\\aa\\person.txt");
// 创建序列化对象
ObjectOutputStream out = new ObjectOutputStream(fos);
// 调用方法序列化指定对象
out.writeObject(person);
// 关闭流
out.close();
}
}
package cn.zss.io.xuliehua;
import java.io.*;
public class PersonDemo2 {
public static void main(String[] args) throws IOException, ClassNotFoundException {
// 反序列化
// 指定反序列化的文件
File file = new File("D:\\Javase\\Day19\\src\\aa\\person.txt");
// 输入流读取文件内容
FileInputStream in = new FileInputStream(file);
// 创建反序列化流
ObjectInputStream ois = new ObjectInputStream(in);
// 创建反序列化方法,来创建对象
Object o = ois.readObject();
// 强制转换为Person类型
Person p = (Person) o;
System.out.println(p);
System.out.println(Person.getEyeNum());
System.out.println(p.getGender()); // null
// 关流
ois.close();
}
}
注意:
-
静态变量不会被序列化
-
transient修饰的变量也不会被序列化;如果该对象反序列化之后,对于transient修饰的变量值会给数据类型默认值
-
serialVersionUID 版本号
-
当实现Serializable接口,默认会给当前对象新增一个成员:
private static final long serialVersionUID
; -
如果没有手动给定,程序会根据当前类的属性和方法自动生成;当类的属性或者方法发生变化时,该值相应也会发生变化;
-
反序列化一个对象时,会先和类中的版本号进行对比,如果版本号一致,可以序列化;如果对比不一致,则会抛出异常
-
-
序列化与反序列:第三方的插件 avro