Java IO源码分析(二)——ByteArrayInputStream 和 ByteArrayOutputStream
简介
ByteArrayInputStream 是字节数组输入流,它继承于InputStream。
它的内部数据存储结构就是字节数组。
ByteArrayOutputStream是字节数组输出流,它继承于OutputStream。
它的内部数据存储结构也是字节数组。
源码分析
InputStream
在分析ByteArrayInputStream之前,应该先看InputStream,父类InputStream是ByteArrayInputStream的父类,主要实现了读取和跳跃的方法。
public abstract class InputStream implements Closeable {
// 最大可跳过的字节数
private static final int MAX_SKIP_BUFFER_SIZE = 2048;
// 向后读取一个字节
public abstract int read() throws IOException;
// 将字节流中的数据装到字节数组的0位开始的位置
public int read(byte b[]) throws IOException {
return read(b, 0, b.length);
}
// 将字节流中的数据装到字节数组的指定位置当中
public int read(byte b[], int off, int len) throws IOException {
if (b == null) {
throw new NullPointerException();
} else if (off < 0 || len < 0 || len > b.length - off) {
throw new IndexOutOfBoundsException();
} else if (len == 0) {
return 0;
}
int c = read();
if (c == -1) {
return -1;
}
b[off] = (byte)c;
int i = 1;
try {
for (; i < len ; i++) {
c = read();
if (c == -1) {
break;
}
b[off + i] = (byte)c;
}
} catch (IOException ee) {
}
return i;
}
// 跳过输入流中的n个字节
public long skip(long n) throws IOException {
long remaining = n;
int nr;
if (n <= 0) {
return 0;
}
int size = (int)Math.min(MAX_SKIP_BUFFER_SIZE, remaining);
byte[] skipBuffer = new byte[size];
while (remaining > 0) {
nr = read(skipBuffer, 0, (int)Math.min(size, remaining));
if (nr < 0) {
break;
}
remaining -= nr;
}
return n - remaining;
}
// 是否还有
public int available() throws IOException {
return 0;
}
// 关闭流
public void close() throws IOException {}
// 标记
public synchronized void mark(int readlimit) {}
// 重置
public synchronized void reset() throws IOException {
throw new IOException("mark/reset not supported");
}
// 是否支持标记方法
public boolean markSupported() {
return false;
}
}
ByteArrayInputStream
public
class ByteArrayInputStream extends InputStream {
// 字节数组,存储数据
protected byte buf[];
// 记录当前可读的第一个位置
protected int pos;
// 标记的位置
protected int mark = 0;
// 数据最大的可读长度
protected int count;
// 初始化字节流数组
public ByteArrayInputStream(byte buf[]) {
this.buf = buf;
this.pos = 0;
this.count = buf.length;
}
// 初始化字节流,填入字节数组的指定位置
public ByteArrayInputStream(byte buf[], int offset, int length) {
this.buf = buf;
this.pos = offset;
// 设置最大可读长度,数组的长度比设置的长度还短,说明传入的长度有问题,就设置为数组的长度
this.count = Math.min(offset + length, buf.length);
this.mark = offset;
}
// 读取单个字节
public synchronized int read() {
// 这里& 0xff的操作是为了只取低八位
return (pos < count) ? (buf[pos++] & 0xff) : -1;
}
// 读取数据到数组的指定位置
public synchronized int read(byte b[], int off, int len) {
// 边界判断
if (b == null) {
throw new NullPointerException();
} else if (off < 0 || len < 0 || len > b.length - off) {
throw new IndexOutOfBoundsException();
}
// 读完了
if (pos >= count) {
return -1;
}
// 剩余长度
int avail = count - pos;
// 如果要求读的长度大于剩余长度,就设置读的长度为剩余长度
if (len > avail) {
len = avail;
}
if (len <= 0) {
return 0;
}
// 底层采用的
System.arraycopy(buf, pos, b, off, len);
pos += len;
return len;
}
// 跳过指定长度的字节
public synchronized long skip(long n) {
long k = count - pos;
if (n < k) {
// 如果是负数那么就不动
// 取n和k最小的一个
k = n < 0 ? 0 : n;
}
// 移动k位
pos += k;
return k;
}
// 是否还有数据可以读
public synchronized int available() {
return count - pos;
}
// 是否支持标记功能
public boolean markSupported() {
return true;
}
// 标记当前位置,这个传入参数是个摆设
public void mark(int readAheadLimit) {
mark = pos;
}
// 重置,也就是将当前指针指向之前mark的位置
public synchronized void reset() {
pos = mark;
}
// 关闭字节流
public void close() throws IOException {
}
}
OutputStream
OutputStream是ByteArrayOutputStream的父类,先看看它的源码。
很短,实现了Closeable, Flushable。
public abstract class OutputStream implements Closeable, Flushable {
public abstract void write(int b) throws IOException;
public void write(byte b[]) throws IOException {
write(b, 0, b.length);
}
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]);
}
}
public void flush() throws IOException {
}
public void close() throws IOException {
}
}
ByteArrayOutputStream
public class ByteArrayOutputStream extends OutputStream {
// 存储数据的字节数组
protected byte buf[];
// 数组长度
protected int count;
// 默认构造,默认大小是32
public ByteArrayOutputStream() {
this(32);
}
// 初始化长度的构造
public ByteArrayOutputStream(int size) {
if (size < 0) {
throw new IllegalArgumentException("Negative initial size: "
+ size);
}
// 初始化一个数组对象
buf = new byte[size];
}
// 查看是否需要扩容
private void ensureCapacity(int minCapacity) {
// overflow-conscious code
if (minCapacity - buf.length > 0)
grow(minCapacity);
}
// 数组的最大长度
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
// 扩容
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = buf.length;
// 默认先扩容两倍
int newCapacity = oldCapacity << 1;
// 如果还是不够就将容量扩到需求的大小
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
// 边界判断
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// 将旧数组复制到新数组
buf = Arrays.copyOf(buf, newCapacity);
}
// 边界判断
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
// 写入单个字节
public synchronized void write(int b) {
// 先判断是否需要扩容
ensureCapacity(count + 1);
// 写入字节
buf[count] = (byte) b;
// 增加可用长度
count += 1;
}
// 写入字节数组
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;
}
// 将当前Stream输出到指定的Streamz中
public synchronized void writeTo(OutputStream out) throws IOException {
// 直接将可用长度的数组写入
out.write(buf, 0, count);
}
// 重置
public synchronized void reset() {
count = 0;
}
// 转化为字符数组
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);
}
public void close() throws IOException {
}
}
总结
输入输出流的本质就是一个中间缓存器,暂时将数据放在中间的缓存区,然后根据指定要求输出或如输入。
ByteArrayInputStream 特点
- 数组实现中间缓存;
- 修改和读取操作都是线程安全了,因为加了synchronized;
- 具有标记回读的功能,就是可以先读后面的数据,然后经过重置,再去读前面标记位置的数据。
ByteArrayOutputStream特点
- 数组实现中间缓存;
- 修改读取也是具有线程安全的;
- 具有扩容功能,应为想要写入的数据是增长的,在写入之前,就会进行依次扩容判断;
- 默认的初始大小是32,如果一个个数据写入的扩容,每次是扩一倍的大小;
- 可以写入到其他的输出流上。
Java IO源码分析(二)——ByteArrayInputStream 和 ByteArrayOutputStream的更多相关文章
- Java IO源码分析(三)——PipedOutputStream和PipedInputStream
简介 PipedOutputStream和PipedInputStream主要用于线程之间的通信 .二者必须配合使用,也就是一段写入,另一端接收.本质上也是一个中间缓存区,讲数据缓存在PipedInp ...
- 多线程之美8一 AbstractQueuedSynchronizer源码分析<二>
目录 AQS的源码分析 该篇主要分析AQS的ConditionObject,是AQS的内部类,实现等待通知机制. 1.条件队列 条件队列与AQS中的同步队列有所不同,结构图如下: 两者区别: 1.链表 ...
- java io 源码研究记录(一)
Java IO 源码研究: 一.输入流 1 基类 InputStream 简介: 这是Java中所有输入流的基类,它是一个抽象类,下面我们简单来了解一下它的基本方法和抽象方法. 基本方法: publ ...
- 框架-springmvc源码分析(二)
框架-springmvc源码分析(二) 参考: http://www.cnblogs.com/leftthen/p/5207787.html http://www.cnblogs.com/leftth ...
- java集合源码分析(三):ArrayList
概述 在前文:java集合源码分析(二):List与AbstractList 和 java集合源码分析(一):Collection 与 AbstractCollection 中,我们大致了解了从 Co ...
- java集合源码分析(六):HashMap
概述 HashMap 是 Map 接口下一个线程不安全的,基于哈希表的实现类.由于他解决哈希冲突的方式是分离链表法,也就是拉链法,因此他的数据结构是数组+链表,在 JDK8 以后,当哈希冲突严重时,H ...
- Fresco 源码分析(二) Fresco客户端与服务端交互(1) 解决遗留的Q1问题
4.2 Fresco客户端与服务端的交互(一) 解决Q1问题 从这篇博客开始,我们开始讨论客户端与服务端是如何交互的,这个交互的入口,我们从Q1问题入手(博客按照这样的问题入手,是因为当时我也是从这里 ...
- Java Reference 源码分析
@(Java)[Reference] Java Reference 源码分析 Reference对象封装了其它对象的引用,可以和普通的对象一样操作,在一定的限制条件下,支持和垃圾收集器的交互.即可以使 ...
- Java 集合源码分析(一)HashMap
目录 Java 集合源码分析(一)HashMap 1. 概要 2. JDK 7 的 HashMap 3. JDK 1.8 的 HashMap 4. Hashtable 5. JDK 1.7 的 Con ...
随机推荐
- Ubuntu 12.10设置root用户登录图形界面
Ubuntu 12.04默认是不允许root登录的,在登录窗口只能看到普通用户和访客登录.以普通身份登陆Ubuntu后我们需要做一些修改,普通用户登录后,修改系统配置文件需要切换到超级用户模式,在终端 ...
- Linux_CentOS 7下Nginx服务器的安装配置
1.安装 1.1 配置epel yum 源 wget http://dl.Fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm rpm ...
- linux执行cmd之一
执行方法: 1.手动执行 2.程序执行 涉及到的权限问题: 1.应用程序的权限 2.被执行文件的权限
- tomcat安装证书https
操作步骤(阿里云官网) 解压已下载保存到本地的Tomcat证书文件. 解压后您将看到文件夹中有2个文件,您可为两个证书文件重命名. 证书文件(domain name.pfx):以.pfx为后缀或文件类 ...
- java开发两年!这些异常处理的方式你得知道,不然你凭什么涨薪!
前言 异常是在程序中导致程序中断运行的一种指令流,当异常发生时,程序将直接中断,不再执行后续的任何操作! 示例:两数相除,若不处理任何异常,则只有在正确输入两个数字时,才能显示出运算结果. publi ...
- 2021版的思维导图MindManager 安装激活以及换机教程
思维导图软件MindManager更新版本啦,相信细心的小伙伴应该已经发现了,MindManager最新版的名称变成了MindManager Windows 21,并且更新了很多新功能,点击查看Min ...
- 巧妙运用Camtasia 旅行Vlog轻松get
旅行时,除了要欣赏当地的美丽风景.享受当地美食外,当然还要将旅行中的各种小细节记录下来.以前我们可能更多地使用相机拍照,现在呢,越来越多的人采用视频拍摄的方式制作Vlog.这种兼具影像与叙事的视频表现 ...
- 【干货】linux使用nginx一个80端口部署多个项目(spring boot、vue、nuxt、微信小程序)
本人只有一个阿里云的ip和一个已经解析过的域名,然后想用80端口部署多个项目,比如输入: www.a.com和www.b.com与www.c.com就能访问不同项目,而不用输入不同端口号区分. 1.这 ...
- maven打包时报No compiler is provided in this environment处理
系统:macOS 开发工具:Idea 问题描述:在idea中执行mvn clean install时报No compiler is provided in this environment. Perh ...
- 编程C语言进阶篇——自定义数据类型:共同体
什么是"自定义数据类型"?顾名思义,就是用户可以随时在程序中自行定义新的数据类型.自定义数据类型时需要设置数据类型的名称及其成员.数据类型成员各属性的设置方法等同于变量设置时相应属 ...