8164278: java.util.Base64.EncOutputStream/DecInputStream is slower than corresponding version in javax.mail package
authorsherman
Wed, 14 Feb 2018 14:13:42 -0800
changeset 48898 fdd4a131d766
parent 48897 3f19b5965355
child 48899 e9676e9ca3d6
8164278: java.util.Base64.EncOutputStream/DecInputStream is slower than corresponding version in javax.mail package Reviewed-by: rriggs
src/java.base/share/classes/java/util/Base64.java
--- a/src/java.base/share/classes/java/util/Base64.java	Wed Feb 14 19:03:12 2018 +0100
+++ b/src/java.base/share/classes/java/util/Base64.java	Wed Feb 14 14:13:42 2018 -0800
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2012, 2016, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2012, 2018, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -693,6 +693,25 @@
             int bits = 0;
             int shiftto = 18;       // pos of first byte of 4-byte atom
             while (sp < sl) {
+                if (bits == 0 && sp + 4 < sl) {           // fast path
+                    int sl0 = sp + ((sl - sp) & ~0b11);
+                    while (sp < sl0) {
+                        int b1 = base64[src[sp++] & 0xff];
+                        int b2 = base64[src[sp++] & 0xff];
+                        int b3 = base64[src[sp++] & 0xff];
+                        int b4 = base64[src[sp++] & 0xff];
+                        if ((b1 | b2 | b3 | b4) < 0) {    // non base64 byte
+                            sp -= 4;
+                            break;
+                        }
+                        int bits0 = b1 << 18 | b2 << 12 | b3 << 6 | b4;
+                        dst[dp++] = (byte)(bits0 >> 16);
+                        dst[dp++] = (byte)(bits0 >>  8);
+                        dst[dp++] = (byte)(bits0);
+                    }
+                    if (sp >= sl)
+                        break;
+                }
                 int b = src[sp++] & 0xff;
                 if ((b = base64[b]) < 0) {
                     if (b == -2) {         // padding byte '='
@@ -762,6 +781,7 @@
         private final int linemax;
         private final boolean doPadding;// whether or not to pad
         private int linepos = 0;
+        private byte[] buf;
 
         EncOutputStream(OutputStream os, char[] base64,
                         byte[] newline, int linemax, boolean doPadding) {
@@ -770,6 +790,7 @@
             this.newline = newline;
             this.linemax = linemax;
             this.doPadding = doPadding;
+            this.buf = new byte[linemax <= 0 ? 8124 : linemax];
         }
 
         @Override
@@ -786,6 +807,14 @@
             }
         }
 
+        private void writeb4(char b1, char b2, char b3, char b4) throws IOException {
+            buf[0] = (byte)b1;
+            buf[1] = (byte)b2;
+            buf[2] = (byte)b3;
+            buf[3] = (byte)b4;
+            out.write(buf, 0, 4);
+        }
+
         @Override
         public void write(byte[] b, int off, int len) throws IOException {
             if (closed)
@@ -806,25 +835,34 @@
                 b2 = b[off++] & 0xff;
                 len--;
                 checkNewline();
-                out.write(base64[b0 >> 2]);
-                out.write(base64[(b0 << 4) & 0x3f | (b1 >> 4)]);
-                out.write(base64[(b1 << 2) & 0x3f | (b2 >> 6)]);
-                out.write(base64[b2 & 0x3f]);
+                writeb4(base64[b0 >> 2],
+                        base64[(b0 << 4) & 0x3f | (b1 >> 4)],
+                        base64[(b1 << 2) & 0x3f | (b2 >> 6)],
+                        base64[b2 & 0x3f]);
                 linepos += 4;
             }
             int nBits24 = len / 3;
             leftover = len - (nBits24 * 3);
-            while (nBits24-- > 0) {
+
+            while (nBits24 > 0) {
                 checkNewline();
-                int bits = (b[off++] & 0xff) << 16 |
-                           (b[off++] & 0xff) <<  8 |
-                           (b[off++] & 0xff);
-                out.write(base64[(bits >>> 18) & 0x3f]);
-                out.write(base64[(bits >>> 12) & 0x3f]);
-                out.write(base64[(bits >>> 6)  & 0x3f]);
-                out.write(base64[bits & 0x3f]);
-                linepos += 4;
-           }
+                int dl = linemax <= 0 ? buf.length : buf.length - linepos;
+                int sl = off + Math.min(nBits24, dl / 4) * 3;
+                int dp = 0;
+                for (int sp = off; sp < sl; ) {
+                    int bits = (b[sp++] & 0xff) << 16 |
+                               (b[sp++] & 0xff) <<  8 |
+                               (b[sp++] & 0xff);
+                    buf[dp++] = (byte)base64[(bits >>> 18) & 0x3f];
+                    buf[dp++] = (byte)base64[(bits >>> 12) & 0x3f];
+                    buf[dp++] = (byte)base64[(bits >>> 6)  & 0x3f];
+                    buf[dp++] = (byte)base64[bits & 0x3f];
+                }
+                out.write(buf, 0, dp);
+                off = sl;
+                linepos += dp;
+                nBits24 -= dp / 4;
+            }
             if (leftover == 1) {
                 b0 = b[off++] & 0xff;
             } else if (leftover == 2) {
@@ -889,6 +927,52 @@
             return read(sbBuf, 0, 1) == -1 ? -1 : sbBuf[0] & 0xff;
         }
 
+        private int eof(byte[] b, int off, int len, int oldOff)
+            throws IOException
+        {
+            eof = true;
+            if (nextin != 18) {
+                if (nextin == 12)
+                    throw new IOException("Base64 stream has one un-decoded dangling byte.");
+                // treat ending xx/xxx without padding character legal.
+                // same logic as v == '=' below
+                b[off++] = (byte)(bits >> (16));
+                if (nextin == 0) {           // only one padding byte
+                    if (len == 1) {          // no enough output space
+                        bits >>= 8;          // shift to lowest byte
+                        nextout = 0;
+                    } else {
+                        b[off++] = (byte) (bits >>  8);
+                    }
+                }
+            }
+            return off == oldOff ? -1 : off - oldOff;
+        }
+
+        private int padding(byte[] b, int off, int len, int oldOff)
+            throws IOException
+        {
+            // =     shiftto==18 unnecessary padding
+            // x=    shiftto==12 dangling x, invalid unit
+            // xx=   shiftto==6 && missing last '='
+            // xx=y  or last is not '='
+            if (nextin == 18 || nextin == 12 ||
+                nextin == 6 && is.read() != '=') {
+                throw new IOException("Illegal base64 ending sequence:" + nextin);
+            }
+            b[off++] = (byte)(bits >> (16));
+            if (nextin == 0) {           // only one padding byte
+                if (len == 1) {          // no enough output space
+                    bits >>= 8;          // shift to lowest byte
+                    nextout = 0;
+                } else {
+                    b[off++] = (byte) (bits >>  8);
+                }
+            }
+            eof = true;
+            return off - oldOff;
+        }
+
         @Override
         public int read(byte[] b, int off, int len) throws IOException {
             if (closed)
@@ -898,82 +982,46 @@
             if (off < 0 || len < 0 || len > b.length - off)
                 throw new IndexOutOfBoundsException();
             int oldOff = off;
-            if (nextout >= 0) {       // leftover output byte(s) in bits buf
-                do {
-                    if (len == 0)
-                        return off - oldOff;
-                    b[off++] = (byte)(bits >> nextout);
-                    len--;
-                    nextout -= 8;
-                } while (nextout >= 0);
-                bits = 0;
+            while (nextout >= 0) {       // leftover output byte(s) in bits buf
+                if (len == 0)
+                    return off - oldOff;
+                b[off++] = (byte)(bits >> nextout);
+                len--;
+                nextout -= 8;
             }
+            bits = 0;
             while (len > 0) {
                 int v = is.read();
                 if (v == -1) {
-                    eof = true;
-                    if (nextin != 18) {
-                        if (nextin == 12)
-                            throw new IOException("Base64 stream has one un-decoded dangling byte.");
-                        // treat ending xx/xxx without padding character legal.
-                        // same logic as v == '=' below
-                        b[off++] = (byte)(bits >> (16));
-                        len--;
-                        if (nextin == 0) {           // only one padding byte
-                            if (len == 0) {          // no enough output space
-                                bits >>= 8;          // shift to lowest byte
-                                nextout = 0;
-                            } else {
-                                b[off++] = (byte) (bits >>  8);
-                            }
-                        }
-                    }
-                    if (off == oldOff)
-                        return -1;
-                    else
-                        return off - oldOff;
+                    return eof(b, off, len, oldOff);
                 }
-                if (v == '=') {                  // padding byte(s)
-                    // =     shiftto==18 unnecessary padding
-                    // x=    shiftto==12 dangling x, invalid unit
-                    // xx=   shiftto==6 && missing last '='
-                    // xx=y  or last is not '='
-                    if (nextin == 18 || nextin == 12 ||
-                        nextin == 6 && is.read() != '=') {
-                        throw new IOException("Illegal base64 ending sequence:" + nextin);
+                if ((v = base64[v]) < 0) {
+                    if (v == -2) {       // padding byte(s)
+                        return padding(b, off, len, oldOff);
                     }
-                    b[off++] = (byte)(bits >> (16));
-                    len--;
-                    if (nextin == 0) {           // only one padding byte
-                        if (len == 0) {          // no enough output space
-                            bits >>= 8;          // shift to lowest byte
-                            nextout = 0;
-                        } else {
-                            b[off++] = (byte) (bits >>  8);
-                        }
+                    if (v == -1) {
+                        if (!isMIME)
+                            throw new IOException("Illegal base64 character " +
+                                Integer.toString(v, 16));
+                        continue;        // skip if for rfc2045
                     }
-                    eof = true;
-                    break;
-                }
-                if ((v = base64[v]) == -1) {
-                    if (isMIME)                 // skip if for rfc2045
-                        continue;
-                    else
-                        throw new IOException("Illegal base64 character " +
-                            Integer.toString(v, 16));
+                    // neve be here
                 }
                 bits |= (v << nextin);
                 if (nextin == 0) {
-                    nextin = 18;    // clear for next
-                    nextout = 16;
-                    while (nextout >= 0) {
-                        b[off++] = (byte)(bits >> nextout);
-                        len--;
-                        nextout -= 8;
-                        if (len == 0 && nextout >= 0) {  // don't clean "bits"
-                            return off - oldOff;
-                        }
+                    nextin = 18;         // clear for next in
+                    b[off++] = (byte)(bits >> 16);
+                    if (len == 1) {
+                        nextout = 8;    // 2 bytes left in bits
+                        break;
                     }
+                    b[off++] = (byte)(bits >> 8);
+                    if (len == 2) {
+                        nextout = 0;    // 1 byte left in bits
+                        break;
+                    }
+                    b[off++] = (byte)bits;
+                    len -= 3;
                     bits = 0;
                 } else {
                     nextin -= 6;