Java IO详解

2023-10-26

一、I/O简介

IO即Input和Output,即输入和输出。这里的输入和输出都是相对于内存来说的,具体见下图。
在这里插入图片描述
InputStream/Reader:所有输入流的基类,前者是字节输入流,后者是字符输入流。
OutputStream/Writer:所有输出流的基类,前者是字节输出流,后者是字符输出流。
字节是计算机存储容量的基本单位(Byte),1B=8b,二进制中占8位。
字符是文字或符号的统称。
字节流什么类型的文件都可以读取,字符流只能读取纯文本文件。

二、字节流

1、InputStream && OutputStream

InputStream 类图如下:
在这里插入图片描述
OutputStream类图如下:
在这里插入图片描述

从jdk8文档中,InputStream 方法如下:
在这里插入图片描述
OutputStream方法如下:
在这里插入图片描述
从 Java 9 开始,InputStream 新增加了多个实用的方法:

readAllBytes():读取输入流中的所有字节,返回字节数组。
readNBytes(byte[] b, int off, int len):阻塞直到读取 len 个字节。
transferTo(OutputStream out):将所有字节从一个输入流传递到一个输出流。

2、FileInputStream && FileOutputStream

我们日常使用的话,比较常见的就是FileInputStream了,其可直接指定文件路径,可以直接读取单字节数据,也可以读取至字节数组中。
FileInputStream 示例代码如下:

 public static void main(String[] args) {
        try {
            FileInputStream fileInputStream = new FileInputStream("inputTest.txt");
            //1、返回从该输入流中可以读取(或跳过)的字节数的估计值,而不会被下一次调用此输入流的方法阻塞。
            int available = fileInputStream.available();
            System.out.println("available:" + available);
            //2、跳过并丢弃来自此输入流的 2字节数据。
            long skip = fileInputStream.skip(2);
            System.out.println("skip:" + skip);
            //3、读取文件内容,为了减少IO,我们创建一个Byte数组作为接收缓冲区
            byte[] bytes = new byte[4];
            int read;
            //4、从输入流读取一些字节数,并将它们存储到缓冲区 b 。
            while ((read = fileInputStream.read(bytes)) != -1) {
                // 把byte数组转换成字符串
                System.out.print(new String(bytes, 0, read));
            }
            if (fileInputStream != null) {
                fileInputStream.close();
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

inputTest.txt文件如下:
在这里插入图片描述
输出结果如下:

available:11
skip:2
adafadsff

FileOutputStream有如下构造方法:

public FileOutputStream(String name, boolean append)

boolean append为true表示在创建的文件存在时,不会清空原文件内容,以追加的方式写入。
若文件不存在,则新增文件。
示例代码如下:

 public static void main(String[] args) {
        try {
            FileOutputStream fileOutputStream = new FileOutputStream("outputTest.txt");
            String s = "lalalaaadhawe";
            // 将字符串转换为byte数组
            byte[] bytes = s.getBytes();
            fileOutputStream.write(bytes);
            fileOutputStream.flush();
            fileOutputStream.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

运行结果如下:
在这里插入图片描述

3、字节流的文件复制

学完了FileInputStream && FileOutputStream,我们可以利用其做文件复制,代码如下:

public static void main(String[] args) {
        try {
            FileInputStream fileInputStream = new FileInputStream("inputTest.txt");
            FileOutputStream fileOutputStream = new FileOutputStream("copy.txt");
            //一次拷贝4个字节
            byte[] bytes = new byte[4];
            int read;
            //4、从输入流读取一些字节数,并将它们存储到缓冲区 b 。
            while ((read = fileInputStream.read(bytes)) != -1) {
                fileOutputStream.write(bytes, 0, read);
            }
            fileOutputStream.flush();
            if (fileInputStream != null) {
                fileInputStream.close();
            }
            if (fileOutputStream != null) {
                fileOutputStream.close();
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

4、BufferedInputStream && BufferedOutputStream

我们通过上面代码,可以看到,为了减少IO次数,可以设立一个缓冲池byte,提高读取效率。而BufferedInputStream 和BufferedOutputStream的设计初衷就是如此,通常我们也是使用带缓冲区的BufferedInputStream ,其源码如下:

public
class BufferedInputStream extends FilterInputStream {
    // 内部缓冲区数组
    protected volatile byte buf[];
    // 缓冲区的默认大小
    private static int DEFAULT_BUFFER_SIZE = 8192;
    // 使用默认的缓冲区大小
    public BufferedInputStream(InputStream in) {
        this(in, DEFAULT_BUFFER_SIZE);
    }
    // 自定义缓冲区大小
    public BufferedInputStream(InputStream in, int size) {
        super(in);
        if (size <= 0) {
            throw new IllegalArgumentException("Buffer size <= 0");
        }
        buf = new byte[size];
    }
}

5、DataInputStream && DataOutputStream

这一对类可以直接写入java基本类型数据(没有String),但写入以后是一个二进制文件的形式,不可以直接查看。
示例代码如下:

DataInputStream dis = new DataInputStream(fileInputStream);
            dis.readInt();
            /**
             * 即使存入越界的树65538,也不会报错,因为超出部分不会被存入,存入的只是超出的部分。
             * short类型占据16位的空间,因此将65538转为二进制数,超出16位的部分自动截掉,只保留16为以内的数据。
             */
            dis.readShort();
            dis.readLong();
            dis.readByte();
            dis.readChar();
            dis.readDouble();
            dis.close()

6、序列化和反序列化 ObjectInputStream && ObjectOutputStream

Java 序列化就是指将对象转换为字节序列的过程,而反序列化则是将字节序列转换成目标对象的过程。具体参考:java中的序列化和transient关键字
ObjectInputStream 用于从输入流中读取 Java 对象(反序列化),ObjectOutputStream 用于将对象写入到输出流(序列化)。
示例如下:

ObjectInputStream input = new ObjectInputStream(new FileInputStream("object.data"));
MyClass object = (MyClass) input.readObject();
input.close();

7、打印流PrintStream

PrintStream源自OutputStream,其为标准字节的输出流,默认输出到控制台,也就是我们常用的System.out.println()方法。
在这里插入图片描述
日志框架的实现原理就是通过PrintStream的使用,输出到日志文件,示例如下:

public static void log(String msg) {
        try {
            // 指向一个日志文件
            PrintStream ps = new PrintStream(new FileOutputStream("log.txt",true));
            // 改变流的输出方向
            System.setOut(ps);
            // 获取系统当前时间
            Date nowTime = new Date();
            // 日期格式化
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
            String strTime = sdf.format(nowTime);
            System.out.println(strTime+": "+msg);
            ps.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }

    }

    public static void main(String[] args) {
        log("第1次打印日志");
        log("第2次打印日志");
        log("第3次打印日志");
    }

运行结果如图:
在这里插入图片描述

三、字符流

首先,确认一个问题,已经有字节流了,可以读取任意文件,为什么还要有字符流呢?
主要有以下原因:
1、对于字符文件,先作为字节传输,再转成字符,耗时耗力。
2、对于字符文件,转成字节之后,再转回来,如果是中文,很容易乱码。

1、Reader && Writer

类图如下:
在这里插入图片描述
在这里插入图片描述

2、FileReader && FileWriter

字符流常用的读写类为FileReader 与 FileWriter,我们还用复制文件来做示例,示例代码如下:

 public static void main(String[] args) throws IOException {
            FileReader fileReader = new FileReader("file.txt");
            FileWriter fileWriter = new FileWriter("file2.txt");
            //一次拷贝4个字节
            char[] chars = new char[1024*1024];
            int read;
            while ((read = fileReader.read(chars)) != -1) {
                fileWriter.write(chars, 0, read);
            }
            fileWriter.flush();
            if (fileReader != null) {
                fileReader.close();
            }
            if (fileWriter != null) {
                fileWriter.close();
            }

    }

执行结果如下:
在这里插入图片描述
中文没有乱码。
BufferedReader和BufferedWriter,类似于BufferedInputStream和BufferedOutputStream,内部都维护了一个字节数组作为缓冲区。

四、随机访问流RandomAccessFile

RandomAccessFile 支持随意跳转到文件的任意位置进行读写,任意位置进入文件。
RandomAccessFile 比较常见的就是断点续传,适用于大文件的分片上传,后面单独开一篇文章进行总结。

五、IO中的设计模式

1、适配器模式

适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。具体参考:java(面向对象)的23种设计模式(5)——适配器模式。
在Java IO中,为了实现字符流和字节流之间的相互转换,就产生了两个适配器的类,InputStreamReader 和 OutputStreamWriter 。
代码如下:

InputStreamReader isr = new InputStreamReader(new FileInputStream(fileName), "UTF-8");
BufferedReader bufferedReader = new BufferedReader(isr);

2、装饰器模式

装饰器模式可以将新功能动态地附加于现有对象而不改变现有对象的功能。具体参考:java(面向对象)的23种设计模式(7)——装饰模式。
InputStream 的子类 FilterInputStream,OutputStream 的子类 FilterOutputStream,Reader 的子类 BufferedReader 以及 FilterReader,还有 Writer 的子类 BufferedWriter、FilterWriter 以及 PrintWriter 等,它们都是抽象装饰类。增强了子类对象的功能。

3、工厂模式

工厂模式,通过定义工厂父类负责创建对象的公共接口,而子类则负责生成具体的对象。具体参考:
java(面向对象)的23种设计模式(3)——工厂模式。

NIO 中大量用到了工厂模式,比如 Files 类的 newInputStream 方法用于创建 InputStream 对象(静态工厂)、 Paths 类的 get 方法创建 Path 对象(静态工厂)、ZipFileSystem 类(sun.nio包下的类,属于 java.nio 相关的一些内部实现)的 getPath 的方法创建 Path 对象(简单工厂)。

4、观察者模式

观察者模式:指多个对象间存在一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。具体参考:java(面向对象)的23种设计模式(11)——观察者模式。
NIO 中的文件目录监听服务基于 WatchService 接口和 Watchable 接口。WatchService 属于观察者,Watchable 属于被观察者。WatchService 用于监听文件目录的变化,同一个 WatchService 对象能够监听多个文件目录。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Java IO详解 的相关文章

随机推荐

  • C6385:从“buffer”中读取的数据无效: 可读大小为“recv()`72”个字节,但可能读取了“25”个字节。

    C 网络编程中接收结构体对象遇到的问题 想从客户端发送一个结构体对象到服务器 在网上查询后发现可以在客户端用memcpy把结构体拷贝到字符串上发送给客户端 再在客户端把字符串转化为结构体 具体代码如下 结构体 typedef struct
  • 算法知识之最长公共子序列问题(动态规划)

    最近朋友让帮做个关于动态规划的最长公共子序列的问题 翻看以前的笔记并完成该题后 顺便写这样一篇文章 希望对大家有所帮助 同时也帮助自己回顾该知识点 一 最长公共子序列的定义 子序列 若给定序列X x1 x2 xm 则另一序列Z z1 z2
  • 刷脸支付必定是站在时代舞台中央的新星

    2020年初这场突如其来的疫情 打乱了各行各业的节奏 据2019年中国刷脸支付技术应用社会价值专题研究报告预计 2022年刷脸支付市场规模将突破7 6亿人 这说明现在大家习惯的支付是扫码支付 而一两年后人们将形成全新的支付习惯 刷脸支付 疫
  • 【布局 Widget】 Flutter SizedBox

    Flutter Sizedbox 是一个 布局组件 用来给 child 添加 tight 约束的 也可以用来添加空白 width height是 Sizedbox 的参数 BoxConstraints get additionalConst
  • 用户名 不在 sudoers文件中,此事将被报告。

    sudo password for lkp lkp 不在 sudoers 文件中 此事将被报告 sudo命令可以让你以root身份执行命令 来完成一些我们这个帐号完成不了的任务 其实并非所有用户都能够执行sudo 因为有权限的用户都在 et
  • CTFshow web1~8 WP Write by SunnyDog

    文章目录 web签到题 查看源码 web2 SQL注入 web3 input伪协议 log注入 Web4 log注入 Web5 代码审计 Web6 SQL注入 但是过滤 Web7 SQL注入 Web8 脚本布尔盲注 web签到题 查看源码
  • 解决:c++: internal compiler error: Killed (program cc1plus)

    TOC 解决 c internal compiler error Killed program cc1plus 1 出现问题 博主在VMware虚拟机中安装sophus库 执行make命令时出现错误 如下图所示 2 分析问题 多方查找 确定
  • 文件自动化处理

    文件自动化处理 1 1 读写文件 1 1 1 文件与文件路径 1 1 2 当前工作目录 1 1 3 路径操作 1 1 3 1 绝对路径和相对路径 1 1 3 2 路径操作 1 1 3 3 路径有效性检查 1 1 4 文件及文件夹操作 1 1
  • 光线追踪算法

    文章目录 光线追踪算法 1 光线追踪算法介绍 2 光线追踪算法重要的公式 3 光线追踪算法在matlab中的应用实例 4 光线追踪算法在Lingo中的应用实例 5 个人感悟 光线追踪算法 1 光线追踪算法介绍 光线追踪算法是一种基于物理光线
  • Android--自定义数字输入框

    工具类 前台页面 keyboadview xml 键盘页面 Java后台 KeyboardPopupWindow 步骤 1 initView 每自定义一个数字输入框 方法内部的代码都要重写并绑定相应输入框控件 2 onKeyDown int
  • 城市扩张对生境质量影响评价与生态安全格局构建—以黄河流域“几”字弯为例

    1 亮点 以高生境质量为 源地 构建生态廊道 城市扩张对生境质量的影响是异质的 提出 几 字弯 两屏三廊七区 的生态格局 2 关键词 城市扩张 生境质量 生态安全格局 城市化 InVEST模型 MCR模型 可持续发展目标 干旱地区 3 摘要
  • awk命令详解

    http www cnblogs com serendipity archive 2011 08 01 2124118 html 简单使用 awk 对于文件中一行行的独处来执行操作 awk F print 1 4 使用 来分割这一行 把这一
  • 二维我的世界源代码(c++)

    include
  • C++ 多线程:互斥量(mutex)

    C 11中的互斥量 声明在
  • Linux命令_pstree & 显示进程树

    目录 1 语法 1 1 常用参数 2 常见用法 2 1 指定PID 2 2 指定用户 2 3 显示某个进程详细信息 2 4 指定查看某个进程的父进程 1 语法 pstree 参数 pstree 参数 PID pstree 参数 USER 1
  • python进程间通信

    进程间通信表示进程之间的数据交换 为了开发并行应用程序 需要在进程间交换数据 下图显示了多个子过程之间同步的各种通信机制 各种通信机制 队列 队列可以用于多进程程序 多处理模块的Queue类与Queue Queue类相似 因此 可以使用相同
  • 双向链表的结点增删细节(p->next->prior = s是啥意思,p->next->prior究竟代表下一个结点的prior指针还是p本身)

    一 写在开头 最近在看双向链表 其中双向链表的结点增删操作代码困扰了我很久 下面就写下一些自己的感悟 大家轻点喷 二 相关代码 include
  • Tomcat后台管理

    Tomcat后台管理讲解 1 Tomcat是什么 Tomcat 服务器是一个免费的开放源代码的Web 应用服务器 属于轻量级应用服务器 2 为什么使用它 优势 适用范围 范围 在中小型系统和并发访问用户不是很多的场合下被普遍使用 是开发和调
  • 气温常年在25度的地方_我国夏季最清凉的5座城市,平均不超过25℃,昆明,贵阳上榜...

    在这个炎热的夏季中 不知道为什么今年的天气非常的怪异 夏天来的非常早 而且气温上升非常迅速 可能第一天还是在25度左右 第二天可能就能达到32度 毫不夸张 所以在这个时候 我们在出去旅游的时候一定要选择一些清凉的地方 今天小编就告诉大家我国
  • Java IO详解

    一 I O简介 IO即Input和Output 即输入和输出 这里的输入和输出都是相对于内存来说的 具体见下图 InputStream Reader 所有输入流的基类 前者是字节输入流 后者是字符输入流 OutputStream Write