【Java IO流】字节流详解

news2024/10/6 1:39:40

在这里插入图片描述

文章目录

  • 1. IO 流概述
  • 2. IO 流分类
  • 3. 字节输出流
  • 4. 字节输入流
  • 5. 文件拷贝
  • 6. IO 流中的异常处理
  • 7. 总结
  • Java编程基础教程系列

1. IO 流概述

什么是 IO 流?

IO 流是存取数据的解决方案,在计算机中数据存放在硬盘的文件中,如果程序需要使用这些数据时,就会从文件中把数据读取到内存中,内存中数据的特点是不能永久化存储,程序停止,数据丢失。那么如何持久的保存程序中的数据呢?

程序中的数据会通过写入的方式存储到硬盘的文件中,特点是可以长期的存储,不会随着程序的终止而丢失,那么 Java 语言是怎样读取和写入数据的呢?

这里就引出了流的概念,流是一个抽象的概念,我们把数据在两设备的传输抽象为流的方式,流的本质是数据传输,根据数据传输特性将流抽象为各种类,方便更直观的进行数据操作。

2. IO 流分类

Java中的流可以从不同的角度进行分类,按照流动方向可以分为输入流和输出流,输入流用于数据的读取,输出流用于数据的写出。按照操作对象的不同可以分为字节流和字符流,字节流可以操作所有类型的文件,例如:文本,图像,音频等,字符流用于操作纯文本文件。

image-20230114213143003

Java中有四种顶层的流 InputStreamOutputStreamReaderWriter ,这四种流是抽象类,不能用来实例化对象,其又分别有更具体的子类,分为文件流,缓冲流,数据流,转换流,Print流,Object流等,都分别有特定的功能或用来操作特定的数据。

我们一般不会使用字节流来操作文本文件,因为会出现乱码的情况,相信学完今天的内容,你就会明白其中的原理。纯文本文件是指使用记事本的形式创建的文件,例如 txt 文件,md 文件,而 Word 文件就不是纯文本文件。

在学习时,为了逻辑清晰,一般通过字节流和字符流两类来学习,每一类又包括输出流和输入流。

总结

  • IO流是存储和读取数据的解决方案
  • I 表示 input,O表示 output,流则是抽象的一种概念,表示数据的传输
  • IO 流用于读取数据,既可以读取本地文件,也可以是网络文件
  • 按照流的方向,IO 流分为输出流(程序到文件)和输入流(文件到程序)
  • 按照文件类型,IO 流分为字节流(操作所有类型)和字符流(操作纯文本文件)

3. 字节输出流

上面说到的四种基本的流类都是抽象类,不能用来实例化对象,我们要使用其子类创建对象用来传输数据。

例如,往本地文件中写出数据时,可以使用 FileOutputStream ,该类被称为字节输出流。使用该类往本地文件中写出数据可以分为三步:

  1. 创建流对象
  2. 写出数据
  3. 释放资源

image-20230117125637749

示例:

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

public class Test {
    public static void main(String[] args) throws IOException {
        /*
        使用 FileOutputStream 往本地文件中写入数据
        FileOutputStream 构造方法的参数既可以使用String类型也可以使用File类
        程序需要进行异常处理,直接抛出异常即可
         */

        //1. 创建流对象
        FileOutputStream fos=new FileOutputStream("test.txt");
        //2. 写出数据
        fos.write(97);
        //3. 释放资源
        fos.close();
    }
}

在程序创建流对象时,程序和文件之间就会建立一个通道,此时我们就可以通过调用 write 方法往文件中写出数据,写出数据完成之后,进行资源释放,相当于打断了这个通道。

image-20230115183723684

细节:

在创建 FileOutputStream 流对象时,构造方法中既可以传入 String 类型也可以传入 File 类对象,如果传入的是 String 类型,其底层会自动创建 File 类对象。如果目标文件不存在,则会创建一个新的文件,并且把数据写出到新创建的文件中,但是要保证父级目录存在。如果文件存在,则会默认清空文件,并且写出数据。

write() 方法传入的参数是一个整数,实际写出到文件中的是参数在字符集表中对应的字符。几乎所有的流操作都要进行释放资源的操作。释放资源实际就是打断了程序和文件之间的流通道,如果不释放资源,文件则一直被程序占用。

上面的方式每次在只能传输一个字节的数据,那么如何一次传输多个字节数据呢?

FileOutputStream 中一共有三种方法进行数据的写出:

方法说明
void write(int b)一次写一个字节数据
void write(byte[] b)一次写一个字节数组的数据
void write(byte[] b,int off,int len)一次写一个字节数组的部分数据

如果要一次性的写多个数据,那么你可以先把数据放到 byte 类型的数组中,然后写入文件中。

示例,在写出数据时:

byte[] bytes={97,98,99,100};
fos.write(bytes);

或者:

//2. 写出数据
String s="abcd";
byte[] bytes = s.getBytes();
fos.write(bytes);

两种方法效果相同,运行结果:

abcd

前面说到,创建流对象时,如果文件存在,则会默认清空文件。那么,我们如何把数据追加或者写入到文件中呢?

其实,在 FileOutputStream 类中的构造方法中,有一个 boolean 类型的参数,这个参数控制写出数据时是否追加在文件末尾,默认传入的是 false ,我们只需要在创建对象时传入 true 即可把数据追加写出到文件末尾。

示例,假设文件中已有数据 Hello:

//2. 写出数据
String s1="abcd";
byte[] bytes1 = s1.getBytes();
fos.write(bytes1);
String s2="\r\n";
byte[] bytes2 = s2.getBytes();
fos.write(bytes2);
String s3="Hello";
byte[] bytes3 = s3.getBytes();
fos.write(bytes3);

运行结果:

Helloabcd
Hello

接下来我们查看一下 JDK 源码中的 FileOutputStream 类,这个问题就不难理解了。

  public FileOutputStream(String name, boolean append)
        throws FileNotFoundException
    {
        this(name != null ? new File(name) : null, append);
    }
    public FileOutputStream(File file, boolean append)
        throws FileNotFoundException
    {
    ...
    }

在不同的操作系统中,换行符的定义是不同的,Windows系统中,换行符是\r\n,表示回车换行,回车是指把光标移动到一行的开始,换行指光标移动到下一行,Java语言对其进行了优化,只需要使用\r或者\n来实现换行,实际上Java在底层会进行补全操作。MacOS 中换行使用 \r,而 Linux 中使用 \n 表示换行。

4. 字节输入流

我们可以使用 FileInputStream 类把本地文件中的数据读取到程序中,该类称为字节输入流类,和字节输出流类似,使用字节输入流读取本地文件可以分为三个步骤:

  1. 创建流对象
  2. 读取数据
  3. 释放资源

示例,假设文件中以后数据abcd:

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;

public class Test {
    public static void main(String[] args) throws IOException {
        /*
        使用 FileInputStream 把本地文件中的数据读取到程序中
        FileInputStream 构造方法的参数既可以使用String类型也可以使用File类对象
        程序需要进行异常处理,直接抛出异常即可
         */

        //1. 创建流对象
        FileInputStream fis=new FileInputStream("test.txt");

        //2. 读取数据
        int b1 = fis.read();
        System.out.println((char)b1);
        int b2 = fis.read();
        System.out.println((char)b2);
        int b3 = fis.read();
        System.out.println((char)b3);
        int b4 = fis.read();
        System.out.println((char)b4);
        int b5 = fis.read();
        System.out.println(b5);
        //3. 释放资源
        fis.close();
    }
}

同样的,在程序创建流对象时,程序和文件之间就会建立一个通道,此时我们就可以通过调用 read() 方法读取本地文件中的数据,读取完数据以后,需要释放资源,相当于打断了这个通道,否则文件将一直被程序占用。

细节:

在创建流对象时,传入的参数既可以是 String 类型,也可以是 File 类的对象。不同的是,如果目标文件不存在则会报错,如果文件存在,则会读取数据。read() 方法的返回值是文件中字符在字符集中对应的十进制值,如果读取到文件末尾,则会返回 -1 。

image-20230115202933750

如果文件中存放的数据恰好是 -1 ,其实它是分负号和 1 两次读取的。如果读取的数据很多时,这样的方法显然是不可取的,此时就要使用循环来读取文件中的数据。

示例:

//2. 读取数据
int b;
while((b=fis.read())!=-1){
     System.out.println((char)b);
}

这里定义一个临时变量是十分重要的,而不是多此一举。否则将无法实现循环打印读取到的数据的效果。

使用 FileInputStream 读取数据时,一次只能读取一个字节的数据,显然这样的方式效率是非常低的,那么怎样解决这个问题呢?此时我们可以使用 read() 方法的重载方法一次读取多个数据,往 read() 方法中传入一个字节类型的数组,read() 方法一次读取多少个数据是由数组的大小决定。

示例,假设文件中存放数据 abc:

import java.io.FileInputStream;
import java.io.IOException;

public class Test {
    public static void main(String[] args) throws IOException {
        //使用字节数组来一次读取多个字节数据
        FileInputStream fis=new FileInputStream("test.txt");

        byte[] bytes=new byte[2];
        int len1 = fis.read(bytes);
        System.out.println(new String(bytes,0,len1));

        int len2= fis.read(bytes);
        System.out.println(new String(bytes,0,len2));
    }
}

上面的例子中,read() 方法每次读取两个字节的数据,并且返回读取到的数据的个数,读取到文件末尾返回 -1 。为了防止 read() 方法读取到最后时获取残留数据,如下图。可以往 String 类构造方法中加入两个参数,表示从某个索引开始,读取 len 个字符。

image-20230115212819664

5. 文件拷贝

前面已经学习了数据的读取和写入,那么我们就可以实现文件拷贝了。之前说过,字节流可以操作所有类型的文件,那么,我们今天使用图片文件作为示例来演示文件拷贝。

示例:

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class Test {
    public static void main(String[] args) throws IOException {

        //1. 创建流对象
        FileInputStream fis=new FileInputStream("C:\\Users\\24091\\Desktop\\java.png");
        FileOutputStream fos=new FileOutputStream("copy.png");
        //2. 拷贝文件
        int b;
        while((b=fis.read())!=-1){
            fos.write(b);
        }
        //3. 释放资源
        fos.close();
        fis.close();
    }
}

在创建多个流对象的程序中释放资源时,先创建的后释放。此时,桌面的 java.png 文件已经被拷贝到了项目中的 copy.png 文件中。

前面说到,FileInputStream 每次读取一个字节的效率是非常低的,那么我们可以改写上面的程序,每次读取多个字节来实现文件的拷贝。

修改示例:

 		//1. 创建流对象
        FileInputStream fis=new FileInputStream("C:\\Users\\24091\\Desktop\\java.png");
        FileOutputStream fos=new FileOutputStream("copy.png");
        //2. 拷贝文件
        int len;
        byte[] bytes = new byte[5 * 1024];
        while((len=fis.read())!=-1){
            fos.write(bytes,0,len);
        }
        //3. 释放资源
        fos.close();
        fis.close();

6. IO 流中的异常处理

在 JDK 1.7 中,Java 提供了一个 autoCloseable 接口,用于在特定情况下进行异常处理。在 Java 7 中,可以把定义流对象的代码写在 try 后面的括号中,表示当 try…catch 语句执行完成后,会自动释放资源,前提是写在括号中的类必须是实现了 autoCloseable 接口的类。

但是这样在括号中定义流对象的代码是难以阅读的,所以在Java 9 中,我们可以把定义流对象的代码放在 try 语句前面,括号中只需要写流的引用变量名,执行逻辑与前面相同。

例如,拷贝文件时使用 try…catch 捕获异常:

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

public class Test {
    public static void main(String[] args) throws FileNotFoundException {
        //1. 创建流对象
        FileInputStream fis = new FileInputStream("C:\\Users\\24091\\Desktop\\java.png");
        FileOutputStream fos = new FileOutputStream("copy.png");
        try (fis; fos) {
            //2. 拷贝文件
            int len;
            byte[] bytes = new byte[5 * 1024];
            while ((len = fis.read()) != -1) {
                fos.write(bytes, 0, len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

在学习 Java 编程基础时,对于 IO 流中出现的异常我们抛出即可,后面在学习 Spring框架时,再做探讨。

7. 总结

在 File 类中,我们可以使用类的对象来操作文件和目录,包括增删查等。不同的是,IO 流用于文件的读写操作,这些操作是 File 无法实现的。在创建流对象时,相当于在文件和程序之间建立了一个流的通道,方便对数据进行操作。

流是个抽象的概念,是对输入输出设备的抽象,无论采取什么样的形式输出输入数据,只是针对流做处理,无关与输入输出的设备,可以说这个思想是很优秀的。


Java编程基础教程系列

【Java集合】Collection 体系集合

【Java基础】泛型详解

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/169922.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Gitlab 项目上传到Maven仓库

Gitlab 项目上传到Maven仓库Gitlab 项目上传到Maven仓库1. 生成Deploy tokens2.项目工程AS的build.gradle配置Maven3. 拉取Maven库Gitlab 项目上传到Maven仓库 1. 生成Deploy tokens 项目地址-》Settings-》Repository-》Deploy tokens-》Expand-》输入Name-》Create deploy …

回收租赁商城系统功能拆解10讲-会员等级

回收租赁系统适用于物品回收、物品租赁、二手买卖交易等三大场景。 可以快速帮助企业搭建类似闲鱼回收/爱回收/爱租机/人人租等回收租赁商城。 回收租赁系统支持智能评估回收价格,后台调整最终回收价,用户同意回收后系统即刻放款,用户微信零…

数据分析面试题--SQL面试题

目录标题1,UNION和JOIN的区别2,连续登录问题3,窗口函数和普通聚合函数的区别4,窗口函数的基本用法5,序号函数:row_number(),rank(),dense_rank()的区别6,窗口函数涉及的一些其他函数7,次日留存率…

CAD软件中如何标注曲线长度?

在CAD设计过程中,如果想要用CAD标注图纸中某一曲线的长度该如何操作呢?今天小编就来给大家分享一个CAD标注曲线长度的小工具,有需要的小伙伴可以一起来看看哦! 此插件可以用多种方式标注多段线、样条曲线的长度,并可以…

Go语言笔记:UDP基础使用与广播

文章目录目的基础说明作为服务器使用作为客户端使用广播总结目的 UDP是比较基础常用的网络通讯方式,这篇文章将介绍Go语言中UDP基础使用的一些内容。 本文中使用 Packet Sender 工具进行测试,其官网地址如下: https://packetsender.com/ 基…

知识站点上关于Notes Domino话题几个问答

大家好,才是真的好。 今天周一我们继续不讲技术,介绍一下知识网站上关于Notes/Domino几个有趣问题的讨论。 国内的知识网站头把交椅是知乎,在中文界中是扛把子.不过在国外,最流行的知识网站叫做Quora,上面关于Notes/…

【Java】【系列篇】【Spring源码解析】【三】【体系】【PostProcessors体系】

PostProcessor英文翻译为后置处理器,在Spring体系里面主要针对的对象为Bean和BeanFactory.有着收尾或完善的作用。一、BeanPostProcessor分支 1.1、作用 在Bean对象在实例化和依赖注入完毕后,在显示调用初始化方法的前后添加我们自己的逻辑。注意是Bean…

【学习笔记】【Pytorch】十六、模型训练套路

【学习笔记】【Pytorch】十六、模型训练套路一、内容概述二、模型训练套路1.代码实现:CPU版本2.代码实现:优先GPU版本a3.代码实现:优先GPU版本b4.计算测试集上的正确率三、使用免费GPU训练模型一、内容概述 本内容主要是介绍一个完整的模型训…

信用评分分卡简介

背景 随着金融科技初创企业的兴起,过去 5 年中出现了许多新的消费信贷机构,与传统银行展开竞争。他们通常瞄准银行认为规模太小或因金融危机期间发生的后期损失而不得不削减贷款的细分市场。通俗的讲就是消费金融公司瞄准了银行的次贷市场。 这些新的消…

【C语言】文件操作修改通讯录(升级版本)可以存储数据

文件操作的内容,我们在上文已经学习了,那么如果有不明白的小伙伴请看这篇文章 【C语言】小王带您实现文件操作(简单图示讲解)_小王学代码的博客-CSDN博客 通讯录我们在之前也学习实现了静态、动态通讯录 【C语言】使用C语言实现静…

分享80个PHP源码,总有一款适合您

PHP源码 分享80个PHP源码,总有一款适合您 下面是文件的名字,我放了一些图片,文章里不是所有的图主要是放不下..., 80个PHP源码下载链接:https://pan.baidu.com/s/1yJ1aR6vt2kDjiVyqj0gPuw?pwdlfl9 提取码&#xff…

深信服EDR任意用户登录与命令执行漏洞

深信服EDR任意用户登录与命令执行漏洞1.深信服EDR简介2.深信服EDR漏洞2.1.后台任意用户登录漏洞2.1.1.漏洞描述2.1.2.影响版本2.1.3.漏洞复现2.2.任意命令执行漏洞2.2.1.漏洞描述2.2.2.影响版本2.2.3.漏洞复现2.2.3.1.构建URL2.2.3.2.效果1.深信服EDR简介 终端检测响应平台&…

C生万物 | 使用宏将一个整数的二进制位的奇数位和偶数位交换

👑作者主页:Fire_Cloud_1 🏠学习社区:烈火神盾 🔗专栏链接:万物之源——C 淋漓尽致——位运算✒题目分析 && 实现思路[位运算]1、获取这个整数的奇数位和偶数位2、使用移位运算使【奇变偶】【偶变奇…

如何通过限流算法防止系统过载

限流算法,顾名思义,就是指对流量进行控制的算法,因此也常被称为流控算法。 我们在日常生活中,就有很多限流的例子,比如地铁站在早高峰的时候,会利用围栏让乘客们有序排队,限制队伍行进的速度&am…

扫雷——“C”

各位uu们我又来啦,今天,小雅兰给大家介绍的又是一个小游戏,就是扫雷这款游戏,这个游戏和我昨天给大家介绍的三子棋游戏有异曲同工之妙,相信很多人都玩过,话不多说,我们进入正题吧. 《扫雷》是一…

【学习笔记】【Pytorch】十七、模型测试套路

【学习笔记】【Pytorch】十七、模型测试套路一、内容概述二、模型测试套路代码实现一、内容概述 利用已经训练好的模型,然后给它提供输入,判断输出是否正确,即模型的应用测试。 在模型测试也会有一些坑: 神经网络的输入一般是4…

【错误记录】Kotlin 代码编译时报错 ( Variable ‘name‘ must be initialized | 初始化块定义在所有属性之后 )

文章目录一、报错信息二、问题分析三、解决方案 ( 初始化块定义在所有属性之后 )一、报错信息 在 Kotlin 中 , init 初始化块 要 定义在所有成员属性之后 ; 如果在 init 初始化块 中 , 使用到了 成员属性 , 有可能出现 编译时报错信息 ; 报错代码示例 : class Hello{init {va…

seata安装及配置

1.下载 下载地址:https://github.com/seata/seata/tags 本文选用seata-1.4.2版 2.解压 tar -zxvf seata-server-1.4.2.tar.gz 3. 初始化数据库 登录mysql,然后创建数据库和数据表: -- -------------------------------- The script used…

【Java】【系列篇】【Spring源码解析】【三】【体系】【Environment体系】

整体结构图 本篇文章仅作简单了解,详细还等到Springboot系列里面详解PropertyResolver 作用 用于针对任何基础源解析属性(Property)的接口 方法解析 // 查看规定指定的key是否有对应的value 对应key的值是null的话也算是不能解析 boolean containsProperty(Stri…

持续丰富营销玩法 东风标致408X引领品牌向上焕新

1月5日,东风标致408X首秀——XSHOW开演,标致全球战略车型408X正式在中国亮相,定位为“新法式无界座驾”,它是东风标致全面向电动化、智能化、网联化的发展的一款汽车,也是引领东风标致向上焕新的一款全新车型。作为东风…