Java IO知识体系详解

  • 知识体系
  • Java IO-分类(传输,操作)

知识体系

IO框架之知识体系:首先了解下Java IO框架包含什么,同时推荐下如何学习IO框架
20221227153836.png

Java IO-分类(传输,操作)

  • IO理解分类 - 从传输方式上
  • IO理解分类 - 从数据操作上
IO理解分类 - 从传输方式上

从数据传输方式或者说是运输方式角度看,可以将 IO 类分为:

  • 字节流
  • 字符流
  • 字节流和字符流的区别
  • 字节转字符InputStreamReader、OutputStreamWriter
  • 字节流和字符流的常用方法

    字节是个计算机看的,字符才是给人看的
字节流

lc5y92w7.png

字符流

lc5y08mw.png

字节流和字符流的区别
  • 字节流读取单个字节,字符流读取单个字符(一个字符根据编码的不同,对应的字节也不同,如 UTF-8 编码是 3 个字节,中文编码是 2 个字节。)
  • 字节流用来处理二进制文件(比如图片、MP3、视频文件),字符流用来处理文本文件(可以看做是特殊的二进制文件,只不过经过了编码,便于人们阅读。)。

    简而言之,字节是个计算机看的,字符才是给人看的;字节流可以处理一切文件,而字符流只能处理文本。

虽然 IO 类很多,但核心的就是 4 个抽象类:InputStream、OutputStream、Reader、Writer。 虽然 IO 类的方法也很多,但核心的也就 2 个:read 和 write。

字节转字符InputStreamReader、OutputStreamWriter

编码就是把字符转换为字节,而解码是把字节重新组合成字符。
如果编码和解码过程使用不同的编码方式那么就出现了乱码。

GBK 编码中,中文字符占 2 个字节,英文字符占 1 个字节;
UTF-8 编码中,中文字符占 3 个字节,英文字符占 1 个字节;
UTF-16be 编码中,中文字符和英文字符都占 2 个字节。

UTF-16be 中的 be 指的是 Big Endian,也就是大端。相应地也有 UTF-16le,le 指的是 Little Endian,也就是小端。

Java 使用双字节编码 UTF-16be,这不是指 Java 只支持这一种编码方式,而是说 char 这种类型使用 UTF-16be 进行编码。char 类型占 16 位,也就是两个字节,Java 使用这种双字节编码是为了让一个中文或者一个英文都能使用一个 char 来存储。
lc5yfhir.png

字节流和字符流的常用方法

InputStream 类

  • int read():读取数据
  • int read(byte b[], int off, int len):从第 off 位置开始读,读取 len 长度的字节,然后放入数组 b 中
  • long skip(long n):跳过指定个数的字节
  • int available():返回可读的字节数
  • void close():关闭流,释放资源

OutputStream 类

  • void write(int b): 写入一个字节,虽然参数是一个 int 类型,但只有低 8 位才会写入,高 24 位会舍弃(这块后面再讲)
  • void write(byte b[], int off, int len): 将数组 b 中的从 off 位置开始,长度为 len 的字节写入
  • void flush(): 强制刷新,将缓冲区的数据写入
  • void close():关闭流

Reader 类

  • int read():读取单个字符
  • int read(char cbuf[], int off, int len):从第 off 位置开始读,读取 len 长度的字符,然后放入数组 b 中
  • long skip(long n):跳过指定个数的字符
  • int ready():是否可以读了
  • void close():关闭流,释放资源

Writer 类

  • void write(int c): 写入一个字符
  • void write( char cbuf[], int off, int len): 将数组 cbuf 中的从 off 位置开始,长度为 len 的字符写入
  • void flush(): 强制刷新,将缓冲区的数据写入
  • void close():关闭流

理解了上面这些方法,基本上 IO 的灵魂也就全部掌握了。

IO理解分类 - 从数据操作上

你细想一下,IO IO,不就是输入输出(Input/Output)嘛;从数据来源或者说是操作对象角度看,IO 类可以分为:

  • Input:将外部的数据读入内存,比如说把文件从硬盘读取到内存,从网络读取数据到内存等等
  • Output:将内存中的数据写入到外部,比如说把数据从内存写入到文件,把数据从内存输出到网络等等。

所有的程序,在执行的时候,都是在内存上进行的,一旦关机,内存中的数据就没了,那如果想要持久化,就需要把内存中的数据输出到外部,比如说文件。

文件操作算是 IO 中最典型的操作了,也是最频繁的操作。那其实你可以换个角度来思考,比如说按照 IO 的操作对象来思考,IO 就可以分类为:文件、数组、管道、基本数据类型、缓冲、打印、对象序列化/反序列化,以及转换等。

  • 文件
  • 数组
  • 管道
  • 基本数据类型
  • 缓冲
  • 打印
  • 对象序列化/反序列化
  • 转换
IO理解分类-数据操作上

lc5yldwi.png

文件

文件流也就是直接操作文件的流,可以细分为字节流(FileInputStream 和 FileOuputStream)和字符流(FileReader 和 FileWriter)。
FileInputStream 的例子:

int b;
FileInputStream fis1 = new FileInputStream("fis.txt");
// 循环读取
while ((b = fis1.read())!=-1) {
    System.out.println((char)b);
}
// 关闭资源
fis1.close();

FileOutputStream 的例子:

FileOutputStream fos = new FileOutputStream("fos.txt");
fos.write("沉默王二".getBytes());
fos.close();

FileReader 的例子:

int b = 0;
FileReader fileReader = new FileReader("read.txt");
// 循环读取
while ((b = fileReader.read())!=-1) {
    // 自动提升类型提升为 int 类型,所以用 char 强转
    System.out.println((char)b);
}
// 关闭流
fileReader.close();

FileWriter 的例子:

FileWriter fileWriter = new FileWriter("fw.txt");
char[] chars = "沉默王二".toCharArray();
fileWriter.write(chars, 0, chars.length);
fileWriter.close();

当掌握了文件的输入输出,其他的自然也就掌握了,都大差不差。

数组

通常来说,针对文件的读写操作,使用文件流配合缓冲流就够用了,但为了提升效率,频繁地读写文件并不是太好,那么就出现了数组流,有时候也称为内存流。

ByteArrayInputStream 的例子:

InputStream is =new BufferedInputStream(
        new ByteArrayInputStream(
                "沉默王二".getBytes(StandardCharsets.UTF_8)));
//操作
byte[] flush =new byte[1024];
int len =0;
while(-1!=(len=is.read(flush))){
    System.out.println(new String(flush,0,len));
}
//释放资源
is.close();

ByteArrayOutputStream 的例子:

ByteArrayOutputStream bos =new ByteArrayOutputStream();
byte[] info ="沉默王二".getBytes();
bos.write(info, 0, info.length);
//获取数据
byte[] dest =bos.toByteArray();
//释放资源
bos.close();

管道

Java 中的管道和 Unix/Linux 中的管道不同,在 Unix/Linux 中,不同的进程之间可以通过管道来通信,但 Java 中,通信的双方必须在同一个进程中,也就是在同一个 JVM 中,管道为线程之间的通信提供了通信能力。

一个线程通过 PipedOutputStream 写入的数据可以被另外一个线程通过相关联的 PipedInputStream 读取出来。

final PipedOutputStream pipedOutputStream = new PipedOutputStream();
final PipedInputStream pipedInputStream = new PipedInputStream(pipedOutputStream);
 
Thread thread1 = new Thread(new Runnable() {
    @Override
    public void run() {
        try {
            pipedOutputStream.write("沉默王二".getBytes(StandardCharsets.UTF_8));
            pipedOutputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
});
 
Thread thread2 = new Thread(new Runnable() {
    @Override
    public void run() {
        try {
            byte[] flush =new byte[1024];
            int len =0;
            while(-1!=(len=pipedInputStream.read(flush))){
                System.out.println(new String(flush,0,len));
            }
 
            pipedInputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
 
    }
});
thread1.start();
thread2.start();

基本数据类型

基本数据类型输入输出流是一个字节流,该流不仅可以读写字节和字符,还可以读写基本数据类型。

DataInputStream 提供了一系列可以读基本数据类型的方法:

DataInputStream dis = new DataInputStream(new FileInputStream("das.txt")) ;
byte b = dis.readByte() ;
short s = dis.readShort() ;
int i = dis.readInt();
long l = dis.readLong() ;
float f = dis.readFloat() ;
double d = dis.readDouble() ;
boolean bb = dis.readBoolean() ;
char ch = dis.readChar() ;

DataOutputStream 提供了一系列可以写基本数据类型的方法:

DataOutputStream das = new DataOutputStream(new FileOutputStream("das.txt"));
das.writeByte(10);
das.writeShort(100);
das.writeInt(1000);
das.writeLong(10000L);
das.writeFloat(12.34F);
das.writeDouble(12.56);
das.writeBoolean(true);
das.writeChar('A');

缓冲

CPU 很快,它比内存快 100 倍,比磁盘快百万倍。那也就意味着,程序和内存交互会很快,和硬盘交互相对就很慢,这样就会导致性能问题。

为了减少程序和硬盘的交互,提升程序的效率,就引入了缓冲流,也就是类名前缀带有 Buffer 的那些,比如说 BufferedInputStream、BufferedOutputStream、BufferedReader、BufferedWriter。
lc5yspah.png

缓冲流在内存中设置了一个缓冲区,只有缓冲区存储了足够多的带操作的数据后,才会和内存或者硬盘进行交互。简单来说,就是一次多读/写点,少读/写几次,这样程序的性能就会提高。

打印

恐怕 Java 程序员一生当中最常用的就是打印流了:System.out 其实返回的就是一个 PrintStream 对象,可以用来打印各式各样的对象。

System.out.println("沉默王二是真的二!");

PrintStream 最终输出的是字节数据,而 PrintWriter 则是扩展了 Writer 接口,所以它的 print()/println() 方法最终输出的是字符数据。使用上几乎和 PrintStream 一模一样。

StringWriter buffer = new StringWriter();
try (PrintWriter pw = new PrintWriter(buffer)) {
    pw.println("沉默王二");
}
System.out.println(buffer.toString());

对象序列化/反序列化

序列化本质上是将一个 Java 对象转成字节数组,然后可以将其保存到文件中,或者通过网络传输到远程。

ByteArrayOutputStream buffer = new ByteArrayOutputStream();
try (ObjectOutputStream output = new ObjectOutputStream(buffer)) {
    output.writeUTF("沉默王二");
}
System.out.println(Arrays.toString(buffer.toByteArray()));

与其对应的,有序列化,就有反序列化,也就是再将字节数组转成 Java 对象的过程。

try (ObjectInputStream input = new ObjectInputStream(new FileInputStream(
        new File("Person.txt")))) {
    String s = input.readUTF();
}

. 转换 InputStreamReader 是从字节流到字符流的桥连接,它使用指定的字符集读取字节并将它们解码为字符。

InputStreamReader isr = new InputStreamReader(
        new FileInputStream("demo.txt"));
char []cha = new char[1024];
int len = isr.read(cha);
System.out.println(new String(cha,0,len));
isr.close();

OutputStreamWriter 将一个字符流的输出对象变为字节流的输出对象,是字符流通向字节流的桥梁。

File f = new File("test.txt") ;
Writer out = new OutputStreamWriter(new FileOutputStream(f)) ; // 字节流变为字符流  
out.write("hello world!!") ;    // 使用字符流输出  
out.close() ;

标签: IO流, IO流学习, Java IO流

添加新评论