编程技术是改变世界的力量。
本站
当前位置:网站首页 > 后端语言 > 正文

Java IO流详解和常用流的使用 java的io流总结

gowuye 2024-04-04 11:53 8 浏览 0 评论

一, IO概念

  • I/O 即 输入Input/ 输出Output 的缩写, 值指的是应用程序和外部设备之间的数据传递, 常见的外部设备包括文件, 管道, 网络连接;

二, “流” 是什么?

Java 是通过流来处理IO的, 那什么是流呢?

流(Stream), 是一个抽象的概念, 指的是一个以先进先出方式发送信息的通道, 发送的信息是一连串的字符或字节数据;

  • 当程序需要 读取数据 的时候, 就会开启一个 通向数据源的流 (即输入流), 这个数据源可以是文件, 内存, 或是网络连接;
  • 类似的, 当程序需要 写出数据 的时候, 就会开启一个 通向目的地的流 (即输出流).
  • 这样来看, 数据就像是在应用程序和外部设备之间流动一样, 所以叫流.

一般来说, 关于 流的特性 有下面几点:

  1. 先进先出 :最先写入输出流的数据最先被输入流读取到。
  2. 顺序存取 :可以一个接一个地往流中写入一串字节,读出时也将按写入顺序读取一串字节,不能随机访问中间的数据。(RandomAccessFile除外)
  3. 只读或只写每个流只能是输入流或输出流的一种,不能同时具备两个功能,输入流只能进行读操作,对输出流只能进行写操作。 在一个数据传输通道中,如果既要写入数据,又要读取数据,则要分别提供两个流。

三, IO流的分类

3.1 从流的方向来看

  • 注意: 输入和输出都是从应用程序的角度划分的 !

输入流: 读取文件到程序的流 , 主要以 InputStreamRearder 作为基类;

输出流: 从程序写出到外部设备的流 , 主要以 OutputStreamWriter 作为基类;

3.2 按照流的操作粒度划分

字节流: 以字节为单元, 可操作任何数据 , 字节流主要由 InputStream和outPutStream作为基类

字符流: 以字符为单元, 只能操作纯字符数据,比较方便 , 字符流主要由 Reader和Writer 组颇为基类

字节流和字符流的用法几乎完成全一样,区别在于字节流和字符流所操作的数据单元不同,字节流操作的单元是数据单元是8位的字节,字符流操作的是数据单元为16位的字符。

3.3 按照流的角色划分

节点流: 直接操作数据读写的流类,比如FileInputStream 可以 从/向 一个特定的IO设备(如磁盘, 网络) 读/写 数据的流, 也叫低级流(主要流);

处理流: 对一个已存在的流的链接和封装,通过对数据进行处理为程序提供功能强大、灵活的读写功能,例如BufferedInputStream(缓冲字节流) 也叫高级流;

补充说明:

四, IO流体系的概览和使用

  • java.io中子类有40个“流”类, 我们只需要掌握其中常用的几个即可. 其他的用到了再去翻API.

4.0 文件类(File)对象的使用

构造一个File对象,并不会导致任何磁盘操作。只有当我们调用File对象的某些方法的时候,才真正进行磁盘操作。

File类的具体方法和使用: 详见此文 Java中的File类及其常用方法详解

4.1 字节流中常用的几种实现类

前面我们提到, 字节流可以处理任何形式的文件, 在字节流中, 文件是以一个个字节的形式流动的, 我们在使用字节流的具体实现类对象时, 也是通过调用read() 或 write() 对这些字节进行操作的.

4.1.1 FileInputStream和 FileOutputStream的使用

4.1.2 FileInputStream

[API中常用方法介绍]

也许有人经常会对具体使用时read的返回值有些许困惑,请仔细阅读下面的代码示例:

@Test
  public void fisTest() thorws IOException{
 
		//新建File类对象, 操纵文件输入流, 从文件中读取数据
        ///ctrl+alt+f--> idea 把局部变量变为全局变量的快捷键
		File file = new File("e://fis.txt");
        FileInputStream fis = new FileInputStream(file);
            
    	//1.1. 一个字节一个字节的读取数据
        // Q: read() 是如何读取字节的?
        // read() 读取返回的是字节的ASCII码, 而 read(byte[])读取返回的是读取完的字节数组的长度;
        int read = 0;
		
        while((read = fis.read()) != -1){
  //每从数据流中读取一次就返回一个字节的数据,  当读取到数据末尾时, 返回值为 -1
			System.out.print(read + "--" + (char)read);
            System.out.println();
        }
        
        System.out.println("=======================================");
        
        //1.2 把读取的字节放入字节数组, 然后直接返回字符串
        byte[] bytes = new byte[12];
        int dataLen = fis.read(bytes); //fis.read(bytes) 返回的结果是字节的总长度
        
        System.out.println("dataLen = "+dataLen);
        System.out.println(new String(bytes, 0, dataLen-1));

		//别忘了关流! 
		fis.close();
    }

总结:

  1. 正如API中说明的那样, int read() **由代表输入流的类对象调用, 每从数据流中读取一次就返回一个字节的数据, 当读取到数据末尾时, 返回值为 -1 ** ,经验证, 返回的实际数据是ASCII码的十进制值. 正如上图所示, 前面是 fis.read() 实际返回的ASCII 值, 后面的经过强制类型装转换后的真正数据.
  2. 由于一个字节一个字节的读取数据并输出效率实在太低, 我们可以使用 int read(bytes[] b) , 把读取到的字节全部放入到指定的字节数组b中, 这里要注意, read(byte[] b) 方法一次性返回数据对应的字节长度, 如果读取到数据末尾同样也是返回 -1;

4.1.2 FileOnputStream

[API中常用方法介绍]

  • 追加写入如果我们是要 追加写入 到文件(即写入到文件中数据的末尾), 那么在创建输出流对象时, 使用下面的构造方法即可;

代码示例如下:

public class FileCopy_1 {
 
    @Test
    public void copyTest() throws IOException {
 
        //1. 创建文件类对象,传入文件路径
        String filePath = "e://source.jpg";
        File file = new File(filePath); //旧文件

        String target_filePath = "d://target.jpg";
        File target_file = new File(target_filePath);

        //2. 创建输入流, 读取磁盘的文件到内存
        FileInputStream fis = new FileInputStream(file);
        //3. 创建输出流, 写出文件数据到其他目录的文件中
        FileOutputStream fos = new FileOutputStream(target_file);

        //4. 输入流读取文件到内存, 再由输出流把内存中的字节数据写入到磁盘文件.
        //这样就完成了一次文件复制, 为了提高效率, 我们可以采取边读入边写出的方式
        int read = 0;

        while((read = fis.read()) != -1){
 
            System.out.println("文件正在复制中, 请等待....");
            fos.write(read);
        }
        System.out.println("文件复制成功!");

        //5. 上面即便是同时复制, 效率还是非常低的, 只能一个字节一个字节的复制.
        //下面我们采用字节数组存储要复制的数据, 当输入流读取完毕后, 由输出流把字节数组写出到磁盘
        byte[] buf = new byte[102400 * 7]; //700kb
        int dataLen = fis.read(buf);

        fos.write(buf, 0, dataLen);

    }

4.1.2 BufferedInputStream和 BufferedOutputStream的使用

缓冲字节流是包装流, 是对FileInput(output)Stream的包装, 仅仅是在他们的基础上加入了缓存机制, 底层的读写操作还是由FileInput(output)Stream 类对象完成的, 所以其构造方法入参是这两个类的对象也就不奇怪了。

@Test
    public void testWriteBufferOutputStream() throws IOException {
 
        //1. 创建文件对象
        File file = new File("e://res.txt");

        //2. 使用文件对象创建新文件
        //file.createNewFile();

        //3. 使用输出缓冲流写出给定数据到文件
        String outStr = "dududu, hello, are u sb? ";

        //4. 创建文件输出流, 底层操控文件的写出
        FileOutputStream fos = new FileOutputStream(file);

        //5. 使用缓冲输出流包装文件输出流, 为什么包装? 包装的意义何在?
        // 把写出的数据放入到缓存中, 到达一定的限度才写出到磁盘, 降低了cpu占用和磁盘IO
        //而且由于是缓存(内存) 写出到磁盘, 肯定速度要快很多
        BufferedOutputStream bos = new BufferedOutputStream(fos);

        bos.write(outStr.getBytes(StandardCharsets.UTF_8));

        System.out.println("写出成功!");

        // 关闭流, 释放资源, 很重要!
        bos.close();
    }
    @Test
    public void testBufferedInputStream() throws IOException {
 
        //1. 创建文件对象
        String pathName = "e://res.txt";
        File file = new File(pathName);
        
        //2. 创建FIS对象
        FileInputStream fis = new FileInputStream(file);
        
        //3. BufferedInputStream 只是对FileInputStream的一个包装流, 底层读取依靠的仍旧是fis.
        //创建bis也就需要fis的对象.
        BufferedInputStream bis = new BufferedInputStream(fis);

        //4. 使用bis读取数据(一个字节一个字节的读取)
        int read = 0;

        while((read = bis.read()) != -1){
 
            System.out.print((char)(read) + "");
        }
        System.out.println("==================================");

        //5. 使用字节数组读取
        byte[] bytes = new byte[1024];

        int dataLen = bis.read(bytes);

        System.out.println(new String(bytes, 0, dataLen));

        //关闭流
        bis.close();
    }

4.1.3 字节流中常用读写方法总结

4.2 字符号流中常用的几种实现类

要特别注意到: 在字符流中, 我们常用的一些流, 比如 FileReader, FileReader, BUffer

4.2.1 FileReader和 FileWriter的使用

4.2.1.1 FileReader

字符流 FileReader/ FileWriter 类结构稍微复杂一些, 但是每个类都有着明确不同的职责, 我们先拿读入流来梳理一下这些类,

继承结构: Reader (抽象类) <-- InputStreamReader (字节和字符转换的桥梁)<— FileReader(方便类, 直接对字符流进行操作)

  1. 先来看 Reader:
  2. 再来看 InputStreamReader:
  3. 最后来看 FileReader:
  • 为什么前面我们说 FileReader 是读取字符流的方便类? 方便在哪里呢?
  • 我们知道, InputStreamReader/Writer 是字符流和字节流的桥梁, 通过此类, 我们可以读取字节, 使用指定字符集转换为字符, 那么使用这个类是如何获得字符流呢? new InputStreamReader( new FileInputStream(file, true), UTF-8) , 是不是很麻烦?
  • 当我们使用 Filerader 获取字符流, 只需要 new FileRader(file) 即可, 方便吧, 就是不能通过这种方法指定字符集;

4.2.1.2 FileWrite

[API中常用方法介绍]

上面我们讲了 FileReader 的继承结构, 和每个类的用处, FileWrite 跟它是基本一模一样的, 所以这节说下FileWrite的几个方法

继承结构: Writer <-- InputStreamWriter <— FileWriter

  • 对于FileWrite 和 FileReader 的方法, 其实他俩分别就只有俩构造方法, 其他的方法都是继承自他的父类 InputStreamWrite 和 OutputStreamReader, 并且完全没有重写, 直接调用的!

4.2.1.3 FileReader和 FileWriter的简单示例

@Test
    public void testFileReader() throws IOException {
 
        //Reader 接口--> InputStreamReader(字节流转为字符流) --> FileReader(字符流)

        //1. 新建文件类对象
        File file = new File("d:\\file.txt");

        //2. 新建字符读入流
        FileReader fileReader = new FileReader(file);

        //3. 一个字节一个字节的读取字节
        int read = 0;

        while((read = fileReader.read()) != -1){
 
            System.out.print((char)read);
        }

        //4. 提高效率, 把读取的字节放入到char数组,统一返回
        char[] chaArr = new char[1024];
        int dataLen = fileReader.read(chaArr);

        System.out.println(new String(chaArr, 0, dataLen));

		//关流
		fileReader.close();
    }



    @Test
    public void testFileWriter() throws IOException {
 
        // Writer <--- InputStreamWriter <--- FileWriter
        //1. 创建文件对象
        File file = new File("e:\\target1.txt");
        //file.createNewFile();
        //2. 创建FileWriter 对象, 传入文件对象
        FileWriter fw = new FileWriter(file);

        //3. 待写入字符串
        String outStr = "我这里可是字符串, 我会被InputStream转换为字节流 ?";

        //4. 写出
        fw.write(outStr);

        //5. 关闭
        fw.close();
    }

4.2.2 BufferedReader和 BufferedWriter的使用

4.2.2.1 BufferedReader

[API 方法说明: ]

对于上面两种方法, 底层都是读取的字节流, 即都是使用了FileInputStream 获取字节流, 然后经过处理转换成字符流, 存放到缓存中;

对于包装类BufferedReder, 可以包装Reader的任何子类, 即可以为任何Reader的子类加上缓冲区;

实际上, BufferedReader 只是一个采用了装饰器设计模式的包装流, 他可以借助构造器读取IO流中Reader 的任何子类, 把这些子类对象读取或者写入的字节流或字符流放入到缓存中.

所有方法:

4.2.1.3 BufferedReader和 BufferedWriter的简单示例

public class BufferedReaderDemo {
 
    @Test
    public void testBufferedReader() throws IOException {
 
        //从文件中读取字节, 把字节转换为字符, 为这个字符加上缓冲区,
        // 每个缓冲区满了之后才向应用程序提交

        //1. 新建文件对象, 传入相应的文件路径
        File file = new File("d:\\file.txt");

        //2. 读取缓冲流是缓冲流, 对结点流进行包装
        FileReader fr = new FileReader(file);

        //两种写法殊途同归. 都行
        //BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(file), StandardCharsets.UTF_8));
        BufferedReader br = new BufferedReader(fr);

        ///
        //3. 按字节读取

        int read = 0;

        while ((read = br.read()) != -1) {
 
            System.out.print((char) read);
        }

        //3.1 按char 字符数组读取
        char[] charArr = new char[1024];
        int dataLen = br.read(charArr);

        System.out.println(new String(charArr, 0, dataLen));

        4. 按行读取
        String sb = null;

        while((sb = br.readLine()) != null){
 

            System.out.println(sb);
        }

        //关流
        br.close();
    }


    @Test
    public void testBufferedWriter() throws IOException {
 
        //1. 新建文件对象, 传入相应的文件路径
        File file = new File("e:\\target1.txt");

        //2. 读取缓冲流是缓冲流, 对结点流进行包装
        FileWriter fr = new FileWriter(file);

        //3. 创建写出缓冲流
        BufferedWriter bw = new BufferedWriter(fr);

        //4. 写出字符串
        String outStr = "译文\n" +
                "人生如果都像初次相遇那般相处该多美好,那样就不会有现在的离别相思凄凉之苦了。\n" +
                "如今轻易地变了心,你却反而说情人间就是容易变心的。\n" +
                "想当初唐皇与贵妃的山盟海誓犹在耳边,却又最终作决绝之别,即使如此,也生不得怨。\n" +
                "但你又怎比得上当年的唐明皇呢,他总还是与杨玉环有过比翼鸟、连理枝的誓愿。\n" +
                "\n" +
                "注释\n" +
                "柬:给……信札。\n" +
                "“何事”句:用汉朝班婕妤被弃的典故。班婕妤为汉成帝妃,被赵飞燕谗害,退居冷宫,后有诗《怨歌行》,以秋扇闲置为喻抒发被弃之怨情。南北朝梁刘孝绰《班婕妤怨》诗又点明“妾身似秋扇”,后遂以秋扇见捐喻女子被弃。这里是说本应当相亲相爱,但却成了相离相弃。\n" +
                "故人:指情人。却道故人心易变(出自娱园本),一作“却道故心人易变”。\n" +
                "“骊山”二句:用唐明皇与杨玉环的爱情典故。《太真外传》载,唐明皇与杨玉环曾于七月七日夜,在骊山华清宫长生殿里盟誓,愿世世为夫妻。白居易《长恨歌》:“在天愿作比翼鸟,在地愿作连理枝。”对此作了生动的描写。后安史乱起,明皇入蜀,于马嵬坡赐死杨玉环。杨死前云:“妾诚负国恩,死无恨矣。”又,明皇此后于途中闻雨声、铃声而悲伤,遂作《雨霖铃》曲以寄哀思。这里借用此典说即使是最后作决绝之别,也不生怨。\n" +
                "薄幸:薄情。锦衣郎:指唐明皇。▲";

        bw.write(outStr, 0, outStr.length());


        //注意这个bufferedwriter的特别之处, 必须得手动刷写, bw.flush();
        bw.flush();
    }
}

4.3 字符流中常用方法总结

4.4 序列化(ObjectInputStream && ObjectOutputStream)

序列化和反序列化: 持久化存储, 网络传输

待补充

4.5 附录一, 字母和汉字在不同字符集中的占位

  • 参考文章:
    • https://blog.csdn.net/mu_wind/article/details/108674284
    • b 站韩顺平
    • https://juejin.cn/post/6850418112567869447#heading-0

相关推荐

爱上开源之golang入门至实战第四章-切片(Slice)

前言Go数组的长度不可改变,在特定场景中这样的集合就不太适用,Go中提供了一种灵活,功能强悍的内置类型切片("动态数组"),与数组相比切片的长度是不固定的,可以追加元素,在追加时可...

Go语言入门必知教程-切片

切片是一种灵活的和可扩展的数据结构,用于实现和管理数据集。切片由多个元素组成,所有元素都是相同类型的。切片是动态数组的一部分,可以根据需要进行增长和收缩。与数组一样,切片也可以索引。切片具有容量和长度...

Go语言基础-切片

切片是什么?切片是Go语言的一种数据结构。和数组相似,不过切片可以在它的结尾增加更多的元素。这样可变长度在实际编程中更为有用。声明切片切片的声明和数组也很相似,只是声明切片时不需要指定大小。例:va...

5分钟掌握GO中切片的基本使用方法

最近Golang越来越火,不少小伙伴都纷纷开始学习Golang,但对于原先为C++或者JAVA的同学,用习惯了数据、list、vector等,会对Go的切片slice不习惯,下面整理出go中slice...

揭秘 Go 切片(Slice)的秘密

当向切片添加新参数时,底层数组会发生什么变化?它会扩展以容纳更多元素吗?在这篇文章中,我们将深入探讨切片的内部工作原理,以及如何利用这些知识来进行更好的内存管理和性能优化。具体而言,我们将探索Go...

【Go语言slice详解】深入掌握Go语言中的slice类型及常用操作!

Go语言中的slice(切片)是一种非常方便的数据结构,可以动态地增加或减少其元素数量,且可以访问底层数组的任意一个子序列。本文将对Go语言中的slice进行详细的讲解。Slice的定义在Go语言中,...

掌握GO中的Slice,这就够了

最近Golang越来越火,不少小伙伴都纷纷开始学习Golang,但对于原先为C++或者JAVA的同学,用习惯了数据、list、vector等,会对Go的切片slice不习惯,下面整理出go中slice...

golang2021面向对象(26)Go语言类型内嵌和结构体内嵌

结构体可以包含一个或多个匿名(或内嵌)字段,即这些字段没有显式的名字,只有字段的类型是必须的,此时类型也就是字段的名字。匿名字段本身可以是一个结构体类型,即结构体可以包含内嵌结构体。?可以粗略地将这个...

2022-11-13:以下go语言代码中,如何获取结构体列表以及结构体内

2022-11-13:以下go语言代码中,如何获取结构体列表以及结构体内的指针方法列表?以下代码应该返回{"S1":["M1","M2"],"S...

Go语言文件和目录操作

文件和目录操作概述一、文件和目录操作概述在计算机中,文件和目录是存储数据的重要方式。在Go语言中,我们可以使用os和io/ioutil包提供的函数和结构体来进行文件和目录操作。本文将详细介绍Go语言中...

跟我一起学习go语言(五)golang中结构体的初始化方法

1、自定义一个结构体typeVertexstruct{X,Yfloat64}2、初始化方法-指针:rect1:=new(Vertex)rect2:=&Vertex...

Go复合数据类型:结构体

一种通用的、对实体对象进行聚合抽象的能力,在Go中,提供这种聚合抽象能力的类型是结构体类型,也就是struct。自定义一个新类型在Go中,我们自定义一个新类型一般有两种方法。第一种是类型定义...

Go语言基础:方法

导读在阅读本文章前,假定你具备如下能力:?已掌握结构体1.方法1.1方法的概念在理解程序中方法的概念时,我们先看看现实中的一些情况,这样相对比较好理解一些。在农村的朋友可能会知道,在医疗落后的情况...

为什么 Go 语言 struct 要使用 tags

在Go语言中,struct是一种常见的数据类型,它可以用来表示复杂的数据结构。在struct中,我们可以定义多个字段,每个字段可以有不同的类型和名称。除了这些基本信息之外,Go还提供了s...

一文带你掌握掌握 Golang结构体与方法

1.Golang结构体的概念及定义结构体是Golang中一种复合类型,它是由一组具有相同或不同类型的数据字段组成的数据结构。结构体是一种用户自定义类型,它可以被用来封装多个字段,从而实现数据的...

取消回复欢迎 发表评论: