这是一段对输入流进行读取和输出的代码
Resource classPathResource = new ClassPathResource("1.txt");
InputStream inputStream = pathResource.getInputStream();
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
int i;
while ((i=inputStream.read())!=-1){
byteArrayOutputStream.write(i);
}
System.out.println(byteArrayOutputStream.toString());
输出数据如下:
以前一直对这样的while循环理解不足,今天突然再度写到这样的代码,就想着深入研究一下。
inputStream.read()
这个方法的官方定义如下
/**
* Reads the next byte of data from the input stream. The value byte is
* returned as an <code>int</code> in the range <code>0</code> to
* <code>255</code>. If no byte is available because the end of the stream
* has been reached, the value <code>-1</code> is returned. This method
* blocks until input data is available, the end of the stream is detected,
* or an exception is thrown.
*
* <p> A subclass must provide an implementation of this method.
*
* @return the next byte of data, or <code>-1</code> if the end of the
* stream is reached.
* @exception IOException if an I/O error occurs.
*/
public abstract int read() throws IOException;
就是说,从输入流中读取数据的下一个byte字节,并返回int类型的字节值,这个字节的值范围在0~255之间。如果输入流已经被读完,就返回-1。
这里还是先讲一下byte的知识。
byte
bit:位。位是电子计算机中最小的数据单位。每一位的状态只能是0或1。比尔盖茨曾经说过,计算机世界都是由0和1组成的。计算机也只能读懂0和1这种二进制数据。
byte:字节。8个二进制位构成1个"字节(Byte)",它是存储空间的基本计量单位。即:1byte=8bit。
编码:编码的知识量太大,这里只是简单提一下开发中常用到的utf-8编码。在utf-8编码中,一个英文字符等于一个字节,一个中文(含繁体)等于三个字节。无论是中文还是英文,都叫字符。中文标点占三个字节,英文标点占一个字节。也就是说一个“千”字,它要被计算机识别,首先转化成对应它的三个byte字节(对应规则在相关的映射表中),再转化成3*8=24(bit)。最终结果是11100101 10001101 10000011 ,这个二进制数字才能被计算机读懂。而英文字母“Q”的转化简单些,它只需要一个字节去表示 10000001。
这里分享个在线转换utf-8编码的地址http://www.mytju.com/classcode/tools/encode_utf8.asp
java中的流传输
我在开发过程中,遇到的流传输、数据传输,基本上都是用byte数组,所以这里也主要讲这个。从文章的第一段代码中,不难看出,inputStream.read()将读取到的文件1.txt的二进制数据赋予了变量i,ByteArrayOutputStream负责将i的值通过write()方法记录起来,然后使用toString()方法将记录的数据重新编码输出。这里看了一下ByteArrayOutputStream的底层源码,把重要的方法放上来,并注上中文注释:
public class ByteArrayOutputStream extends OutputStream {
/**
* byte数组,用于存储读取到的byte字节。
*/
protected byte buf[];
/**
* 有效字节数
*/
protected int count;
/**
* 初始化byte[] 数组,32长度
*/
public ByteArrayOutputStream() {
this(32);
}
public ByteArrayOutputStream(int size) {
if (size < 0) {
throw new IllegalArgumentException("Negative initial size: "
+ size);
}
buf = new byte[size];
}
/**
* byte数组扩容,这是为了防止传入的数据量大于byte[]初始容量
* */
private void ensureCapacity(int minCapacity) {
// overflow-conscious code
if (minCapacity - buf.length > 0)
grow(minCapacity);
}
/**
* 定义byte数组最大容量
*/
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;
}
/**
* 把读取的一个字节放入byte[]
*/
public synchronized void write(int b) {
ensureCapacity(count + 1);
buf[count] = (byte) b;
count += 1;
}
/**
* 将byte[]数组中存储的字节以平台默认编码(这里是utf-8)重新编码,并返回String类型的数据,也就是文本的内容。
*/
public synchronized String toString() {
return new String(buf, 0, count);
}
源码思路:定义一个byte[]变量,存储所有读到的字节,作为一个缓冲域。这个缓冲域随着对象不断的读入数据而不断增长,当数据量大于缓冲域的空间,就会对缓冲域进行扩容处理。当数据全部读入缓冲区中,里面的内容大概是这样的,buf[]={117,115,101,114...}。最后通过调用toString()方法将缓冲区的数据以特定编码再次输出。
讲一下源码中几个关键的点:
- int newCapacity = oldCapacity << 1; 这里的意思是数组空间扩容1倍。oldCapacity 左移一位,即左边高位去掉,右边地位补0。例:十进制8的二进制表示成 0000 1000 左移1位,8<<1,变成0001 0000 。再转成十进制,即变成16。这就完成了扩容。
- new String(buf, 0, count) 表示 将字符数组buf 从第一个字符(java以0开始,也可以说是第0个字符)开始,长度为count的的所有字符构建成一个字符串,也就是String了。这个方法帮我们完成了由字节转化到字符的操作。
这样来看,这个ByteArrayOutputStream 其实很简单。我这里自己也写了个根据输入流输出文本数据的工具类,结果发现代码真的差不多,就稍微封装了一下。
/**
* 千里明月
*/
public class OutputStreamUtil {
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
private static int count=0;
private static byte[] bytes=new byte[10];
public static String write(InputStream inputStream) throws IOException {
int i =0 ;
while ((i=inputStream.read())!=-1){
write(i);
}
return encode();
}
public static void write(int i){
if (bytes.length<=count){
grow(count+1);
}
bytes[count]= (byte) i;
System.out.print(bytes[count]);
count++;
}
private static void grow(int capacity) {
int oldcapacity= bytes.length;
int newcapacity= oldcapacity<<1;
if (newcapacity<capacity){
newcapacity=capacity;
}
if (newcapacity<MAX_ARRAY_SIZE){
bytes = Arrays.copyOf(bytes, newcapacity);
}
}
public static String encode(){
return new String(bytes,0,count);
}
}
测试类:
public class TestResource {
public static void main(String[] args) throws IOException {
Resource classPathResource = new ClassPathResource("1.txt");
InputStream inputStream = classPathResource.getInputStream();
String write = OutputStreamUtil.write(inputStream);
System.out.println(write);
inputStream.close();
}
}