< prev index next >

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

Print this page
*** 23,10 ***
--- 23,14 ---
   * questions.
   */
  
  package java.io;
  
+ import java.util.Arrays;
+ import java.util.Objects;
+ import jdk.internal.misc.InternalLock;
+ import jdk.internal.misc.VM;
  
  /**
   * Writes text to a character-output stream, buffering characters so as to
   * provide for the efficient writing of single characters, arrays, and strings.
   *

*** 62,26 ***
   * @author      Mark Reinhold
   * @since       1.1
   */
  
  public class BufferedWriter extends Writer {
  
      private Writer out;
  
      private char cb[];
      private int nChars, nextChar;
  
!     private static int defaultCharBufferSize = 8192;
  
      /**
       * Creates a buffered character-output stream that uses a default-sized
       * output buffer.
       *
       * @param  out  A Writer
       */
      public BufferedWriter(Writer out) {
!         this(out, defaultCharBufferSize);
      }
  
      /**
       * Creates a new buffered character-output stream that uses an output
       * buffer of the given size.
--- 66,53 ---
   * @author      Mark Reinhold
   * @since       1.1
   */
  
  public class BufferedWriter extends Writer {
+     private static final int DEFAULT_INITIAL_BUFFER_SIZE = 512;
+     private static final int DEFAULT_MAX_BUFFER_SIZE = 8192;
  
      private Writer out;
  
      private char cb[];
      private int nChars, nextChar;
+     private final int maxChars;  // maximum number of buffers chars
  
!     /**
+      * 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 buffered character-output stream.
+      */
+     private BufferedWriter(Writer out, int initialSize, int maxSize) {
+         Objects.requireNonNull(out);
+         if (initialSize <= 0) {
+             throw new IllegalArgumentException("Buffer size <= 0");
+         }
+ 
+         this.out = out;
+         this.cb = new char[initialSize];
+         this.nChars = initialSize;
+         this.maxChars = maxSize;
+     }
  
      /**
       * Creates a buffered character-output stream that uses a default-sized
       * output buffer.
       *
       * @param  out  A Writer
       */
      public BufferedWriter(Writer out) {
!         this(out, initialBufferSize(), DEFAULT_MAX_BUFFER_SIZE);
      }
  
      /**
       * Creates a new buffered character-output stream that uses an output
       * buffer of the given size.

*** 90,54 ***
       * @param  sz   Output-buffer size, a positive integer
       *
       * @throws     IllegalArgumentException  If {@code sz <= 0}
       */
      public BufferedWriter(Writer out, int sz) {
!         super(out);
-         if (sz <= 0)
-             throw new IllegalArgumentException("Buffer size <= 0");
-         this.out = out;
-         cb = new char[sz];
-         nChars = sz;
-         nextChar = 0;
      }
  
      /** Checks to make sure that the stream has not been closed */
      private void ensureOpen() throws IOException {
          if (out == null)
              throw new IOException("Stream closed");
      }
  
      /**
       * Flushes the output buffer to the underlying character stream, without
       * flushing the stream itself.  This method is non-private only so that it
       * may be invoked by PrintStream.
       */
      void flushBuffer() throws IOException {
!         synchronized (lock) {
!             ensureOpen();
!             if (nextChar == 0)
!                 return;
!             out.write(cb, 0, nextChar);
!             nextChar = 0;
          }
      }
  
      /**
       * Writes a single character.
       *
       * @throws     IOException  If an I/O error occurs
       */
      public void write(int c) throws IOException {
!         synchronized (lock) {
!             ensureOpen();
!             if (nextChar >= nChars)
!                 flushBuffer();
!             cb[nextChar++] = (char) c;
          }
      }
  
      /**
       * Our own little min method, to avoid loading java.lang.Math if we've run
       * out of file descriptors and we're trying to print a stack trace.
       */
      private int min(int a, int b) {
--- 121,91 ---
       * @param  sz   Output-buffer size, a positive integer
       *
       * @throws     IllegalArgumentException  If {@code sz <= 0}
       */
      public BufferedWriter(Writer out, int sz) {
!         this(out, sz, sz);
      }
  
      /** Checks to make sure that the stream has not been closed */
      private void ensureOpen() throws IOException {
          if (out == null)
              throw new IOException("Stream closed");
      }
  
+     /**
+      * Grow char array to fit an additional len characters if needed.
+      * If possible, it grows by len+1 to avoid flushing when len chars
+      * are added.
+      */
+     private void growIfNeeded(int len) {
+         int neededSize = nextChar + len + 1;
+         if (neededSize > nChars && nChars < maxChars) {
+             int newSize = min(neededSize, maxChars);
+             cb = Arrays.copyOf(cb, newSize);
+             nChars = newSize;
+         }
+     }
+ 
      /**
       * Flushes the output buffer to the underlying character stream, without
       * flushing the stream itself.  This method is non-private only so that it
       * may be invoked by PrintStream.
       */
      void flushBuffer() throws IOException {
!         Object lock = this.lock;
!         if (lock instanceof InternalLock locker) {
!             locker.lock();
!             try {
!                 lockedFlushBuffer();
!             } finally {
+                 locker.unlock();
+             }
+         } else {
+             synchronized (lock) {
+                 lockedFlushBuffer();
+             }
          }
      }
  
+     private void lockedFlushBuffer() throws IOException {
+         ensureOpen();
+         if (nextChar == 0)
+             return;
+         out.write(cb, 0, nextChar);
+         nextChar = 0;
+     }
+ 
      /**
       * Writes a single character.
       *
       * @throws     IOException  If an I/O error occurs
       */
      public void write(int c) throws IOException {
!         Object lock = this.lock;
!         if (lock instanceof InternalLock locker) {
!             locker.lock();
!             try {
!                 lockedWrite(c);
+             } finally {
+                 locker.unlock();
+             }
+         } else {
+             synchronized (lock) {
+                 lockedWrite(c);
+             }
          }
      }
  
+     private void lockedWrite(int c) throws IOException {
+         ensureOpen();
+         growIfNeeded(1);
+         if (nextChar >= nChars)
+             flushBuffer();
+         cb[nextChar++] = (char) c;
+     }
+ 
      /**
       * Our own little min method, to avoid loading java.lang.Math if we've run
       * out of file descriptors and we're trying to print a stack trace.
       */
      private int min(int a, int b) {

*** 165,36 ***
       *          of the given array
       *
       * @throws  IOException  If an I/O error occurs
       */
      public void write(char[] cbuf, int off, int len) throws IOException {
!         synchronized (lock) {
!             ensureOpen();
!             if ((off < 0) || (off > cbuf.length) || (len < 0) ||
!                 ((off + len) > cbuf.length) || ((off + len) < 0)) {
!                 throw new IndexOutOfBoundsException();
!             } else if (len == 0) {
!                 return;
              }
! 
!             if (len >= nChars) {
!                 /* If the request length exceeds the size of the output buffer,
-                    flush the buffer and then write the data directly.  In this
-                    way buffered streams will cascade harmlessly. */
-                 flushBuffer();
-                 out.write(cbuf, off, len);
-                 return;
              }
  
!             int b = off, t = off + len;
!             while (b < t) {
!                 int d = min(nChars - nextChar, t - b);
!                 System.arraycopy(cbuf, b, cb, nextChar, d);
!                 b += d;
!                 nextChar += d;
!                 if (nextChar >= nChars)
!                     flushBuffer();
              }
          }
      }
  
      /**
--- 233,50 ---
       *          of the given array
       *
       * @throws  IOException  If an I/O error occurs
       */
      public void write(char[] cbuf, int off, int len) throws IOException {
!         Object lock = this.lock;
!         if (lock instanceof InternalLock locker) {
!             locker.lock();
!             try {
!                 lockedWrite(cbuf, off, len);
!             } finally {
!                 locker.unlock();
              }
!         } else {
!             synchronized (lock) {
!                 lockedWrite(cbuf, off, len);
              }
+         }
+     }
+ 
+     private void lockedWrite(char[] cbuf, int off, int len) throws IOException {
+         ensureOpen();
+         Objects.checkFromIndexSize(off, len, cbuf.length);
+         if (len == 0) {
+             return;
+         }
+ 
+         if (len >= maxChars) {
+             /* If the request length exceeds the max size of the output buffer,
+                flush the buffer and then write the data directly.  In this
+                way buffered streams will cascade harmlessly. */
+             flushBuffer();
+             out.write(cbuf, off, len);
+             return;
+         }
  
!         growIfNeeded(len);
!         int b = off, t = off + len;
!         while (b < t) {
!             int d = min(nChars - nextChar, t - b);
!             System.arraycopy(cbuf, b, cb, nextChar, d);
!             b += d;
!             nextChar += d;
!             if (nextChar >= nChars) {
+                 flushBuffer();
              }
          }
      }
  
      /**

*** 218,25 ***
       *          of the given string
       *
       * @throws  IOException  If an I/O error occurs
       */
      public void write(String s, int off, int len) throws IOException {
!         synchronized (lock) {
!             ensureOpen();
! 
!             int b = off, t = off + len;
!             while (b < t) {
!                 int d = min(nChars - nextChar, t - b);
!                 s.getChars(b, b + d, cb, nextChar);
!                 b += d;
!                 nextChar += d;
!                 if (nextChar >= nChars)
!                     flushBuffer();
              }
          }
      }
  
      /**
       * Writes a line separator.  The line separator string is defined by the
       * system property {@code line.separator}, and is not necessarily a single
       * newline ('\n') character.
       *
--- 300,39 ---
       *          of the given string
       *
       * @throws  IOException  If an I/O error occurs
       */
      public void write(String s, int off, int len) throws IOException {
!         Object lock = this.lock;
!         if (lock instanceof InternalLock locker) {
!             locker.lock();
!             try {
!                 lockedWrite(s, off, len);
!             } finally {
!                 locker.unlock();
!             }
!         } else {
!             synchronized (lock) {
!                 lockedWrite(s, off, len);
              }
          }
      }
  
+     private void lockedWrite(String s, int off, int len) throws IOException {
+         ensureOpen();
+         growIfNeeded(len);
+         int b = off, t = off + len;
+         while (b < t) {
+             int d = min(nChars - nextChar, t - b);
+             s.getChars(b, b + d, cb, nextChar);
+             b += d;
+             nextChar += d;
+             if (nextChar >= nChars)
+                 flushBuffer();
+         }
+     }
+ 
      /**
       * Writes a line separator.  The line separator string is defined by the
       * system property {@code line.separator}, and is not necessarily a single
       * newline ('\n') character.
       *

*** 250,26 ***
       * Flushes the stream.
       *
       * @throws     IOException  If an I/O error occurs
       */
      public void flush() throws IOException {
!         synchronized (lock) {
!             flushBuffer();
!             out.flush();
          }
      }
  
!     @SuppressWarnings("try")
      public void close() throws IOException {
!         synchronized (lock) {
!             if (out == null) {
!                 return;
!             }
!             try (Writer w = out) {
-                 flushBuffer();
              } finally {
!                 out = null;
!                 cb = null;
              }
          }
      }
  }
--- 346,54 ---
       * Flushes the stream.
       *
       * @throws     IOException  If an I/O error occurs
       */
      public void flush() throws IOException {
!         Object lock = this.lock;
!         if (lock instanceof InternalLock locker) {
!             locker.lock();
+             try {
+                 lockedFlush();
+             } finally {
+                 locker.unlock();
+             }
+         } else {
+             synchronized (lock) {
+                 lockedFlush();
+             }
          }
      }
  
!     private void lockedFlush() throws IOException {
+         flushBuffer();
+         out.flush();
+     }
+ 
      public void close() throws IOException {
!         Object lock = this.lock;
!         if (lock instanceof InternalLock locker) {
!             locker.lock();
!             try {
!                 lockedClose();
              } finally {
!                 locker.unlock();
!             }
+         } else {
+             synchronized (lock) {
+                 lockedClose();
              }
          }
      }
+ 
+     @SuppressWarnings("try")
+     private void lockedClose() throws IOException {
+         if (out == null) {
+             return;
+         }
+         try (Writer w = out) {
+             flushBuffer();
+         } finally {
+             out = null;
+             cb = null;
+         }
+     }
  }
< prev index next >