src/share/classes/java/lang/String.java

Print this page
rev 6546 : 7197183: Alternate implementation of String.subSequence which uses shared backing array.
Reviewed-by: duke

@@ -23,10 +23,11 @@
  * questions.
  */
 
 package java.lang;
 
+import java.io.ObjectStreamException;
 import java.io.ObjectStreamField;
 import java.io.UnsupportedEncodingException;
 import java.nio.charset.Charset;
 import java.util.ArrayList;
 import java.util.Arrays;

@@ -108,11 +109,11 @@
  */
 
 public final class String
     implements java.io.Serializable, Comparable<String>, CharSequence {
     /** The value is used for character storage. */
-    private final char value[];
+    final char value[];
 
     /** Cache the hash code for the string */
     private int hash; // Default to 0
 
     /** use serialVersionUID from JDK 1.0.2 for interoperability */

@@ -963,10 +964,11 @@
      *          equivalent to this string, {@code false} otherwise
      *
      * @see  #compareTo(String)
      * @see  #equalsIgnoreCase(String)
      */
+    @Override
     public boolean equals(Object anObject) {
         if (this == anObject) {
             return true;
         }
         if (anObject instanceof String) {

@@ -981,10 +983,13 @@
                             return false;
                     i++;
                 }
                 return true;
             }
+        } else if (anObject instanceof SubSequence) {
+            // turn the tables to keep this method smaller.
+            return anObject.equals(this);
         }
         return false;
     }
 
     /**

@@ -1157,10 +1162,22 @@
             k++;
         }
         return len1 - len2;
     }
 
+// This is the method we want instead of the default bridge.
+//    public int compareTo(Object other) {
+//        if(other instanceof String) {
+//            return compareTo((String) other);
+//        } else if (other instanceof SubSequence) {
+//            // delegate to keep this method small.
+//            return - ((SubSequence) other).compareTo(this);
+//        } else {
+//            throw new ClassCastException();
+//        }
+//    }
+//
     /**
      * A Comparator that orders {@code String} objects as by
      * {@code compareToIgnoreCase}. This comparator is serializable.
      * <p>
      * Note that this Comparator does <em>not</em> take locale into account,

@@ -1960,22 +1977,11 @@
     }
 
     /**
      * Returns a new character sequence that is a subsequence of this sequence.
      *
-     * <p> An invocation of this method of the form
-     *
-     * <blockquote><pre>
-     * str.subSequence(begin,&nbsp;end)</pre></blockquote>
-     *
-     * behaves in exactly the same way as the invocation
-     *
-     * <blockquote><pre>
-     * str.substring(begin,&nbsp;end)</pre></blockquote>
-     *
-     * This method is defined so that the {@code String} class can implement
-     * the {@link CharSequence} interface. </p>
+     * @implNote The character sequence refers to the original String.
      *
      * @param   beginIndex   the begin index, inclusive.
      * @param   endIndex     the end index, exclusive.
      * @return  the specified subsequence.
      *

@@ -1985,12 +1991,214 @@
      *          or if {@code beginIndex} is greater than {@code endIndex}
      *
      * @since 1.4
      * @spec JSR-51
      */
+    @Override
     public CharSequence subSequence(int beginIndex, int endIndex) {
-        return this.substring(beginIndex, endIndex);
+        if (beginIndex < 0) {
+            throw new StringIndexOutOfBoundsException(beginIndex);
+        }
+        if (endIndex > value.length) {
+            throw new StringIndexOutOfBoundsException(endIndex);
+        }
+        int subLen = endIndex - beginIndex;
+        if (subLen < 0) {
+            throw new StringIndexOutOfBoundsException(subLen);
+        }
+
+        return (subLen == value.length)
+                ? this
+                : new SubSequence(this, beginIndex, subLen);
+    }
+
+    /**
+     * A CharSequence implemented as a sub-sequence of a String.
+     */
+    private final static class SubSequence implements
+        java.io.Serializable, CharSequence, Comparable<CharSequence> {
+
+        /**
+         *  The String of which we are a sub-sequence.
+         */
+        private final String source;
+
+        /**
+         * The offset within the String of our first character.
+         */
+        private final int offset;
+
+        /**
+         * The number of characters in this sub-sequence.
+         */
+        private final int count;
+
+        /**
+         * Cached hash code value.
+         */
+        private int hashCache = 0;
+
+        /**
+         * Construct a new sub-sequence.
+         *
+         * @implNote Input values are not validated.
+         *
+         * @param source The String of which we are a sub-sequence.
+         * @param offset The offset within the String of our first character.
+         * @param count The number of characters in this sub-sequence.
+         */
+        SubSequence(String source, int offset, int count) {
+            this.source = source;
+            this.offset = offset;
+            this.count = count;
+        }
+
+        @Override
+        public boolean equals(Object other) {
+            if (other == this) {
+                // it's me!
+                return true;
+            }
+
+            final char[] val1 = source.value;
+            int offset1 = offset;
+            final char[] val2;
+            int offset2;
+            int each;
+            if (other instanceof SubSequence) {
+                SubSequence likeMe = (SubSequence)other;
+                val2 = likeMe.source.value;
+                offset2 = likeMe.offset;
+                each = likeMe.count;
+            } else if (other instanceof String) {
+                String similar = (String)other;
+                val2 = similar.value;
+                offset2 = 0;
+                each = similar.value.length;
+            } else {
+                // not of recognized type.
+                return false;
+            }
+
+            if (each != count) {
+                // not the same length
+                return false;
+            }
+
+            offset1 += each;
+            offset2 += each;
+            while (--each >= 0) {
+                if (val1[--offset1] != val2[--offset2]) {
+                    // unequal char
+                    return false;
+                }
+            }
+
+            // chars were all equal.
+            return true;
+        }
+
+        /**
+         * Return the hash code value for this object.
+         *
+         * @implSpec The hash code of a SubSequence is the same as that of a
+         * String containing the same characters.
+         *
+         * @return a hash code value for this object.
+         */
+        @Override
+        public int hashCode() {
+            int h = hashCache;
+            if (h == 0 && count > 0) {
+                char val[] = source.value; // avoid getfield opcode
+                for (int i = 0; i < count; i++) {
+                    h = 31 * h + val[offset + i];
+                }
+
+                // harmless data race updating hashCache.
+                hashCache = h;
+            }
+
+            return h;
+        }
+
+        @Override
+        public String toString() {
+            return new String(source.value, offset, count);
+        }
+
+        public boolean isEmpty() {
+            return count == 0;
+        }
+
+
+        @Override
+        public int compareTo(CharSequence other) {
+            int otherLen = other.length();
+            for(int each=0; each < count; each++) {
+                if(each >= otherLen) {
+                    return 1;
+                }
+
+                int diff = other.charAt(each) - source.value[offset+each];
+
+                if(0 == diff) {
+                    continue;
+                }
+
+                return diff;
+            }
+
+            return (otherLen > count) ? -1 : 0;
+        }
+
+        @Override
+        public int length() {
+            return count;
+        }
+
+        @Override
+        public char charAt(int index) {
+            if(index < 0 || index >= count) {
+                throw new IndexOutOfBoundsException();
+            }
+            return source.value[offset+index];
+        }
+
+        @Override
+        public CharSequence subSequence(int start, int end) {
+            int len = end - start;
+            if (start < 0 ||
+                end < start ||
+                len > count) {
+                throw new IndexOutOfBoundsException();
+            }
+
+            if (0 == len) {
+                // it's empty.
+                return new String();
+            }
+
+            if(start == 0 && len == count) {
+                // exactly the same sequence.
+                return this;
+            }
+
+            // create an even smaller sub-sequence
+            return new SubSequence(source, offset+start, len);
+        }
+
+        /**
+         * {@inheritDoc}
+         *
+         * @implNote We replace this sub-sequence with a String containing the
+         * same sequence of characters.
+         */
+        public Object writeReplace() throws ObjectStreamException {
+            // It's better to just replace with string.
+            return toString();
+        }
     }
 
     /**
      * Concatenates the specified string to the end of this string.
      * <p>