当前位置: 首页 > article >正文

JavaIO 在 Android 中的应用

主要是学习如何设计这样的 IO 系统,学习思想而不是代码本身。

1、装饰模式在 IO 中的应用

IO 嵌套其实使用到了装饰模式。装饰模式在 Android 中有大量的使用实例,比如 Context 体系:

可以看到 Context 还是基本上遵循了标准装饰模式的结构:

各个部分的介绍如下:

  • Component:抽象构建接口。
  • ConcreteComponent:具体的构建对象,实现组件对象接口,通常就是被装饰的原始对象,被扩展功能。
  • Decorator:所有装饰器的抽象父类,需要定义一个与组件接口一致的接口,内部持有一个被装饰的 Component 对象。
  • ConreteDecoratorA/ConreteDecoratorB:实际的装饰器对象,实现具体的扩展功能。

体系中存在一个接口 Component,所有装饰器的抽象父类 Decorator 要实现 Component 接口并持有需要被装饰的对象(可以是 Component 也可以是 ConcreteComponent),实际的装饰器 ConreteDecorator 继承抽象父类 Decorator 并扩展功能(添加方法)。

把装饰模式应用到 IO 体系中,会有下图:

InputStream 相当于标准装饰模式结构图中的 Component,它实际上是一个抽象类,所有子类必须实现一个抽象的 read()。而红框标记的 ByteArrayInputStream、PipedInputStream、FileInputStream 和 ObjectInputStream 都是 InputStream 的具体子类,可以直接用的。FilterInputStream 就是所有装饰器的抽象父类了,使用了装饰模式的 BufferedInputStream 和 DataInputStream 是具体的装饰类。

2、Java IO 体系

装饰模式的优点是灵活,这也导致了 IO 体系不断的扩展产生了众多的 IO 类与嵌套关系,尤其是 IO 流的嵌套给不了解 IO 体系的初学者造成了很大的困扰和麻烦。为了捋顺这些 IO 类,可以从 JDK 版本演进的角度学习这些 IO 类。

IO 操作是从内存角度出发,而不是程序员角度。从内存取出数据是输出流,从外部读入数据进内存是输入流。

FilterOutputStream/FilterInputStream 是装饰模式中的抽象装饰基类,它们继承自 OutputStream/InputStream,按照装饰模式的标准,在内部持有了一个 OutputStream/InputStream 对象并通过构造方法初始化,用以具体的装饰子类接收流。

对于如下常见的 IO 使用模式:

    DataOutputStream out = new DataInputStream(
                           new BufferedInputStream(
                           new FileInputStream(new File(file))));

FileInputStream 可以把文件转换成流,BufferedInputStream 使用缓冲区提升流的读取速度,DataInputStream 按照数据格式读取数据,如 readInt()、readLong() 等等,便于使用者读取数据。三个流的功能在逐步增强。

BufferedInputStream 读取速度快的原因是在执行读取数据的 read() 时,使用了缓存数组 byte[] buffer 去读取多个字节,减少了硬盘磁头的调度次数,从而提高了流的读取速度。

3、字节流与字符流

字节流与字符流的一个显著区别就是字符流有 readLine() 可以读取一行字符。但是字符流很少使用,因为我们大部分的 IO 操作,如对 png、apk、zip 等文件,你把数据读取成字符流没有任何实际意义,只有像 json、xml 这种使用字符串表示,知道文件构造就能搞懂意义的数据格式,才需要用字符的形式来读取。

字节流与字符流对应关系如下:

实际上字符流内部使用了字节流,以 OutputStreamWriter 为例:

public class OutputStreamWriter extends Writer {

    private final StreamEncoder se;
    
    public OutputStreamWriter(OutputStream out, String charsetName)
        throws UnsupportedEncodingException
    {
        super(out);
        if (charsetName == null)
            throw new NullPointerException("charsetName");
        se = StreamEncoder.forOutputStreamWriter(out, this, charsetName);
    }
    
    public void write(String str, int off, int len) throws IOException {
        se.write(str, off, len);
    }
}

OutputStreamWriter 的构造方法需要一个字节流 OutputStream 和字符集 charsetName,在执行写操作时调用 StreamEncoder 的 write(),内部实际上还是用字节流按照传入的字符集进行输出的。

也就是说字节流可以转换成字符流(但是没有将字符流转换成字节流的渠道):

并且,如下的使用方式也被称为“标准使用方式”:

    private static void test5() throws IOException {
        BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(new File("a.txt")),"GBK"));
        bufferedWriter.write("测试字符流");
        bufferedWriter.flush();
        bufferedWriter.close();
    }

在 OutputStreamWriter() 中传递一个字节流 FileOutputStream 作为参数。当然在不考虑指定字符集的前提下,也可以对以上代码做一个简化:

    private static void test5() throws IOException {
        BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter("a.txt"));
        bufferedWriter.write("测试字符流2");
        bufferedWriter.flush();
        bufferedWriter.close();
    }

相当于使用 FileWriter 代替了 OutputStreamWriter(FileOutputStream),可以这样做的原因是,FileWriter 实际上就是做了一个封装的工作:

public class FileWriter extends OutputStreamWriter {
    
    public FileWriter(String fileName) throws IOException {
        super(new FileOutputStream(fileName));
    }
}

通过 FileWriter 的构造方法可以看出,它内部就是执行的 new OutputStreamWriter(new FileOutputStream(fileName)))。

针对字符流,可以看以下问题加深理解:

字节流与字符流的选择

在所有的硬盘上保存文件或进行传输的时候都是以字节的方法进行的,包括图片,而字符是只有在内存中才会形成的,所以使用字节的操作是最多的。

如果仅仅需要 Java 程序实现一个拷贝功能,应该选用字节流进行操作并且采用边读边写的方式以节省内存。而如果你需要读取/写入一个文本文件,或者其它以字符串展示的文件,如 xml、JSON,可以选择使用字符流,进行读写操作的代码看起来更直观。

字节流与字符流的转换

其实只能将字节流转换成字符流,转换桥梁是 InputStreamReader 和 OutputStreamWriter。以输入字节流转换成输入字符流为例,InputStreamReader 有两个构造方法:

    InputStreamReader(InputStream in);
    InputStreamReader(InputStream in,String CharsetName); // 用指定字符集创建字符流

输出流类似。此外,为了避免频繁的字节流与字符流转换,BufferedWriter 封装了 OutputStreamWriter,BufferedReader 封装了 InputStreamReader。

4、RandomAccessFile、NIO

普通的 File 只能按照顺序访问,而 RandomAccessFile 可以访问文件的任意位置。

RandomAccessFile 的 seek(int position) 可以将光标调整到 position 的位置,随后从这个位置开始读/写,通过 setLength(long len) 给写入文件预留空间。

常用于网络数据的断点续传。

NIO 在 Android 中用到最多的是 FileChannel。FileInputStream 中就使用了 FileChannel,FileChannel 内部使用了 ByteBuffer,可以加快 IO 速度,速度可以达到普通流的10倍。

NIO 主要是在读写大文件的时候使用,在 Android 的使用环境下用的很少,基本就用到 FileChannel。

建议学习 Java 编程思想中 IO 章节的部分;
系统总结流的用法以及结构图;


http://www.kler.cn/a/448605.html

相关文章:

  • Java程序打包成exe,无Java环境也能运行
  • Servlet学习中遇到的一些问题及解决
  • tomcat的安装以及配置(基于linuxOS)
  • Kubeadm+Containerd部署k8s(v1.28.2)集群(非高可用版)
  • microk8s使用
  • 在福昕(pdf)阅读器中导航到上次阅读页面的方法
  • 基于三维先验知识的自监督医学图像分割方法
  • 在福昕(pdf)阅读器中导航到上次阅读页面的方法
  • vue3和element-plus笔记
  • 【刷题23】多源BFS
  • MFC/C++学习系列之简单记录——序列化机制
  • 《机器学习》支持向量机
  • Docker日志与监控
  • Maven的介绍以及安装,仓库的使用和在idea使用maven
  • [Unity Shader]【游戏开发】【图形渲染】Shader数学基础7-矩阵变换概览及其几何意义
  • 前端路由模式详解:Hash 模式、History 模式与 Memory 模式
  • 深度学习作业十一 LSTM
  • 【LeetCode】52、N 皇后 II
  • Python的sklearn中的RandomForestRegressor使用详解
  • MySQL InnoDB 存储引擎 Redo Log(重做日志)详解
  • KMP模式匹配算法——详细讲解、清晰易懂
  • THM:Vulnerability Capstone[WriteUP]
  • Python中SKlearn的K-means使用详解
  • Flutter组件————Container
  • Windows下使用git配置gitee远程仓库
  • 【C语言】后端开发。数据一致性和分布式锁