1、内存操作流的引入,为何需要内存操作流?
首先我们谈谈虚拟文件的概念。内存虚拟文件,把一块内存虚拟成一个硬盘上的文件,原来该写到硬盘文件上的内容会被写到这个内存中,原来该从一个硬盘上读取的内容可以改为从内存中直接读取。如果程序在运行过程中要产生一些临时文件,可以用虚拟文件的方式来实现。我们不用访问硬盘,而是直接访问内存,会提高应用程序的效率。
内存操作流:用于处理临时存储信息的,程序结束,数据就从内存中消失。
字节数组:
ByteArrayInputStream
ByteArrayOutputStream
字符数组:
CharArrayReader
CharArrayWriter
字符串:
StringReader
StringWriter
1.1 内存操作流的使用场景说明?
假设已经有一个写好的压缩函数,该函数接收2个参数:一个是输入流对象,一个是输出流对象,它从输入流对象中读取数据,并将压缩后的结果写入输出流对象。程序需要将一台计算机的屏幕图形通过网络不断传送到另外的计算机上,为了节省带宽,我们就需要对图像的像素数据进行压缩后,在通过网络发送出去的。如果没有内存虚拟文件,我们就必须先将一副屏幕图像的像素数据写入到硬盘上的一个临时文件,再以这个文件作为输入流对象去调用那个压缩函数,接着又从压缩函数生成的压缩文件中读取压缩后的数据,再通过网络发送出去,最后删除压缩前后所生成的两个临时文件。可见这样的效率是非常低的。
解决这种低效率的方案:
1.2 内存操作流的内存虚拟文件功能
JDK中提供了ByteArrayInputStream和ByteArrayOutputStream这两个类可实现类似内存虚拟文件的功能,我们将抓取到的计算机屏幕图像的所有像素数据保存在一个数组中,然后根据这个数组创建一个ByteArrayInputStream流对象,同时创建一个用于保存压缩结果的ByteArrayOutputStream流对象,将这两个对象作为参数传递给压缩函数,最后从ByteArrayOutputStream流对象中返回包含有压缩结果的数组。我们要在程序分配一个存储数据的内存块, 通常都用定义一个字节数组来实现的。
2 源码分析
2.1 ByteArrayInputStream源码分析
该类继承自InputStream 抽象类,它的内部缓冲区通过定义一个字节数组实现的。
package java.io;
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() {
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) {
k = n < 0 ? 0 : n;
}
pos += k;
return k;
}
public synchronized int available() {
return count - pos;
}
public boolean markSupported() {
return true;
}
public void mark(int readAheadLimit) {
mark = pos;
}
public synchronized void reset() {
pos = mark;
}
public void close() throws IOException {
}
}
2.2 ByteArrayOutputStream源码分析
该类继承自OutputStream 抽象类,它默认的构造分配的是32个字节元素空间,内存空间不足时动态扩展为原来的2倍。
package java.io;
import java.util.Arrays;
public class ByteArrayOutputStream extends OutputStream {
protected byte buf[];//存储数据的缓冲区
protected int count;//缓冲区中的有效字节数
public ByteArrayOutputStream() {
this(32);
}
public ByteArrayOutputStream(int size) {
if (size < 0) {
throw new IllegalArgumentException("Negative initial size: " + size);
}
buf = new byte[size];
}
/*
该方法是用来对buf数组进行扩充,buf数组是动态增长的,扩充策略是先扩大为原来的二倍,
如果buf的size还不够,则直接让buf的size等于minCapacity,具体可以参照grow()方法
*/
private void ensureCapacity(int minCapacity) {
if (minCapacity - buf.length > 0)
grow(minCapacity);
}
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
private void grow(int minCapacity) {
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;
}
/*
将此 byte 数组输出流的全部内容写入到指定的输出流参数中,
这与使用 out.write(buf, 0, count) 调用该输出流的 write 方法效果一样。
*/
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 {
}
}
3 内存操作流的使用案例
内存操作流的使用和其他IO流一样。
public class ByteArrayStreamDemo {
public static void main(String[] args) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
for (int x = 0; x < 10; x++) {
baos.write(("hello" + x).getBytes());
}
// 源码这里什么都没做,所以不需要close()
// baos.close();
byte[] bys = baos.toByteArray();
ByteArrayInputStream bais = new ByteArrayInputStream(bys);
int by = 0;
while ((by = bais.read()) != -1) {
System.out.print((char) by);
}
// bais.close();
}
}