blog
  • 吕系小的博客
  • Java
    • 读书笔记
      • Thinking in Java
        • Object
          • 面向对象
          • 对象的创建与生命周期
          • 对象与基本类型的存储位置
        • 控制流程
          • 实现类似goto的操作
        • 初始化与清理
          • 对象的创建过程
          • 垃圾回收器如何工作
          • JIT
        • 复用类
          • 继承与初始化
          • final关键字
        • 多态
          • 构造器和多态
          • 方法和调用绑定
      • Java核心技术卷1
        • 面向对象
        • Unicode和char类型
        • Object:所有类的超类
        • 理解方法调用
        • 代理
        • lambda
        • 接口
        • 异常
        • 并发
          • 线程
          • volatile
          • 阻塞队列
          • 线程安全的集合
          • Callable与Future
          • 同步
          • 执行器
      • Java核心技术卷2
        • 流库
          • 流
          • 并行流
          • 基本类型流
          • 约简操作
          • 群组、分区、下游收集器
          • 收集结果
          • Optional
        • IO
          • 输入、输出流
          • 组合流过滤器
          • 文本输入与输出
          • 字符编码方式
          • 读写二进制数据
          • 随机访问文件
          • 对象输入、输出流与序列化
          • Path
          • 目录
          • 内存映射文件
          • Files
        • 日期和时间
          • 时间线
          • 本地时间
          • 日期调整期
          • 时区时间
          • 格式化和解析
        • 脚本、编译与注解处理
          • 脚本
          • 编译器
          • 注解
          • 标准注解
          • 源码级注解处理
        • 安全
          • 类加载器
          • 安全管理器与访问权限
          • 数字签名
      • 深入理解Java虚拟机
        • Java内存区域
        • 垃圾收集机制
      • Head First Java
        • Chapter1.Breaking the Sur face
        • Chapter2.A Trip to Objectville.There will be objects.
        • Chapter3.Know Your Variables
        • Chapter4.How Objects Behave
        • Chapter7.Better Living in Objectville
        • Chapter9.constructors and garbage collection
        • Chapter10.statics
      • 深入分析JavaWeb技术内幕
        • 深入web请求过程
        • 深入分析JavaIO的工作机制
      • Spring IN ACTION
        • Spring之旅
          • 依赖注入
          • IOC 容器
        • 装配bean
          • 组件扫描与自动装配
          • 通过Java代码装配bean
        • 高级装配
          • profile
          • 条件化bean
          • 处理自动装配的歧义性
          • bean的作用域
          • Environment
          • 属性占位符
    • 前后端那些事
      • 跨域访问
      • Token验证机制
    • 学习笔记
  • 数据库
    • MySQL
      • 基础篇
        • SQL分类及帮助文档
        • MySQL架构
        • 存储引擎
        • MySQL支持的数据类型
        • 数据类型的选择
        • 常用函数
      • 索引篇
        • 索引
      • 事务篇
      • 实际问题
        • 成绩分组取最大值问题
  • 计算机操作系统
    • 计算机操作系统组成与分类
    • 进程
      • 进程
      • 进程管理
      • 多线程结构进程
      • KLT与ULK
      • 处理器调度
    • 存储管理
      • 存储管理的主要模式
      • 存储管理的功能
      • 虚拟存储器的概念
      • 存储器的硬件支撑
      • 页式存储管理的基本原理
      • 页式虚拟存储管理
      • 页面调度
      • 反置页表
      • 段式存储管理
      • 段式虚拟存储
      • 段页式存储管理
    • 设备管理
      • IO缓冲区
    • 文件管理
      • 文件的存储
      • 文件的逻辑结构
      • 文件的物理结构
Powered by GitBook
On this page
  • Java的I/O类库的基本架构
  • 基于字节的I/O操作接口
  • 基于字符的I/O操作接口
  • 字节与字符的转化接口
  • 几种访问文件的方式
  • Java访问磁盘文件
  • Java序列化技术
  1. Java
  2. 读书笔记
  3. 深入分析JavaWeb技术内幕

深入分析JavaIO的工作机制

Java的I/O类库的基本架构

Java的I/O操作类在包java.io下,大概有将近80个类,这些类大概可以分成如下4组

  • 基于字节操作的I/O接口:InputStream和OutputStream。

  • 基于字符操作的I/O接口:Writer和Reader。

  • 基于磁盘操作的I/O接口:File。

  • 基于网络操作的I/O接口:Socket。

前两组主要是传输数据的数据格式,后两组主要是传输数据的方式。I/O的核心问题要么是数据格式影响I/O操作,要么是传输方式影响I/O操作。数据格式和传输方式是影响效率最关键的因素。

基于字节的I/O操作接口

  • InputStream

    • ByteArrayInputStream

    • FileInputStream

      • SoketInputStream

    • FilterInputStream

      • InflaterInputStream

        • ZipInputStream

      • BufferedInputStream

      • DataInputStream

    • ObjectInputStream

    • PipedInputStream

  • OutputStream

    • ByteArrayOutputStream

    • FileOutputStream

      • SocketOutputStream

    • FilterOutputStream

      • DataOutputStream

      • PrintStream

      • ZipOutputStream

    • ObjectOutputStream

    • PipedOutputStream

额外说明,操作数据的方式是可以组合使用的,必须要指定流最终写到什么地方。

OutputStream out = new BufferedOutputStream(new ObjectOutputStream(new FileOutputStream("fileName")));

基于字符的I/O操作接口

  • Writer

    • OutputStreamWriter

      • FileWriter

    • BufferedWriter

    • StringWriter

    • PipedWriter

    • PrintWriter

    • CharArrayWriter

  • Reader

    • InputStreamReader

      • FileReader

    • BufferedReader

    • CharArrayReader

    • FilterReader

    • PipedReader

    • StringReader

字节与字符的转化接口

数据持久化或网络传输都是以字节进行的,所以必须要有从字符到字节或从字节到字符的转化。

InputStreamReader类是从字节到字符的转化桥梁,从InputStream到Reader的过程要指定编码字符集,否则将采用操作系统默认的字符集,很可能会出现乱码问题。StreamDecoder正是完成从字节到字符的解码的实现类。

FileReader继承了InputStreamReader类,实际上是读取文件流,然后通过StreamDecoder解码成char,只不过这里的解码字符集是默认字符集。

写入的过程也是类似,通过OutputStreamWriter类完成了从字节到字符的编码过程,由StreamEncoder完成编码过程。

几种访问文件的方式

读取和写入文件I/O操作都调用操作系统提供的接口,因为磁盘设备是由操作系统管理的,应用程序要访问物理设备只能通过系统调用的方式来工作。读和写分别对应rread()和write()两个系统调用。而只要是系统调用就可能存在内核空间地址和用户空间地址切换的问题,这是操作系统为了保护系统本身的运行安全,而将内核程序运行使用的内存空间和用户程序运行的内存空间进行隔离造成的。这种行为必然存在数据可能需要从内核空间向用户空间复制的问题。

磁盘I/O操作,数据从磁盘复制到内核空间,然后又从内核空间复制到用户空间,将会非常缓慢。这时操作系统为了加速I/O访问,在内核空间使用缓存机制。如果用户程序访问的是同一段磁盘地址的空间数据,那么操作系统将从内核缓存中直接取出返回给用户程序,这样可以减小I/O的响应时间。

标准访问文件的方式

标准访问文件的方式就是当应用程序调用read()接口时,操作系统检查在内核的高速缓存中有没有需要的数据,如果已经缓存了,那么就直接从缓存中返回,如果没有,则从磁盘中读取,然后缓存在操作系统的缓存中。

写入的方式是,用户的应用程序调用write()接口将数据从用户地址空间复制到内核地址空间的缓存中。什么时候再写到磁盘中由操作系统决定,除非显式地调用了sync同步命令。

直接I/O的方式

所谓的直接I/O的方式就是应用程序直接访问磁盘数据,而不经过操作系统内核数据缓冲区。

但是直接I/O也有负面影响,如果访问的数据不在应用程序缓存中,那么每次数据都会直接从磁盘进行加载,这种直接加载会非常缓慢。通常直接I/O与异步I/O结合使用会得到比较好的性能。

同步访问文件的方式

数据的读取和写入都是同步操作的,它与标准访问文件的方式不同的是,只有当数据被成功写到磁盘时才返回给应用程序成功的标志。这种访问文件的方式性能比较差,只有在一些对数据安全性要求比较高的场景中才会使用,而且通常这种操作方式的硬件都是定制的。

异步访问文件的方式

异步访问文件的方式就是当访问数据的线程发出请求之后,线程会接着去处理其他事情,而不是阻塞等待,当请求的数据返回后继续处理下面的操作。这种访问文件的方式可以明显地提高应用程序的效率,但是不会改变访问文件的效率。

内存映射的方式

内存映射的方式是指操作系统将内存中的某一块区域与磁盘中的文件关联起来,当要访问内存中的一段数据时,转换为访问文件的某一段数据。这种方式的目的同样是减少数据从内核空间缓存到用户空间缓存的数据复制操作,因为这两个空间的数据是共享的。

Java访问磁盘文件

在Java中通常的File并不代表一个真实存在的文件对象,当你指定一个路径描述符时,它就会返回一个代表这个路径的虚拟对象,这个可能是一个真实存在的文件或者是一个包含多个文件的目录。

为何要这样设计呢?因为在大多数情况下,我们并不关心这个文件是否真的存在,而是关心对这个文件到底如何操作。

何时会真正检查一个文件存不存在?就是在真正要读取这个文件时。

下面以读取文件的程序为例介绍如何从磁盘读取一段文本字符

try{
    StringBuffer str = new StringBuffer();
    char[] buf = new char[1024];
    FileReader f = new FileReader("file");
    while(f.read(buf) > 0){
        str.append(buf);
    }
} catch(IOException e){

}

当传入一个文件路径时,将会根据这个路径创建一个File对象来标识这个文件,然后根据这个File对象创建真正读取文件的操作对象,这时将会真正创建一个关联真实存在的磁盘文件的文件描述符FileDescriptor,通过这个对象可以直接控制这个磁盘文件。由于我们需要读取的是字符格式,所以需要StreamDecoder类将byte解码为char格式。至于如何从磁盘驱动器上读取一段数据,操作系统会帮我们完成。至于操作系统如何将数据持久化到磁盘及如何建立数据结构的,需要根据当前操作系统使用何种文件系统来回答。

Java序列化技术

Java序列化就是将一个对象转化成一串二进制表示的字节数组,通过保存或转移这些字节数据来达到持久化的目的。需要持久化,对象必须继承java.io.Serializable接口。反序列化则是相反的过程,将这个字节数组再重新构造成对象。

反序列化时,必须有原始类作为模板,才能将这个对象还原,从这个过程可以猜测,序列化的数据并不像class文件那样保存类的完整的结构信息。

复杂对象的序列化情况

  • 当父类继承Serializable接口时,所有子类都可以被序列化

  • 子类实现了Serializable接口,父类没有,父类中的属性不能序列化,但是在子类中属性仍能正确序列化。

  • 如果序列化的对属性是对象,则这个对象也必须实现Seriablizable接口,否则会报错。

  • 在反序列化时,如果对象的属性有修改或删减,则修改的部分属性会丢失,但不会报错。

  • 在反序列化时,如果serialVersionUID被修改,则反序列化时会失败。

在纯Java环境下,Java序列化能够很好地工作,但是在多语言环境下,用Java序列化存储后,很难用其他语言还原出结构。在这种情况下,还是要尽量存储通用的数据结构,如JSON或者XML结构数据。

Previous深入web请求过程NextSpring IN ACTION

Last updated 6 years ago