Java_io体系之ByteArrayInputStream、ByteArrayOutputStream简介、走进源码及示例——04
一:ByteArrayInputStream
1、 类功能简介:
字节数组输入流:此流中内部包含一个缓存字节数组做为缓存区、该缓存字节数组中的字节组成了供ByteArrayInputStream读取到程序中的流。既然要将缓存字节数组中的
字节按顺序读取到程序中、那么就要有变量来记录当前读取的位置、和字节中的总数、这两个变量后面会有介绍、还有就是此流支持mark方法。并且还有一个要注意的地方就
是、调用close()方法对此流无效、当你调用过close()方法之后、ByteArrayInputStream中的其他方法仍然可以正常调用、并且此流不会产生IOException。
2、 ByteArrayInputStream API简介:
A:关键字段
protected byte[] buf; 内置缓存数组
protected int pos; buf中下一个被读取的字节下标
protected int count buf中有效可读字节总数
protected int mark=0 标记当前流读取下一个字节的位置、默认是起始下标。
B:构造方法
ByteArrayInputStream(byte[] buf); 使用传入的整个buf字节数组作为bos的缓存字节数组供读取
ByteArrayInputStream(byte[] buf, int off, int len); 使用从下标off开始len个字节作为bos的缓存字节数组供读取
C:一般方法
synchronized int available(); 当前流中有效可供读取的字节数。
void close(); 关闭当前流
boolean markSupport(); 当前流是否支持mark
void mark(int readlimit); 标记当前流读取的位置
synchronized int read(); 读取buf中下一个字节
synchronized int read(byte[] b, int offset, int len) 将buf中字节读取到下标从offset开始len个字节的字节数组中
synchronized reset(); 将当前流的读取位置重置到最后一次调用mark方法时的位置
synchronized long skip(long n); 跳过bos中n个字节
3、源码分析:
/**
* 一个字节数组包含一个内置缓存数组、该缓存数组中的字段就是供ByteArrayInputStream读取到程序中的流。
*/
public class ByteArrayInputStream extends InputStream {
/**
* 构造方法传入的一个字节数组、作为数据源、read()读取的第一个字节应该是
* 这个数组的第一个字节、即buf[0]、下一个是buf[pos];
*/
protected byte buf[];
/**
* 下一个从输入流缓冲区中读取的字节的下标、不能为负数、
* 下一个读入的字节应该是buf[pos];
*/
protected int pos;
/**
* 当前输入流的当前的标记位置、默认是开始、即下标是0.
*/
protected int mark = 0;
/**
* 注意:这里count不代表buf中可读字节总数!
*
* 此值应该始终是非负数,并且不应大于 buf 的长度。
* 它比 buf 中最后一个可从输入流缓冲区中读取的字节位置大一。
*/
protected int count;
/**
* 创建一个使用buf[]作为其缓冲区数组的ByteArrayInputStream、
* 这个buf[]不是复制来的、并且给pos、count赋初始值。
* 传入的是作为源的字节数组。
* 赋给的是作为缓存区的字节数组。
*/
public ByteArrayInputStream(byte buf[]) {
this.buf = buf;
this.pos = 0;
//初始化buf的长度
this.count = buf.length;
}
/**
* 创建一个使用buf[]的起始下标offset,长度为length的字节数组作为其缓冲区数组的ByteArrayInputStream、
* 这个buf[]不是复制来的、并且给pos、count赋初始值。
* 传入的是作为源的字节数组。
* 赋给的是作为缓存区的字节数组。
* 将偏移量设为offset
*/
public ByteArrayInputStream(byte buf[], int offset, int length) {
this.buf = buf;
this.pos = offset;
//初始化buf的长度、取min的意义在于剔除不合理参数、避免出现异常、
this.count = Math.min(offset + length, buf.length);
this.mark = offset;
}
/**
* 开始读取源字节数组中的字节、并将字节以整数形式返回
* 返回值在 0 到 255 之间、当读到最后一个的下一个时返回 -1.
*/
public synchronized int read() {
return (pos < count) ? (buf[pos++] & 0xff) : -1;
}
/**
* 从源字节数中下标为pos开始的len个字节读取到 b[]的从下标off开始长度为len的字节数组中。
* 同时将下一个将要被读取的字节下标设置成pos+len。这样当循环读取的时候就可以读取源中所有的字节。
* 返回读取字节的个数。
* 当读到最后一个的下一个时返回 -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();
}
//如果buf中没有字节可供读取、返回-1;
if (pos >= count) {
return -1;
}
//如果buf中剩余字节不够len个、返回count-pos;这个值是作为实际读取字节数返回的
if (pos + len > count) {
len = count - pos;
}
//如果传入的要求读取的字节数小于0、则返回0。
if (len <= 0) {
return 0;
}
//将buf 下标为pos的之后的字节数组复制到b中下标从b开始长度为len的数组中
System.arraycopy(buf, pos, b, off, len);
//设置下一个被读取字节的下标。
pos += len;
return len;
}
/**
* 返回实际跳过的字节个数、同时将pos后移 “ 实际跳跃的字节数” 位
* 下次再读的时候就从pos开始读取。那此时就会忽略被跳过的字节。
*/
public synchronized long skip(long n) {
//如果要跳过的字节数大于剩余字节数、那么实际跳过的字节数就是剩余字节数
if (pos + n > count) {
n = count - pos;
}
if (n < 0) {
return 0;
}
//最后设置偏移量、即下一个被读取字节的下标。
pos += n;
return n;
}
/**
* 返回当前字节数组输入流实际上还有多少能被读取的字节个数。
*/
public synchronized int available() {
return count - pos;
}
/**
* 查看此输入流是否支持mark。
*/
public boolean markSupported() {
return true;
}
/**
* 设置当前流的mark位置、
* 值得注意的是 readAheadLimit这个参数无效
* 只是把pos的当前值传给mark。
* mark取决于创建ByteArrayStream时传入的byte[]b, int offset ...中的offset、若未使用这个构造方法、则取默认值0。
*/
public void mark(int readAheadLimit) {
mark = pos;
}
/**
* 将mark的值赋给pos、使流可以从此处继续读取。下次读取的时候还是从pos下标开始读取
* 也就是继续接着上次被mark的地方进行读取。
*/
public synchronized void reset() {
pos = mark;
}
/**
* 关闭此流、释放所有与此流有关的资源。调用此方法没有任何效果
*/
public void close() throws IOException {
}
}
4、实例演示:
package com.chy.io.original.test;
import java.io.ByteArrayInputStream;
public class ByteArrayInputStreamTest {
private static byte[] byteArray = new byte[100];
static{
for(byte i = 0 ;i<100 ;i++){
byteArray[i] = i;
}
}
public static void test(){
//创建一个有字节缓存数组的bais、byteArray通过bais的构造方法传递给了ByteArrayInputStream缓存数组、所以此流的内容就是buteArray。
ByteArrayInputStream bais = new ByteArrayInputStream(byteArray);
//ByteArrayInputStream bais = new ByteArrayInputStream(byteArray, 0, byteArray.length);与上面效果相同。
//查看此流缓存字节数组中即byteArray中有效可供读取的字节数、 result: 100
int available = bais.available();
System.out.println("befor skip: " + available);
System.out.println("----------------------------------");
//跳过此流的换成字节数组前33个即从33开始读取、因为byte是从0开始的。
bais.skip(33);
//查看查看此流缓存字节数组有效可供读取的字节数 result: 67
available = bais.available();
System.out.println("after skip: " + bais.available());
System.out.println("----------------------------------");
//当有效可供读取的字节数大于33个时、读取下一个、并且将读取之后、当前缓存字节数组中有效字节数赋给上面的available
//result: 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66
while(33 < available){
System.out.print(bais.read() + " ");
available = bais.available();
}
System.out.println("\r\n" + "----------------------------------");
//如果ByteArrayInputStream不支持mark则退出、实际上我们从源码中已经知道此流的markSupported()一直返回的都是true
if(!bais.markSupported()){
return;
}
//此时缓存字节数组中还剩67--99这些字节未读取。共33个。
//result: 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99
//markB中传入的初始大小值bais.available() 是 33
bais.mark(98);
byte[] markB = new byte[bais.available()];
while((bais.read(markB, 0, markB.length)) != -1){
for(byte b : markB){
System.out.print(b + " ");
}
}
System.out.println("\r\n" + "----------------------------------");
//重置标记位置
//result:67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99
//resetB中传入的初始大小值bais.available() 是 33
bais.reset();
byte[] resetB = new byte[bais.available()];
while((bais.read(resetB, 0, resetB.length)) != -1){
for(byte b : resetB){
System.out.print(b + " ");
}
}
System.out.println("\r\n" + "----------------------------------");
}
/**
* 结果说明:
* 1、前面的都好理解、本质就是每读一个字节、ByteArrayInputStream的计数器就会加一、若有不理解就看下源码。
* 2、read() 是将从缓存字节数组中读到的字节以整数形式返回。
* 3、read(byte[] b, int off, int len) 是将缓存字节数组中最多len个字节读取出来放到b的off下标到off+len-1中去
* 所以我们对b的操作也就是对读取结果的操作。
* 3、mark函数的参数98是没有任何意义的、无论你传什么整数都一样。当你调用mark函数时、此流就会把当前缓存字节数组中下一个
* 即将被读取的字节的下标赋给mark、上面程序中就是 67、对应的字节值也是67。下面继续读取剩下的缓存字节数组中的字节值、打
* 印的就是从67 - 99
* 3、后面bais.reset()函数 、如果不调用这个函数直接循环打印、则没有任何输出结果、因为记录字节缓存数组的下标的值pos已经到字
* 节数组最后了、下一个已经没有了、但是当你调用 了basi.reset()函数之后、就会输出67 - 99、原因就是当调用reset函数时、此
* 流会把上面调用mark函数时mark所记录的当时的缓存字节数组下标重新赋给pos、所以输出结果依然是67 - 99 而非什么都没有。
*
*/
public static void main(String[] args) {
test();
}
}
二:ByteArrayOutputStream
1、 类功能简介:
字节数组输出流、用于将字节或者字节数组写入到ByteArrayOutputStream(以下简称bos)内置缓存字节数组buf中、这里buf可以看做是bos的目的地、与其他流不同的是
我们可以对buf操作、比如将它转换为String到程序中、或者直接作为一个字节数组将其写入到另一个低级字节数组输出流中、同样close()方法对此流也没有作用、当调用
bos.close()关闭此流时、bos的其他方法仍然能正常使用、并且不会抛出IOException异常。
2、 ByteArrayOutputStream API简介:
A:关键字段
protected byte[] buf; 内置缓存字节数组、用于存放写入的字节
protected int count; 缓存字节数组buf中的字节数
B:构造方法
ByteArrayOutputStream() 使用默认大小32位的buf创建bos
ByteArrayOutputStream(int size) 使用指定大小的buf创建bos
C:一般方法
void close(); 关闭此流、没有效果 synchronized void reset(); 清空buf synchronized int size(); 返回buf中现有字节个数 synchronized byte[] toByteArray(); 将buf读取到程序中 synchronized String toString(); 使用默认编码将buf转换成字符串 synchronized String toString(String charsetName); 使用指定编码将buf转换成字符串 synchronized void write(int b) 将一个整数表示的字节写入buf中 synchronized void write(byte[] b, int off, int len) 将一个下标从off开始的len个字节写入到buf中 synchronized void writeTo(OutputStream out); 将buf作为一个字节数组写入到另一个低级字节输出流中
3、 源码分析:
public class ByteArrayOutputStream extends OutputStream {
/**
* 存储字节数组的缓存字节数组、存放在内存中.
*/
protected byte buf[];
/**
* 缓存字节数组中有效字节数
*/
protected int count;
/**
* 创建一个带有初始大小为32字节的字节缓存字节数组的字节数组输出流。
* 当缓存字节数组存满时会自动增加一倍容量。
*/
public ByteArrayOutputStream() {
//默认缓存字节数组大小为32位。
this(32);
}
/**
* 创建一个指定初始大小的字节数组输出流。
* 参数不能为负。
*/
public ByteArrayOutputStream(int size) {
if (size < 0) {
throw new IllegalArgumentException("Negative initial size: "
+ size);
}
//将传入的size设为缓存字节数组的大小
buf = new byte[size];
}
/**
*将一个指定的字节写入到字节数组输出流中、存放在字节数组输出流的缓存字节数组即buf中。
*缓存字节数组满时会自动扩容。
*/
public synchronized void write(int b) {
int newcount = count + 1;
//当缓存字节数组存满时、自动增加一倍容量。
if (newcount > buf.length) {
buf = Arrays.copyOf(buf, Math.max(buf.length << 1, newcount));
}
buf[count] = (byte)b;
count = newcount;
}
/**
* 将一个指定的字节数组从下标off开始len长个字节写入到缓存字节数组中。
* 缓存字节数组满时会自动扩容。
*/
public synchronized void write(byte b[], int off, int len) {
if ((off < 0) || (off > b.length) || (len < 0) ||
((off + len) > b.length) || ((off + len) < 0)) {
throw new IndexOutOfBoundsException();
} else if (len == 0) {
return;
}
int newcount = count + len;
//当缓存字节数组存满时、自动增加一倍容量。
if (newcount > buf.length) {
buf = Arrays.copyOf(buf, Math.max(buf.length << 1, newcount));
}
System.arraycopy(b, off, buf, count, len);
count = newcount;
}
/**
* 调用另一个OutputStream 的 write(byte[] b, 0, count);方法、将此字节数组输出流的缓存字节数组中的全部字节写入到
* 另一个OutputStream指定的目的地中去。
*/
public synchronized void writeTo(OutputStream out) throws IOException {
out.write(buf, 0, count);
}
/**
* 重置指定字节写入缓存字节数组的位置、使后面写入的字节重新从缓存字节数组buf的下标0开始写入到缓存字节数组buf中。
*/
public synchronized void reset() {
count = 0;
}
/**
* 将此输出流的缓存字节数组转换成程序中指定代表此流中所有字节的字节数组。
*/
public synchronized byte[] toByteArray() {
return Arrays.copyOf(buf, count);
}
/**
* 返回此字节数组输出流的缓存字节数组的size、即其中的字节的个数
*/
public synchronized int size() {
return count;
}
/**
* 将当前缓存字节数组中所有字节转换成程序指定的表示他的String。
*/
public synchronized String toString() {
return new String(buf, 0, count);
}
/**
* 根据给定的编码将当前缓存字节数组中所有字节转换成程序指定的表示他的String。
*/
public synchronized String toString(String charsetName)
throws UnsupportedEncodingException
{
return new String(buf, 0, count, charsetName);
}
/**
* 关闭此流、没有什么用!
* 关闭之后方法还是能用!
*/
public void close() throws IOException {
}
}
4、 实例演示:
package com.chy.io.original.test;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
public class ByteArrayOutputStreamTest {
//对应String "abcdefghijklmnopqrstuvwxyz";
private static final byte[] byteArray = {
0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F,
0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A
};
public static void test() throws IOException{
//一般创建一个带有默认大小的缓存字节数组 buf 的字节输出流就可以了。
ByteArrayOutputStream baos = new ByteArrayOutputStream();
//字节数组输出流中的 buf 中有效的字节数、缓存字节数组是存在程序内存中的。
System.out.println(baos.size());
/**
* 会把int型97强转成byte类型写入 buf 中、baos.toString()是按照系统默认的编码将字节转换成字符串
* toString()的内部实现关键代码就是:return new String(buf, 0, count);
* result: a
*/
baos.write(97);
System.out.println(baos.toString());
/**
* 将byteArray的前四个字节添加到缓存字节数组中
* result: aabcd
*/
baos.write(byteArray, 0, 4);
System.out.println(baos.toString());
//将整个字节数组添加到缓存字节数组中去
baos.write(byteArray);
System.out.println(baos.toString());
//将baos中buf的字节转入到一个新的byte[]中去。核心代码:Arrays.copyOf(buf, count);
byte[] newByteArray = baos.toByteArray();
for(byte b : newByteArray){
System.out.println(String.valueOf(b));
}
/**
* baos.writeTo()方法的本质是调用传入的OutputStream实现类的write(byte[] b, 0, len);方法、
* 将baos现有的buf字节写入到实现类指定的目的地中。
* result: 在D盘有个名为 bytearrayoutputstream.txt的文件夹、里面有一段为“aabcdabcdefghijklmnopqrstuvwxyz”的内容。
* 至于为什么不是字节型、这与FileOutputStream的write(byte[] b, 0, len);有关、后面讨论。
*
*/
baos.writeTo(new FileOutputStream(new File("D:" + File.separator + "bytearrayoutputstream.txt")));
/**
* 将缓存数组清零
* result: 缓存字节数组清零前:31
* result: 缓存字节数组清零后:0
*/
System.out.println("缓存字节数组清零前:" + baos.size());
baos.reset();
System.out.println("缓存字节数组清零后:" + baos.size());
}
public static void main(String[] args) throws IOException {
test();
}
}
、将buf转换成字符串、将buf作为另一个流的写入的字节数组参数、清空buf、将buf读取到程序中等。