主要内容包括OutputStream及其部分子类,以分析源代码的方式学习。关心的问题包括:每个字节输出流的作用,各个流之间的主要区别,何时使用某个流,区分节点流和处理流,流的输出目标等问题。 OutputStream的类树如下所示,其中,ObjectOutputStream和PipedOutputStream本文将不做讨论。

java.io.OutputStream (implements java.io.Closeable, java.io.Flushable)
java.io.ByteArrayOutputStream
java.io.FileOutputStream
java.io.FilterOutputStream
java.io.BufferedOutputStream
java.io.DataOutputStream (implements java.io.DataOutput)
java.io.PrintStream (implements java.lang.Appendable, java.io.Closeable)
java.io.ObjectOutputStream (implements java.io.ObjectOutput, java.io.ObjectStreamConstants)
java.io.PipedOutputStream

OutputStream源码分析

package java.io;

//它是抽象类,并且实现了两个接口Closeable和Flushable。
public abstract class OutputStream implements Closeable, Flushable { //作为抽象类中唯一的抽象方法,(非抽象)子类必须实现这个方法。
//我们可以看到,这个类还提供了另外两个write方法,但是它们最终都是要调用这个方法来完成具体的实现
//对于一个输出流,我们需要关心输出的内容到哪里去了,从这个write方法中我们根本看不到输出的目的地,所以实现这个方法的子类必须告诉这一点
//而实现这个方法的子类,就是节点流。
//注意:作为字节输出流,为何这里参数传递为int型,而非byte型,这个在后面子类实现中再分析
public abstract void write(int b) throws IOException; //此方法直接输出一个字节数组中的全部内容,调用了下面的write方法
public void write(byte b[]) throws IOException {
write(b, 0, b.length);
} //功能:要输出的内容已存储在了字节数组b[]中,但并非全部输出,只输出从数组off位置开始的len个字节。因此,需要对传入的三个参数作合理性判断
public void write(byte b[], int off, int len) throws IOException {
//数组不能为空,否则抛出NullPointerException
if (b == null) {
throw new NullPointerException();
} else if ((off < 0) || (off > b.length) || (len < 0) ||
((off + len) > b.length) || ((off + len) < 0)) {
//此处判断off+len<0是多余的
throw new IndexOutOfBoundsException();
} else if (len == 0) {
return;
}
//最终会调用第一个write方法。注意:1.子类可能会复写当前的write方法;2.在输出的过程中,还是一个一个字节输出的。
for (int i = 0 ; i < len ; i++) {
write(b[off + i]);
}
} //这两个方法就是实现两个接口时分别需要实现的方法,但这里方法中内容是空的,子类可以override这两个方法,如果子类不复写,则此方法为空。
public void flush() throws IOException { }
public void close() throws IOException { } }

  关于override父类或接口的方法时,原以为要和父类或接口中声明的一样,包括权限,现在看来不然。

package java.io;
import java.io.IOException;
public interface Flushable {
//此处的方法权限为包权限,而在OutputStream中则成为了public权限
void flush() throws IOException;
} package java.lang;
public interface AutoCloseable {
//此处的方法权限为包权限,而子接口Closeable中也变成了public权限
void close() throws Exception;
} package java.io;
import java.io.IOException;
public interface Closeable extends AutoCloseable {
public void close() throws IOException;
}

ByteArrayOutputStream

package java.io;
import java.util.Arrays;
public class ByteArrayOutputStream extends OutputStream {
//这里可以回答输出流写到哪里的问题:当我们调用write方法时,把内容都存储到了这个byte数组buf中,且是按照追加的方式添加
//而count则指向下一个可以写入的位置,它的初始值默认为0
protected byte buf[];
protected int count; public ByteArrayOutputStream() {
this(32);
}
//类的构造方法只有两个,实际的工作只是在堆中为数组buf申请一块内存,大小可以指定,默认大小为32
public ByteArrayOutputStream(int size) {
if (size < 0) {
throw new IllegalArgumentException("Negative initial size: "
+ size);
}
buf = new byte[size];
} //此方法是确保buf的大小不少于minCapacity,如果buf的空间不够,则调用grow()方法来扩展空间。
private void ensureCapacity(int minCapacity) {
if (minCapacity - buf.length > 0)
grow(minCapacity);
} //这个方法的实现值得我们思考一些问题:数组空间不够了,需要扩展,该如何扩展呢?
//我们可能会这样做:既然你需要minCapacity这么多,那就扩展这么多吧。这里没有这么做,如果这样做,那当用户说我还需要一个字节的空间,那我们就又要在扩展一次,而每一次扩展,都会很耗时。
//耗时的原因是扩展的方式,本人猜测应该是这么扩展(不确定):重新申请更大的一块内存,然后把原数组的内容拷贝过去。若真如此,那确实会很耗时。
//这里的策略是:先把原数组的大小通过左移运算扩展为2倍,若这样还不够,那再把大小改为你需要的大小minCapacity。
//注意:左移运算可能会溢出,使得数组大小变为负数,如果存在溢出,则将其改为Integer.MAX_VALUE。这样的大小是肯定够的,如果这样还不够,那么你传入的minCapacity参数一定有问题
private void grow(int minCapacity) {
int oldCapacity = buf.length;
int newCapacity = oldCapacity << 1;
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity < 0) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
newCapacity = Integer.MAX_VALUE;
}
//确定扩展后的数组大小后,通过调用Arrays.copyOf来复制数组,大家可以去研究看是否是先申请更大的一块内存,然后在拷贝。
buf = Arrays.copyOf(buf, newCapacity);
} //这里实现了父类的抽象方法,从它可以看出,输出流的内容都到了这个类在堆中申请的内存中了,己buf数组。
//现在也可以回答另外一个问题:对于字节流为何传入int型参数。
//首先,无论用户传入何种类型参数,我们都强制转换为byte类型。这样可以方便用户,因为它不需要自己实现强制类型转换
//举例:int a = 10; write((byte)a);
//要求用户传入byte类型时,用户需要自己做强制类型转换,但现在我们帮用户做了,岂不方便?
//这样一来,用户在使用时必须注意这一点:这是字节输出流,如果传入short、char或int等,只把它当作byte处理。
public synchronized void write(int b) {
ensureCapacity(count + 1);
buf[count] = (byte) b;
count += 1;
} //override了父类的方法,把byte b[]中从off开始的len个字节复制到了buf的后面,同时count增加了len
public synchronized void write(byte b[], int off, int len) {
if ((off < 0) || (off > b.length) || (len < 0) ||
((off + len) - b.length > 0)) {
throw new IndexOutOfBoundsException();
}
ensureCapacity(count + len);
System.arraycopy(b, off, buf, count, len);
count += len;
} //调用此方法,则用户可以把buf中的全部内容输出到用户传入的输出流中
public synchronized void writeTo(OutputStream out) throws IOException {
out.write(buf, 0, count);
} public synchronized void reset() {
count = 0;
} //调用此方法,则用户可以得到一个byte数组,其内容为buf中的全部内容
public synchronized byte toByteArray()[] {
return Arrays.copyOf(buf, count);
} public synchronized int size() {
return count;
} public synchronized String toString() {
return new String(buf, 0, count);
} public synchronized String toString(String charsetName)
throws UnsupportedEncodingException
{
return new String(buf, 0, count, charsetName);
} @Deprecated
public synchronized String toString(int hibyte) {
return new String(buf, hibyte, 0, count);
} //个人认为,此方法既然与父类一样为空,但又写一遍是否多余?为何不像flush方法一样,在这里省去不写
public void close() throws IOException {
}
}

FileOutputStream

  这个类比较复杂,其中还包含nio包中的内容,因此我只看明白了其中一小部分:它是节点流;我们用它来写文件很方便。

package java.io;
import java.nio.channels.FileChannel;
import sun.nio.ch.FileChannelImpl;
import sun.misc.IoTrace;
public class FileOutputStream extends OutputStream{
public FileOutputStream(String name) throws FileNotFoundException {
this(name != null ? new File(name) : null, false);
} public FileOutputStream(String name, boolean append)
throws FileNotFoundException
{
this(name != null ? new File(name) : null, append);
}
public FileOutputStream(File file) throws FileNotFoundException {
this(file, false);
} //构造方法一共5个,但实质上只有两个,这是其中一个,另一个是public FileOutputStream(FileDescriptor fdObj),但我都看不懂
//只说我理解的比较简单的东西:当我们写文件时,我们会选择这个类,原因就是它提供了方法使我们方便地写文件
//它的构造方法--我们可以直接传入一个File对象,或者代表文件pathName的String,我们就可以指明输出流的目标是哪个文件了
//其中,append表示是否以追加方式写文件,默认为false,则会覆盖之前文件中的内容
public FileOutputStream(File file, boolean append)
throws FileNotFoundException
{
... ...
} //这是必须实现父类的那个方法,我们看不到具体实现,因为它是native方法
//我们选择这个类操作文件的另一个原因是:这个方法的实现细节一定包含相关的文件操作命令,而其它类不具备这个方法,则不能把流写到文件中
private native void write(int b, boolean append) throws IOException;
}

FilterOutputStream

  它不是节点流,与父类主要差别就是它多了个成员变量。我们一般不会使用这个类,它是另外三个节点输出流的父类。理解它很简单:它什么活也不干,都交给传入的out去做。

package java.io;
public class FilterOutputStream extends OutputStream {
//此成员变量非常重要,基本上这个类和其父类OutputStream的最主要差别就是它有这个成员变量
//注意到权限为protected,因此在子类中可以直接使用
protected OutputStream out; //构造方法,传入OutputStream子类对象后,基本上该FilterOutputStream做的事情,它全交给这个传入的对象去做
public FilterOutputStream(OutputStream out) {
this.out = out;
}
//我们一般从这个方法中就能看到节点输出流的目的地,这里它并没有真正实现,只是调用了传入的out去做,所以FilterOutputStream不是节点流
public void write(int b) throws IOException {
out.write(b);
}
//表面上调用了下面的write方法,最终还是调用了out的write方法
public void write(byte b[]) throws IOException {
write(b, 0, b.length);
}
//间接调用out的write方法,以字节为单位地输出
//这里对传入的参数的判断比较有意思,虽然对参数的要求与OutputStream对应方法对参数的要求一致,但形式确不一样了
//我的理解:四个量是或的关系,若有一个为负,则最高位必定为1,则最终结果一定为负,因此要求都不能为负
public void write(byte b[], int off, int len) throws IOException {
if ((off | len | (b.length - (len + off)) | (off + len)) < 0)
throw new IndexOutOfBoundsException(); for (int i = 0 ; i < len ; i++) {
write(b[off + i]);
}
}
//自己不做,交给out去flush
public void flush() throws IOException {
out.flush();
}
//自己不做,交给out去close,但是关闭前先调用了flush方法
public void close() throws IOException {
try {
flush();
} catch (IOException ignored) {
}
out.close();
}
}

BufferedOutputStream

  它是处理流,有个缓冲数组,能起到缓冲作用,似乎缓冲很有用,详细就不懂了

package java.io;
public class BufferedOutputStream extends FilterOutputStream {
//这个类的核心就是这个buf,会将要输出的内容先存在这个数组里,当这个数组满之后再一次全部输出,当然未满是也可以主动输出
//这个buf似乎与ByteArrayOutputStream有些像,但还是有差别:这个buf大小固定后不会再扩展空间
protected byte buf[];
protected int count;
public BufferedOutputStream(OutputStream out) {
this(out, 8192);
} //此构造方法需要传入OutputStream实例,可以设置buf大小,默认为8192字节
public BufferedOutputStream(OutputStream out, int size) {
super(out);
if (size <= 0) {
throw new IllegalArgumentException("Buffer size <= 0");
}
buf = new byte[size];
} //private方法,将buf中缓存的内容全部输出
private void flushBuffer() throws IOException {
if (count > 0) {
out.write(buf, 0, count);
count = 0;
}
}
//这个写方法根本没有写,只是把要写的内容先存到了buf中。如果buf已经满了,那才会先把buf内容输出,然后再向buf里写
public synchronized void write(int b) throws IOException {
if (count >= buf.length) {
flushBuffer();
}
buf[count++] = (byte)b;
}
public synchronized void write(byte b[], int off, int len) throws IOException {
//如果要写入的字节数len比buf的长度还大,那就不需要缓冲了,直接调用out的write方法写就可以
if (len >= buf.length) {
flushBuffer();
out.write(b, off, len);
return;
}
//如果buf剩余的空间比len小,那就先输出buf内容,腾出空间后再写
if (len > buf.length - count) {
flushBuffer();
}
System.arraycopy(b, off, buf, count, len);
count += len;
}
//用户需要调用此方法才能实现真正的输出,但是不要每次调用write都紧接着调用flush,那就失去了缓冲的意义了
//另:在close时,父类FilterOutputStream会调用flush方法的,不用担心,所以你如果调用close的话,该输出的都会输出
public synchronized void flush() throws IOException {
flushBuffer();
out.flush();
}
}

DataOutputStream

  处理流,提供了多个很常用的方法。

package java.io;
public class DataOutputStream extends FilterOutputStream implements DataOutput {
//这个written参数会不断地累加,但有什么意义没弄明白
protected int written;
private byte[] bytearr = null; public DataOutputStream(OutputStream out) {
super(out);
} private void incCount(int value) {
int temp = written + value;
if (temp < 0) {
temp = Integer.MAX_VALUE;
}
written = temp;
} public synchronized void write(int b) throws IOException {
out.write(b);
incCount(1);
} public synchronized void write(byte b[], int off, int len)
throws IOException
{
out.write(b, off, len);
incCount(len);
} public void flush() throws IOException {
out.flush();
} //这个类的核心就是为我们提供了类似writeBoolean这样的方法,我们可以方便地把这些常见类型转为字节并输出,因为这是字节流
public final void writeBoolean(boolean v) throws IOException {
out.write(v ? 1 : 0);
incCount(1);
} //直接输出
public final void writeByte(int v) throws IOException {
out.write(v);
incCount(1);
} //short占两个字节,那么就先把高字节输出,再把低字节输出
//>>>表示无符号右移,右移8位后在与0xFF做与运算,则可保证此int值的更高位为零,也就是只保留了原int的8-15位
public final void writeShort(int v) throws IOException {
out.write((v >>> 8) & 0xFF);
out.write((v >>> 0) & 0xFF);
incCount(2);
} //与writeShort完全一致,我的理解是这样在使用时名称很形象
public final void writeChar(int v) throws IOException {
out.write((v >>> 8) & 0xFF);
out.write((v >>> 0) & 0xFF);
incCount(2);
} //先高字节内容,后低字节
public final void writeInt(int v) throws IOException {
out.write((v >>> 24) & 0xFF);
out.write((v >>> 16) & 0xFF);
out.write((v >>> 8) & 0xFF);
out.write((v >>> 0) & 0xFF);
incCount(4);
} private byte writeBuffer[] = new byte[8]; //这里没有像之前一个字节一个字节地写,而是先存到writeBuffer中,可能是觉得这样更好,怎么个好法,不懂
public final void writeLong(long v) throws IOException {
writeBuffer[0] = (byte)(v >>> 56);
writeBuffer[1] = (byte)(v >>> 48);
writeBuffer[2] = (byte)(v >>> 40);
writeBuffer[3] = (byte)(v >>> 32);
writeBuffer[4] = (byte)(v >>> 24);
writeBuffer[5] = (byte)(v >>> 16);
writeBuffer[6] = (byte)(v >>> 8);
writeBuffer[7] = (byte)(v >>> 0);
out.write(writeBuffer, 0, 8);
incCount(8);
} //float和int型都占用4个字节,因此对float转为对应的int字节流,再调用writeInt
//Float.floatToIntBits(v)这个方法的实现可能与IEEE规范中关于浮点数规范有关
public final void writeFloat(float v) throws IOException {
writeInt(Float.floatToIntBits(v));
} //double和long型都占用8个字节,因此对double转为对应的long字节流,再调用writeLong
//Double.doubleToLongBits(v)这个方法的实现可能与IEEE规范中关于浮点数规范有关
public final void writeDouble(double v) throws IOException {
writeLong(Double.doubleToLongBits(v));
} //还可以byte处理字符串
public final void writeBytes(String s) throws IOException {
int len = s.length();
for (int i = 0 ; i < len ; i++) {
out.write((byte)s.charAt(i));
}
incCount(len);
} //还可以char处理字符串
public final void writeChars(String s) throws IOException {
int len = s.length();
for (int i = 0 ; i < len ; i++) {
int v = s.charAt(i);
out.write((v >>> 8) & 0xFF);
out.write((v >>> 0) & 0xFF);
}
incCount(len * 2);
} public final void writeUTF(String str) throws IOException {
writeUTF(str, this);
} //可以处理utf-8,这个方法很常用,但其实现还需仔细学习
static int writeUTF(String str, DataOutput out) throws IOException {
int strlen = str.length();
int utflen = 0;
int c, count = 0; /* use charAt instead of copying String to char array */
for (int i = 0; i < strlen; i++) {
c = str.charAt(i);
if ((c >= 0x0001) && (c <= 0x007F)) {
utflen++;
} else if (c > 0x07FF) {
utflen += 3;
} else {
utflen += 2;
}
} if (utflen > 65535)
throw new UTFDataFormatException(
"encoded string too long: " + utflen + " bytes"); byte[] bytearr = null;
if (out instanceof DataOutputStream) {
DataOutputStream dos = (DataOutputStream)out;
if(dos.bytearr == null || (dos.bytearr.length < (utflen+2)))
dos.bytearr = new byte[(utflen*2) + 2];
bytearr = dos.bytearr;
} else {
bytearr = new byte[utflen+2];
} bytearr[count++] = (byte) ((utflen >>> 8) & 0xFF);
bytearr[count++] = (byte) ((utflen >>> 0) & 0xFF); int i=0;
for (i=0; i<strlen; i++) {
c = str.charAt(i);
if (!((c >= 0x0001) && (c <= 0x007F))) break;
bytearr[count++] = (byte) c;
} for (;i < strlen; i++){
c = str.charAt(i);
if ((c >= 0x0001) && (c <= 0x007F)) {
bytearr[count++] = (byte) c; } else if (c > 0x07FF) {
bytearr[count++] = (byte) (0xE0 | ((c >> 12) & 0x0F));
bytearr[count++] = (byte) (0x80 | ((c >> 6) & 0x3F));
bytearr[count++] = (byte) (0x80 | ((c >> 0) & 0x3F));
} else {
bytearr[count++] = (byte) (0xC0 | ((c >> 6) & 0x1F));
bytearr[count++] = (byte) (0x80 | ((c >> 0) & 0x3F));
}
}
out.write(bytearr, 0, utflen+2);
return utflen + 2;
} //不知道这个方法有什么用
public final int size() {
return written;
}
}

OutputStream类详解的更多相关文章

  1. URLConnection类详解-转

    转-http://www.cnblogs.com/shijiaqi1066/p/3753224.html 1. URLConnection概述 URLConnection是一个抽象类,表示指向URL指 ...

  2. java之StringBuffer类详解

    StringBuffer 线程安全的可变字符序列. StringBuffer源码分析(JDK1.6): public final class StringBuffer extends Abstract ...

  3. java之AbstractStringBuilder类详解

    目录 AbstractStringBuilder类 字段 构造器 方法   public abstract String toString() 扩充容量 void  expandCapacity(in ...

  4. java之StringBuilder类详解

    StringBuilder 非线程安全的可变字符序列 .该类被设计用作StringBuffer的一个简易替换,用在字符串缓冲区被单个线程使用的时候(这种情况很普遍).如果可能,建议优先采用该类,因为在 ...

  5. Java String类详解

    Java String类详解 Java字符串类(java.lang.String)是Java中使用最多的类,也是最为特殊的一个类,很多时候,我们对它既熟悉又陌生. 类结构: public final ...

  6. QAction类详解:

    先贴一段描述:Qt文档原文: Detailed Description The QAction class provides an abstract user interface action tha ...

  7. JAVAEE学习——struts2_01:简介、搭建、架构、配置、action类详解和练习:客户列表

    一.struts2是什么 1.概念 2.struts2使用优势以及历史 二.搭建struts2框架 1.导包 (解压缩)struts2-blank.war就会看到 2.书写Action类 public ...

  8. Struts2-整理笔记(二)常量配置、动态方法调用、Action类详解

    1.修改struts2常量配置(3种) 第一种 在str/struts.xml中添加constant标签 <struts> <!-- 如果使用使用动态方法调用和include冲突 - ...

  9. C# 内置 DateTime类详解

    C# 内置 DateTime类详解 摘抄自微软官方文档,用来方便自己查阅:网址:https://msdn.microsoft.com/zh-cn/library/system.datetime(v=v ...

随机推荐

  1. Python使用心得之魔法参数**kw

    通过设置字典类型参数直接传递给接收魔法参数(**kw)的方法,对应赋值.示例如下: jdbcConnectionDict = {, , 'database': 'test', 'charset': ' ...

  2. ksum问题

    2sum: Given an array of integers, return indices of the two numbers such that they add up to a speci ...

  3. python selenium2示例 - 生成 HTMLTestRunner 测试报告

    前言 在python selenium2自动化测试过程中,一个合适的报告是必须的,而HTMLTestRunner模块为我们提供了一个很好的报告生成功能. 什么是HTMLTestRunner HTMLT ...

  4. VS 2017开发ASP.NET Core Web应用过程中发现的一个重大Bug

    今天试着用VS 2017去开发一个.net core项目,想着看看.net core的开发和MVC5开发有什么区别,然后从中发现了一个VS2017的Bug. 首先,我们新建项目,ASP.NET Cor ...

  5. 使用HTML5的canvas做图片剪裁

    前言 图片裁剪上传,不仅是一个很贴合用户体验的功能,还能够统一特定图片尺寸,优化网站排版,一箭双雕. 需求就是那么简单,在浏览器里裁剪图片并上传到服务器. 我第一个想到的方法就是,将图片和裁剪参数(x ...

  6. Struts2基础学习(一)—初识Struts2

      目录 一.什么是Struts2 二.搭建Struts2的开发环境 三.Struts2的配置文件 四.MVC模式 一.什么是Struts2      Struts2是一个非常优秀的MVC框架,由传统 ...

  7. JavaScript基础学习(三)—数组

    一.数组简介     JavaScript数组的每一项都可以保存任何类型的数据,也就是说数组的第一个位置保存字符串,第二个位置可以保存数值,第三个位置可以保存对象,而且数组的大小是可以动态调整的,即可 ...

  8. Python:generator的send()方法流程分析

    先来一个简单地例子: def foo(): print('starting') while True: r = yield 2 print(r) f = foo() print(f.send(None ...

  9. [转]html中offsetTop、clientTop、scrollTop、offsetTop各属性介绍

    HTML精确定位:scrollLeft,scrollWidth,clientWidth,offsetWidth scrollHeight: 获取对象的滚动高度. scrollLeft:设置或获取位于对 ...

  10. 对MySQL数据量日益增长产生的一点小想法

    最近一直在想一个问题 MySQL数据量日益庞大,目前单表总记录数有 300W+,导致sql语句执行的速度变慢,如果一直这样增长下去,总有一天会爆炸的.怎么办??怎么办?? 第一:想到的必然是 添加索引 ...