java IO流体系--通识篇

1.I/O流是什么

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

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

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

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

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

image

 

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 类的更多操作就不一一展开了,难度不大可自行学习:

image

 

3.I/O流分类

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

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

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

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

    ASCII:1个字符1个字节(8个二进制位)

    GBK :1个中文字符2个字节(16个二进制位)

    UTF-8:1个中文字符3个字节(24个二进制位)

    image

    字符流命名规则:XxxxReader 或 XxxxWriter

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

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

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

image

 

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);

执行结果:

15120522 ==utf-8==>渊

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

I/O流具体使用

按流的角色分类(节点流>>>处理流)来讲解Java的IO流。

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

构造方法和描述
FileOutputStream(File file)创建文件输出流以写入由指定的 File对象表示的文件。
FileOutputStream(File file, boolean append)创建文件输出流以写入由指定的 File对象表示的文件。
FileOutputStream(FileDescriptor fdObj)创建文件输出流以写入指定的文件描述符,表示与文件系统中实际文件的现有连接。
FileOutputStream(String name)创建文件输出流以指定的名称写入文件。
FileOutputStream(String name, boolean append)创建文件输出流以指定的名称写入文件。

所有方法

返回值类型方法和描述
void close()关闭此文件输出流并释放与此流相关联的任何系统资源。
protected void finalize()清理与文件的连接,并确保当没有更多的引用此流时,将调用此文件输出流的 close方法。
FileChannel getChannel()返回与此文件输出流相关联的唯一的FileChannel对象。
FileDescriptor getFD()返回与此流相关联的文件描述符。
void write(byte[] b)将 b.length个字节从指定的字节数组写入此文件输出流。
void write(byte[] b, int off, int len)将 len字节从位于偏移量 off的指定字节数组写入此文件输出流。
void write(int b)将指定的字节写入此文件输出流。

FileInputStream

构造方法和描述
FileInputStream(File file)通过打开与实际文件的连接创建一个 FileInputStream ,该文件由文件系统中的 File对象 file命名。
FileInputStream(FileDescriptor fdObj)创建 FileInputStream通过使用文件描述符 fdObj ,其表示在文件系统中的现有连接到一个实际的文件。
FileInputStream(String name)通过打开与实际文件的连接来创建一个 FileInputStream ,该文件由文件系统中的路径名 name命名。

所有方法

返回值类型方法和描述
int read()从该输入流读取一个字节的数据。
int read(byte[] b)从该输入流读取最多 b.length个字节的数据为字节数组。
int read(byte[] b, int off, int len)从该输入流读取最多 len字节的数据为字节数组。
long skip(long n)跳过并从输入流中丢弃 n字节的数据。
int available()返回从此输入流中可以读取(或跳过)的剩余字节数的估计值,而不会被下一次调用此输入流的方法阻塞。
void close()关闭此文件输入流并释放与流相关联的任何系统资源。
protected void finalize()确保当这个文件输入流的 close方法没有更多的引用时被调用。
FileChannel getChannel()返回与此文件输入流相关联的唯一的FileChannel对象。
FileDescriptor getFD()返回表示与此 FileInputStream正在使用的文件系统中实际文件的连接的 FileDescriptor对象。

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

构造方法和描述
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对象,给出一个带有布尔值的文件名,表示是否附加写入的数据。

所有方法

返回值类型方法和描述
void write(int c)写一个字符
void write(char[] cbuf)写入一个字符数组。
void write(char[] cbuf, int off, int len)写入字符数组的一部分。
void write(String str)写一个字符串
void write(String str)写一个字符串
void close()关闭流,先刷新。
void flush()刷新流。
String getEncoding()返回此流使用的字符编码的名称。
Writer append(char c)将指定的字符附加到此作者。
Writer append(CharSequence csq)将指定的字符序列附加到此作者。
Writer append(CharSequence csq, int start, int end)将指定字符序列的子序列附加到此作者。

FileReader

构造方法和描述
FileReader(File file)创建一个新的 FileReader ,给出 File读取。
FileReader(FileDescriptor fd)创建一个新的 FileReader ,给定 FileDescriptor读取。
FileReader(String fileName)创建一个新的 FileReader ,给定要读取的文件的名称。

所有方法

返回值类型方法和描述
void close()关闭流并释放与之相关联的任何系统资源。
String getEncoding()返回此流使用的字符编码的名称。
int read()读一个字符
int read(char[] cbuf)将字符读入数组。
int read(char[] cbuf, int offset, int length)将字符读入数组的一部分。
int read(CharBuffer target)尝试将字符读入指定的字符缓冲区。
boolean ready()告诉这个流是否准备好被读取。
void reset()重置流。
long skip(long n)跳过字符
void mark(int readAheadLimit)标记流中的当前位置。
boolean markSupported()告诉这个流是否支持mark()操作。

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数组类型的缓冲区,缓冲区会随着数据的不断写入而自动增长。

构造方法和描述
ByteArrayOutputStream()创建一个新的字节数组输出流。
ByteArrayOutputStream(int size)创建一个新的字节数组输出流,具有指定大小的缓冲区容量(以字节为单位)。

所有方法

返回值类型方法和描述
void close()关闭 ByteArrayOutputStream没有任何效果。
void reset()将此字节数组输出流的 count字段重置为零,以便丢弃输出流中当前累积的所有输出。
int size()返回缓冲区的当前大小。
byte[] toByteArray()创建一个新分配的字节数组。
String toString()使用平台的默认字符集将缓冲区内容转换为字符串解码字节。
String toString(int hibyte)已弃用此方法无法将字节正确转换为字符。从JDK 1.1的,要做到这一点的优选方法是通过toString(String enc)方法,它需要一个编码名称参数,或toString()方法,它使用平台的默认字符编码。
String toString(String charsetName)通过使用命名的charset解码字节,将缓冲区的内容转换为字符串。
void write(byte[] b, int off, int len)从指定的字节数组写入 len字节,从偏移量为 off开始,输出到这个字节数组输出流。
void write(int b)将指定的字节写入此字节数组输出流。
void writeTo(OutputStream out)将此字节数组输出流的完整内容写入指定的输出流参数,就像使用 out.write(buf, 0, count)调用输出流的写入方法 out.write(buf, 0, count) 。

ByteArrayInputStream

构造方法和描述
ByteArrayInputStream(byte[] buf)创建一个 ByteArrayInputStream ,使其使用 buf作为其缓冲区数组。
ByteArrayInputStream(byte[] buf, int offset, int length)创建 ByteArrayInputStream使用 buf作为其缓冲器阵列。

所有方法

返回值类型方法和描述
int available()返回可从此输入流读取(或跳过)的剩余字节数。
void close()关闭 ByteArrayInputStream没有任何效果。
void mark(int readAheadLimit)设置流中当前标记的位置。
boolean markSupported()测试 InputStream是否支持标记/复位。
int read()从该输入流读取下一个数据字节。
int read(byte[] b, int off, int len)将 len字节的数据读入此输入流中的字节数组。
void reset()将缓冲区重置为标记位置。
long skip(long n)从此输入流跳过 n个字节的输入。

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

构造方法和描述
CharArrayWriter()创建一个新的CharArrayWriter。
CharArrayWriter(int initialSize)用指定的初始大小创建一个新的CharArrayWriter。

所有方法

返回值类型方法和描述
CharArrayWriter append(char c)将指定的字符附加到此作者。
CharArrayWriter append(CharSequence csq)将指定的字符序列附加到此作者。
CharArrayWriter append(CharSequence csq, int start, int end)将指定字符序列的子序列附加到此作者。
void close()关闭流。
void flush()冲洗流。
void reset()重置缓冲区,以便您可以再次使用它,而不会丢弃已经分配的缓冲区。
int size()返回缓冲区的当前大小。
char[] toCharArray()返回输入数据的副本。
String toString()将输入数据转换为字符串。
void write(char[] c, int off, int len)将字符写入缓冲区。
void write(int c)将一个字符写入缓冲区。
void write(String str, int off, int len)将一部分字符串写入缓冲区。
void writeTo(Writer out)将缓冲区的内容写入另一个字符流。

CharArrayReader

构造方法和描述
CharArrayReader(char[] buf)从指定的字符数组中创建CharArrayReader。
CharArrayReader(char[] buf, int offset, int length)从指定的字符数组中创建CharArrayReader。

所有方法

返回值类型方法和描述
void close()关闭流并释放与之相关联的任何系统资源。
void mark(int readAheadLimit)标记流中的当前位置。
boolean markSupported()告诉这个流是否支持mark()操作。
int read()读一个字符
int read(char[] b, int off, int len)将字符读入数组的一部分。
boolean ready()告诉这个流是否准备好被读取。
void reset()将流重新设置为最近的标记,或将其重新设置为从未被标记的开始。
long skip(long n)跳过字符

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

构造方法和描述
StringWriter()使用默认的初始字符串缓冲区大小创建一个新的字符串写入程序。
StringWriter(int initialSize)使用指定的初始字符串缓冲区大小创建一个新的字符串写入器。

所有方法

返回值类型方法和描述
StringWriter append(char c)将指定的字符附加到此作者。
StringWriter append(CharSequence csq)将指定的字符序列附加到此作者。
StringWriter append(CharSequence csq, int start, int end)将指定字符序列的子序列附加到此作者。
void close()关闭 StringWriter没有任何效果。
void flush()冲洗流。
StringBuffer getBuffer()返回字符串缓冲区本身。
String toString()以缓冲区的当前值作为字符串返回。
void write(char[] cbuf, int off, int len)写一个字符数组的一部分。
void write(int c)写一个字符
void write(String str)写一个字符串
void write(String str, int off, int len)写一个字符串的一部分

StringReader

构造方法和描述
StringReader(String s)创建一个新的字符串阅读器。

所有方法

返回值类型方法和描述
void close()关闭流并释放与之相关联的任何系统资源。
void mark(int readAheadLimit)标记流中的当前位置。
boolean markSupported()告诉这个流是否支持mark()操作。
int read()读一个字符
int read(char[] cbuf, int off, int len)将字符读入数组的一部分。
boolean ready()告诉这个流是否准备好被读取。
void reset()将流重新设置为最近的标记,如果从未被标记,则将其重置到字符串的开头。
long skip(long ns)跳过流中指定数量的字符。

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

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

构造方法和描述
PipedOutputStream()创建一个尚未连接到管道输入流的管道输出流。
PipedOutputStream(PipedInputStream snk)创建连接到指定管道输入流的管道输出流。

所有方法

返回值类型方法和描述
void close()关闭此管道输出流,并释放与此流相关联的任何系统资源。
void connect(PipedInputStream snk)将此管道输出流连接到接收器。
void flush()刷新此输出流并强制任何缓冲的输出字节被写出。
void write(byte[] b, int off, int len)从指定的字节数组写入 len个字节,从偏移量 off开始输出到这个管道输出流。
void write(int b)写入指定 byte到管道输出流。

PipedInputStream

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

构造方法和描述
PipedInputStream()创建一个 PipedInputStream ,所以它还不是 connected。
PipedInputStream(int pipeSize)创建一个 PipedInputStream ,使其尚未connected,并使用指定的管道大小作为管道的缓冲区。
PipedInputStream(PipedOutputStream src)创建一个 PipedInputStream ,使其连接到管道输出流 src 。
PipedInputStream(PipedOutputStream src, int pipeSize)创建一个 PipedInputStream ,使其连接到管道输出流 src ,并为管道缓冲区使用指定的管道大小。

所有方法

返回值类型方法和描述
int available()返回可以从该输入流读取而不阻塞的字节数。
void close()关闭此管道输入流,并释放与流相关联的任何系统资源。
void connect(PipedOutputStream src)使此管道输入流连接到管道输出流 src 。
int read()从这个管道输入流读取数据的下一个字节。
int read(byte[] b, int off, int len)从这个管道输入流读取最多 len个字节的数据到字节数组。
protected void receive(int b)接收一个字节的数据。

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

构造方法和描述
PipedWriter()创建一个尚未连接到管道读取器的管道写入器。
PipedWriter(PipedReader snk)创建连接到指定管道读取器的管道写入器。

所有方法

返回值类型方法和描述
void close()关闭此管道输出流,并释放与此流相关联的任何系统资源。
void connect(PipedReader snk)将此管道写入器连接到接收器。
void flush()刷新此输出流并强制任何缓冲的输出字符被写出。
void write(char[] cbuf, int off, int len)从指定的字符数组写入 len字符,从偏移量 off开始 off到这个管道输出流。
void write(int c)写入指定 char到管道输出流。

PipedReader

构造方法和描述
PipedReader()创建一个 PipedReader ,所以它还不是 connected 。
PipedReader(int pipeSize)创建一个 PipedReader ,使其尚未 connected并使用指定的管道大小作为管道的缓冲区。
PipedReader(PipedWriter src)创建一个 PipedReader ,使其连接到管道写入器 src 。
PipedReader(PipedWriter src, int pipeSize)创建一个 PipedReader ,使其连接到管道写入器 src ,并使用指定的管道大小作为管道的缓冲区。

所有方法

返回值类型方法和描述
void close()关闭此管道流,并释放与流相关联的任何系统资源。
void connect(PipedWriter src)使这个管道读取器连接到管道写入器 src 。
int read()从这个管道流读取下一个数据字符。
int read(char[] cbuf, int off, int len)从这个管道数据流读取最多 len字符数组。
boolean ready()告诉这个流是否准备好阅读。

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

构造方法和描述
BufferedOutputStream(OutputStream out)创建一个新的缓冲输出流,以将数据写入指定的底层输出流。
BufferedOutputStream(OutputStream out, int size)创建一个新的缓冲输出流,以便以指定的缓冲区大小将数据写入指定的底层输出流。

所有方法

返回值类型方法和描述
void flush()刷新缓冲输出流。
void write(byte[] b, int off, int len)从指定的字节数组写入 len个字节,从偏移 off开始到缓冲的输出流。
void write(int b)将指定的字节写入缓冲的输出流。

BufferedInputStream

构造方法和描述
BufferedInputStream(InputStream in)创建一个 BufferedInputStream并保存其参数,输入流 in ,供以后使用。
BufferedInputStream(InputStream in, int size)创建 BufferedInputStream具有指定缓冲区大小,并保存其参数,输入流 in ,供以后使用。

所有方法

返回值类型方法和描述
int available()返回从该输入流中可以读取(或跳过)的字节数的估计值,而不会被下一次调用此输入流的方法阻塞。
void close()关闭此输入流并释放与流相关联的任何系统资源。
void mark(int readlimit)见的总承包 mark的方法 InputStream 。
boolean markSupported()测试这个输入流是否支持 mark和 reset方法。
int read()见 read法 InputStream的一般合同。
int read(byte[] b, int off, int len)从给定的偏移开始,将字节输入流中的字节读入指定的字节数组。
void reset()见 reset法 InputStream的一般合同。
long skip(long n)见 skip法 InputStream的一般合同。

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(Writer out)创建使用默认大小的输出缓冲区的缓冲字符输出流。
BufferedWriter(Writer out, int sz)创建一个新的缓冲字符输出流,使用给定大小的输出缓冲区。

所有方法

返回值类型方法和描述
void close()关闭流,先刷新。
void flush()刷新流。
void newLine()写入行的分隔符,不一定是‘\n’,由系统属性决定。
void write(char[] cbuf, int off, int len)写入字符数组的一部分。
void write(int c)写一个字符
void write(String s, int off, int len)写一个字符串的一部分。

BufferedWriter

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

构造方法和描述
BufferedReader(Reader in)创建使用默认大小的输入缓冲区的缓冲字符输入流。
BufferedReader(Reader in, int sz)创建使用指定大小的输入缓冲区的缓冲字符输入流。

所有方法

返回值类型方法和描述
void close()关闭流并释放与之相关联的任何系统资源。
Stream<String> lines()返回一个 Stream ,其元素是从这个 BufferedReader读取的行。
void mark(int readAheadLimit)标记流中的当前位置。
boolean markSupported()告诉这个流是否支持mark()操作。
int read()读一个字符
int read(char[] cbuf, int off, int len)将字符读入数组的一部分。
String readLine()读一行文字。
boolean ready()告诉这个流是否准备好被读取。
void reset()将流重置为最近的标记。
long skip(long n)跳过字符

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

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

构造方法和描述
OutputStreamWriter(OutputStream out)创建一个使用默认字符编码的OutputStreamWriter。
OutputStreamWriter(OutputStream out, Charset cs)创建一个使用给定字符集的OutputStreamWriter。
OutputStreamWriter(OutputStream out, CharsetEncoder enc)创建一个使用给定字符集编码器的OutputStreamWriter。
OutputStreamWriter(OutputStream out, String charsetName)创建一个使用命名字符集的OutputStreamWriter。

所有方法

返回值类型方法和描述
void close()关闭流,先刷新。
void flush()刷新流。
String getEncoding()返回此流使用的字符编码的名称。
void write(char[] cbuf, int off, int len)写入字符数组的一部分。
void write(int c)写一个字符
void write(String str, int off, int len)写一个字符串的一部分。

InputStreamReader

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

构造方法和描述
InputStreamReader(InputStream in)创建一个使用默认字符集的InputStreamReader。
InputStreamReader(InputStream in, Charset cs)创建一个使用给定字符集的InputStreamReader。
InputStreamReader(InputStream in, CharsetDecoder dec)创建一个使用给定字符集解码器的InputStreamReader。
InputStreamReader(InputStream in, String charsetName)创建一个使用命名字符集的InputStreamReader。

所有方法

返回值类型方法和描述
void close()关闭流并释放与之相关联的任何系统资源。
String getEncoding()返回此流使用的字符编码的名称。
int read()读一个字符
int read(char[] cbuf, int offset, int length)将字符读入数组的一部分。
boolean ready()告诉这个流是否准备好被读取。

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();

构造方法

构造方法和描述
PrintStream(File file)使用指定的文件创建一个新的打印流,而不需要自动换行。
PrintStream(File file, String csn)使用指定的文件和字符集创建新的打印流,而不需要自动换行。
PrintStream(OutputStream out)创建一个新的打印流。
PrintStream(OutputStream out, boolean autoFlush)创建一个新的打印流。
PrintStream(OutputStream out, boolean autoFlush, String encoding)创建一个新的打印流。
PrintStream(String fileName)使用指定的文件名创建新的打印流,无需自动换行。
PrintStream(String fileName, String csn)创建一个新的打印流,不需要自动换行,具有指定的文件名和字符集。

所有方法

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

返回值类型方法和描述
PrintStream append(char c)将指定的字符附加到此输出流。
PrintStream append(CharSequence csq)将指定的字符序列附加到此输出流。
PrintStream append(CharSequence csq, int start, int end)将指定字符序列的子序列附加到此输出流。
boolean checkError()刷新流并检查其错误状态。
protected void clearError()清除此流的内部错误状态。
void close()关闭流。
void flush()刷新流。
PrintStream format(Locale l, String format, Object... args)使用指定的格式字符串和参数将格式化的字符串写入此输出流。
PrintStream format(String format, Object... args)使用指定的格式字符串和参数将格式化的字符串写入此输出流。
void print(Object obj)打印一个对象。
void println()通过写入行分隔符字符串来终止当前行。
void println(Object x)打印一个对象,然后终止该行。
PrintStream printf(Locale l, String format, Object... args)使用指定的格式字符串和参数将格式化的字符串写入此输出流的便利方法。
PrintStream printf(String format, Object... args)使用指定的格式字符串和参数将格式化的字符串写入此输出流的便利方法。
protected void setError()将流的错误状态设置为 true 。
void write(byte[] buf, int off, int len)从指定的字节数组写入 len个字节,从偏移 off开始到此流。
void write(int b)将指定的字节写入此流。

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();

构造方法

构造方法和描述
PrintWriter(File file)使用指定的文件创建一个新的PrintWriter,而不需要自动的线路刷新。
PrintWriter(File file, String csn)使用指定的文件和字符集创建一个新的PrintWriter,而不需要自动进行线条刷新。
PrintWriter(OutputStream out)从现有的OutputStream创建一个新的PrintWriter,而不需要自动线路刷新。
PrintWriter(OutputStream out, boolean autoFlush)从现有的OutputStream创建一个新的PrintWriter。
PrintWriter(String fileName)使用指定的文件名创建一个新的PrintWriter,而不需要自动执行行刷新。
PrintWriter(String fileName, String csn)使用指定的文件名和字符集创建一个新的PrintWriter,而不需要自动线路刷新。
PrintWriter(Writer out)创建一个新的PrintWriter,没有自动线冲洗。
PrintWriter(Writer out, boolean autoFlush)创建一个新的PrintWriter。

所有方法

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

返回值类型方法和描述
PrintWriter append(char c)将指定的字符附加到此作者。
PrintWriter append(CharSequence csq)将指定的字符序列附加到此作者。
PrintWriter append(CharSequence csq, int start, int end)将指定字符序列的子序列附加到此作者。
boolean checkError()如果流未关闭,请刷新流并检查其错误状态。
protected void clearError()清除此流的错误状态。
void close()关闭流并释放与之相关联的任何系统资源。
void flush()刷新流。
PrintWriter format(Locale l, String format, Object... args)使用指定的格式字符串和参数将格式化的字符串写入此写入程序。
PrintWriter format(String format, Object... args)使用指定的格式字符串和参数将格式化的字符串写入此写入程序。
void print(Object obj)打印一个对象。
void println()通过写入行分隔符字符串来终止当前行。
void println(Object x)打印一个对象,然后终止该行。
PrintWriter printf(Locale l, String format, Object... args)使用指定的格式字符串和参数将格式化的字符串写入该writer的方便方法。
PrintWriter printf(String format, Object... args)使用指定的格式字符串和参数将格式化的字符串写入该writer的方便方法。
protected void setError()表示发生错误。
void write(char[] buf)写入一个字符数组。
void write(char[] buf, int off, int len)写一个字符数组的一部分。
void write(int c)写一个字符
void write(String s)写一个字符串
void write(String s, int off, int len)写一个字符串的一部分。

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。

Person{name='张三, age=0, sex='男} Person{name='李四, age=0, sex='男} Person{name='如花, age=0, sex='女}

几个要点:

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

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

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

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

12.1.序列化流

ObjectOutputStream

修饰符构造方法和描述
protected ObjectOutputStream()为完全重新实现ObjectOutputStream的子类提供一种方法,不必分配刚刚被ObjectOutputStream实现使用的私有数据。
public ObjectOutputStream(OutputStream out)创建一个写入指定的OutputStream的ObjectOutputStream。

所有方法

返回值类型方法和描述
protected void annotateClass(类<?> cl)子类可以实现此方法,以允许类数据存储在流中。
protected void annotateProxyClass(类<?> cl)子类可以实现这种方法来存储流中的自定义数据以及动态代理类的描述符。
void close()关闭流。
void defaultWriteObject()将当前类的非静态和非瞬态字段写入此流。
protected void drain()排除ObjectOutputStream中的缓冲数据。
protected boolean enableReplaceObject(boolean enable)启用流来替换流中的对象。
void flush()刷新流。
ObjectOutputStream.PutField putFields()检索用于缓冲要写入流的持久性字段的对象。
protected Object replaceObject(Object obj)该方法将允许ObjectOutputStream的可信子类在序列化期间将一个对象替换为另一个对象。
void reset()复位将忽略已写入流的任何对象的状态。
void useProtocolVersion(int version)指定在编写流时使用的流协议版本。
void write(byte[] buf)写入一个字节数组。
void write(byte[] buf, int off, int len)写入一个子字节数组。
void write(int val)写一个字节。
void writeBoolean(boolean val)写一个布尔值。
void writeByte(int val)写入一个8位字节。
void writeBytes(String str)写一个字符串作为字节序列。
void writeChar(int val)写一个16位的字符。
void writeChars(String str)写一个字符串作为一系列的字符。
protected void writeClassDescriptor(ObjectStreamClass desc)将指定的类描述符写入ObjectOutputStream。
void writeDouble(double val)写一个64位的双倍。
void writeFields()将缓冲的字段写入流。
void writeFloat(float val)写一个32位浮点数。
void writeInt(int val)写一个32位int。
void writeLong(long val)写一个64位长
void writeObject(Object obj)将指定的对象写入ObjectOutputStream。
protected void writeObjectOverride(Object obj)子类使用的方法来覆盖默认的writeObject方法。
void writeShort(int val)写一个16位短。
protected void writeStreamHeader()提供了writeStreamHeader方法,因此子类可以在流中附加或预先添加自己的头。
void writeUnshared(Object obj)将“非共享”对象写入ObjectOutputStream。
void writeUTF(String str)此字符串的原始数据写入格式为 modified UTF-8 。

12.2.反序列化流

ObjectInputStream

修饰符构造方法和描述
protected ObjectInputStream()为完全重新实现ObjectInputStream的子类提供一种方法,不必分配刚刚被ObjectInputStream实现使用的私有数据。
public ObjectInputStream(InputStream in)创建从指定的InputStream读取的ObjectInputStream。

所有方法

返回值类型方法和描述
int available()返回可以读取而不阻塞的字节数。
void close()关闭输入流。
void defaultReadObject()从此流读取当前类的非静态和非瞬态字段。
protected boolean enableResolveObject(boolean enable)启用流以允许从流中读取的对象被替换。
int read()读取一个字节的数据。
int read(byte[] buf, int off, int len)读入一个字节数组。
boolean readBoolean()读取布尔值。
byte readByte()读取一个8位字节。
char readChar()读一个16位字符。
protected ObjectStreamClass readClassDescriptor()从序列化流读取类描述符。
double readDouble()读64位双倍。
ObjectInputStream.GetField readFields()从流中读取持久性字段,并通过名称获取它们。
float readFloat()读32位浮点数。
void readFully(byte[] buf)读取字节,阻塞直到读取所有字节。
void readFully(byte[] buf, int off, int len)读取字节,阻塞直到读取所有字节。
int readInt()读取一个32位int。
String readLine()已弃用此方法无法将字节正确转换为字符。有关详细信息和替代方案,请参阅DataInputStream。
long readLong()读64位长。
Object readObject()从ObjectInputStream读取一个对象。
protected Object readObjectOverride()此方法由ObjectOutputStream的受信任子类调用,该子类使用受保护的无参构造函数构造ObjectOutputStream。
short readShort()读取16位短。
protected void readStreamHeader()提供了readStreamHeader方法来允许子类读取和验证自己的流标题。
Object readUnshared()从ObjectInputStream读取一个“非共享”对象。
int readUnsignedByte()读取一个无符号的8位字节。
int readUnsignedShort()读取无符号16位短。
String readUTF()以 modified UTF-8格式读取字符串。
void registerValidation(ObjectInputValidation obj, int prio)在返回图之前注册要验证的对象。
protected 类<?> resolveClass(ObjectStreamClass desc)加载本地类等效的指定流类描述。
protected Object resolveObject(Object obj)此方法将允许ObjectInputStream的受信任子类在反序列化期间将一个对象替换为另一个对象。
protected 类<?> resolveProxyClass(String[] interfaces)返回一个代理类,它实现代理类描述符中命名的接口; 子类可以实现此方法从流中读取自定义数据以及动态代理类的描述符,从而允许它们为接口和代理类使用备用加载机制。
int skipBytes(int len)跳过字节。

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字符代码表就知道了。

image

 

对照ASCII字符代码表结果

image

(图片来源于百度)

13.1.字节流--数据

DataOutputStream

构造方法和描述
DataOutputStream(OutputStream out)创建一个新的数据输出流,以将数据写入指定的底层输出流。

所有方法

返回值类型方法和描述
void flush()刷新此数据输出流。
int size()返回计数器的当前值 written ,到目前为止写入此数据输出流的字节数。
void write(byte[] b, int off, int len)写入 len从指定的字节数组起始于偏移 off基础输出流。
void write(int b)将指定的字节(参数 b的低8位)写入底层输出流。
void writeBoolean(boolean v)将 boolean写入底层输出流作为1字节值。
void writeByte(int v)将 byte作为1字节值写入底层输出流。
void writeBytes(String s)将字符串作为字节序列写入基础输出流。
void writeChar(int v)将 char写入底层输出流作为2字节值,高字节优先。
void writeChars(String s)将字符串写入底层输出流作为一系列字符。
void writeDouble(double v)双参数传递给转换 long使用 doubleToLongBits方法在类 Double ,然后写入该 long值基础输出流作为8字节的数量,高字节。
void writeFloat(float v)浮子参数的转换 int使用 floatToIntBits方法在类 Float ,然后写入该 int值基础输出流作为一个4字节的数量,高字节。
void writeInt(int v)将底层输出流写入 int作为四字节,高位字节。
void writeLong(long v)将 long写入底层输出流,为8字节,高字节为首。
void writeShort(int v)将 short写入底层输出流作为两个字节,高字节优先。
void writeUTF(String str)使用 modified UTF-8编码以机器无关的方式将字符串写入基础输出流。

DataInputStream

构造方法和描述
DataInputStream(InputStream in)创建使用指定的底层InputStream的DataInputStream。

所有方法

返回值类型方法和描述
int read(byte[] b)从包含的输入流中读取一些字节数,并将它们存储到缓冲区数组 b 。
int read(byte[] b, int off, int len)从包含的输入流读取最多 len个字节的数据为字节数组。
boolean readBoolean()见的总承包 readBoolean的方法 DataInput 。
byte readByte()见的总承包 readByte的方法 DataInput 。
char readChar()见 readChar方法的总合同 DataInput 。
double readDouble()见 readDouble方法 DataInput的总体合同。
float readFloat()见 readFloat法 DataInput的一般合同。
void readFully(byte[] b)见的总承包 readFully的方法 DataInput 。
void readFully(byte[] b, int off, int len)见的总承包 readFully的方法 DataInput 。
int readInt()见 readInt方法 DataInput的一般合同。
String readLine()已弃用此方法无法将字节正确转换为字符。从JDK 1.1开始,读取文本行的BufferedReader.readLine()方法是通过BufferedReader.readLine()方法。使用DataInputStream类读取行的程序可以转换为使用BufferedReader类替换以下形式的代码: DataInputStream d = new DataInputStream(in);与: BufferedReader d = new BufferedReader(new InputStreamReader(in));
long readLong()见的总承包 readLong的方法 DataInput 。
short readShort()见 readShort方法 DataInput的一般合同。
int readUnsignedByte()见的总承包 readUnsignedByte的方法 DataInput 。
int readUnsignedShort()见 readUnsignedShort法 DataInput的一般合同。
String readUTF()见 readUTF法 DataInput的一般合同。
static String readUTF(DataInput in)从流in读取以modified UTF-8格式编码的Unicode字符串的表示; 这个字符串然后作为String返回。
int skipBytes(int n)见 skipBytes法 DataInput的一般合同。

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();
构造方法和描述
PushbackInputStream(InputStream in)创建一个 PushbackInputStream并保存其参数,输入流 in供以后使用。
PushbackInputStream(InputStream in, int size)使用 PushbackInputStream的推回缓冲区创建 size ,并保存其参数,输入流 in供以后使用。

所有方法

返回值类型方法和描述
int available()返回从该输入流中可以读取(或跳过)的字节数的估计值,而不会被下一次调用此输入流的方法阻塞。
void close()关闭此输入流并释放与流相关联的任何系统资源。
void mark(int readlimit)标记此输入流中的当前位置。
boolean markSupported()测试这个输入流是否支持 mark和 reset方法,而不是。
int read()从该输入流读取下一个数据字节。
int read(byte[] b, int off, int len)从该输入流读取最多 len字节的数据为字节数组。
void reset()将此流重新定位到最后在此输入流上调用 mark方法时的位置。
long skip(long n)跳过并丢弃来自此输入流的 n字节的数据。
void unread(byte[] b)将一个字节数组复制回推回缓冲区的前端。
void unread(byte[] b, int off, int len)通过将字节数组复制到推回缓冲区的前端来推回一部分数组。
void unread(int b)通过将其复制到推回缓冲区的前端来推回一个字节。

14.2.字符流--回退

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

构造方法和描述
PushbackReader(Reader in)用一个字符的后置缓冲区创建一个新的推回阅读器。
PushbackReader(Reader in, int size)使用给定大小的推回缓冲区创建一个新的推回阅读器。

所有方法

返回值类型方法和描述
void close()关闭流并释放与之相关联的任何系统资源。
void mark(int readAheadLimit)标记流中的当前位置。
boolean markSupported()告诉这个流是否支持mark()操作,它不是。
int read()读一个字符
int read(char[] cbuf, int off, int len)将字符读入数组的一部分。
boolean ready()告诉这个流是否准备好被读取。
void reset()重置流。
long skip(long n)跳过字符
void unread(char[] cbuf)将一个字符数组复制回推回缓冲区的前端。
void unread(char[] cbuf, int off, int len)通过将一部分字符复制到推回缓冲区的前端来推回。
void unread(int c)将单个字符复制回推回缓冲区的前端。

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流位于其它的包下。

image

Java往期文章

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

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

更多优质文章和资源👇

image

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

posted @ 2022-03-13 21:26  渊渟岳  阅读(220)  评论(0编辑  收藏  举报