1.概述
ByteArrayInputStream即字节数组输入流,ByteArrayOutputStream即字节数组输出流,这俩对象在创建时,需要提供一个byte[]作为输入来源、输出目的地,所有的方法都是围绕这个byte[]进行操作。byte[]保存在内存的一块空间,也就是说所有操作完全不涉及磁盘文件,一般用于流数据的中转。
2.1 构造器
ByteArrayInputStream提供了俩个构造器,核心是要传入一个字节数组,其他的下面成员变量会详解:
1 2 3 4 5 6 7 8 9 10 11 12
| 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; }
|
2.2 成员变量
1 2 3 4 5 6 7 8 9 10 11
| protected byte buf[];
protected int pos;
protected int mark = 0;
protected int count;
|
2.3 内部方法
读取单个字节数据:1 2 3 4
| public synchronized int read() { return (pos < count) ? (buf[pos++] & 0xff) : -1; }
|
批量读取字节数据:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| 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; }
|
将下次要读取的坐标往后推移n位,但是不能超过最大坐标值:1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public synchronized long skip(long n) {
long k = count - pos;
if (n < k) { k = n < 0 ? 0 : n; }
pos += k; return k; }
|
其他方法:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| 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 {
}
|
3.ByteArrayOutputStream
ByteArrayInputStream类关注读取坐标(pos)的管理,而ByteArrayOutputStream类关注字节数组容量管理,因为某个实例可能会被无限写入,构造器初始化的容量很大几率不够使用,因此需要动态扩容。
ByteArrayInputStream类还有一个特点,可以获取写入结果,或者将写入结果写入其他输出流,这是其他输出流没有的。
3.1 构造器
ByteArrayOutputStream也提供了俩个构造器,核心时创建一个字节数据,用于后续的写入:
1 2 3 4 5 6 7 8 9 10
| public ByteArrayOutputStream() { this(32); }
public ByteArrayOutputStream(int size) { if (size < 0) { throw new IllegalArgumentException("Negative initial size: " + size); } buf = new byte[size]; }
|
3.2 成员变量
1 2 3 4 5 6 7 8
| protected byte buf[];
protected int count;
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
|
3.3 内部方法
传入一个假定容量,如果大于当前字节数组长度,进行扩容:1 2 3 4
| private void ensureCapacity(int minCapacity) { if (minCapacity - buf.length > 0) grow(minCapacity); }
|
字节数组扩容:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| 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); }
|
字节数组容量最大值处理:1 2 3 4 5 6 7 8
| private static int hugeCapacity(int minCapacity) { if (minCapacity < 0) throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE; }
|
单字节写入:1 2 3 4 5 6 7 8 9 10 11
| public synchronized void write(int b) {
ensureCapacity(count + 1);
buf[count] = (byte) b;
count += 1; }
|
多字节写入:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| 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; }
|
其他方法:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| 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 { }
|
4.使用场景
4.1 MultipartFile
有些文件的来源不一定是磁盘,还有可能来自网络,比如SpringMVC框架接收上传文件的MultipartFile类,服务端接收到文件后是没有路径这个概念的,对外提供的getInputStream()方法返回的是个ByteArrayInputStream,这种设计使文件不知道来源的情况下,也能融入到IO流体系中。
4.2 多次使用
InputStream
4.3 避免创建临时文件
之前有个需求,简单说就是某个远程服务器有一些excel文件,但是表头都是数据库字段名,前端无法通过URL直接导出(都是英文,用户也看不懂),因此需要后端做一层处理,将这些excel表头修改为中文描述再呈现给用户。正常情况下,读取到远程excel的InputStream后,创建一个Workbook对象并进行修改,并将结果写入HttpServletResponse即可。
但是公司因为一些原因不能通过流的形式将文件下载到浏览器,必须将文件放入云服务器然后返回路径,前端只能通过路径去下载。上传到云服务器必须提供文件的InputStream,但是Workbook只能将结果写入OutputStream。一般情况下你可能会想到先写到本地,然后创建本地的InputStream再上传到云服务器,最后删除本地文件。
现在我们可以利用ByteArrayInputStream、ByteArrayOutputStream来避免这种情况:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| public static void main(String[] args) throws Exception{
String excelPath = "excel path";
InputStream inputStream = getInputStreamByUrl(excelPath);
XSSFWorkbook xwb = new XSSFWorkbook(inputStream);
XSSFSheet sheetAt = xwb.getSheetAt(0); XSSFRow headRow = sheetAt.getRow(0);
XSSFCell cell1 = headRow.getCell(0); cell1.setCellValue("A"); XSSFCell cell2 = headRow.getCell(1); cell2.setCellValue("B");
ByteArrayOutputStream tempOutputStream = new ByteArrayOutputStream(); xwb.write(tempOutputStream);
InputStream tempInputStream = new ByteArrayInputStream(tempOutputStream.toByteArray());
upload(tempInputStream); }
|