当前位置:   article > 正文

【JavaSE】IO流_javase io流

javase io流

一、IO流技术


输入(Input)指的是:可以让程序从外部系统获得数据(核心含义是“读”,读取外部数据)。

输出(Output)指的是:程序输出数据给外部系统从而可以操作外部系统(核心含义是“写”,将数据写出到外部系统)。

java.io包为我们提供了相关的API,实现了对所有外部系统的输入输出操作,这就是我们这章所要学习的技术。

数据源

数据源data source,提供数据的原始媒介。常见的数据源有:数据库、文件、其他程序、内存、网络连接、IO设备:

数据源分为:

  1. 源设备:为程序提供数据,一般对应输入流;
  2. 目标设备:程序数据的目的地,一般对应输出流;

流是一个抽象、动态的概念,是一连串连续动态的数据集合

对于输入流而言,数据源就像水箱,流(stream)就像水管中流动着的水流,程序就是我们最终的用户。我们通过流(A Stream)将数据源(Source)中的数据(information)输送到程序(Program)中。

对于输出流而言,目标数据源就是目的地(dest),我们通过流(A Stream)将程序(Program)中的数据(information)输送到目的数据源(dest)中。

流与源数据源和目标数据源之间的关系:

1.1 两种程序写法


try-catch-finally

  1. /**
  2. * IO程序的经典写法
  3. */
  4. public class Test01 {
  5. public static void main(String[] args) {
  6. FileInputStream fis = null; // 作用域为全局
  7. StringBuilder sb = new StringBuilder(); // 用于字符串拼接
  8. try{
  9. fis = new FileInputStream("D:\\JAVA\\workSpace\\ioDemo\\a.txt");
  10. int temp = 0;
  11. // 当temp等于-1时,表示已经到了文件结尾,停止读取
  12. while( (temp = fis.read()) != -1){
  13. sb.append( (char)temp );
  14. }
  15. System.out.println(sb);
  16. }catch (Exception e){
  17. e.printStackTrace();
  18. }finally {
  19. try {
  20. // 保证了即使遇到异常情况,也会关闭流对象
  21. if( fis != null) {
  22. fis.close();
  23. }
  24. } catch (IOException e) {
  25. e.printStackTrace();
  26. }
  27. }
  28. }
  29. }
  30. // 输出结果
  31. kk 123456 sgsg

try-with-resource

在JDK7以及以后的版本中可以使用try-with-resource语法更优雅的关闭资源。

java.lang.AutoCloseable接口:

java.lang.AutoCloseable接口中包含了一个close方法,该方法用于关闭资源。

只要是实现了java.lang.AutoCloseable接口的对象,都可以使用try-with-resource关闭资源,使用最新的try-with-resource简化:

  1. /**
  2. * try-with-resource写法
  3. */
  4. public class Test02 {
  5. public static void main(String[] args) {
  6. try (FileInputStream fis = new FileInputStream("D:\\JAVA\\workSpace\\ioDemo\\a.txt")) {
  7. StringBuilder sb = new StringBuilder();
  8. int temp = 0;
  9. while ((temp = fis.read()) != -1) {
  10. sb.append((char) temp);
  11. }
  12. System.out.println(sb);
  13. } catch (IOException e) {
  14. e.printStackTrace();
  15. }
  16. }
  17. }

1.2 流概念的细分与体系


1.2.1 流的细分

按流的方向分类:

  • 输入流:数据流向是数据源到程序(以InputStream、Reader结尾的流);
  • 输出流:数据流向是程序到目的地(以OutPutStream、Writer结尾的流);

按处理的数据单元分类:

  • 字节流:以字节为单位获取数据,命名上以Stream结尾的流一般是字节流,如FileInputStream、FileOutputStream;

  • 字符流:以字符为单位获取数据,命名上以Reader/Writer结尾的流一般是字符流,如FileReader、FileWriter;

按处理对象不同分类:

  • 节点流可以直接从数据源或目的地读写数据,如FileInputStream、FileReader、DataInputStream等;
  • 处理流不直接连接到数据源或目的地,是”处理流的流”。通过对其他流的处理提高程序的性能,如BufferedInputStream、BufferedReader等。处理流也叫包装流;

节点流处于IO操作的第一线,所有操作必须通过它们进行;处理流可以对节点流进行包装,提高性能或提高程序的灵活性。

1.2.2 流的体系

Java为我们提供了多种多样的IO流,我们可以根据不同的功能及性能要求挑选合适的IO流,如图所示,为Java中IO流类的体系。

抽象类

  1. InputStream / OutputStream:字节流的抽象类;
  2. Reader / Writer:字符流的抽象类;

节点流

  1. FileInputStream / FileOutputStream:节点流,以字节为单位直接操作“文件”;
  2. ByteArrayInputStream / ByteArrayOutputStream:节点流,以字节为单位直接操作“字节数组对象”;
  3. FileReader/FileWriter:节点流,以字符为单位直接操作“文本文件”(注意:只能读写文本文件);

处理流

  1. ObjectInputStream / ObjectOutputStream:处理流,以字节为单位直接操作“对象”;
  2. DataInputStream / DataOutputStream:处理流,以字节为单位直接操作“基本数据类型与字符串类型”;
  3. BufferedReader / BufferedWriter:处理流,将Reader/Writer对象进行包装,增加缓存功能,提高读写效率;
  4. BufferedInputStream / BufferedOutputStream:处理流,将InputStream/OutputStream对象进行包装,增加缓存功能,提高读写效率;
  5. InputStreamReader / OutputStreamWriter:处理流,转换流,将字节流对象转化成字符流对象;
  6. PrintStream:处理流,将OutputStream进行包装,可以方便地输出字符,更加灵活;

1.3 IO中四大抽象类


InputStream / OutputStreamReader / writer类是所有IO流类的抽象父类。

InputStream

此抽象类是表示字节输入流的所有类的父类。InputSteam是一个抽象类,它不可以实例化。 数据的读取需要由它的子类来实现。根据节点的不同,它派生了不同的节点流子类 。

继承自InputSteam的流都是用于向程序中输入数据,且数据的单位为字节(8 bit)。

常用方法:

方法名

使用说明

int read()

读取一个字节的数据,并将字节的值作为int类型返回(0-255之间的一个值)。如果未读出字节则返回-1(返回值为-1表示读取结束)

void close()

关闭输入流对象,释放相关系统资源

OutputStream

此抽象类是表示字节输出流的所有类的父类。输出流接收输出字节并将这些字节发送到某个目的地。

方法名

使用说明

void write(int n)

向目的地中写入一个字节

void close()

关闭输出流对象,释放相关系统资源

Reader

Reader用于读取的字符输入流抽象类,数据单位为字符

方法名

使用说明

int read()

读取一个字符的数据,并将字符的值作为int类型返回(0-65535之间的一个值,即Unicode值)。如果未读出字符则返回-1(返回值为-1表示读取结束)

void close()

关闭流对象,释放相关系统资源

Writer

Writer用于输出的字符输出流抽象类,数据单位为字符。

方法名

使用说明

void write(int n)

向输出流中写入一个字符

void close()

关闭输出流对象,释放相关系统资源

1.4 文件字节流-FileStream


FileInputStream通过字节的方式读取文件,适合读取所有类型的文件(图像、视频、文本文件等);

FileOutputStream通过字节的方式写数据到文件中,适合所有类型的文件(图像、视频、文本文件等);

FileInputStream

  1. /**
  2. * 文件字节输入流
  3. */
  4. public class FileInputStreamTest {
  5. public static void main(String[] args) {
  6. try(FileInputStream fis = new FileInputStream("D:\\JAVA\\workSpace\\ioDemo\\a.txt")){
  7. StringBuilder sb = new StringBuilder();
  8. int temp = 0;
  9. while( (temp = fis.read()) != -1 ){
  10. sb.append((char)temp);
  11. }
  12. System.out.println(sb);
  13. }catch (IOException e) {
  14. throw new RuntimeException(e);
  15. }
  16. }
  17. }

FileOutputStream

  1. /**
  2. * 字节输出流
  3. */
  4. public class FileOutputStreamTest {
  5. public static void main(String[] args) {
  6. String str = "kkkkkkk 18";
  7. // true表示内容会追加到文件末尾(append),false表示覆盖写
  8. try(FileOutputStream fos = new FileOutputStream("D:\\JAVA\\workSpace\\ioDemo\\a.txt",true)){
  9. // 将整个字节数组写入到文件中
  10. fos.write(str.getBytes());
  11. // 将数据从内存写入到磁盘中
  12. fos.flush();
  13. }catch(IOException e){
  14. e.printStackTrace();
  15. }
  16. }
  17. }

注意fos.flush()操作才是将数据从内存写入磁盘

1.4.1 通过字节缓冲区提高读写效率

通过创建一个指定长度的字节数组作为缓冲区,以此来提高IO流的读写效率。该方式适用于读取较大文件时的缓冲区定义。

注意:缓冲区的长度一定是2的整数幂。一般情况下1024B的长度较为合适。

Q:为什么加缓冲区会提高读写IO的效率?

答:因为在操作系统中,CPU、内存处理的速度是非常快的,而与硬件交互的IO操作往往是低了很多个数量级的;如果还是采用上述一个字节一个字节进行一次写入的话,一个字节产生一次IO操作,但是实际上文件在内存中和磁盘中的存储单元是远远大于1个B的(以2的整数次幂为标准),也就是说一次IO写入或读出的数据只要不超过这个范围就可以一次性写入或读出;

所以建立缓冲区的作用就是把数据囤一囤再一块进行IO,这样最耗时的IO就用最少的次数却能达到一样的效果,速度和效率就提升了;

来看看chat老师的答案

IO操作引入缓冲区可以提高效率的原因主要有以下几点:

  1. 减少系统调用次数: 操作系统在执行IO操作时需要切换上下文,这是一项相对较为昂贵的操作。使用缓冲区可以减少实际的IO操作次数,因为数据可以在内存中累积一段时间,然后一次性写入磁盘或从磁盘读取,从而减少了系统调用的频率。
  2. 减少磁盘访问次数: 磁盘IO是相对慢速的操作,通过使用缓冲区,可以将多个小的IO操作合并为一个较大的IO操作,从而减少了磁盘访问的次数。这有助于减少磁盘头寻道的时间,提高了IO操作的效率。
  3. 数据局部性: 缓冲区可以使数据在内存中形成局部性,这意味着相关数据项在内存中是相邻的。当数据按照顺序存储在内存中时,CPU可以更有效地访问这些数据,而不需要频繁地在内存中进行跳跃。
  4. 异步IO: 缓冲区可以用于实现异步IO操作。在异步IO中,应用程序可以继续执行其他任务,而不必等待IO操作完成。当IO操作完成后,数据会被复制到应用程序的缓冲区中,这提高了程序的并发性和响应性。
  5. 数据重用: 缓冲区允许多次访问相同的数据,而无需重新从磁盘读取或重新写入。这在需要多次处理相同数据的情况下可以大大减少IO操作的成本。

总之,引入缓冲区可以将多个小的IO操作合并成更大的操作,减少了系统调用和磁盘访问的次数,提高了IO操作的效率,同时也提供了更好的数据管理和数据访问方式,有助于提高应用程序的性能和响应速度。

测试

  1. /**
  2. * 采用字节缓冲区进行文件复制
  3. */
  4. public class FileStreamBuffer {
  5. public static void main(String[] args) {
  6. long t1 = System.currentTimeMillis();
  7. copyFile1("D:\\JAVA\\workSpace\\ioDemo\\1.png","D:\\JAVA\\workSpace\\ioDemo\\2.png");
  8. long t2 = System.currentTimeMillis();
  9. System.out.println("未引入缓冲区耗时: " + (t2 - t1));
  10. long t3 = System.currentTimeMillis();
  11. copyFile2("D:\\JAVA\\workSpace\\ioDemo\\1.png","D:\\JAVA\\workSpace\\ioDemo\\3.png");
  12. long t4 = System.currentTimeMillis();
  13. System.out.println("引入缓冲区后耗时: " + (t4 - t3));
  14. }
  15. /**
  16. * 实现文件的拷贝(未引入缓冲区)
  17. * @param sour 源文件
  18. * @param dest 目的文件
  19. */
  20. public static void copyFile1(String sour, String dest){
  21. try(FileInputStream fis = new FileInputStream(sour);
  22. FileOutputStream fos = new FileOutputStream(dest);){
  23. int temp = 0;
  24. while( ( temp = fis.read()) != -1){
  25. fos.write(temp);
  26. }
  27. // 将文件从内存写入磁盘
  28. fos.flush();
  29. }catch(IOException e){
  30. e.printStackTrace();
  31. }
  32. }
  33. /**
  34. * 实现文件的拷贝(引入缓冲区)
  35. * @param sour 源文件
  36. * @param dest 目的文件
  37. */
  38. public static void copyFile2(String sour, String dest){
  39. try(FileInputStream fis = new FileInputStream(sour);
  40. FileOutputStream fos = new FileOutputStream(dest);){
  41. byte[] buffer = new byte[1024]; // 缓冲数组
  42. int temp = 0;
  43. while( ( temp = fis.read(buffer)) != -1){
  44. // 每次都从buffer中取本次相对位置0开始temp字节长度的内容写入内存
  45. fos.write(buffer,0,temp);
  46. }
  47. // 将文件从内存写入磁盘
  48. fos.flush();
  49. }catch(IOException e){
  50. e.printStackTrace();
  51. }
  52. }
  53. }

输出结果

  1. 未引入缓冲区耗时: 10108
  2. 引入缓冲区后耗时: 16

结果还是很明显的,一千倍左右的差距;

注意

  • 为了减少对硬盘的读写次数,提高效率,通常设置缓存数组。相应地,

>读取时使用的方法为:read(byte[] b)

>写入时的方法为:write(byte[ ] b, int off, int length)

  • 程序中如果遇到多个流,每个流都要单独关闭,防止其中一个流出现异常后导致其他流无法关闭的情况;关闭的顺序为:后开先关

1.4.2 缓冲字节流-BufferedStream

Java缓冲流本身并不具有IO流的读取与写入功能,只是在别的流(节点流或其他处理流)上加上缓冲功能提高效率,就像是把别的流包装起来一样,因此缓冲流是一种处理流(包装流);

BufferedInputStreamBufferedOutputStream这两个流是缓冲字节流,通过内部缓存数组来提高操作流的效率;缓存区的大小默认是8192字节,也可以使用其它的构造方法自己指定大小。

  1. /**
  2. * 字节缓冲流的基本使用
  3. */
  4. public class BufferedStream {
  5. public static void main(String[] args) {
  6. long t1 = System.currentTimeMillis();
  7. copyFile3("D:\\JAVA\\workSpace\\ioDemo\\1.png","D:\\JAVA\\workSpace\\ioDemo\\4.png");
  8. long t2 = System.currentTimeMillis();
  9. System.out.println("使用缓冲字节流耗时: " + (t2-t1));
  10. }
  11. /**
  12. * 使用字节缓冲流进行文件复制
  13. * @param sour 源文件
  14. * @param dest 目的文件
  15. */
  16. public static void copyFile3(String sour, String dest){
  17. try(FileInputStream fis = new FileInputStream(sour);
  18. FileOutputStream fos = new FileOutputStream(dest);
  19. // 创建缓冲字节流(处理流)
  20. BufferedInputStream bis = new BufferedInputStream(fis);
  21. BufferedOutputStream bos = new BufferedOutputStream(fos);){
  22. int temp = 0;
  23. while( ( temp = bis.read()) != -1){
  24. bos.write(temp);
  25. }
  26. bos.flush();
  27. }catch (IOException e ){
  28. e.printStackTrace();
  29. }
  30. }
  31. }
  32. // 控制台输出
  33. 使用缓冲字节流耗时: 57

1.5 文件字符流-FileReader/FileWriter


当执行IO操作处理的文件类型是文本文件时,可以使用文件字符流进行读写,会以字符为单位进行处理

FileReader

  1. /**
  2. * 文件字符输入流
  3. */
  4. public class FileReaderTest {
  5. public static void main(String[] args) {
  6. try(FileReader fr = new FileReader("D:\\JAVA\\workSpace\\ioDemo\\aa.txt")){
  7. int temp = 0;
  8. StringBuilder sb = new StringBuilder();
  9. while( ( temp = fr.read()) != -1){
  10. sb.append((char)temp);
  11. }
  12. System.out.println(sb);
  13. }catch (IOException e){
  14. e.printStackTrace();
  15. }
  16. }
  17. }
  18. // 输出结果
  19. 文件字符输入流 FileReader测试

FileWriter:

  1. /**
  2. * 文件字符输出流
  3. */
  4. public class FileWriterTest {
  5. public static void main(String[] args) {
  6. try(FileWriter fw = new FileWriter("D:\\JAVA\\workSpace\\ioDemo\\aa.txt",true)){
  7. String str = "当执行IO操作处理的文件类型是文本文件时,可以使用文件字符流进行读写,会以字符为单位进行处理;";
  8. fw.write(str);
  9. fw.flush();
  10. }catch (IOException e ){
  11. e.printStackTrace();
  12. }
  13. }
  14. }

1.5.1 缓冲字符流-BufferedReader/BufferedWriter

乍一看文件字符流的作用,好像和文件字节流没有什么区别,这难道是鸡肋的类吗??

实际情况下,文本文件中包含有换行段落等间隔符,如果以文件字节流读取还需要进行转换,提高了操作成本;这个时候结合缓冲字符流(处理流)的文件字符流(节点流)就可以轻易地对文本文件进行行段的处理;

BufferedReader

  • 基本方法

  • 测试

对于下面的文本文件,我们采用BufferedReader进行读取:

  1. /**
  2. * 字符输入缓冲流
  3. */
  4. public class BufferedReaderTest {
  5. public static void main(String[] args) {
  6. // 简化创建
  7. try (BufferedReader br = new BufferedReader(new FileReader("D:\\JAVA\\workSpace\\ioDemo\\aa.txt"))){
  8. String temp = "";
  9. // readLine() 一次读取一行
  10. while( (temp = br.readLine()) != null){
  11. System.out.println(temp);
  12. }
  13. }catch (IOException e){
  14. e.printStackTrace();
  15. }
  16. }
  17. }
  18. //输出结果
  19. 当执行IO操作处理的文件类型是文本文件时,可以使用文件字符流进行读写;
  20. 会以字符为单位进行处理;当执行IO操作处理的文件类型是文本文件时;
  21. 可以使用文件字符流进行读写,会以字符为单位进行处理;
  22. 当执行IO操作处理的文件类型是文本文件时\br,
  23. 可以使用文件字符流进行读写,会以字符为单位进行处理;
  24. 当执行IO操作处理的文件类型是文本文件时,
  25. 可以使用文件字符流进行读写,会以字符为单位进行处理;

BufferedWriter

  • 基本方法

  • 测试
  1. /**
  2. * 字符输出缓冲流
  3. */
  4. public class BufferedWriterTest {
  5. public static void main(String[] args) {
  6. try(BufferedWriter bw = new BufferedWriter(new FileWriter("D:\\JAVA\\workSpace\\ioDemo\\aa.txt"))){
  7. bw.write("白日依山尽");
  8. bw.newLine(); // 换行
  9. bw.write("黄河入海流");
  10. bw.flush();
  11. }catch (IOException e){
  12. e.printStackTrace();
  13. }
  14. }
  15. }

案例1:为文件中的内容添加行号

  1. /**
  2. * 为文件的内容添加行号
  3. */
  4. public class LineNumberTest {
  5. public static void main(String[] args) {
  6. try(BufferedReader br = new BufferedReader(new FileReader("D:\\JAVA\\workSpace\\ioDemo\\aa.txt"));
  7. BufferedWriter bw = new BufferedWriter(new FileWriter("D:\\JAVA\\workSpace\\ioDemo\\aaa.txt"))){
  8. int index = 1; // 定义序号
  9. String temp = "";
  10. while( (temp = br.readLine()) != null){
  11. bw.write(index +". " + temp);
  12. index++;
  13. bw.newLine(); // 换行
  14. }
  15. bw.flush();
  16. }catch (IOException e){
  17. e.printStackTrace();
  18. }
  19. }
  20. }
  21. //aa.txt
  22. 白日依山尽
  23. 黄河入海流
  24. // aaa.txt
  25. 1. 白日依山尽
  26. 2. 黄河入海流

1.6 转换流-InputStreamReader/OutputStreamWriter


InputStreamReader / OutputStreamWriter用来实现将字节流转化成字符流

即:

  • 输入端,将字节数据转换为字符数据进行处理;
  • 输出端,将字节数据转换为字符数据进行处理;

常用的转换流功能是:解决乱码问题

乱码的产生是由于编码的类型和阶码的类型不匹配导致的;

案例1:解决乱码问题

采用ANSI(实际上是GBK)对文本文件进行编码,使用转换流进行乱码处理:

  1. /**
  2. * 转换流的使用1(出现乱码)
  3. */
  4. public class InputStreamReaderTest {
  5. public static void main(String[] args) {
  6. try(InputStreamReader isr = new InputStreamReader(new FileInputStream("D:\\JAVA\\workSpace\\ioDemo\\aaa1.txt"))){
  7. StringBuilder sb = new StringBuilder();
  8. int temp = 0;
  9. while( ( temp = isr.read()) != -1){
  10. sb.append((char)temp);
  11. }
  12. System.out.println(sb);
  13. }catch (IOException e){
  14. e.printStackTrace();
  15. }
  16. }
  17. }
  18. //输出结果
  19. 1. ������ɽ��
  20. 2. �ƺ��뺣��

由于Java是采用UTF-8作为编码格式的,因此实际上就存在着文件编码和解码类型不匹配的问题;

解决

try(InputStreamReader isr = new InputStreamReader(new FileInputStream("D:\\JAVA\\workSpace\\ioDemo\\aaa1.txt"),"gbk")){

只需要在转换流的创建中加入和编码对应的解码类型即可(此处指定为gbk);

  1. // 输出结果
  2. 1. 白日依山尽
  3. 2. 黄河入海流

案例2:通过字节流读取文本文件并添加行号

分析题意

基于上图所示的代码如下:

  1. /**
  2. * 转换流的使用2
  3. */
  4. public class InputStreamReaderTest {
  5. public static void main(String[] args) {
  6. // 依次创建转换流(字节输入流)、字符输出缓冲流
  7. try(InputStreamReader isr = new InputStreamReader(new FileInputStream("D:\\JAVA\\workSpace\\ioDemo\\aa.txt"),"utf-8");
  8. BufferedWriter bw = new BufferedWriter(new FileWriter("D:\\JAVA\\workSpace\\ioDemo\\aaaa.txt"));){
  9. //
  10. StringBuilder sb = new StringBuilder();
  11. int temp = 0;
  12. int index = 1;
  13. while( ( temp = isr.read()) != -1){
  14. bw.write(index + ". "+(char)temp);
  15. bw.newLine();
  16. index++;
  17. }
  18. bw.flush();
  19. }catch (IOException e){
  20. e.printStackTrace();
  21. }
  22. }
  23. }

注意第7-8行,程序只是将字节输入流转换成了字符输入流,此时读取的方式还是逐个字符逐个字符读取,这导致第13-14行中每一个while循环中isr.read()返回的都是一个字符的ASCII码,而非一行的!!!

写入文件的结果如上图所示,这个并非我们想要的结果;

改进

上述问题的原因在于输入流最后的处理形式是“逐字符”而非“逐行”!!所以在转换流将字节流转为字符流后,还需要将字符流转换为字符缓冲流,使用其中的readLine()方法才能实现“逐行”;

  1. /**
  2. * 转换流的使用2
  3. */
  4. public class InputStreamReaderTest {
  5. public static void main(String[] args) {
  6. // 依次创建转换流(字节输入流)、字符缓冲流、字符输出缓冲流
  7. try(InputStreamReader isr = new InputStreamReader(new FileInputStream("D:\\JAVA\\workSpace\\ioDemo\\aa.txt"),"utf-8");
  8. BufferedReader br = new BufferedReader(isr); // 字符输入缓冲流
  9. BufferedWriter bw = new BufferedWriter(new FileWriter("D:\\JAVA\\workSpace\\ioDemo\\aaaa.txt"));){
  10. //
  11. StringBuilder sb = new StringBuilder();
  12. String temp = null;
  13. int index = 1;
  14. while( ( temp = br.readLine()) != null){ // 逐行读取
  15. bw.write(index + ". "+temp);
  16. bw.newLine();
  17. index++;
  18. }
  19. bw.flush();
  20. }catch (IOException e){
  21. e.printStackTrace();
  22. }
  23. }
  24. }

结果

案例3:通过转换流实现键盘输入屏幕输出

首先分析“键盘输入”是如何实现的,可能很快就会想到是System.in,那么同理屏幕输出是System.out,那么它们都是什么类型的输入输出流对象呢?

  1. public final static PrintStream out = null; // out对象定义
  2. public final static InputStream in = null; // in对象定义

可以看到它们都是字节流对象,那么就要考虑实际情况了,键盘的输入是包含多字符、可换行的,那么在处理输入流时就要将其转换成缓冲字符输入流进行逐行处理;同理屏幕的输出是包含多字符、可换行的,因此输出流应该是缓冲字符输出流

  1. /**
  2. * 案例3:通过转换流实现键盘输入屏幕输出
  3. */
  4. public class ConvertStreamTest3 {
  5. public static void main(String[] args) {
  6. // 实现 字节流->字符流->缓冲字符流 的转换
  7. try(BufferedReader br = new BufferedReader( new InputStreamReader( System.in));
  8. BufferedWriter bw = new BufferedWriter( new OutputStreamWriter( System.out));){
  9. String inputStr = "";
  10. while( !( inputStr = br.readLine()).equals("quit")){
  11. bw.write(inputStr);
  12. bw.newLine();
  13. bw.flush();
  14. }
  15. }catch (IOException e){
  16. e.printStackTrace();
  17. }
  18. }
  19. }

输出结果

  1. sjakjl
  2. sjakjl
  3. 456
  4. 456
  5. 撒寄快递
  6. 撒寄快递
  7. quit
  8. Process finished with exit code 0

1.7 字符输出流-PrintWriter


基于前文可以知道,要在文件中写入带有换行的字符是需要用到两个流对象的:缓冲字符输出流BufferedWriter(处理流)中嵌套文件字符输出流FileWriter(节点流);

PrintWriter集两者为一体,具有自动行刷新缓冲字符输出流,特点是可以按行写出字符串,并且可通过

println()方法实现自动换行;

案例1:通过PrintWriter为文件添加行号

  1. /**
  2. * 添加行号
  3. */
  4. public class PrintWriterTest {
  5. public static void main(String[] args) {
  6. try(BufferedReader br = new BufferedReader(new FileReader("D:\\JAVA\\workSpace\\ioDemo\\aa.txt"));
  7. PrintWriter pw = new PrintWriter("D:\\JAVA\\workSpace\\ioDemo\\aaaaa.txt");){
  8. int index =1;
  9. String temp = "";
  10. while( ( temp = br.readLine()) != null ){
  11. pw.println((index++) +". " + temp);
  12. }
  13. pw.flush();
  14. }catch (IOException e){
  15. e.printStackTrace();
  16. }
  17. }
  18. }
  19. //aa.txt
  20. 白日依山尽
  21. 黄河入海流
  22. //aaaaa.txt
  23. 1. 白日依山尽
  24. 2. 黄河入海流

1.8 数据流-DataStream


数据流将“基本数据类型与字符串类型”作为数据源,从而允许程序以与机器无关的方式从底层输入输出流中操作Java基本数据类型与字符串类型。

DataInputStreamDataOutputStream提供了可以存取与机器无关的所有Java基础类型数据(如:int、double、String等)的方法;

  1. public class TestDataStream {
  2. public static void main(String[] args) {
  3. //创建数据输出流对象与文件字节输出流对象
  4. try(DataOutputStream dos = new DataOutputStream(new FileOutputStream("d:/data"));
  5. //创建数据输入流对象与文件字节输入流对象
  6. DataInputStream dis = new DataInputStream(new FileInputStream("d:/data"))){
  7. //将如下数据写入到文件中
  8. dos.writeChar('a');
  9. dos.writeInt(10);
  10. dos.writeDouble(Math.random());
  11. dos.writeBoolean(true);
  12. dos.writeUTF("你好中国");
  13. //手动刷新缓冲区:将流中数据写入到文件中
  14. dos.flush();
  15. //直接读取数据:读取的顺序要与写入的顺序一致,否则不能正确读取数据。
  16. System.out.println("char: " + dis.readChar());
  17. System.out.println("int: " + dis.readInt());
  18. System.out.println("double: " + dis.readDouble());
  19. System.out.println("boolean: " + dis.readBoolean());
  20. System.out.println("String: " + dis.readUTF());
  21. }catch(IOException e){
  22. e.printStackTrace();
  23. }
  24. }
  25. }

1.9 对象流-ObjectStream


其中嵌套的是字节流;

1.9.1 基本使用:

  1. /**
  2. * 基本使用
  3. */
  4. public class ObjectStreamTest1 {
  5. public static void main(String[] args) {
  6. try(ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:\\JAVA\\workSpace\\ioDemo\\aaaaa.txt",false));
  7. ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:\\JAVA\\workSpace\\ioDemo\\aaaaa.txt"));){
  8. // 写入文件
  9. oos.writeInt(100);
  10. oos.writeChar('a');
  11. oos.writeBoolean(false);
  12. oos.writeUTF("卡卡的");
  13. oos.flush(); // 刷新
  14. // 读出文件内容
  15. System.out.println("int: "+ois.readInt());
  16. System.out.println("char: "+ois.readChar());
  17. System.out.println("boolean: "+ois.readBoolean());
  18. System.out.println("String: "+ois.readUTF());
  19. }catch (IOException e){
  20. e.printStackTrace();
  21. }
  22. }
  23. }

1.9.2 序列化和反序列化

当两个进程远程通信时,彼此可以发送各种类型的数据。 无论是何种类型的数据,都会以二进制序列的形式在网络上传送。比如,我们可以通过http协议发送字符串信息;我们也可以在网络上直接发送Java对象。发送方需要把这个Java对象转换为字节序列,才能在网络上传送;接收方则需要把字节序列再恢复为Java对象才能正常读取。

序列化:把数据结构或Java对象转换为二进制字节序列的过程;

反序列化:字节序列恢复为数据结构或Java对象的过程;

涉及的类和接口

ObjectOutputStream代表对象输出流,它的writeObject(Object obj)方法可对参数指定的obj对象进行序列化,把得到的字节序列写到一个目标输出流中;

ObjectInputStream代表对象输入流,它的readObject()方法从一个源输入流中读取字节序列,再把它们反序列化为一个对象,并将其返回;

只有实现了Serializable接口的类的对象才能被序列化。 Serializable接口是一个空接口(标识接口),只起到标记作用;

序列化和反序列化常见应用场景

  • 对象在进行网络传输(比如远程方法调用 RPC 的时候)之前需要先被序列化,接收到序列化的对象之后需要再进行反序列化;
  • 将对象存储到文件之前需要进行序列化,将对象从文件中读取出来需要进行反序列化;
  • 将对象存储到数据库(如 Redis)之前需要用到序列化,将对象从缓存数据库中读取出来需要反序列化;
  • 将对象存储到内存之前需要进行序列化,从内存中读取出来之后需要进行反序列化。

Q:如果有些字段不想进行序列化怎么办?

对于不想进行序列化的变量,可以使用transient关键字修饰;

  • transient关键字的作用是:阻止实例中那些用此关键字修饰的的变量序列化;当对象被反序列化时,被
  • transient 修饰的变量值不会被持久化和恢复;

几点注意

  • transient 只能修饰变量,不能修饰类和方法。
  • transient 修饰的变量,在反序列化后变量值将会被置成类型的默认值。例如,如果是修饰 int 类型,那么反序列后结果就是 0
  • static 变量因为不属于任何对象(Object),所以无论有没有 transient 关键字修饰,均不会被序列化。

参考博客:

看懂java序列化,这篇就够了 - 掘金

序列化
  1. /**
  2. * 实现序列化
  3. */
  4. public class ObjectStreamTest2 {
  5. public static void main(String[] args) {
  6. try(ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:\\JAVA\\workSpace\\ioDemo\\aaaaa.txt"))){
  7. User user = new User(1,"kk",18);
  8. oos.writeObject(user);
  9. oos.flush();
  10. }catch (IOException e){
  11. e.printStackTrace();
  12. }
  13. }
  14. }

反序列化
  1. /**
  2. * 实现反序列化
  3. */
  4. public class ObjectStreamTest3 {
  5. public static void main(String[] args) {
  6. try(ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:\\JAVA\\workSpace\\ioDemo\\aaaaa.txt"))){
  7. User user = (User) ois.readObject();
  8. System.out.println(user);
  9. }catch (IOException | ClassNotFoundException e){
  10. e.printStackTrace();
  11. }
  12. }
  13. }
  14. // 输出结果
  15. Users{userid=1, username='kk', userage='18'}

1.10 IO中的设计模式思考


装饰器模式:是GOF23种设计模式中较为常用的一种模式。它可以实现对原有类的包装和装饰,使新的类具有更强的功能

IO流体系中大量使用了装饰器模式,让流具有更强的功能、更强的灵活性。比如:

  1. FileInputStream fis = new FileInputStream(src);
  2. BufferedInputStream bis = new BufferedInputStream(fis);

显然BufferedInputStream装饰了原有的FileInputStream,让普通的FileInputStream也具备了缓存功能,提高了效率。

致谢&部分资源出处:百战程序员 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/笔触狂放9/article/detail/819887
推荐阅读
相关标签
  

闽ICP备14008679号