< prev index next >

src/java.base/share/classes/java/io/BufferedOutputStream.java

Print this page
@@ -23,20 +23,30 @@
   * questions.
   */
  
  package java.io;
  
+ import java.util.Arrays;
+ import jdk.internal.misc.InternalLock;
+ import jdk.internal.misc.VM;
+ 
  /**
   * The class implements a buffered output stream. By setting up such
   * an output stream, an application can write bytes to the underlying
   * output stream without necessarily causing a call to the underlying
   * system for each byte written.
   *
   * @author  Arthur van Hoff
   * @since   1.0
   */
  public class BufferedOutputStream extends FilterOutputStream {
+     private static final int DEFAULT_INITIAL_BUFFER_SIZE = 512;
+     private static final int DEFAULT_MAX_BUFFER_SIZE = 8192;
+ 
+     // initialized to null when BufferedOutputStream is sub-classed
+     private final InternalLock lock;
+ 
      /**
       * The internal buffer where data is stored.
       */
      protected byte buf[];
  

@@ -46,18 +56,57 @@
       * {@code buf[0]} through {@code buf[count-1]} contain valid
       * byte data.
       */
      protected int count;
  
+     /**
+      * Max size of the internal buffer or -1 if internal buffer cannot be resized.
+      */
+     private final int maxBufSize;
+ 
+     /**
+      * Returns the buffer size to use when no output buffer size specified
+      */
+     private static int initialBufferSize() {
+         if (VM.isBooted() && Thread.currentThread().isVirtual()) {
+             return DEFAULT_INITIAL_BUFFER_SIZE;
+         } else {
+             return DEFAULT_MAX_BUFFER_SIZE;
+         }
+     }
+ 
+     /**
+      * Creates a new buffered output stream.
+      */
+     private BufferedOutputStream(OutputStream out, int initialSize, int maxSize) {
+         super(out);
+ 
+         if (initialSize <= 0) {
+             throw new IllegalArgumentException("Buffer size <= 0");
+         }
+ 
+         if (getClass() == BufferedOutputStream.class) {
+             // use InternalLock and resizable buffer when not sub-classed
+             this.lock = new InternalLock();
+             this.buf = new byte[initialSize];    // resizable
+             this.maxBufSize = maxSize;
+         } else {
+             // use monitors and no resizing when sub-classed
+             this.lock = null;
+             this.buf = new byte[maxSize];
+             this.maxBufSize = -1;
+         }
+     }
+ 
      /**
       * Creates a new buffered output stream to write data to the
       * specified underlying output stream.
       *
       * @param   out   the underlying output stream.
       */
      public BufferedOutputStream(OutputStream out) {
-         this(out, 8192);
+         this(out, initialBufferSize(), DEFAULT_MAX_BUFFER_SIZE);
      }
  
      /**
       * Creates a new buffered output stream to write data to the
       * specified underlying output stream with the specified buffer

@@ -66,33 +115,61 @@
       * @param   out    the underlying output stream.
       * @param   size   the buffer size.
       * @throws  IllegalArgumentException if size &lt;= 0.
       */
      public BufferedOutputStream(OutputStream out, int size) {
-         super(out);
-         if (size <= 0) {
-             throw new IllegalArgumentException("Buffer size <= 0");
-         }
-         buf = new byte[size];
+         this(out, size, size);
      }
  
      /** Flush the internal buffer */
      private void flushBuffer() throws IOException {
          if (count > 0) {
              out.write(buf, 0, count);
              count = 0;
          }
      }
  
+     /**
+      * Grow buf to fit an additional len bytes if needed.
+      * If possible, it grows by len+1 to avoid flushing when len bytes
+      * are added. A no-op if the buffer is not resizable.
+      */
+     private void growIfNeeded(int len) {
+         if (maxBufSize > 0) {
+             int neededSize = count + len + 1;
+             int bufSize = buf.length;
+             if (neededSize > bufSize && bufSize < maxBufSize) {
+                 int newSize = Math.min(neededSize, maxBufSize);
+                 buf = Arrays.copyOf(buf, newSize);
+             }
+         }
+     }
+ 
      /**
       * Writes the specified byte to this buffered output stream.
       *
       * @param      b   the byte to be written.
       * @throws     IOException  if an I/O error occurs.
       */
      @Override
-     public synchronized void write(int b) throws IOException {
+     public void write(int b) throws IOException {
+         if (lock != null) {
+             lock.lock();
+             try {
+                 lockedWrite(b);
+             } finally {
+                 lock.unlock();
+             }
+         } else {
+             synchronized (this) {
+                 lockedWrite(b);
+             }
+         }
+     }
+ 
+     private void lockedWrite(int b) throws IOException {
+         growIfNeeded(1);
          if (count >= buf.length) {
              flushBuffer();
          }
          buf[count++] = (byte)b;
      }

@@ -112,19 +189,36 @@
       * @param      off   the start offset in the data.
       * @param      len   the number of bytes to write.
       * @throws     IOException  if an I/O error occurs.
       */
      @Override
-     public synchronized void write(byte[] b, int off, int len) throws IOException {
-         if (len >= buf.length) {
-             /* If the request length exceeds the size of the output buffer,
+     public void write(byte[] b, int off, int len) throws IOException {
+         if (lock != null) {
+             lock.lock();
+             try {
+                 lockedWrite(b, off, len);
+             } finally {
+                 lock.unlock();
+             }
+         } else {
+             synchronized (this) {
+                 lockedWrite(b, off, len);
+             }
+         }
+     }
+ 
+     private void lockedWrite(byte[] b, int off, int len) throws IOException {
+         int max = (maxBufSize > 0) ? maxBufSize : buf.length;
+         if (len >= max) {
+             /* If the request length exceeds the max size of the output buffer,
                 flush the output buffer and then write the data directly.
                 In this way buffered streams will cascade harmlessly. */
              flushBuffer();
              out.write(b, off, len);
              return;
          }
+         growIfNeeded(len);
          if (len > buf.length - count) {
              flushBuffer();
          }
          System.arraycopy(b, off, buf, count, len);
          count += len;

@@ -136,10 +230,25 @@
       *
       * @throws     IOException  if an I/O error occurs.
       * @see        java.io.FilterOutputStream#out
       */
      @Override
-     public synchronized void flush() throws IOException {
+     public void flush() throws IOException {
+         if (lock != null) {
+             lock.lock();
+             try {
+                 lockedFlush();
+             } finally {
+                 lock.unlock();
+             }
+         } else {
+             synchronized (this) {
+                 lockedFlush();
+             }
+         }
+     }
+ 
+     private void lockedFlush() throws IOException {
          flushBuffer();
          out.flush();
      }
  }
< prev index next >