博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
OpenJDK 源码阅读之 Java 字节流输出类的实现
阅读量:5998 次
发布时间:2019-06-20

本文共 4539 字,大约阅读时间需要 15 分钟。

Java 的输入输出总是给人一种很混乱的感觉,要想把这个问题搞清楚,必须对各种与输入输出相关的类之间的关系有所了解。只有你了解了他们之间的关系,知道设计这个类的目的是什么,才能更从容的使用他们。

这是这个系列的第二篇,描述字节输出类的实现,第一篇见:

字节流输出

图1 Java 字节输出类

  • OutputStream

OutputStream是所有字节输出类的超类,这是个抽象类,需要实现其中定义的 write 函数,才能有实用的功能。

public abstract void write(int b) throws IOException;

其它方法都是在 write 的基础上实现的。例如这个多态的 write :

public void write(byte b[], int off, int len) throws IOException {
if (b == null) {
throw new NullPointerException(); } else if ((off < 0) || (off > b.length) || (len < 0) || ((off + len) > b.length) || ((off + len) < 0)) {
throw new IndexOutOfBoundsException(); } else if (len == 0) {
return; } for (int i = 0 ; i < len ; i++) {
write(b[off + i]); }}
  • FileOutputStream

FileOutputStream 会将内容输出到 File 或者 FileDescriptor, 此类是按照字节输出,如果想按照字符输出,可以使用FileReader 类。

构造器中,需要指明输出的文件:

public FileOutputStream(File file, boolean append)    throws FileNotFoundException{
String name = (file != null ? file.getPath() : null); SecurityManager security = System.getSecurityManager(); if (security != null) {
security.checkWrite(name); } if (name == null) {
throw new NullPointerException(); } this.fd = new FileDescriptor(); this.append = append; fd.incrementAndGetUseCount(); open(name, append);}

写入操作是一个 native 函数,与操作系统相关。

private native void write(int b, boolean append) throws IOException;

如果对比一下字节输入类,你会发现输入和输出在实现上有很大的相似性,它们是对称的。

  • ByteArrayOutputStream

ByteArrayOutputStream 会将数据写入字节数组中, 可以通过 toByteArray,toString 得到这些数据。

protected byte buf[];

初始化时,可以指定这个数组的大小:

public ByteArrayOutputStream(int size) {
if (size < 0) {
throw new IllegalArgumentException("Negative initial size: " + size); } buf = new byte[size];}

写入时,会写入这个数组。write 会先保证数组的大小,如果不够用,还会自动进行扩充。

public synchronized void write(int b) {
ensureCapacity(count + 1); buf[count] = (byte) b; count += 1;}
  • FilterOutputStream

所有有过滤功能的类的基类,例如,对输出流进行转化,或者添加新的功能。初始化时,需要提供一个底层的流,用于写入数据,FilterOUtputStream 类的所有方法都是通过调用这个底层流的方法实现的。

初始化时,

protected OutputStream out;public FilterOutputStream(OutputStream out) {
this.out = out;}

写入时:

public void write(int b) throws IOException {
out.write(b);}
  • BufferedOutputStream

BufferedOutputStream 是 FilterOutputStream 的子类,提供缓冲功能,所以,你不用每写入一个字节都要调用操作系统的write 方法,而是积累到缓冲区,然后一起写入。

缓冲区就是一个字节数组,在构造器中被初始化。

protected byte buf[];public BufferedOutputStream(OutputStream out) {
this(out, 8192);}public BufferedOutputStream(OutputStream out, int size) {
super(out); if (size <= 0) {
throw new IllegalArgumentException("Buffer size <= 0"); } buf = new byte[size];}

当调用 write(b) 时,并不真正写入,而是将要写入的数据存放在缓冲区内,等缓冲区满后,一次性写入数据。

public synchronized void write(int b) throws IOException {
if (count >= buf.length) {
flushBuffer(); } buf[count++] = (byte)b;}
  • DataOutputStream

DataOutputStream 可以按 Java 的基本类型写入数据。写入的原理是,将基本类型数据中的字节分离出来,然后将这些字节写入。例如:

public final void writeBoolean(boolean v) throws IOException {
out.write(v ? 1 : 0); incCount(1);}

boolean 类型就是按照 0/1 的方式写入的。

public final void writeShort(int v) throws IOException {
out.write((v >>> 8) & 0xFF); out.write((v >>> 0) & 0xFF); incCount(2);}

short 是两个字节,需要将其中的两个字节分离出来,分别写入,incCount 加了2. writeChar 同理,因为它也是写入两个字节。

浮点数比较特殊,没法直接分离出各个字节,要调用 Float 的一个静态方法,把浮点数转化成四个字节,再通过 writeInt 写入。floatToInitBits 会调用一个 native 方法, 按照 IEEE 754 标准,完成其主要功能。

public final void writeFloat(float v) throws IOException {
writeInt(Float.floatToIntBits(v)); }
  • PipedOutputStream

管道输出流可以与一个管道输入流相关联,关联后,共用一个缓冲区,输出流写入数据,输入流读取数据,二者应该处于不同线程,否则可能出现死锁。

原理上一篇文章在介绍 PipedInputStream 时,已经阐述。

另外,我觉得在这里,有必要说一下那几个用于压缩和解压缩的类,实现就不说了,就讲下他们的功能与关系。

JAVA IO 压缩与解压缩

  • InflaterInputStream: 用于解压 deflate 格式的压缩数据,底层流为压缩后的数据,read 返回解压后的数据。
  • InflaterOutputStream: 用于解压 deflate 格式的压缩数据,底层流为压缩后的数据,write 写入解压后的数据。
  • DeflaterInputStream: 用于压缩成 deflate 格式的数据,底层流为未压缩数据,read 返回压缩后的数据。
  • DeflaterOutputStream: 用于压缩成 deflate 格式的数据,底层流为未压缩数据,write 写入压缩后的数据。

  • GZIPInputStream: 用于解压 GZip 格式的压缩数据,底层流为压缩后的数据,read 返回解压后的数据。它是 InflaterInputStream 的子类。

  • GZIPOutputStream: 用于压缩成 Gzip格式的数据,底层流为未压缩数据,write 写入压缩后的数据。是 DeflaterOutputStream 的子类(注意不是InflaterOutputStream) 。

不得不说,这个API设计的真是太反直觉了。GZIP 格式的解压和压缩一个是 GZIPInputStream,一个是 GZIPOutputStream。而 deflate 格式的解压和压缩,一个是 InflaterInputStream/InflaterOutputStream,另一个是 DeflaterInputStream/DeflaterOutputStream。当同时需要对 gzip 和 deflate 压缩和解压缩时,就感觉,真是反直觉。

转载于:https://www.cnblogs.com/Iambda/p/3933455.html

你可能感兴趣的文章
Java剖析工具JProfiler进行远程剖析
查看>>
产品经理技能树 目录
查看>>
VLC for android 使用手记
查看>>
用于组织,测试和操作对象及类的工具和技术-3 了解类中的方法
查看>>
几个JavaScript模板引擎的比较
查看>>
SVN Working Copy xxx locked 并 cleanup失败之解 :del lock /q/s
查看>>
设计模式学习笔记之-适配器模式
查看>>
Android开发 Retrofit实现token失效刷新的处理方案
查看>>
一次服务器CPU占用率高的定位分析
查看>>
Tomcat通过集群实现SSO
查看>>
ecshop 二次开发页面 分页技术,获取分页错误
查看>>
在iOS中 简单使用MKMapView显示地图
查看>>
oracel分页查询
查看>>
也发一个自己实现的android简单文件选择器代码。支持多卡,排序
查看>>
所有配置正确,安装成功,但MySQL 服务无法启动(mysql-5.7.13-winx64)
查看>>
如何编写高质量的代码
查看>>
Android中内容观察者的使用---- ContentObserver类详解 (转)
查看>>
Ubuntu虚拟机中编译运行cgminer挖矿软件
查看>>
左手书法二十四篇
查看>>
npm的permission问题
查看>>