`

common-io 之ByteArrayOutputStream阅读

阅读更多


首先我们来分析它里面的的实例变量:

buffers: 可以看成是一个buffer仓库,里面放的是已经读取的所有数据
currentBufferIndex: 就是正在使用的buffer的index
count: 用来存放buffers里面的所有的字节数
currentBuffer: 就是当前的使用buffer,这个比较好理解。
filledBufferSum: 这个起初的时候我特别不理解,后来我理解,主要是用了保存所有的满buffer的字节数的总和。

举个例子:
          起始的时候第一个buffer的大小为32,它的filledBufferSum为0,count为0,然后我们给当前的buffer放入5个字节的数据,现在count为5, 下一次我们count - filedBufferSum 就是我们下一次要存储的buffer的指针,比如我们要放25个字节,那么现在count就变成30了,filledBufferSum仍然是0,我们再放入3个字节,现在count变成33,比buffer的初始大小大了,就扩容,新建一个buffer,把老的buffer放到buffers里面,然后filledBufferSum就变成32了,把扩容后剩余的1个字节放到新申请的buffer里面,下一次比如我们想再放入10个字节的数据,count是33,filedBufferSum是32,我们存放的指针应该是1,因为0字节我们存放了上次扩容后的剩余的字节数。


1. 首先来看一下构造函数:

 

public ByteArrayOutputStream() {
        this(1024);
    }


public ByteArrayOutputStream(int size) {
        if (size < 0) {
            throw new IllegalArgumentException(
                "Negative initial size: " + size);
        }
        needNewBuffer(size);
    }
 

 
    一个是无参数的时候创建一个大小为1024的buffer,一个是根据用户输入的大小创建buffer,这个都比较好理解,关键是needNewBuffer函数,这个放到下面进行讲解。

2. 下面来看一下needNewBuffer函数,这个是这个类的灵魂,我感觉

private void needNewBuffer(int newcount) {
        if (currentBufferIndex < buffers.size() - 1) {
            //Recycling old buffer
            filledBufferSum += currentBuffer.length;
            
            currentBufferIndex++;
            currentBuffer = getBuffer(currentBufferIndex);
        } else {
            //Creating new buffer
            int newBufferSize;
            if (currentBuffer == null) {
                newBufferSize = newcount;
                filledBufferSum = 0;
            } else {
                newBufferSize = Math.max(
                    currentBuffer.length << 1, 
                    newcount - filledBufferSum);
                filledBufferSum += currentBuffer.length;
            }
            
            currentBufferIndex++;
            currentBuffer = new byte[newBufferSize];
            buffers.add(currentBuffer);
        }
    }
 
      首先我们为了好理解期间,我们先来讲else分支,if分支我们放到reset函数里讲解。
这个就是我们传统意义上的创建新的buffer。这个里面如果currentBuffer是null的话,
就初始化一个,这个分支是在创建的时候会走这个分支,下一个分支是将当前的buffer
的length乘以2和所需要的大小进行比较取最大值来当成新的buffer的大小,
然后把filledBufferSum的值进行更改。下面几行代码是来真正创建buffer的地方,
并把他加到buffers里面去。


3. 下面我们来看一下write函数
public 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;
        }
        synchronized (this) {
            int newcount = count + len;
            int remaining = len;
            int inBufferPos = count - filledBufferSum;
            while (remaining > 0) {
                int part = Math.min(remaining, currentBuffer.length - inBufferPos);
                System.arraycopy(b, off + len - remaining, currentBuffer, inBufferPos, part);
                remaining -= part;
                if (remaining > 0) {
                    needNewBuffer(newcount);
                    inBufferPos = 0;
                }
            }
            count = newcount;
        }
    }

 
       首先是一些检验,放在数组越界,这些判断是和父类里面的判断是相同的。
下面是才真正的实现写操作。 首先来计算新的count,并将要写的字节数当成初始的remaining,并来计算这次要写的指针位置, 就是上次的总大小减去已经存放满的buffer里面的字节数。
      在这里将remaining和当前所剩余的空间做了一个比较,取最小值。然后做数组拷贝动作。然后判断是不是已经完全写完,如果没写完的话就是分配空间了,然后执行分配空间动作,最后在循环的写入到buffer里面。

4. 下面我们在巩固一下,理解一下write函数

  /**
     * Write a byte to byte array.
     * @param b the byte to write
     */
    public synchronized void write(int b) {
        int inBufferPos = count - filledBufferSum;
        if (inBufferPos == currentBuffer.length) {
            needNewBuffer(count + 1);
            inBufferPos = 0;
        }
        currentBuffer[inBufferPos] = (byte) b;
        count++;
    }
 
      只要理解了上面的,这个函数就特别好理解,首先是得到这次应该写入的指针位置,如果发现没有空间的话进行分配新的空间,然后把数据写到buffer里面,并累加写入的数据总数。


5. write函数的研究
public synchronized int write(InputStream in) throws IOException {
        int readCount = 0;
        int inBufferPos = count - filledBufferSum;
        int n = in.read(currentBuffer, inBufferPos, currentBuffer.length - inBufferPos);
        while (n != -1) {
            readCount += n;
            inBufferPos += n;
            count += n;
            if (inBufferPos == currentBuffer.length) {
                needNewBuffer(currentBuffer.length);
                inBufferPos = 0;
            }
            n = in.read(currentBuffer, inBufferPos, currentBuffer.length - inBufferPos);
        }
        return readCount;
    }
 
          这个函数其实也比较好理解,主要是计算应该写入的指针位置,然后从InputStream里面读到currentBuffer里面,从inBufferPos开始,总共读最大剩余长度个字节,如果读到的不为-1,则变化当前的指针和readCount,然后如果当前的指针和currentBuffer的length相等时,则进行扩充缓冲区,然后进行循环,直到读完。


6. reset函数
public synchronized void reset() {
        count = 0;
        filledBufferSum = 0;
        currentBufferIndex = 0;
        currentBuffer = getBuffer(currentBufferIndex);
    }
 
       我起始得时候不理解,现在理解了。比如我们首先对第一个文件进行了写操作,然后缓冲区并没有释放,我们要读下一个文件,那么我们现在肯定不能再去申请空间了,要对上一次的缓冲区进行复用,这里就先将一些状态变量清0,然后将当前的buffer设置为第0个buffer,这样下一次操作的时候就可以使用上次分配的buffer了。为了把问题说明白,我们再来看一下
needNewBuffer的if分支,
if (currentBufferIndex < buffers.size() - 1) {
            //Recycling old buffer
            filledBufferSum += currentBuffer.length;
            
            currentBufferIndex++;
            currentBuffer = getBuffer(currentBufferIndex);
 
 

这个里面,我们可以看到调用了这个函数后,currentBufferIndex是0,如果buffers还有其他缓冲区的话就将当前的bufferIndex加1,并返回下一个缓冲区,这样感觉效率比较高,到这里我们估计你应该彻底理解这个里面的流程了。


7. 下面我们来看一下writeTo函数的实现
 public synchronized void writeTo(OutputStream out) throws IOException {
        int remaining = count;
        for (int i = 0; i < buffers.size(); i++) {
            byte[] buf = getBuffer(i);
            int c = Math.min(buf.length, remaining);
            out.write(buf, 0, c);
            remaining -= c;
            if (remaining == 0) {
                break;
            }
        }
    }
 
      这个函数主要是实现了把buf中的数据直接写到用户指定的OutputStream里面。这个里面和JDK里面的实现的唯一的差别就是这里会循环使得buffers中的数据会全部写到指定的OutputStream里面。

8. 下面我们来发一下toByteArray的实现。
public synchronized byte[] toByteArray() {

 

 

        int remaining = count;
        if (remaining == 0) {
            return EMPTY_BYTE_ARRAY; 
        }
        byte newbuf[] = new byte[remaining];
        int pos = 0;
        for (int i = 0; i < buffers.size(); i++) {
            byte[] buf = getBuffer(i);
            int c = Math.min(buf.length, remaining);
            System.arraycopy(buf, 0, newbuf, pos, c);
            pos += c;
            remaining -= c;
            if (remaining == 0) {
                break;
            }
        }
        return newbuf;
    }

 
      这个函数很好理解,就是创建一个count大小的byte数组,然后循环buffers,将每一个缓冲区中的数据都copy到将要返回的字节数组里面。

 

下面完整的程序:

 

public class ByteArrayOutputStream extends OutputStream {

    /** A singleton empty byte array. */
    private static final byte[] EMPTY_BYTE_ARRAY = new byte[0];

    /** The list of buffers, which grows and never reduces. */
    private final List<byte[]> buffers = new ArrayList<byte[]>();
    /** The index of the current buffer. */
    private int currentBufferIndex;
    /** The total count of bytes in all the filled buffers. */
    private int filledBufferSum;
    /** The current buffer. */
    private byte[] currentBuffer;
    /** The total count of bytes written. */
    private int count;

    /**
     * Creates a new byte array output stream. The buffer capacity is 
     * initially 1024 bytes, though its size increases if necessary. 
     */
    public ByteArrayOutputStream() {
        this(1024);
    }

    /**
     * Creates a new byte array output stream, with a buffer capacity of 
     * the specified size, in bytes. 
     *
     * @param size  the initial size
     * @throws IllegalArgumentException if size is negative
     */
    public ByteArrayOutputStream(int size) {
        if (size < 0) {
            throw new IllegalArgumentException(
                "Negative initial size: " + size);
        }
        needNewBuffer(size);
    }

    /**
     * Makes a new buffer available either by allocating
     * a new one or re-cycling an existing one.
     *
     * @param newcount  the size of the buffer if one is created
     */
    private void needNewBuffer(int newcount) {
        if (currentBufferIndex < buffers.size() - 1) {
            //Recycling old buffer
            filledBufferSum += currentBuffer.length;
            
            currentBufferIndex++;
            currentBuffer = buffers.get(currentBufferIndex);
        } else {
            //Creating new buffer
            int newBufferSize;
            if (currentBuffer == null) {
                newBufferSize = newcount;
                filledBufferSum = 0;
            } else {
                newBufferSize = Math.max(
                    currentBuffer.length << 1, 
                    newcount - filledBufferSum);
                filledBufferSum += currentBuffer.length;
            }
            
            currentBufferIndex++;
            currentBuffer = new byte[newBufferSize];
            buffers.add(currentBuffer);
        }
    }

    /**
     * Write the bytes to byte array.
     * @param b the bytes to write
     * @param off The start offset
     * @param len The number of bytes to write
     */
    @Override
    public 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;
        }
        synchronized (this) {
            int newcount = count + len;
            int remaining = len;
            int inBufferPos = count - filledBufferSum;
            while (remaining > 0) {
                int part = Math.min(remaining, currentBuffer.length - inBufferPos);
                System.arraycopy(b, off + len - remaining, currentBuffer, inBufferPos, part);
                remaining -= part;
                if (remaining > 0) {
                    needNewBuffer(newcount);
                    inBufferPos = 0;
                }
            }
            count = newcount;
        }
    }

    /**
     * Write a byte to byte array.
     * @param b the byte to write
     */
    @Override
    public synchronized void write(int b) {
        int inBufferPos = count - filledBufferSum;
        if (inBufferPos == currentBuffer.length) {
            needNewBuffer(count + 1);
            inBufferPos = 0;
        }
        currentBuffer[inBufferPos] = (byte) b;
        count++;
    }

    /**
     * Writes the entire contents of the specified input stream to this
     * byte stream. Bytes from the input stream are read directly into the
     * internal buffers of this streams.
     *
     * @param in the input stream to read from
     * @return total number of bytes read from the input stream
     *         (and written to this stream)
     * @throws IOException if an I/O error occurs while reading the input stream
     * @since Commons IO 1.4
     */
    public synchronized int write(InputStream in) throws IOException {
        int readCount = 0;
        int inBufferPos = count - filledBufferSum;
        int n = in.read(currentBuffer, inBufferPos, currentBuffer.length - inBufferPos);
        while (n != -1) {
            readCount += n;
            inBufferPos += n;
            count += n;
            if (inBufferPos == currentBuffer.length) {
                needNewBuffer(currentBuffer.length);
                inBufferPos = 0;
            }
            n = in.read(currentBuffer, inBufferPos, currentBuffer.length - inBufferPos);
        }
        return readCount;
    }

    /**
     * Return the current size of the byte array.
     * @return the current size of the byte array
     */
    public synchronized int size() {
        return count;
    }

    /**
     * Closing a <tt>ByteArrayOutputStream</tt> has no effect. The methods in
     * this class can be called after the stream has been closed without
     * generating an <tt>IOException</tt>.
     *
     * @throws IOException never (this method should not declare this exception
     * but it has to now due to backwards compatability)
     */
    @Override
    public void close() throws IOException {
        //nop
    }

    /**
     * @see java.io.ByteArrayOutputStream#reset()
     */
    public synchronized void reset() {
        count = 0;
        filledBufferSum = 0;
        currentBufferIndex = 0;
        currentBuffer = buffers.get(currentBufferIndex);
    }

    /**
     * Writes the entire contents of this byte stream to the
     * specified output stream.
     *
     * @param out  the output stream to write to
     * @throws IOException if an I/O error occurs, such as if the stream is closed
     * @see java.io.ByteArrayOutputStream#writeTo(OutputStream)
     */
    public synchronized void writeTo(OutputStream out) throws IOException {
        int remaining = count;
        for (byte[] buf : buffers) {
            int c = Math.min(buf.length, remaining);
            out.write(buf, 0, c);
            remaining -= c;
            if (remaining == 0) {
                break;
            }
        }
    }

    /**
     * Fetches entire contents of an <code>InputStream</code> and represent
     * same data as result InputStream.
     * <p>
     * This method is useful where,
     * <ul>
     * <li>Source InputStream is slow.</li>
     * <li>It has network resources associated, so we cannot keep it open for
     * long time.</li>
     * <li>It has network timeout associated.</li>
     * </ul>
     * It can be used in favor of {@link #toByteArray()}, since it
     * avoids unnecessary allocation and copy of byte[].<br>
     * This method buffers the input internally, so there is no need to use a
     * <code>BufferedInputStream</code>.
     * 
     * @param input Stream to be fully buffered.
     * @return A fully buffered stream.
     * @throws IOException if an I/O error occurs
     */
    public static InputStream toBufferedInputStream(InputStream input)
            throws IOException {
        ByteArrayOutputStream output = new ByteArrayOutputStream();
        output.write(input);
        return output.toBufferedInputStream();
    }

    /**
     * Gets the current contents of this byte stream as a Input Stream. The
     * returned stream is backed by buffers of <code>this</code> stream,
     * avoiding memory allocation and copy, thus saving space and time.<br>
     * 
     * @return the current contents of this output stream.
     * @see java.io.ByteArrayOutputStream#toByteArray()
     * @see #reset()
     * @since Commons IO 2.0
     */
    private InputStream toBufferedInputStream() {
        int remaining = count;
        if (remaining == 0) {
            return new ClosedInputStream();
        }
        List<ByteArrayInputStream> list = new ArrayList<ByteArrayInputStream>(buffers.size());
        for (byte[] buf : buffers) {
            int c = Math.min(buf.length, remaining);
            list.add(new ByteArrayInputStream(buf, 0, c));
            remaining -= c;
            if (remaining == 0) {
                break;
            }
        }
        return new SequenceInputStream(Collections.enumeration(list));
    }

    /**
     * Gets the curent contents of this byte stream as a byte array.
     * The result is independent of this stream.
     *
     * @return the current contents of this output stream, as a byte array
     * @see java.io.ByteArrayOutputStream#toByteArray()
     */
    public synchronized byte[] toByteArray() {
        int remaining = count;
        if (remaining == 0) {
            return EMPTY_BYTE_ARRAY; 
        }
        byte newbuf[] = new byte[remaining];
        int pos = 0;
        for (byte[] buf : buffers) {
            int c = Math.min(buf.length, remaining);
            System.arraycopy(buf, 0, newbuf, pos, c);
            pos += c;
            remaining -= c;
            if (remaining == 0) {
                break;
            }
        }
        return newbuf;
    }

    /**
     * Gets the curent contents of this byte stream as a string.
     * @return the contents of the byte array as a String
     * @see java.io.ByteArrayOutputStream#toString()
     */
    @Override
    public String toString() {
        return new String(toByteArray());
    }

    /**
     * Gets the curent contents of this byte stream as a string
     * using the specified encoding.
     *
     * @param enc  the name of the character encoding
     * @return the string converted from the byte array
     * @throws UnsupportedEncodingException if the encoding is not supported
     * @see java.io.ByteArrayOutputStream#toString(String)
     */
    public String toString(String enc) throws UnsupportedEncodingException {
        return new String(toByteArray(), enc);
    }

}
 


2
0
分享到:
评论

相关推荐

    common.io详细说明

    根据提供的信息,我们可以深入探讨`common.io`相关的知识点,这里主要聚焦于Java IO系统的核心概念、流类别及其应用。下面将详细阐述标题和描述中提到的知识点。 ### Java IO系统概述 Java IO(输入/输出)系统是...

    java生成二维码_java将二维码转换成base64

    import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.util.Base64; public class QRCodeToBase64 { public static void main(String[] args) throws ...

    RSA+bcpkix-jdk15.zip

    import java.io.ByteArrayOutputStream; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.math.BigInteger;...

    WEB条形码生成代码

    import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.util.HashMap; import java.util.Map; public class BarcodeGenerator { public byte[] generateBarcode...

    android二维码生成示例

    import java.io.OutputStream; import java.util.HashMap; import java.util.Map; public class QRCodeGenerator { public static void generateQRCode(String content, OutputStream outputStream) throws ...

    java实现二维码和语音

    import java.io.ByteArrayOutputStream; import java.io.IOException; import java.net.URL; import java.nio.charset.StandardCharsets; public class VoiceGenerator { public static void main(String[] args) ...

    二维码制作带logo前端后台两种方式

    import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.HashMap; import java.util.Map; public byte[] generateQRCode(String data) throws WriterException, IOException { Map,...

    spring boot整合二维码和条形码

    import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.HashMap; import java.util.Map; @Service public class CodeGeneratorService { public byte[] generateQRCode(String ...

    Spring Boot技术知识点:如何实现二维码生成工具

    import java.io.ByteArrayOutputStream; import java.util.HashMap; import java.util.Map; @Service public class QrCodeService { public byte[] generateQrCode(String content) throws WriterException {...

    ganymed-ssh2-build210.jar java远程访问linux服务器操作、上传下载文件

    ByteArrayOutputStream byteArray = new ByteArrayOutputStream(1024*1024); byte[] b = new byte[1024*1024]; int i; while ((i = fis.read(b)) != -1) { byteArray.write(b, 0, i); } fis.close(); ...

    javaweb二维码生成

    import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.HashMap; import java.util.Map; public class QRCodeGenerator { public static BufferedImage generateQRCodeImage...

    使用python调用zxing库生成二维码图片详解

    ByteArrayOutputStream = JClass("java.io.ByteArrayOutputStream") MatrixToImageWriter = JClass("com.google.zxing.client.j2se.MatrixToImageWriter") EncodeHintType = JClass(...

    java 二维码

    可以使用`ByteArrayOutputStream`将图像转换为字节数组,然后根据需求将其转换为流。同时,处理文件路径时要注意文件系统的兼容性,使用`File.separator`来确保路径在不同操作系统下都能正常工作。 4. **高级特性**...

    Java 生成二维码的工具资料整理

    import java.io.ByteArrayOutputStream; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.Map; public class QRCodeGenerator { ...

    Java二维码带logo和不带logo实现

    对于生成流文件,可以使用`ServletOutputStream`或者`ByteArrayOutputStream`来替代`File`对象。例如,如果要在HTTP响应中返回二维码图片,可以这样做: ```java OutputStream outputStream = response....

    Java二维码登录流程实现代码(包含短地址生成,含部分代码)

    import java.io.ByteArrayOutputStream; import java.io.FileOutputStream; import java.io.IOException; import java.util.HashMap; import java.util.Map; public class QRCodeGenerator { public static void ...

    Java生成中间logo的二维码的示例代码

    import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.util.Date; import java.util.HashMap; import java.util.Map; import javax.imageio.ImageIO; import ...

Global site tag (gtag.js) - Google Analytics