Java学习笔记

IO也就是`Input/Output` ,数据拿到计算机内存中的过程即为输入,反之,数据从内存输出到外部存储(可以是远程主机、磁盘、数据库等)的过程即为输出。数据传输过程类似于水流,因此称作IO流。IO流在Java中分为输出流和输入流,根据数据的处理方式又分为字节流和字符流。(这里的输入输出是以程序为中心的,输入指程序接收输入,输出指程序把数据输出到外部存储) ## Java IO流 Java IO流有四个基类,分别是输入流`InputStream`(字节输入流),`Reader`(字符输入流),`OutputStream`(字节输出流),`Writer`(字符输出流),其余的IO相关类都是派生于这四个抽象基类。 ### 字节流与字符流 字节流: 1. 以字节为单位处理数据,适用于处理二进制数据 2. 直接操作字节,不涉及编码转换,可以处理任何类型的数据 字符流: 1. 以字符为单位处理数据,适合处理文本数据 2. 自动处理字符编码和解码(将字节传为字符) 3. 性能逊于字节流处理,因为还有编解码消耗 4. 对于不知道编码类型的数据,使用字节流处理会带来乱码问题,而使用字符流就不会出现这样的问题 ## 字节流 ### InputStream `InputStream`用于从源读取字节流到内存中,它是一个抽象类,是所有字节输入流的父类。 #### 常用方法 1. `read()`:返回输入流中下一个字节的数据,如果未读取任何字节,返回`-1`,表示结束 2. `read(byte b[])`:从输入流中读取一些字节放到字节数组`b`中,如果数组`b`的长度为0,则不读取,如果没有可以读取的字节,返回`-1`。最多可以读`b.length`个字节,返回读取的字节数 3. `read(byte b[], int off, int len)`:`off`是偏移量,`len`是指定的要读取的最大字节数,其余与`read(byte b[])`一致(这里的偏移量`off`是针对字节数组`b`的,加入偏移为2,则从`b`的第3个下标开始填充) 4. `skip(long n)`:忽略输入流中的`n`个字节,返回实际忽略的字节数 5. `avaliable()`:返回输入流中可以读取的字节数 6. `close()`:关闭输入流并释放资源 7. `readAllBytes()`:读取输入流中的所有字节,返回字节数组 8. `readNBytes(byte[] b, int off, int len)`:阻塞直到读取`len`个字节 9. `transferTo(OutputStream out)`:将所有字节流从一个输入流传递到一个输出流,输出流自动写入 使用的输入文件为`text.txt`: ``` hello,world! sjska 12345678910 qpwoeiruty ``` ```java String filePath = "text.txt"; FileInputStream file = new FileInputStream(filePath); System.out.println(file.available()); // 输出输入流中可以读取的字节数 44 byte[] bytes = new byte[10]; file.read(bytes); for (byte b : bytes){ System.out.println(b); System.out.println(Character.toChars(b)); } // 输出bytes数组的内容,依次输出 104 h 101 e 108 l 108 l 111 o 44 , 119 w 111 o 114 r 108 l int read = file.read(); System.out.println(Character.toChars(read)); // 读取了一个字节,输出 d byte[] bytes1 = new byte[7]; file.read(bytes1, 2,5); for(byte b : bytes1){ System.out.println(b); System.out.println(Character.toChars(b)); } // 读取5个字节,并且偏移量为2,输出bytes1数组的内容,依次输出 // 0 0 33 ! 13 10 115 s 106 j // bytes1数组的前两个字节偏移了,为空,13和10分别表示换行符和回车符 // 跳过4个字节 file.skip(4); file.read(bytes); for(byte b : bytes){ System.out.println(b); System.out.println(Character.toChars(b)); } // 再读取10个字节存在bytes中,其内容为 // 10 49 1 50 2 51 3 52 4 53 5 54 6 55 7 56 8 57 9 // 第一个10是回车符,它之前的换行符已经被skip跳过了 // 之后的1~9为存储的文本,49~57为其对应的ASCII码 String outFilePath = "text1.txt" OutputStream fileOut = new FileOutputStream("outFilePath"); // 将输入流file中的字节全部放入输出流fileOut file.transferTo(fileOut); // 如果没有执行transferTo方法,这里读取输入流中剩余全部字符放在返回的字符数组中 // 但是执行了transferTo,输入流中已经没有字节了,所以什么都没读到 byte[] bytes2 = file.readAllBytes(); for(byte b : bytes2){ System.out.println(b); System.out.println(Character.toChars(b)); } ``` 输出文件`text1.txt`: ``` 10 qpwoeiruty ``` ### OutputStream 字节输出流,将字节输出到指定地方(文件等地),`OutputStream`是所有字节输出流的父类。 #### 常用方法 1. `write(int b)`:将特定字节写入到输出流 2. `write(byte b[])`:将字节数组`b`写入到输出流 3. `write(byte b[], int off, int len)`:增加了`off`偏移量以及`len`(要写入的最大字节数),与字节输入流相同,这里的`off`也是对于字节数组`b`来说 4. `flush()`:刷新此输出流并强制写出所有缓冲的输出字节 5. `close()`:关闭输出流并释放资源 `FileOutputStream`是使用最多的字节输出流对象,用于将字节写入到文件中,当调用`write`方法的时候,首先将数据写入到`FileOutputStream`的内存缓冲区,当缓冲区满、手动调用`flush`方法、手动调用`close`方法(其实也是触发了`flush`方法的调用)、程序退出触发`close`方法时,才会把数据写入到文件中。 ```java String filePath = "test.txt"; FileOutputStream fis = new FileOutputStream(filePath); // 将 Z 写入到输出流 fis.write(90); // 往输出流写入换行符 fis.write(10); // 网输出流写入回车 fis.write(13); // 往输出流写入 hello,world! byte[] bytes = new String("hello,world!").getBytes(); fis.write(bytes); fis.write(10); // 偏移量为3,网输出流写入字节数组 fis.write(bytes,3,9); ``` 得到的输出文件`test.txt": ``` Z hello,world! lo,world! ``` ## 字符流 基于字节流的IO若不知道编码方式就容易出现乱码问题,字符流对象方便我们对字符进行流操作,对于音频、视频、图片等媒体文件建议使用字节流进行处理,而对于文本文件建议使用字符流进行处理。 字符流默认采用的编码方式是`Unicode`编码。 ### Reader `Reader`用于从文件读取字符流到内存,它是所有字符输入流的父类。 #### 常用方法 1. `read()`:从输入流读取一个字符 2. `read(char[] cbuf)`:用于从输入流读取字符到字符数组`cbuf`中 3. `read(char[] cbuf, int off, int len)`:用于从输入流读取字符到字符数组`cbuf`中,并增加了偏移量`off`以及读取的字符数量`len` 4. `skip(long n)`:忽略输入流中的`n`个字符,返回实际忽略的字符数量 5. `close()`:关闭输入流并释放资源 `FileReader`是使用较多的字符输入流。 ```java // 文件使用上面的 test.txt String filePath = "test.txt"; FileReader fr = new FileReader(filePath); // 读取一个字符,输出90,也就是Z System.out.println(fr.read()); char [] cBuf = new char[10]; // 读取一系列字符串到字符数组中 // 依次输出 换行符 回车符 h e l l o , w o fr.read(cBuf); for(char c : cBuf){ System.out.println(c); } // 跳过一个字符 fr.skip(1); char [] cBuf1 = new char[6]; // 读取4个字符串到字符数组中,偏移量为2 // 输出 空 空 l d ! 换行符 fr.read(cBuf1,2,4); for(char c : cBuf1){ System.out.println(c); } ``` ### Writer `Writer`用于将字符输出到文件中,它是所有字符输出流的父类。 #### 常用方法 1. `write(int c)`:向输出流写入单个字符 2. `write(char[] cbuf)`:向输出流写入字符数组 3. `write(char[] cbuf, int off, int len)`:向输出流写入字符数组,并包含偏移量`off`以及字符数量`len` 4. `write(String str)`:向输出流写入字符串 5. `write(String str, int off, int len)`:向输出流写入字符串,并且包含偏移量`off`以及字符数量`len` 6. `append(CharSequence csq)`:将指定的字符序列`csp`附加到指定的`Writer`对象并返回该`Writer`对象 7. `append(char c)`:将指定的字符附加到指定的`Writer`对象并返回该`Writer`对象 8. `flush()`:刷新该输出流,强制输出所有缓冲的输出字符 9. `close()`:关闭输出流并释放资源 ```java String filePath = "simple.txt"; FileWriter fw = new FileWriter(filePath); // 向输出流写入字母Z fw.write(90); // 写入换行符 fw.write(13); // 写入字符串 hello,world! fw.write("hello,world!"); // 写入换行符 fw.write(13); char[] charArray = "the night".toCharArray(); // 写入字符数组 fw.write(charArray); // 写入换行符 fw.write(13); // 写入字符串,偏移量2,写入5个字符 // 因此写入的是 34567 fw.write("123456789",2,5); // 写入换行符 fw.write(13); // 写入字符数组,偏移量4,写入2个字符 // 因此写入的是 ni fw.write(charArray,4,2); // 写入换行符 fw.write(13); CharSequence cs = new String("char sequence"); // 写入换行符 fw.write(13); // 在输出流后追加字符序列 char sequence Writer append = fw.append(cs); // 刷新字符输入流 append.flush(); ``` 最终得到的`simple.txt`内容如下: ``` Z hello,world! the night 34567 ni char sequence ``` ## 缓冲流 由于IO操作很耗时,所以采用缓冲流,一次写入/读出多个字节,从而避免频繁的IO操作,提高流的传输效率。 ### 字节缓冲流 字节缓冲流采用**装饰器模式**来增强`InputStream`和`OutputStream`子类对象的功能。 Java的输入输出流有自带的内部缓冲区,为什么还需要字节缓冲流? 1. 内部缓冲区的大小固定且较小,而字节缓冲流可以自定义缓冲区大小,更灵活 2. 字节缓冲区性能更高 #### BufferedInputStream `BufferedInputStream`从源头读取数据到内存的过程不会一个字节一个字节读取,而是会先将读取到的字节存放在缓冲区,并从内部缓冲区中单独读取字节,大大减少IO次数,提高了读取效率。 未完待续......

版权声明: 如无特别声明,本文版权归 月梦の技术博客 所有,转载请注明本文链接。

(采用 CC BY-NC-SA 4.0 许可协议进行授权)

本文标题:《 Java IO知识总结 》

本文链接:https://ymiir.netlify.app//draft/2023-12-06-JavaIO

本文最后一次更新为 天前,文章中的某些内容可能已过时!