< 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 +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
  
-     private static int defaultCharBufferSize = 8192;
+     /**
+      * 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, defaultCharBufferSize);
+         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 +121,91 @@
       * @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;
+         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 {
-         synchronized (lock) {
-             ensureOpen();
-             if (nextChar == 0)
-                 return;
-             out.write(cb, 0, nextChar);
-             nextChar = 0;
+         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 {
-         synchronized (lock) {
-             ensureOpen();
-             if (nextChar >= nChars)
-                 flushBuffer();
-             cb[nextChar++] = (char) c;
+         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 +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 {
-         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;
+         Object lock = this.lock;
+         if (lock instanceof InternalLock locker) {
+             locker.lock();
+             try {
+                 lockedWrite(cbuf, off, len);
+             } finally {
+                 locker.unlock();
              }
- 
-             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;
+         } 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;
+         }
  
-             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();
+         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 +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 {
-         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();
+         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 +346,54 @@
       * Flushes the stream.
       *
       * @throws     IOException  If an I/O error occurs
       */
      public void flush() throws IOException {
-         synchronized (lock) {
-             flushBuffer();
-             out.flush();
+         Object lock = this.lock;
+         if (lock instanceof InternalLock locker) {
+             locker.lock();
+             try {
+                 lockedFlush();
+             } finally {
+                 locker.unlock();
+             }
+         } else {
+             synchronized (lock) {
+                 lockedFlush();
+             }
          }
      }
  
-     @SuppressWarnings("try")
+     private void lockedFlush() throws IOException {
+         flushBuffer();
+         out.flush();
+     }
+ 
      public void close() throws IOException {
-         synchronized (lock) {
-             if (out == null) {
-                 return;
-             }
-             try (Writer w = out) {
-                 flushBuffer();
+         Object lock = this.lock;
+         if (lock instanceof InternalLock locker) {
+             locker.lock();
+             try {
+                 lockedClose();
              } finally {
-                 out = null;
-                 cb = null;
+                 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 >