本节内容:文件处理
本文大部分内容摘自:原文链接:https://blog.csdn.net/qq_44715943/article/details/116501936
一、什么是IO流
- I : Input
- O : Output
通过IO可以完成硬盘文件的读和写。
二、IO流的分类
按照 流的方向 进行分类:
以内存作为参照物:
往内存中去:叫做输入(Input)。或者叫做读(Read)。
从内存中出来:叫做输出(Output)。或者叫做写(Write)。
通过这张图更好地理解。
按照 读取数据方式 不同进行分类:
1. 按照 字节 的方式读取数据,一次读取1个字节byte,等同于一次读取8个二进制位。
这种流是万能的,什么类型的文件都可以读取。包括:文本文件,图片,声音文件,视频文件 等…
eg.
假设文件file1.txt,采用字节流的话是这样读的:
a中国bc张三fe
第一次读:一个字节,正好读到’a’
第二次读:一个字节,正好读到’中’字符的一半。
第三次读:一个字节,正好读到’中’字符的另外一半。
2. 按照 字符 的方式读取数据的,一次读取一个字符.
这种流是为了方便读取 普通文本文件 而存在的,这种流不能读取:图片、声音、视频等文件。只能读取 纯文本文件,连word文件都无法读取。
注意:
纯文本文件,不单单是.txt文件,还包括 .java、.ini、.py 。总之只要 能用记事本打开 的文件都是普通文本文件。
eg.
假设文件file1.txt,采用字符流的话是这样读的:
a中国bc张三fe
第一次读:'a’字符('a’字符在windows系统中占用1个字节。)
第二次读:'中’字符('中’字符在windows系统中占用2个字节。)
综上所述:流的分类:
输入流、输出流
字节流、字符流
三、IO流四大家族首领
字节流
java.io.InputStream 字节输入流
java.io.OutputStream 字节输出流
字符流
java.io.Reader 字符输入流
java.io.Writer 字符输出流
注意:
四大家族的首领都是抽象类。(abstract class)
所有的流都实现了:
java.io.Closeable接口,都是可关闭的,都有 close() 方法。
流是一个管道,这个是内存和硬盘之间的通道,用完之后一定要关闭,不然会耗费(占用)很多资源。养成好习惯,用完流一定要关闭。
所有的 输出流 都实现了:
java.io.Flushable接口,都是可刷新的,都有 flush() 方法。
养成一个好习惯,输出流在最终输出之后,一定要记得flush()刷新一下。这个刷新表示将通道/管道当中剩余未输出的数据强行输出完(清空管道!)刷新的作用就是清空管道。
ps:如果没有flush()可能会导致丢失数据。
在java中只要“类名”以 Stream 结尾的都是字节流。以“ Reader/Writer ”结尾的都是字符流。
四、Java要掌握的流(16个)
文件专属:
- 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.PrintWriter
- java.io.PrintStream(掌握)
对象专属流:
- java.io.ObjectInputStream(掌握)
- java.io.ObjectOutputStream(掌握)
File文件类
- java.io.File
Windows/Linux 小tips:
Windows:D:\Soft\QQ\Plugin
Linux: D:/Soft/QQ/Plugin
注意: Windows各个文件之间分隔符为:” \ “;Linux各个文件之间分割符为:” / “
下面我将对上课所讲的内容进行详细描述:
1. 应用无法直接与硬件相交互,要通过操作系统。
2. 什么样的inputstream写就要用什么样的outputstream读。
java.io.FileReader
FileReader 文件字符输入流,只能读取普通文本。读取文本内容时,比较方便,快捷。
构造方法
FileReader(String fileName) name为文件路径
FileReader(File file)
方法
int read() 读取一个字符,返回值为该字符ASCII码;读到文件末尾返回-1
int read(char[] c) 读c数组长度的字节到c数组中,返回值为读到的字符个数;读到文件末尾返回-1
int read(char[] c, int off, int len) 从c素组off位置读len长度的字符到c数组中,返回值为读到的字符个数;读到文件末尾返回-1
long skip(long n) 跳过n个字符
void close() 关闭文件输入流
import java.io.FileReader;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Scanner;
//FileNotFoundException是IOException的一个子类,
public class Main {
public static void main(String[] args) throws IOException
{
FileReader fileIn = new FileReader("d://1.txt");
//假设我在文本中写 1hello
Scanner in = new Scanner(fileIn);
int i = fileIn.read();//读取一个字符,此处为1
String j = in.next();//读取一个字符串,此处为hello
System.out.println(i);
System.out.println(j);
}
}
输出:
hello
49
注意:
1. 该输入流需要抛出两个异常,一个是FileNotFoundException(找不到文件),一个是IOException(文件内部可能为空),但由于FileNotFoundException是IOException的一个子类,我们只需要throws IOException即可。
2. 注意到有两种读取方式
int i = fileIn.read();//读取一个字符,此处为1
String j = in.next();//读取一个字符串,此处为hello
这两种读取方式是不同的。
第一行使用FileReader类和文件路径“d://1.txt”创建了一个文件读取器对象,它将打开并读取文本文件。这个对象被传递给Scanner对象,以便从文本文件中读取数据。Scanner类允许通过next()或nextLine()等方法遍历文本文件,并将其转换为程序内部的数据类型,比如字符串类型、整数类型等。
第二行使用了FileReader类的read()方法直接从文件中读取单个字符并返回其ASCII值作为int类型。可以在循环结构中使用这个方法来逐个读取所有的字符。
在读取大量文本时,使用Scanner方法较为常见,因为它支持按诸如空格或特定字符(例如逗号)分割文本内容,并且提供了许多方便的方法来处理各种数据类型。而使用read()方法则非常适合读取较小的文本文件,或者需要精细控制读取进程的情况。
java.io.PrintWriter
public class Main {
public static void main(String[] args) throws FileNotFoundException {
//使用PrintWriter写入文件
PrintWriter out = new PrintWriter("d://1.txt");
out.print("Hello, ");
out.println("world!");
}
}
这段代码的作用是在文本文件1中写入Hello,world!。、
但是值得注意的是,该程序执行完之后并没有将Hello,world!写入到文件中,这是因为将更改写入到磁盘之前,需要先关闭文件流或手动调用flush()方法。否则,可能会出现数据丢失或无法正常保存数据的情况。
加上一句即可
out.close();
或者用try-catch,
public class Main {
public static void main(String[] args) throws FileNotFoundException {
// 使用PrintWriter写入文件
try (PrintWriter out = new PrintWriter("d://1.txt")) {
out.print("Hello, ");
out.println("world!");
} catch (IOException e) {
System.err.println(e);
}
}
}
这个代码段使用了Java 7或更高版本中的新特性,称为"try-with-resources"语句。它允许您在代码块结束时自动关闭资源,而无需显式地调用close()方法或清理资源。
在这个例子中,PrintWriter对象被包含在try-with-resources块中。当代码块执行完成时,PrintWriter将自动关闭和释放资源,以便其他程序可以访问该文件。由于printWriter被包含在try-with-resources块中,因此你不需要手动关闭该流,即便发生异常也会自动关闭该流。
值得注意的是,在try-with-resources块之外创建的对象,需要在使用完后进行显式的关闭操作。例如,如果您使用FileOutputStream来写入文件,则仍然需要在try-with-resources块之外调用close()方法来关闭文件输出流并释放资源。
java.io.FileOutputStream
这是一种字节输出流。
构造方法
FileOutputStream(String name) name为文件路径
FileOutputStream(String name, boolean append) name为文件路径,append为true表示在文件末尾追加;为false表示清空文件内容,重新写入
FileOutputStream(File file)
FileOutputStream(File file, boolean append) append为true表示在文件末尾追加;为false表示清空文件内容,重新写入
方法
void write(int b) 将指定字节写入文件中
void write(byte[] b) 将b.length个字节写入文件中
void write(byte[] b, int off, int len) 将b素组off位置开始,len长度的字节写入文件中
void flush() 刷新此输出流并强制写出所有缓冲的输出字节
void close() 关闭文件输出流
实现把中国写进文本文件中
public class Main {
//实现把中国写进文本文件中
public static void main(String[] args) throws IOException {
FileOutputStream fileOut = new FileOutputStream("D:\\2.txt");
String s = "中国";//字符串存入汉字,一个汉字占用两个字节
byte[] bt = s.getBytes();//定义一个字节数组,获取字符串的字节,存入字节数组
fileOut.write(bt);//直接把整个字节数组写入到文件就好了
fileOut.close();//使用后记得关闭哦
}
}
java.io.FileInputStream
这是一种字节输入流
构造方法
FileInputStream(String name) name为文件路径
FileInputStream(File file)
方法
int read() 读取一个字节,返回值为该字节ASCII码;读到文件末尾返回-1
int read(byte[] b) 读b数组长度的字节到b数组中,返回值为读到的字节个数;读到文件末尾返回-1
int read(byte[] b, int off, int len) 从b素组off位置读len长度的字节到b数组中,返回值为读到的字节个数;读到文件末尾返回-1
int available() 返回文件有效的字节数
long skip(long n) 跳过n个字节
void close() 关闭文件输入流
实现把中国从文件中读取并输出
public class Main {
//实现把中国从文件中读取并输出
public static void main(String[] args) throws IOException {
FileInputStream fileIn = new FileInputStream("D:\\2.txt");
Scanner sc = new Scanner(fileIn);
String s = sc.next();
System.out.println(s);
fileIn.close();
}
}
输出:
中国
补充:学习对象流前言(选看,只需要知道用到Object输出流的类一定要implements Serializable)
1、java.io.NotSerializableException: Student对象不支持序列化!!!!
2、参与序列化和反序列化的对象,必须实现 Serializable 接口。
3、注意:通过源代码发现,Serializable接口只是一个 标志接口:
public interface Serializable {
}
这个接口当中什么代码都没有。
3.1 Serializable接口起什么作用呢?
起到 标识 的作用,标志的作用,java虚拟机看到这个类实现了这个接口,可能会对这个类进行特殊待遇。
Serializable这个标志接口是给java虚拟机参考的,java虚拟机看到这个接口之后,会为该类自动生成一个序列化版本号。
4、序列化版本号有什么用呢?
区分两个类是否相同。
5、java语言中是采用什么机制来区分类的?
第一:首先通过 类名 进行比对,如果类名不一样,肯定不是同一个类。
第二:如果类名一样,再怎么进行类的区别?靠 序列化版本号 进行区分。
eg.
小明编写了一个类:com.baidu.java.bean.Student implements Serializable
小红编写了一个类:com.baidu.java.bean.Student implements Serializable
不同的人编写了同一个类,但“这两个类确实不是同一个类”。这个时候序列化版本就起上作用了。
对于java虚拟机来说,java虚拟机是可以区分开这两个类的,因为这两个类都实现了Serializable接口,都有默认的序列化版本号,他们的序列化版本号不一样。所以区分开了。(这是自动生成序列化版本号的好处)
6、这种自动生成序列化版本号有什么缺陷?
Java虚拟机看到Serializable接口之后,会自动生成一个序列化版本号。
这种自动生成的序列化版本号缺点是:一旦代码确定之后,不能进行后续的修改,因为只要修改,必然会重新编译,此时会生成全新的序列化版本号,这个时候java虚拟机会认为这是一个全新的类。(这样就不好了!)
7、最终结论:
凡是一个类实现了Serializable接口,建议给该类提供一个固定不变的序列化版本号。
这样,以后这个类即使代码修改了,但是版本号不变,java虚拟机会认为是同一个类。
java.io.ObjectOutputStream
ObjectOutputStream:序列化对象
构造方法
ObjectOutputStream(OutputStream out) out为OutputStream对象
方法方法 参考API public class ObjectOutputStreamTest01 { public static void main(String[] args) throws Exception{ // 创建java对象 Student s = new Student(1111, "zhangsan"); // 序列化 ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("students")); // 序列化对象 oos.writeObject(s); // 刷新 oos.flush(); // 关闭 oos.close(); } }
实现往文件中写入学生对象,可能在文件是一串乱码,因为这是给机器看的。
public class Main {
//实现往文件中写入学生对象,可能在文件是一串乱码,因为这是给机器看的。
public static void main(String[] args) throws IOException {
FileOutputStream fileOut = new FileOutputStream("D://2.txt");//在用Object输出流之前要先用文件输出流哦!
ObjectOutputStream out = new ObjectOutputStream(fileOut);//用到Object输出流的类一定要implements Serializable
Stu stu1 = new Stu(18,"xh",60);
out.writeObject(stu1); //往文件中写入一个学生对象
out.close();//后用先关闭
fileOut.close();
}
}
class Stu implements Serializable //用到Object输出流的类一定要implements Serializable
{
int age;
String name;
int score;
public Stu(int age, String name, int score) {
this.age = age;
this.name = name;
this.score = score;
}
}
java.io.ObjectInputStream
ObjectInputStream:反序列化对象
构造方法
ObjectInputStream(InputStream in) in为InputStream对象
方法参考API public class ObjectInputStreamTest01 { public static void main(String[] args) throws Exception{ ObjectInputStream ois = new ObjectInputStream(new FileInputStream("students")); // 开始反序列化,读 Object obj = ois.readObject(); // 反序列化回来是一个学生对象,所以会调用学生对象的toString方法。 System.out.println(obj); ois.close(); } }
实现输出文件中的学生对象信息
public class Main {
//实现输出文件中的学生对象信息
public static void main(String[] args) throws IOException, ClassNotFoundException{
// FileOutputStream fileOut = new FileOutputStream("D:\\2.txt");//在用Object输出流之前要先用文件输出流哦!
// ObjectOutputStream out = new ObjectOutputStream(fileOut);//用到Object输出流的类一定要implements Serializable
//
// Stu stu1 = new Stu(18,"xh",60);
// out.writeObject(stu1); //往文件中写入一个学生对象
//
// out.close();
FileInputStream fileIn = new FileInputStream("D:\\2.txt");//2.txt包含之前Object输出流写的一个stu1对象信息
ObjectInputStream in = new ObjectInputStream(fileIn);
Stu stu =(Stu)in.readObject();//读取对象,要强制类型转换
//然后你就可以对该对象随意操作了
stu.print();//打印一下该学生信息
in.close();
fileIn.close();
}
}
class Stu implements Serializable //用到Object输出流的类一定要implements Serializable
{
int age;
String name;
int score;
public Stu(int age, String name, int score) {
this.age = age;
this.name = name;
this.score = score;
}
void print()
{
System.out.println(this.age + " " + this.name + " " + this.score);
}
}
输出:
18 xh 60