1. IO 流概述
1.1 什么是 IO 流
I:Input,O:Output。IO 流可以完成硬盘文件的读和写。
1.2 IO 流的分类
-
按流的方向分:
- 输入流:进到内存中叫输入;
- 输出流;从内存中出去叫输出;
-
按读取数据的方式分:
- 字节流:按字节方式读取数据,一次读取 1 个字节 byte,等同于 8 个二进制位,万能流,所有类型文件都可以读取;
- 字符流:按字符方式读取数据,一次读取一个字符,只能读取纯文本文件(txt 文件),不能读取音频等文件,word 也不行;
1.3 close() 和 flush()
所有流都实现了 Closeable 接口,都是可关闭的,都有 close() 方法,用完之后一定要关闭,养成良好习惯,否则会耗费很多资源。
所有输出流都实现了 Flushable 接口,都是可刷新的,都有 flush() 方法,养成良好习惯,输出流最终输出后,一定要记得 flush() 刷新以下,表示将通道中剩余的未输出的数据强行输出完(清空管道)(抖两下(狗头)),否则可能会导致丢失数据。
1.4 流的继承关系图
字节流:
字符流:
2. 流的四大家族
-
InputStream:字节输入流
-
OutputStream:字节输出流
-
Reader:字符输入流
-
Writer:字符输出流
它们都是抽象类,是基础的 IO 类,被很多子类继承。而且大部分的 IO 的源码都是 native 标志的,就是说源码都是 C/C++ 写的。
2.1 InputStream
InputStream 是字节输入流, InputStream 是一个抽象类,所有继承了 InputStream 的类都是字节输入流,主要了解以下子类即可:
- FileInputStream:文件字节输入流
- ObjectInputStream:对象字节输入流(反序列化输入流)
- BufferedInputStream:缓冲字节输入流(继承 FilterInputStream,是包装流)
该类所有方法在出错条件下会引发 IOException 异常。主要方法有:
方法 | 介绍 |
---|---|
close() | 关闭流并释放资源 |
read() | 读取下一个字节数据,读不到返回 -1 |
available() | 返回流中可读的字节数量 |
read(byte[] b) | 从输入流中读取一定数量的字节并将其存储在缓冲区数组 b 中 |
read(byte[] b, int off, int len) | 从 off 位置读取 len 个字节放入字节数组 |
skip(long n) | 跳过指定的字节不读 |
2.2 OutputStream
- FileOutputStream:文件字节输出流
- ObjectOutputStream:对象字节输出流(基本类型输出流)
- BufferedInputStream:缓冲字节输出流(继承 FilterOutputStream,是包装流)
- PrintStream:字节打印流
该类所有方法返回 void,在出错条件下会引发 IOException 异常。主要方法有:
方法 | 介绍 |
---|---|
close() | 关闭流并释放资源 |
flush() | 刷新输出流,强制写出所有的缓冲字节 |
write(int b) | 向输出流中写入单个字节 |
write(byte[] b) | 将 b.length 个字节从指定字节数组中写入输出流 |
write(byte[] b, int off, int len) | 将指定字节数组中从 off 开始的 len 个字节写入输出流 |
2.3 Reader
- BufferReader:缓冲字符输入流
- InputStreamReader:转换流(字节输入流转字符输入流)
- FileReader:文件字符输入流
该类方法在出错条件下会引发 IOException 异常。主要方法有:
方法 | 介绍 |
---|---|
close() | 关闭流并释放资源 |
read() | 读取下一个字符,读不到返回 -1 |
read(char[] b) | 将字符读入数组 |
read(char[] b, int off, int len) | 读取 b 中从 off 开始的 len 个字符 |
2.4 Writer
- BufferWriter:缓冲字符输出流
- OutputStreamWriter:转换流(字节输出流转字符输出流)
- FileWriter:文件字符输出流
- PrintWriter:字符打印流
该类所有方法返回 void,在出错条件下会引发 IOException 异常。主要方法有:
方法 | 介绍 |
---|---|
close() | 关闭流并释放资源 |
flush() | 刷新输出流 |
write(int c) | 向输出流写入单个字符 |
write(char[] b) | 向输出流中写入字符数组 |
write(char[] b, int off, int len) | 向输出流中写入以 off 为起点的 len 个字符 |
append(char[] c) | 追加指定字符到此 writer |
3. 文件流
文件流主要有:
- FileInputStream:文件字节输入流
- FileOutputStream:文件字节输出流
- FileReader:文件字符输入流
- FileWriter:文件字符输入流
3.1 FileInputStream
FileInputStream 主要按照字节方式读取文件, 例如我们准备读取一个文件,该文件是 C 盘下的 test.txt。
public class FileInputStreamTest01 {
public static void main(String[] args) {
FileInputStream fis = null; // 输入流
try {
fis = new FileInputStream("c:\\test.txt");
int readCount = 0;
while ((readCount = fis.read()) != -1) {
//直接打印
//System.out.print(b);
//输出字符
System.out.print((char)b);
}
}catch(FileNotFoundException e) {
e.printStackTrace();
}catch(IOException e) {
e.printStackTrace();
}finally {
if (fis != null){
try {
fis.close();
}catch(IOException e) {
e.printStackTrace();
}
}
}
}
}
3.2 FileOutputStream
FileOutputStream 主要按照字节方式写文件,例如:我们做文件的复制,首先读取文件,读取后再将该文件另写一份保存到磁盘上,这就完成了备份。
public class FileOutputStreamTest01 {
public static void main(String[] args) {
FileInputStream fis = null; // 输入流
FileOutputStream fos = null; // 输出流
try {
fis = new FileInputStream("c:\\test.txt");
fos = new FileOutputStream("d:\\test.txt.bak");
byte[] bytes = new byte[1024 * 1024]; // 一次最多拷贝 1M
int readCount = 0;
while ((readCount = fis.read(bytes)) != -1) { // 边读边写
fos.write(bytes, 0, readCount); // 读多少写多少
}
System.out.println("文件复制完毕! ");
fos.flush(); // 输出流要刷新
}catch(FileNotFoundException e) {
e.printStackTrace();
}catch(IOException e) {
e.printStackTrace();
}finally {
// 建议分开 try,一起try的话,第一个异常,那第二个就关不了
if (fis != null){
try {
fis.close();
}catch(IOException e) {
e.printStackTrace();
}
}
if (fos != null){
try {
fos.close();
}catch(IOException e) {
e.printStackTrace();
}
}
}
}
}
3.3 FileReader
FileReader 是一字符为单位读取文件,也就是一次读取两个字节。
public class FileReaderTest01 {
public static void main(String[] args) {
FileReader fr = null;
try {
fr = new FileReader("c:\\test.txt");
int readCount = 0;
while ((readCount = fr.read()) != -1) {
//输出字符
System.out.print((char)b);
}
}catch(FileNotFoundException e) {
e.printStackTrace();
}catch(IOException e) {
e.printStackTrace();
}finally {
if (fr != null){
try {
fr.close();
}catch(IOException e) {
e.printStackTrace();
}
}
}
}
}
3.4 FileWriter
public class FileWriterTest01 {
public static void main(String[] args) {
FileWriter fw = null;
try {
//以下方式会将文件的内容进行覆盖,FileOutputStream 也是一样
//w = new FileWriter("c:\\test.txt");
//w = new FileWriter("c:\\test.txt", false);
//以下为 true 表示,在文件后面追加
fw = new FileWriter("c:\\test.txt", true);
fw.write("你好你好!!!! ");
fw.write("\n"); //换行
char[] chars = {'我', '是', '中', '国', '人'};
fw.write(chars);
fw.flush();
}catch(FileNotFoundException e) {
e.printStackTrace();
}catch(IOException e) {
e.printStackTrace();
}finally {
if (fw != null){
try {
fw.close();
}catch(IOException e) {
e.printStackTrace();
}
}
}
}
}
4. 缓冲流
缓冲流主要是为了提高效率而存在的,减少物理读取次数,使用时不需要 byte、char 数组,自带缓冲,主要有:
- BufferedInputStream:字节缓冲输入流
- BufferedOutputStream:字节缓冲输出流
- BufferedReader:字符缓冲输入流,提供了实用方法 readLine(),可以直接读取一行
- BufferedWriter:字符缓冲输出流,提供了 newLine() 可以写换行符
下图是 BufferedInputStream 和 BufferedOutputStream 的作用示意图,其中 FileInputStream 是作为参数传递到 BufferedInputStream 中的,即 BufferedInputStream(FileInputStream)
。当一个流的构造方法需要一个流的时候,像 FileInputStream 这样作为参数传递进来的叫节点流,外层的流就是包装流。
BufferedReader 和 BufferedWriter 也是类似的。
public class BufferedReaderTest01 {
public static void main(String[] args) throws Exception{
FileReader reader = new FileReader("Copy02.java");
// 当一个流的构造方法中需要一个流的时候,这个被传进来的流叫做:节点流。
// 外部负责包装的这个流,叫做:包装流,还有一个名字叫做:处理流。
// 像当前这个程序来说:FileReader就是一个节点流。BufferedReader就是包装流/处理流。
BufferedReader br = new BufferedReader(reader);
// br.readLine()方法读取一个文本行,但不带换行符。
String s = null;
while((s = br.readLine()) != null){
System.out.print(s);
}
// 关闭流,对于包装流来说,只需要关闭最外层流就行,里面的节点流会自动关闭。(可以看源代码。)
br.close();
}
}
5. 转换流
转换流主要有两个 InputStreamReader 和 OutputStreamWriter:
- InputStreamReader:主要是将字节流输入流转换成字符输入流
- OutputStreamWriter:主要是将字节流输出流转换成字符输出流
public class BufferedReaderTest02 {
public static void main(String[] args) throws Exception{
// 字节流
FileInputStream in = new FileInputStream("Copy02.java");
// 通过转换流转换(字节流-->字符流)
InputStreamReader reader = new InputStreamReader(in);
// 这个构造方法只能传字符流。
BufferedReader br = new BufferedReader(reader);
String line = null;
while((line = br.readLine()) != null){ // 读一行返回字符串
System.out.println(line);
}
// 关闭最外层
br.close();
}
}
6. 打印流
打印流主要包含两个:PrintStream 和 PrintWriter,分别对应字节流和字符流。
System.out 其实对应的就是 PrintStream,默认输出到控制台,我们可以重定向它的输出,可以定向到文件,也就是执行 System.out.println(“hello”)
不输出到屏幕,而输出到文件。
public class PrintStreamTest {
public static void main(String[] args) throws Exception{
// 联合起来写
System.out.println("hello world!");
// 分开写
PrintStream ps = System.out;
ps.println("hello zhangsan");
ps.println("hello lisi");
ps.println("hello wangwu");
// 标准输出流不需要手动close()关闭。
// 可以改变标准输出流的输出方向吗? 可以
/*
// 这些是之前System类使用过的方法和属性。
System.gc();
System.currentTimeMillis();
PrintStream ps2 = System.out;
System.exit(0);
System.arraycopy(....);
*/
// 标准输出流不再指向控制台,指向“log”文件。
PrintStream printStream = new PrintStream(new FileOutputStream("log"));
// 修改输出方向,将输出方向修改到"log"文件。
System.setOut(printStream);
// 再输出
System.out.println("hello world");
System.out.println("hello kitty");
System.out.println("hello zhangsan");
}
}
7. 对象流
对象流可以将 Java 对象转换成二进制写入磁盘,这个过程通常叫做序列化,并且还可以从磁盘读出完整的 Java 对象,而这个过程叫做反序列化。
对象流主要包括: ObjectInputStream 和 ObjectOutputStream。
实现序列化和反序列化
如果实现序列化该类必须实现序列化接口 java.io.Serializable,该接口没有任何方法,该接口只是一种标记接口,标记这个类是可以序列化和反序列化的。
那么它起到一个什么作用呢?起到标识的作用,标志的作用,Java 虚拟机看到这个类实现了这个接口,可能会对这个类进行特殊待遇。Serializable 这个标志接口是给 Java 虚拟机参考的,Java 虚拟机看到这个接口之后,会为该类自动生成一个序列化版本号。
可以一次序列化多个对象,将对象放入集合,序列化集合,集合和里面的对象都要实现 Serializable 接口。
如果不希望类里面的某个属性序列化,可以加关键字 transient。
Java语言中是采用什么机制来区分类的?
第一:首先通过类名进行比对,如果类名不一样,肯定不是同一个类。
第二:如果类名一样,再怎么进行类的区别?靠序列化版本号进行区分。
这种自动生成的序列化版本号缺点是:一旦代码确定之后,不能进行后续的修改,因为只要修改,必然会重新编译,此时会生成全新的序列化版本号,这个时候 Java 虚拟机会认为这是一个全新的类。(这样就不好了!)
所以凡是一个类实现了 Serializable 接口,建议给该类提供一个不变的序列化版本号:
private static final long serialVersionUID = xxxxxxxxx;
建议手动写,不建议自动生成。
序列化:
public class ObjectOutputStreamTest01 {
public static void main(String[] args) throws Exception{
// 创建student对象,实现了 Serializable 接口
Student s = new Student(1111, "zhangsan");
// 序列化
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("students"));
// 序列化对象
oos.writeObject(s);
// 刷新
oos.flush();
// 关闭
oos.close();
}
}
反序列化:
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();
}
}
8. File 类
File 类和四大家族没有关系,所以File类不能完成文件的读和写。
一个 File 对象有可能对应的是目录,也可能是文件。File 只是一个路径名的抽象表示形式。
常用方法如下:
public class FileTest01 {
public static void main(String[] args) throws Exception {
// 创建一个File对象
File f1 = new File("D:\\file");
// 判断是否存在!
System.out.println(f1.exists());
// 如果D:\file不存在,则以文件的形式创建出来
/*if(!f1.exists()) {
// 以文件形式新建
f1.createNewFile();
}*/
// 如果D:\file不存在,则以目录的形式创建出来
/*if(!f1.exists()) {
// 以目录的形式新建。
f1.mkdir();
}*/
// 可以创建多重目录吗?
File f2 = new File("D:/a/b/c/d/e/f");
/*if(!f2.exists()) {
// 多重目录的形式新建。
f2.mkdirs();
}*/
File f3 = new File("D:\\course\\01-开课\\学习方法.txt");
// 获取文件的父路径
String parentPath = f3.getParent();
System.out.println(parentPath); //D:\course\01-开课
File parentFile = f3.getParentFile();
System.out.println("获取绝对路径:" + parentFile.getAbsolutePath());
File f4 = new File("copy");
System.out.println("绝对路径:" + f4.getAbsolutePath()); // C:\Users\Administrator\IdeaProjects\javase\copy
}
}
public class FileTest02 {
public static void main(String[] args) {
File f1 = new File("D:\\course\\01-开课\\开学典礼.ppt");
// 获取名称
System.out.println("文件名称:" + f1.getName());
// 判断是否是一个目录
System.out.println(f1.isDirectory());
// 判断是否是一个文件
System.out.println(f1.isFile());
// 获取文件最后一次修改时间
long haoMiao = f1.lastModified(); // 这个毫秒是从1970年到现在的总毫秒数。
// 将总毫秒数转换成日期?????
Date time = new Date(haoMiao);
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
String strTime = sdf.format(time);
System.out.println(strTime);
// 获取文件大小
System.out.println(f1.length()); //216064字节。
}
}
public class FileTest03 {
public static void main(String[] args) {
// File[] listFiles()
// 获取当前目录下所有的子文件。
File f = new File("D:\\course\\01-开课");
File[] files = f.listFiles();
// foreach
for(File file : files){
//System.out.println(file.getAbsolutePath());
System.out.println(file.getName());
}
}
}