Java IO详解

一、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 对象能够监听多个文件目录。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值