1.I/O流是什么

Java的I/O流是实现编程语言的输入/输出的基础能力,操作的对象有外部设备、存储设备、网络连接等等,是所有服务器端的编程语言都应该具备的基础能力。

I = Input(输入),输入是相对程序而言,既程序从外部设备、存储设备或网络连接中读取数据

O = Output(输出),输出也是相对程序而言,既程序写入数据到外部设备、存储设备或网络连接;

“流”(stream)是一个抽象、动态的概念,是一连串连续动态的数据集合,是一连串的1和0。

在Java编程语言中,运行程序存放在JVM中,而Java 虚拟机运行在内存上,所以程序是装载到内存里,所以常见的几种IO情况有:

java IO流体系--通识篇-LMLPHP

2.File 类

为什么在学习IO流之前需要File类呢?

在操作系统中无非就两样东西:文件和目录(文件夹)

在Java中就是通过File 类来操作文件和目录,File类可以新建、删除、重命名文件和目录,但不能访问文件内容本身。如果要访问文件内容本身,则需要通过IO流来完成

所以在学习IO流操作磁盘文件数据之前,先要学会如何操作文件和目录。

2.1.操作文件和目录

目录或文件:检测-》新建-》访问-》重命名-》删除。

目录操作

File file = new File("D:/test");
// 1.检测目录是否存在
boolean exists = file.exists();
System.out.println("目录是否存在:"+exists);
// 2.不存在就新增目录
if (!exists){
    boolean mkdir = file.mkdir();
    System.out.println("add: "+mkdir);
}
// 3.目录访问
String absolutePath = file.getAbsolutePath();
System.out.println("目录在哪:"+absolutePath);

// 4.目录存在就修改:等价于删除后,再新建
boolean exists1 = file.exists();
File file1 = new File("D:/test1");
if (exists1){
    boolean renameTo = file.renameTo(file1);
    System.out.println("update: "+renameTo);
}
// 5.目录删除
boolean exists2 = file1.exists();
if (exists2){
    boolean delete = file1.delete();
    System.out.println("delete: "+delete);
}

文件操作

错误示范:新建文件。未确定目录是否存在就创建文件,报错:java.io.IOException: 系统找不到指定的路径。

File file = new File("D:/test/test.txt");
if (!file.exists()){
    boolean newFile = file.createNewFile();
    System.out.println(newFile);
}

正确示范:新建文件。

File file = new File("D:/test/test.txt");
// 判断是否需要新建目录,代码可以直接写成一行,方便理解就不那么写
if (!file.isDirectory()){
    // 获得文件的目录,对于多级目录可以使用mkdirs()方法创建目录
    File parentFile = file.getParentFile();
    boolean mkdir = parentFile.mkdir();
    System.out.println("目录不存在,先新建目录:"+mkdir);
}
if (!file.exists()){
    boolean newFile = file.createNewFile();
    System.out.println(newFile);
}

访问文件、修改文件名和删除文件

// 访问文件
String path = file.getPath();
String name = file.getName();
System.out.println("文件路径和名称:"+path+",文件名:"+name);

// 修改文件名:获得原文件目录,再修改文件名。File.separator由系统指定路径分隔(linux 和window分隔符不一样)
String newName ="newTest"+name.substring(name.lastIndexOf(".")); // 新名+后缀
File file1 = new File(file.getParentFile() +File.separator+ newName);
boolean renameTo = file.renameTo(file1);
System.out.println("update: "+renameTo);

// 删除文件
boolean delete = file1.delete();
System.out.println("delete:"+file1.exists());

关于File 类的更多操作就不一一展开了,难度不大可自行学习:

java IO流体系--通识篇-LMLPHP

3.I/O流分类

按数据流的方向不同分类:输入流,输出流按处理数据单位不同分类:字节流,字符流

  • (1)字节流:数据流中最小的数据单元是字节基类为InputStream和OutputStream

  • (2)字符流:数据流中最小的数据单元是字符基类为Reader和Writer

    不同字符编码的同一个字符占用的字节数不同。

    java IO流体系--通识篇-LMLPHP

    字符流命名规则:XxxxReader 或 XxxxWriter

    字节流命名规则:XxxxInputStream、XxxxOutputStream 或XxxxStream

按流的角色不同分类:节点流,处理流

  • (1)节点流是可以从一个特定的数据源(如内存、磁盘、网络等)中读取或写入数据的流。
  • (2)处理流是对一个已存在的流的连接和封装,通过调用封装后的流的功能来实现数据的读写。

java IO流体系--通识篇-LMLPHP

3.字节和字符

字符流和字节流的历史渊源:先有字节流,再有字符流。字节流符合机器的操作,但字符流更符合人类的正常使用习惯。字符流的操作最终还是转换为字节流来完成。

感受下int数据变成字符的案例:int>16进制字符串>字节数组>字符串

/**
 * 整型转换成汉字:15120522 ==utf-8==> 渊
 * 一个utf-8的汉字,占用3个字节
 */
String string = Integer.toHexString(15120522); //"E6B88A"
byte[] bytes = new byte[string.length() / 2];
for(int i = 0; i < bytes.length; i ++){
    byte high = Byte.parseByte(string.substring(i * 2, i * 2 + 1), 16);
    byte low = Byte.parseByte(string.substring(i * 2 + 1, i * 2 + 2), 16);
    bytes[i] = (byte) (high << 4 | low);
}
String result = new String(bytes, "utf-8");
System.out.println("15120522 ==utf-8==>"+result);

执行结果:

一个utf-8的汉字,占用3个字节。整数(15120522)转化成16进制 (E6B88A),每两位16进制(既是8位二进制)代表一个字节,这3个字节(E6B88A)在utf-8编码下表示汉字“渊”。

4.文件节点流

对磁盘文件的内容进行读写,Java程序和磁盘文件的沟通桥梁。

字节流:FileOutputStream 和 FileInputStream

字符流:FileReader 和 FileWriter

4.1.字节流--文件

FileOutputStream 和 FileInputStream

File file = new File("D:/test.txt");
// 写
FileOutputStream fout = new FileOutputStream(file);
fout.write(65);//ASCII编码:65=A
fout.write("ZZZ".getBytes());//
fout.write("渊".getBytes());
fout.close();// 关流

// 读
FileInputStream fin = new FileInputStream(file);
byte[] bytes2 = new byte[10]; // 一次读10个字节
fin.read(bytes2); //将数据读取到bytes2
fin.close();// 关流
System.out.println(new String(bytes2));

FileOutputStream

所有方法

FileInputStream

所有方法

4.2.字符流--文件

FileReader 和 FileWriter

对于文本的读写,很明显字符流更适合人类的语言的读写操作。

File file = new File("D:/test.txt");
// 写
FileWriter fileWriter = new FileWriter(file);
fileWriter.write("渊渟岳");
fileWriter.close();

FileReader fileReader = new FileReader(file);
// 如果读取的文件内容很少,甚至知道长度的时候
//char[] c = new char[10];
//fileReader.read(c);
//System.out.println(String.valueOf(c));

// 如果读取的文件内容很多可以循环读取
int len = -1;  //用于接收每次读取数据的长度
char[] chars = new char[2];
while ((len=fileReader.read(chars))!=-1){
    System.out.println(new String(chars,0,len));
}

fileWriter.close();

FileWriter

所有方法

FileReader

所有方法

5.数组节点流

内存操作流的一种,对内存中的数组进行读写(对应的不是文件,而是内存中的数组)。Java程序通过数组节点流,将数据以byte(字节)或char(字符)数组的形式缓存到内存中。

字节流:ByteArrayOutputStream 和 ByteArrayInputStream

字符流:CharArrayWriter 和 CharArrayReader

5.1.字节流--数组

ByteArrayOutputStream 和 ByteArrayInputStream

// 默认byte数组大小为32
ByteArrayOutputStream baout = new ByteArrayOutputStream();
baout.write("渊渟岳".getBytes());
baout.close();// 关闭无效果
// 获取数据类型一
byte[] bytes = baout.toByteArray();// 获取字节数组数据
System.out.println(new String(bytes));
// 获取数据类型二
String string = baout.toString();//使用平台的默认字符集将缓冲区内容转换为字符串解码字节
System.out.println(string);

// 读取byte数组数据
byte[] bytes1 = new byte[50];
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
byteArrayInputStream.read(bytes1);//读取数据到新的字节数组
System.out.println(bytes1.length+"|"+new String(bytes1));

ByteArrayOutputStream

在创建ByteArrayOutputStream类实例时,内存中会创建一个byte数组类型的缓冲区,缓冲区会随着数据的不断写入而自动增长。

所有方法

ByteArrayInputStream

所有方法

5.2.字符流--数组

CharArrayWriter 和 CharArrayReader

// 默认char数组大小为32
CharArrayWriter charArrayWriter = new CharArrayWriter();
charArrayWriter.write("渊渟岳");
charArrayWriter.close();// 关闭无效果
// 获取数据类型一
char[] chars = charArrayWriter.toCharArray();// 获取字符数组数据
System.out.println(String.valueOf(chars));
// 获取数据类型二
String string = charArrayWriter.toString();
System.out.println(string);

// 读取char数组数据
char[] chars1 = new char[50];
CharArrayReader charArrayReader = new CharArrayReader(chars);
charArrayReader.read(chars1);//读取数据到新的字符数组
System.out.println(chars1.length+"|"+new String(chars1));

CharArrayWriter

所有方法

CharArrayReader

所有方法

6.字符串节点流

内存操作流的一种,对内存中的字符串进行读写。Java程序通过字符串节点流,将数据以StringBuffer的形式缓存到内存中。

字节流:无

字符流:StringWriter 和 StringReader

6.1.字符流--字符串

StringWriter 和 StringReader

StringWriter stringWriter = new StringWriter();
stringWriter.write("渊渟岳");
stringWriter.close();
// 获取字符串缓冲区本身
StringBuffer buffer = stringWriter.getBuffer();
System.out.println(String.valueOf(buffer));
// 获取缓冲区的当前值的字符串
String string = stringWriter.toString();// 等价于:buffer.toString()
System.out.println(string);
//读取内存中的字符串数据
char[] chars = new char[50];
StringReader stringReader = new StringReader(String.valueOf(buffer));
stringReader.read(chars);
System.out.println(chars.length+"|"+String.valueOf(chars));

StringWriter

所有方法

StringReader

所有方法

7.管道节点流

主要用于线程之间的数据通讯。

字节流:PipedInputStream 和 PipedOutputStream

字符流:PipedReader 和 PipedWriter

7.1.字节流--管道

PipedInputStream 和 PipedOutputStream

管道输入流线程类

public class PipedInputThread implements Runnable{
    private PipedInputStream pipedInputStream = new PipedInputStream();

    public PipedInputStream getPipedInputStream() {
        return pipedInputStream;
    }
    @Override
    public void run() {
        byte[] buf = new byte[1024];
        try {
            int len = pipedInputStream.read(buf);
            System.out.println(new String(buf, 0, len));
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            try {
                pipedInputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

测试:主线程充当管道输出线程类

public static void main(String[] args) throws IOException {
    PipedOutputStream pipedOutputStream = new PipedOutputStream();
    // 管道输入流线程类
    PipedInputThread pipedInputThread = new PipedInputThread();
    // 管道连接:接水管
    pipedOutputStream.connect(pipedInputThread.getPipedInputStream());
    /**
     * 管道的读写操作是互相阻塞的,当缓冲区为空时,读操作阻塞;当缓冲区满时,写操作阻塞
     * 管道流的读写线程开启无先后顺序
     */
    new Thread(pipedInputThread).start();
    pipedOutputStream.write("渊渟岳".getBytes());

    pipedOutputStream.close();
}

PipedOutputStream

管道输出流可以连接到管道输入流以创建通信管道。管道输出流是管道的发送端,既“写”。

所有方法

PipedInputStream

管道输入流应连接到管道输出流; 管道输入流读取管道输出流的字节数据,既“读”。

所有方法

7.2.字符流--管道

PipedReader 和 PipedWriter

和字节流例子的写法类似

管道输入流线程类

public class PipedReaderThread implements Runnable{
    private PipedReader pipedReader = new PipedReader();

    public PipedReader getPipedReader() {
        return pipedReader;
    }
    @Override
    public void run() {
        try {

            char[] chars = new char[1024];
            int len = pipedReader.read(chars);
            System.out.println(new String(chars, 0, len));
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            try {
                pipedReader.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

测试:主线程充当管道输出线程类

public static void main(String[] args) throws IOException{
 PipedWriter pipedWriter = new PipedWriter();
 // 管道输入流线程类
 PipedReaderThread pipedReaderThread = new PipedReaderThread();
 // 管道连接:接水管
 pipedWriter.connect(pipedReaderThread.getPipedReader());
 /**
   * 管道的读写操作是互相阻塞的,当缓冲区为空时,读操作阻塞;当缓冲区满时,写操作阻塞
   * 管道流的读写线程开启无先后顺序
   */
 new Thread(pipedReaderThread).start();
 pipedWriter.write("渊渟岳");
 pipedWriter.close();
}

PipedWriter

所有方法

PipedReader

所有方法

8.网络节点流

对网络通讯进行数据读写的IO流。

SocketInputStream 和 SocketOutputStream用于Socket编程使用的IO流。

这两个类都是非公共类,只能在同一个package中才可以创建对象。由AbstractPlainSocketImpl类创建了Socket的输入和输出流,源码如下:

protected synchronized InputStream getInputStream() throws IOException {
    synchronized (fdLock) {
        if (isClosedOrPending())
            throw new IOException("Socket Closed");
        if (shut_rd)
            throw new IOException("Socket input is shutdown");
        if (socketInputStream == null)
            socketInputStream = new SocketInputStream(this);
    }
    return socketInputStream;
}

protected synchronized OutputStream getOutputStream() throws IOException {
    synchronized (fdLock) {
        if (isClosedOrPending())
            throw new IOException("Socket Closed");
        if (shut_wr)
            throw new IOException("Socket output is shutdown");
        if (socketOutputStream == null)
            socketOutputStream = new SocketOutputStream(this);
    }
    return socketOutputStream;
}

9.缓冲流

通过内存数组缓冲数据来提高读写速度,字节使用byte[]缓冲,字符使用char[]缓冲,默认数组长度都是8192,可通过构造方法自定义缓冲区大小。

字节流:BufferedInputStream 和 BufferedOutputStream

字符流:BufferedReader和 BufferedWriter

9.1.字节流--缓冲

BufferedInputStream 和 BufferedOutputStream

File file = new File("D:/test.txt");
// 缓冲默认大小:8192个byte
BufferedOutputStream bufOut = new BufferedOutputStream(new FileOutputStream(file));
bufOut.write("渊渟岳".getBytes());
bufOut.close();

// 缓冲默认大小:8192个byte
BufferedInputStream bufIn = new BufferedInputStream(new FileInputStream(file));
int len = -1; //用于保存每次读取数据的长度
byte[] bytes =new byte[1024]; //每次最大读取1024个字节
//读取数据保存到bytes数组,并判断读到的数据长度是否不等于-1,如果是 -1,表示已经读取到文件的末尾
while ((len=bufIn.read(bytes))!=-1){
    //将每次读到的bytes数组,按读到的长度(数组长度)转换成字符串,然后输出
    System.out.println(new String(bytes,0,len));
}
bufIn.close();

BufferedOutputStream

所有方法

BufferedInputStream

所有方法

9.2.字符流--缓冲

BufferedReader和 BufferedWriter

File file = new File("D:/test.txt");
// 缓冲默认大小:8192个char
BufferedWriter bw = new BufferedWriter(new FileWriter(file));
bw.write("渊渟岳");
bw.close();
// 缓冲默认大小:8192个byte
BufferedReader br = new BufferedReader(new FileReader(file));
int len = -1; //用于保存每次读取数据的长度
char[] chars =new char[1024]; //每次最大读取1024个字符
//读取数据保存到chars数组,并判断读到的数据长度是否不等于-1,如果是 -1,表示已经读取到文件的末尾
while ((len=br.read(chars))!=-1){
    //将每次读到的chars数组,按读到的长度(数组长度)转换成字符串,然后输出
    System.out.println(new String(chars,0,len));
}
br.close();

BufferedReader

所有方法

BufferedWriter

相比其他IO流,BufferedWriter可以每次读取一行字符,并返回字符串对象:【String  readLine()】,对于需要一行一行的读取数据的操作,使用这个方法无疑是最明智的。

所有方法

10.转换流

顾名思义,对流进行转换,对什么流怎么转换呢?

字节流转换为字符流,转换的对象是继承了OutputStream和InputStream的字节流。转换流是字符和字节的桥梁,字符转换流原理:字节流+编码表。

字节流转字符流:OutputStreamWriter 和InputStreamReader

字符流转字节流:无

File file = new File("D:/test.txt");
// 只要是字节流就行
Writer writer = new OutputStreamWriter(new FileOutputStream(file), "UTF-8");
writer.write("UTF-8字符串");
writer.close();

Reader reader = new InputStreamReader(new FileInputStream(file), "UTF-8");
int len = -1;
char[] chars = new char[1024];
while ((len=reader.read(chars))!=-1){
    System.out.println(new String(chars,0,len));
}
reader.close();

10.1字符流--转换

OutputStreamWriter

将字节输出流转换为字符输出流。

所有方法

InputStreamReader

将字节输入流转换为字符输入流。

所有方法

11.打印流

打印流都是基于转换流和缓冲流来实现的方便打印功能的IO流。

字符流:PrinterWriter

字节流:PrintStream

11.1.字节流--打印

PrintStream 部分构造方法源码

private PrintStream(boolean autoFlush, OutputStream out) {
    super(out);
    this.autoFlush = autoFlush;
    this.charOut = new OutputStreamWriter(this);
    this.textOut = new BufferedWriter(charOut);
}

private PrintStream(boolean autoFlush, OutputStream out, Charset charset) {
    super(out);
    this.autoFlush = autoFlush;
    this.charOut = new OutputStreamWriter(this, charset);
    this.textOut = new BufferedWriter(charOut);
}

字节打印流PrintStream是经常用到的IO流,刚入门Java就已经开始用字节打印流了,只是不知道罢了就是这个【System.out.println()】,out 就是System类中的一个PrintStream类型的静态成员变量。System类中还有一个err 静态成员变量,使用err打印消息到控制台时,字体颜色是红色的。

例子

System.out.println("控制台输出一般信息--黑色");
System.err.println("控制台输出错误信息--红色");
// 打印数据到文件
File file = new File("D:/test.txt");
PrintStream printStream = new PrintStream(file);
printStream.println("每次打印一行就换行");
printStream.print("我打印从来不换行");
printStream.close();

构造方法

所有方法

print() 和 println()做了全类型的方法重载,支持所有类型参数的打印,表中就不一一列出。

11.2.字符流--打印

PrinterWriter 部分构造方法源码

public PrintWriter(OutputStream out, boolean autoFlush) {
    this(new BufferedWriter(new OutputStreamWriter(out)), autoFlush);

    // save print stream for error propagation
    if (out instanceof java.io.PrintStream) {
        psOut = (PrintStream) out;
    }
}
public PrintWriter(File file) throws FileNotFoundException {
    this(new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file))),
         false);
}

例子

// 打印字符到文件
File file = new File("D:/test.txt");
PrintWriter pw = new PrintWriter(file);
pw.println("每次打印一行就换行");
pw.print("我打印从来不换行");
pw.close();

构造方法

所有方法

print() 和 println()做了全类型的方法重载,支持所有类型参数的打印,表中就不一一列出。

12.序列化流

序列化:将对象的状态信息转换为可以存储或传输的形式的过程。程序通过序列化把Java对象转换成二进制字节流,然后就可以把二进制字节流写入网络或磁盘。

反序列化:读取磁盘或网络中的二进制字节流数据,转化为Java对象

序列化流:ObjectOutputStream

反序列化流:ObjectInputStream

例子

测试对象:Person 类

// 序列化对象信息:必须实现序列化标记接口Serializable
public class Person implements Serializable {
    // 序列化版本UID
    private static final long serialVersionUID = 1L;
    private String name;
    private transient int age;//年龄字段不做序列化
    private String sex;
    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;
    }
    public String getSex() {
        return sex;
    }
    public void setSex(String sex) {
        this.sex = sex;
    }

    @Override
    public String toString() {
        return "Person{" + "name='" + name + ", age=" + age + ", sex='" + sex + '}';
    }
    public Person() {
    }
    public Person(String name, Integer age, String sex) {
        this.name = name;
        this.age = age;
        this.sex = sex;
    }
}

序列化和反序列化

public class Demo2 {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        //数据准备:集合类都实现了序列化接口Serializable
        List<Person> list = new ArrayList<>();
        list.add(new Person("张三",38,"男"));
        list.add(new Person("李四",38,"男"));
        list.add(new Person("如花",18,"女"));

        // 序列化保存到普通文件
        File file = new File("D:/demo2.txt");
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(file));
        objectOutputStream.writeObject(list);
        objectOutputStream.close();

        // 读取普通文件反序列化
        ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(file));
        List<Person> personList = (List<Person>) objectInputStream.readObject();
        objectInputStream.close();
        for (Person person:personList){
            System.out.println(person);
        }
        
    }
}

执行结果:年龄没有被序列化,int默认0所以取出来为0。

几个要点:

需要实现 Serializable 标记接口才可以被序列化;

序列化版本UID:serialVersionUID,绑定对象版本,不因为对象的改变而导致序列化和反序列化失败;

被transient 修饰的成员变量不会被序列化;

Externalizable接口--外部化接口;他是Serializable接口的子接口,能手动控制序列化的方式。

12.1.序列化流

ObjectOutputStream

所有方法

12.2.反序列化流

ObjectInputStream

所有方法

13.数据流

数据流用于实现将java基本类型转换成二进制来进行读写操作。在序列化流中便用到了数据流,通过数据流等其他处理完成对象的序列化和反序列化。

数据输入输出流相比其他流还有些特殊的方法,分别是readUTF和writeUTF方法使用了一种特殊的UTF编码/解码方式,只能用于java程序。

例子

File file = new File("D:/test.txt");
DataOutputStream dataOutputStream = new DataOutputStream(new FileOutputStream(file));
//dataOutputStream.write("渊渟岳".getBytes());
dataOutputStream.writeUTF("渊渟岳"); // UTF编码
dataOutputStream.close();

DataInputStream dataInputStream = new DataInputStream(new FileInputStream(file));
//int len = -1;
//byte[] bytes = new byte[1024];
//while ((len=dataInputStream.read(bytes))!=-1){
//    System.out.println(new String(bytes,0,len));
//}
String s = dataInputStream.readUTF();
System.out.println(s);
dataInputStream.close();

执行结果:保存的文件数据和读取出来的数据有差异,看不见的符号分别是:"空白"+水平制表符(其二进制为00100000 00001001),对照ASCII字符代码表就知道了。

java IO流体系--通识篇-LMLPHP

对照ASCII字符代码表结果

java IO流体系--通识篇-LMLPHP

(图片来源于百度)

13.1.字节流--数据

DataOutputStream

所有方法

DataInputStream

所有方法

14.回退输入流

回退流只有输入流(读取),一般的输入流都只能按顺序读取一次流中的数据,而回退流支持读取数据后还可以回推数据。其中的原理是通过流中内置的缓冲数组和位置指针来实现数据的读取(read)和回退(unread)。意义在于多次读取流中的数据,比如:文件解压时需要读取压缩包中的文件头来判断文件类型,具体实现可阅读java.util.zip.ZipInputStream类的源码。

// zip输入流 使用 回退流,指定缓冲区为512
public ZipInputStream(InputStream in, Charset charset) {
    super(new PushbackInputStream(in, 512), new Inflater(true), 512);
    ……
}

字节流:PushbackInputStream

字符流:PushbackReader

14.1.字节流--回退

PushbackInputStream

例子

File file = new File("D:/test.txt");
FileOutputStream fileOutputStream = new FileOutputStream(file);
fileOutputStream.write("Y123我不是文件头我是内容部分哈哈哈".getBytes());
fileOutputStream.close();
// 回退流缓冲区默认大小1个字节,太小,所以使用时必须指定缓冲区大小
PushbackInputStream pushbackInputStream = new PushbackInputStream(new FileInputStream(file), 512);
int len = -1;
byte[] bytes = new byte[512];
if((len=pushbackInputStream.read(bytes))!=-1){
    String s = new String(bytes, 0, len);
    String fileHead = s.substring(0, 4);//自行规定文件头
    if ("Y123".equals(fileHead)){
        //去掉文件头,回退后面的数据
        pushbackInputStream.unread(s.substring(4,s.length()).getBytes());
        System.out.println("文件头="+fileHead);
        // 因为文件头对上了,所以继续处理后续数据
        while ((len=pushbackInputStream.read(bytes))!=-1){
            System.out.println(new String(bytes,0,len));
        }
    }
}
pushbackInputStream.close();

所有方法

14.2.字符流--回退

字符流的回退和字节流的回退很相似,自行实现。

所有方法

15.过滤流

装饰模式的作用是动态地扩展(增强)一个对象的功能,也即是在不改变其结构同时向一个现有的对象添加新的功能。通过继承过滤流来对输入输出流的行为进行扩展,但是功能的具体实现并不是在过滤流中,而是在过滤流的子类中编写具体功能的实现。过滤流的作用是充当抽象装饰器,为了强制其子类(具体实现类)按照其构造形式传入一个IO流对象。

可能是历史原因,过滤流出现了下面三种源码设计:

  • FilterInputStream:不是抽象类,构造方法为protected
  • FilterOutputStream:不是抽象类,构造方法为public
  • FilterReader:抽象类,构造方法为protected
  • FilterWriter:抽象类,构造方法为protected

例子:以FilterReader流为例,自定义FilterReaderDemo处理流:字母自动转成大写

/**
 * 自动转成大写的处理流
 */
public class FilterReaderDemo extends FilterReader {

    protected FilterReaderDemo(Reader in) {
        super(in);
    }

    @Override
    public int read() throws IOException {
        System.out.println("");
        int i = super.read();
        if(i==-1) return i;
        return Character.toUpperCase(i);
    }

    @Override
    public int read(char[] cbuf,int offset,int len) throws IOException{
        int res = super.read(cbuf, offset, len);
        for (int i = 0; i < res; i++) {
            cbuf[i] = Character.toUpperCase(cbuf[i]);
        }
        return res;
    }
}

测试

public static void main(String[] args) throws IOException {
    File file = new File("D:/test.txt");
    FileWriter fileWriter = new FileWriter(file);
    fileWriter.write("qwertyufghjk");
    fileWriter.close();

    FilterReaderDemo filterReaderDemo = new FilterReaderDemo(new FileReader(file));
    int len = -1;
    char[] chars = new char[1024];
    // read(char cbuf[])方法,其实是调用了read(char[] cbuf,int offset,int len)
    while ((len=filterReaderDemo.read(chars))!=-1){
        System.out.println(new String(chars,0,len));
    }
    filterReaderDemo.close();
}

执行结果:QWERTYUFGHJK

16.总结

前面仅仅总结了java.io包下的输入/输出流的体系,还有一些例如ZipInputStream、AudioInputStream 、CipherInputStream等具有压缩/解压、访问音频文件、加密/ 解密等功能的字节流,位于JDK的java.util.zip、javax.sound.sampled、javax.crypto包等,还有很多的具有特殊功能的IO流位于其它的包下。

java IO流体系--通识篇-LMLPHP

Java往期文章

Java全栈学习路线、学习资源和面试题一条龙

我心里优秀架构师是怎样的?免费下载经典编程书籍

更多优质文章和资源👇

java IO流体系--通识篇-LMLPHP

原创不易、三联支持:分享,点赞,在看👇

03-13 21:51