IO流是什么
I:input
o:output
通过IO可以完成硬盘文件的读和写
IO流分类
按流的方向分类:
- 往内存中去,叫输入(Input),或者叫读(Read) 输入流
- 从内存中出来,叫做(Output),或者叫写(Write) 输出流
按读取数据方式不同分类:
- 按照字节的方式读取数据,一次读取一个字节(这种流是万能的,可以读取文本、图片、音视频) 字节流
- 按照字符的方式读取数据,一次读取一个字符(这种流只能读取普通txt文件) 字符流
IO流四大家族
四大家族的首领:
以stream结尾的是字节流
java.io.InputStream 字节输入流
java.io.OutputStream 字节输出流
以Reader/Writer结尾的是字符流
java.io.Reader 字符输入流
java.io.Writer 字符输出流
以上四个类均是抽象类
所有的流都实现了java.io.Closeable接口,都是可关闭的,都有close()方法,流是一个内存和硬盘之间的通道,如果一直保持打开而不关闭,会耗费很多资源。
所有的输出流都实现了java.io.Flushable接口,都是可刷新的,都有flush方法,输出流在最终输出之后,一定要flush一下,把通道中剩余的未输出数据输出。
java.io包下需要掌握的流
文件专属
java.io.FileInputStream
java.io.FileOutputStream
java.io.FileReader
java.io.FileWriter
转换流:字节流转换位字符流
java.io.InputStreamReader
java.io.OutputStreamWriter
缓冲流
java.io.BufferedReader
java.io.BufferedWriter
java.io.BufferedInputStream
java.io.BufferedOutputStream
数据流
java.io.DataInputStream
java.io.DataOutputStream
标准输出流
java.io.ObjectInputStream
java.io.ObjectOutputStream
对象专属流
java.io.PrintWriter
java.io.PrintStream
FileInputStream类
构造方法
Constructor and Description |
---|
FileInputStream(File file) 通过打开与实际文件的连接创建一个 |
FileInputStream(FileDescriptor fdObj) 创建 |
FileInputStream(String name) 通过打开与实际文件的连接来创建一个 |
打开一个文件,并一个字节一个字节地读取数据
public class test{
public static void main(String[] args) {
FileInputStream fis = null;
try {
fis = new FileInputStream("C:\\Users\\dell\\Desktop\\demo.txt");
//也可以这样
//FileInputStream fis = new FileInputStream("C:/Users/dell/Desktop/demo.txt");
while(true){
int readData = fis.read();
//若读到文件末尾,fis.read方法会返回-1
if(readData == -1){
break;
}
System.out.println(readData);
}
} catch (IOException e) {
e.printStackTrace();
}finally{
if(fis!=null){
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
以上方法存在一个明显的缺点:一个字节一个字节地读取,硬盘和内存交互太频繁,大部分时间浪费在了交互上了。
read方法的重载方法中有一个read(byte [])方法,可以一次读多个字节。这个方法的返回值是每次读取到的字节数,若没有读取到字节,则返回-1。
修改后的程序的读取部分如下:
byte []bytes = new byte[4];
while(true){
int readCount = fis.read(bytes);
if(readCount == -1){
break;
}
System.out.println(new String(bytes));
这样做还有一个问题是:假设有下面这种情况,有6个字节abcdef,第一次读取后bytes中是[a,b,c,d],文件中还剩ef没有被读取,之后再次读取文件时,会读取ef,由于bytes已经满了,读取到的ef会覆盖掉ab,最后输出的将是efcd,这是我们不想看到的。
也就是说我们不应该将byte数组全部转换为字符串输出,而是读到几个字节,就将几个字节转换为字符串,需要调用构造方法
String(byte[] bytes, int begin,int length );
修改后的程序如下(最终版)
byte []bytes = new byte[4];
while(true){
int readCount = fis.read(bytes);
if(readCount == -1){
break;
}
System.out.println(new String(bytes,0,readCount));
}
FileInputStream中的其他方法
int available()获取当前文件中还未读的字节数,便于创建合适大小的byte数组(不适合大文件)
long skip(int num)跳过几个字节不读取
FIleOutputStream类
构造方法 |
---|
FileOutputStream(File file) 创建文件输出流以写入由指定的 |
FileOutputStream(File file, boolean append) 创建文件输出流以写入由指定的 |
FileOutputStream(FileDescriptor fdObj) 创建文件输出流以写入指定的文件描述符,表示与文件系统中实际文件的现有连接。 |
FileOutputStream(String name) 创建文件输出流以指定的名称写入文件。 |
FileOutputStream(String name, boolean append) 创建文件输出流以指定的名称写入文件。 |
模板
public class test{
public static void main(String[] args) {
FileOutputStream fos = null;
try {
//demo.txt不存在会先创建一个!
//若存在,则先将原文件清空
fos = new FileOutputStream("C:\\Users\\dell\\Desktop\\demo.txt");
byte []bytes = {97,98,99,100,101,102};
//将byte数组全部写入
fos.write(bytes);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally{
if(fos !=null){
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
如果是以追加方式,可以使用构造函数
fos = new FileOutputStream("C:\\Users\\dell\\Desktop\\demo.txt",true);
FileReader类
文件文本输入流,只能读取普通文本。
读取文本内容时,比较方便,快捷。
构造方法 |
---|
FileReader(File file) 创建一个新的 FileReader ,给出 File读取。 |
FileReader(FileDescriptor fd) 创建一个新的 FileReader ,给定 FileDescriptor读取。 |
FileReader(String fileName) 创建一个新的 FileReader ,给定要读取的文件的名称。 |
读取文件:和FileInputStream类似,只不过byte数组变成char数组了
public class test{
public static void main(String[] args){
FileReader fr = null;
try {
fr = new FileReader("Demo/src/demo.txt");
int readCount = 0;
char []chars = new char[4];//一次读四个字符
while((readCount = fr.read(chars)) != -1){
System.out.println(new String(chars,0,readCount));
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally{
if(fr != null){
try {
fr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
输出结果为
中文是两个字节,也被读进内存了,如果是用FileInputStream则可能会出现乱码,因为FileInputStream每次只读一个字节。
FileWriter类
文件字符输出流。
只能输出普通文本。
除了简单的输出到文件,FileWrite还提供了很多更方便的方法
例如
write(char []args)
将字符数组输出到文件
write(String s)
将字符串输出到文件
构造方法
FileWriter(File file) 给一个File对象构造一个FileWriter对象。 |
FileWriter(File file, boolean append) 给一个File对象构造一个FileWriter对象。 |
FileWriter(FileDescriptor fd) 构造与文件描述符关联的FileWriter对象。 |
FileWriter(String fileName) 构造一个给定文件名的FileWriter对象。 |
FileWriter(String fileName, boolean append) 构造一个FileWriter对象,给出一个带有布尔值的文件名,表示是否附加写入的数据。 |
BufferedReader类
带有缓冲区的字符输入流。
使用这个流的时候不需要自定义char数组,或者byte数组。
构造函数
BufferedReader(Reader in) 创建一个使用默认大小输入缓冲区的缓冲字符留对象
BufferedReader(Reader in, int sz) 创建一个使用指定大小输入缓冲区的缓冲字符留对象
新概念:当一个流的构造方法中需要一个流的时候,这个被传进来的流叫做节点流,外部负责包装的这个流叫包装流
关闭包装流,其里面的节点流自动关闭!
常用方法
1.readLine() 读取一行,到达文件末尾返回null
2.使用字节流来初始化BufferedReader的方法(需要先将字节流对象转换为字符流对象才可以)
首先创建一个字节流对象,用这个对象来初始化一个InputStreamReader对象,再用这个InputStreamReader对象来初始化BufferedReader对象即可
BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(文件路径)))
BufferedWrite类
与BufferedReader类类似
DataOutputStream类
这个流可以将数据连同数据的类型一并写入文件。
注意:这个文件不是普通的文本文档(这个文件使用记事本打开会是乱码)
构造方法
DataOutputStream(OutputStream out);
常用方法
public class test{
public static void main(String[] args) {
DataOutputStream dos = null;
try {
dos = new DataOutputStream(new FileOutputStream("data"));
int i =1;
long l = 2L;
short s = 3;
byte b = 4;
float f= 3.0F;
double d = 3.14;
boolean gender = false;
char c= 'a';
dos.writeInt(i);
dos.writeLong(l);
dos.writeShort(s);
dos.writeByte(b);
dos.writeFloat(f);
dos.writeDouble(d);
dos.writeBoolean(gender);
dos.writeChar(c);
//清空缓冲区
dos.flush();
//关闭流
dos.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
打开输出的data文件后发现是乱码
DataInputStream类
DataOutputStream 写的文件,只能是用DataInputStream去读取文件。并且读的顺序需要和写的顺序一致才可以正常取出数据。
读取文件
public class test{
public static void main(String[] args) {
DataInputStream dos = null;
try {
dos = new DataInputStream(new FileInputStream("data"));
int i =dos.readInt();
long l = dos.readLong();
short s = dos.readShort();
byte b = dos.readByte();
float f= dos.readFloat();
double d = dos.readDouble();
boolean gender = dos.readBoolean();
char c= dos.readChar();
System.out.println(i);
System.out.println(l);
System.out.println(s);
System.out.println(b);
System.out.println(f);
System.out.println(d);
System.out.println(gender);
System.out.println(c);
//关闭流
dos.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
PrintStream类
标准字节输出流,默认输出到控制台
构造函数
PrintStream(OutputStream out);
最常用的一个类。。。
public class test{
public static void main(String[] args) {
//out本来就是一个PrintStream类型的属性
PrintStream ps = System.out;
ps.println("hello world");
}
}
重定向输出
public class test{
public static void main(String[] args) {
try {
//创建一个通向log文件的流
PrintStream printStream = new PrintStream(new FileOutputStream("log"));
//将输出方向修改到"log"文件
System.setOut(printStream);
System.out.println("hello world");
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
}
实现一个日志工具
public class test{
public static void log(String msg) {
try {
//创建一个指向log.txt的对象
PrintStream ps = new PrintStream(new FileOutputStream("log.txt"));
//修改输出方向为log.txt
System.setOut(ps);
//获取当前日期,并格式化
Date date = new Date();
SimpleDateFormat sdfs = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String nowTime = sdfs.format(date);
//输出当前时刻发生的事情
System.out.println(nowTime+":"+msg);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
}
File类
File类和四大家族没有关系,所以File类不能完成文件的读和写
File是文件和目录路径名的抽象表示形式,例如:
C:\Drivers 这是一个File对象
C:\Drivers\demo.txt 这也是一个File对象
File类常用的方法
boolean exists()
判断文件是否存在
boolean createNewFile()
创建一个文件,文件名为File对象初始化时的参数
File f = new File("Demo/src/file"); f.createNewFile();
boolean mkdirs()
以多层目录的形式创建
File f = new File("Demo/src/a/b/c/d"); f.mkdirs();
String getParent()
获取文件的父路径
String getAbsolutePath()
获取绝对路径
boolean delete()
删除文件
String getName()
获取文件名
boolean isDirectory()
判断是否是一个目录
boolean isFile()
判断是否是一个文件
long lastModified()
返回文件最后修改时间,返回值为毫秒,可以作为参数创建一个Date对象来返回日期。
long length()
获取文件大小
File[] listFiles()
获取当前目录下的子文件
序列化与反序列化
序列化(Serialize):java对象存储到文件中,将java对象的状态保存下来的过程
反序列化(DeSerialize):将硬盘上的数据重新恢复到内存中,恢复成java对象
- 使用ObjectOutputStream和ObjectInputStream分别进行序列化和反序列化
- 通过序列化和反序列化的对象,必须实现Serializable接口
Serializable接口如下:
这个接口啥都没有,只是起到标识的作用,这个标识是给jvm参考的,jvm会给实现Serializable的类自动生成一个序列化版本号,就算两个类名字一模一样,他们的序列化版本号也是不同的
自动生成序列化版本号有什么缺陷呢?
一旦代码确定后,不能进行后续修改。因为只要进行修改,必然重新编译,此时会生成全新的序列化版本号
序列化:
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.Serializable;
public class test{
public static void main(String []args) throws IOException {
Student s = new Student(1111,"张三");
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("students"));
//写入文件
oos.writeObject(s);
//刷新缓冲区
oos.flush();
//关闭流
oos.close();
}
}
//要进行序列化的类必须
class Student implements Serializable {
private int no;
private String name;
public Student(int no, String name) {
this.no = no;
this.name = name;
}
public Student() {
}
}
反序列化:
public class test{
public static void main(String []args) throws IOException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("students"));
try {
Object obj = ois.readObject();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
ois.close();
}
}
一次性序列化多个对象
writeObject方法可以接受一个集合作为参数
public class test{
public static void main(String []args) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("students"));
ArrayList<Student> sList = new ArrayList<>();
sList.add(new Student(1,"张三"));
sList.add(new Student(2,"李四"));
sList.add(new Student(3,"王五"));
oos.writeObject(sList);
oos.flush();
oos.close();
}
}
一次性反序列化多个对象
public class test{
public static void main(String []args) throws IOException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("students"));
try {
Object obj = ois.readObject();
if(obj instanceof List){
List<Student> list = (List<Student>) obj;
for(Object it: list){
System.out.println(it);
}
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
transient关键字
加在属性前面,默认该属性不参与序列化
例如
private transient String name;
在序列化的过程中,name的值不会保存到文件里。
自动生成序列化版本号的缺陷
一旦代码确定后,不能进行后续修改。因为只要进行修改,必然重新编译,此时会生成全新的序列化版本号,jvm会认为这是一个全新的类。
所以我们需要给实现Serializable接口的类提供一个固定不变的序列化版本号。
声明序列化版本号的语法:
private static final long serialVersionUID = 8683452581122892189L;
//jvm识别一个类的时候,先通过类名,如果类名一致再通过序列化版本号进行识别
使用IDEA自动生成序列化版本号
找到设置
打上对勾后,点击apply,就可以了。
在我们想生成的类的类名处按alt+Enter,即可生成序列化版本号