精华内容
下载资源
问答
  • Java String 源码解析

    2021-01-20 03:08:16
    1.String 内部存储结构是? 答:String 内部存储...2.String源码中有哪些重要的构造方法? 答:4个重要的构造方法,源码如下 (1). public String(String original) { this.value = original.value; this.hash = o
  • Java String源码解析

    千次阅读 2016-05-30 10:14:31
    String类概要 所有的字符串字面量都属于String类,String对象创建后不可改变,...Java语言提供了对字符串连接运算符的特别支持(+),该符号也可用于将其他类型转换成字符串。 字符串的连接实际上是通过StringBuffer或

    String类概要

    • 所有的字符串字面量都属于String类,String对象创建后不可改变,因此可以缓存共享,StringBuilder,StringBuffer是可变的实现
    • String类提供了操作字符序列中单个字符的方法,比如有比较字符串,搜索字符串等
    • Java语言提供了对字符串连接运算符的特别支持(+),该符号也可用于将其他类型转换成字符串。
    • 字符串的连接实际上是通过StringBuffer或者StringBuilder的append()方法来实现的
    • 一般情况下,传递一个空参数在这类构造函数或方法会导致NullPointerException异常被抛出。
    • String表示一个字符串通过UTF-16(unicode)格式,补充字符通过代理对表示。索引值参考字符编码单元,所以补充字符在String中占两个位置。

    String是不可变的

    • String是常量,一旦被创建就不可被改变,因此可以用来共享

    从String的怪异现象讲起

    String是否相等

    ==判断的是对象的内存起始地址是否相同,equals判断自定义的语义是否相同

    • JVM为了提高内存效率,将所有不可变的字符串缓存在常量池中,当有新的不可变的字符串需要创建时,如果常量池中存在相等的字符串就直接将引用指向已有的字符串常量,而不会创建新对象
    • new创建的对象存储在堆内存,不可能与常量区的对象具有相同地址
    • 直接用字面量初始化String要比用new 关键字创建String对象效率更高
    public class Demo {
        public static void main(String[] args) throws Exception {
            String s = "abc";
            String s1 = "abc";
            String s2 = "a" + "bc";
            final String str1 = "a";
            final String str2 = "bc";
            String s3 = str1 + str2;
            String s4 = new String("abc");
            System.out.println(s == s1);
            System.out.println(s == s2);
            System.out.println(s == s3);
            System.out.println(s == s4);
        }
    } //结果:true    true    true    false

    为什么String不可变

    final修饰变量,如果是基本类型那么内容运行期间不可变,如果是引用类型那么引用的对象(包括数组)运行期地址不可变,但是对象(数组)的内容是可以改变的

    • final只是保证value不会指向其他的数组,但不保证数组内容不可修改
    • private属性保证了不可以在类外访问数组,也就不能改变其内容
    • String内部没有改变value内容的函数,所以String就不可变了
    • String声明为final杜绝了通过继承的方法添加新的函数
    • 基于数组的构造方法,会拷贝数组元素,从而避免了通过外部引用修改value的情况
    • 用String构造其他可变对象时,涉及的数组只是返回的数组的拷贝而不是原数组,例如 new StringBuilder(str),会把str数组进行拷贝后传递给StringBuilder而不是传递原数组

    当然只要类库设计人愿意,只要增加一个类似的setCharAt(index)的接口,String就变成可变的了

        private final char value[];
        private int hash; // Default to 0  
        public String(char value[]) {
            this.value = Arrays.copyOf(value, value.length);
        }  

    通过反射改变String

    • final 只在编译器有效,在运行期间无效,因此可以通过反射改变value引用的对象
    • s与str始终具有相同的内存地址,反射改变了s的内容,并没有新创建对象
    • s 与 s1对应常量池中的两个对象,所以即便通过反射修改了s的内容,他们两个的内存地址还是不同的
    public class Demo {
        public static void main(String[] args) throws Exception {
            String s = "abc";
            String str = s;
            String s1 = "bbb";
            System.out.println(str == s);
            Field f = s.getClass().getDeclaredField("value");
            f.setAccessible(true);
            f.set(s, new char[]{'b', 'b', 'b'});
            System.out.println(str + "    " + s);
            System.out.println(s == str);
            System.out.println(s == s1);
        }
    }  //结果:bbb    bbb    true    false

    String的HashCode

    s的内容改变了但是hashCode值并没有改变,虽然s与s1的内容是相同的但是他们hashCode值并不相同

    • Object的hashCode方法返回的是16进制内存地址,String类重写了hashCode的,hashCode值的计算是基于字符串内容的
    • String的hashCode值初始为0,由于String是不可变的,当第一次运行完hashCode方法后String类对HashCode值进行了缓存,下一次在调用时直接返回hash值
    public class Demo {
        public static void main(String[] args) throws Exception {
            String s = "abc";
            String s1 = "bbb";
            System.out.println(s.hashCode());
            Field f = s.getClass().getDeclaredField("value");
            f.setAccessible(true);
            f.set(s, new char[]{'b', 'b', 'b'});
            System.out.println(s + "    "+ s1);
            System.out.println(s.hashCode() +" " +s1.hashCode());
        }
    }  //结果:96354    bbb    bbb    96354 97314

    String hashCode的源码

        public int hashCode() {
            int h = hash;
            if (h == 0 && value.length > 0) {
                char val[] = value;
                for (int i = 0; i < value.length; i++) {
                    h = 31 * h + val[i];
                }
                hash = h;
            }
            return h;
        }  

    toString方法中的this

    • Java为String类重载了“+”操作符,String类与其他类对象进行连接时会调用其他类的toString方法
    • 如果在其他类的toString方法中用“+”对this进行连接就会出现无限递归调用而出现栈溢出错误
    • 解决方法将this换做super.this
    public class Demo {
        @Override
        public String toString() {
            //会造成递归调用
    //        return "address"+super.toString();
            return "address"+super.toString();
        }
        public static void main(String[] args) {
            System.out.println(new Demo());
        }
    }  

    CodePoints与CodeUnit

    String的length表示的是代码单元的个数,而不是字符的个数

    • codePoints是代码点, 表示的是例如’A’, ‘王’ 这种字符,每种字符都有一个唯一的数字编号,这个数字编号就叫unicode code point。目前code point的数值范围是0~0x10FFFF。
    • codeUnit是代码单元, 它根据编码不同而不同, 可以理解为是字符编码的基本单元,java中的char是两个字节, 也就是16位的。这样也反映了一个char只能表示从u+0000~u+FFFF范围的unicode字符, 在这个范围的字符也叫BMP(basic Multiligual Plane ), 超出这个范围的叫增补字符,增补字符占用两个代码单元。
    public class Demo {
        public static void main(String[] args) {
            String s = "\u1D56B";
            System.out.println(s);
            System.out.println(s.length());
        }
    }  

    我们看看String是怎么处理增补字符的

    • 首先value字符数组的长度是根据代码单元来定的,每出现一个Surrogate字符数组长度在count的基础上加一
    • BMP字符直接存储,增补字符的用两个char分别存储高位和低位
        public String(int[] codePoints, int offset, int count) {
            if (offset < 0) {
                throw new StringIndexOutOfBoundsException(offset);
            }
            if (count < 0) {
                throw new StringIndexOutOfBoundsException(count);
            }
            // Note: offset or count might be near -1>>>1.
            if (offset > codePoints.length - count) {
                throw new StringIndexOutOfBoundsException(offset + count);
            }
            final int end = offset + count;
            // Pass 1: Compute precise size of char[]
            int n = count;
            for (int i = offset; i < end; i++) {
                int c = codePoints[i];
                if (Character.isBmpCodePoint(c))
                    continue;
                else if (Character.isValidCodePoint(c))
                    n++;
                else throw new IllegalArgumentException(Integer.toString(c));
            }
            // Pass 2: Allocate and fill in char[]
            final char[] v = new char[n];
            for (int i = offset, j = 0; i < end; i++, j++) {
                int c = codePoints[i];
                if (Character.isBmpCodePoint(c))
                    v[j] = (char)c;
                else
                    Character.toSurrogates(c, v, j++);
            }
            this.value = v;
        }  
        static void toSurrogates(int codePoint, char[] dst, int index) {
            // We write elements "backwards" to guarantee all-or-nothing
            dst[index+1] = lowSurrogate(codePoint);
            dst[index] = highSurrogate(codePoint);
        }  

    源码解析

    声明

    • String类可序列化,可比较,实现CharSequence接口提供了对字符的基本操作
    • String内部使用final字符数组进行存储,涉及value数组的操作都使用了拷贝数组元素的方法,保证了不能在外部修改字符数组
    • String重写了Object的hashCode函数使hash值基于字符数组内容,但是由于String缓存了hash值,所以即便通过反射改变了字符数组内容,hashhashCode返回值不会自动更新
    • serialVersionUID 用来确定类的版本是否正确,如果不是同一个类会抛出InvalidCastException异常
    public final class String
        implements java.io.Serializable, Comparable<String>, CharSequence { 
        private final char value[];
        private static final long serialVersionUID = -6849794470754667710L;  
        /** Cache the hash code for the string */
        private int hash; // Default to 0
        public int hashCode() {
            int h = hash;
            if (h == 0 && value.length > 0) {
                char val[] = value;
                for (int i = 0; i < value.length; i++) {
                    h = 31 * h + val[i];
                }
                hash = h;
            }
            return h;
        }  

    构造函数

    • String主要提供了通过String,StringBuilder,StringBuffer,char数组,int数组(CodePoint),byte数组(需要指定编码)进行初始化
    • 当通过字符串初始化字符串时,并没有执行value数组拷贝,因为original的value数组是不可以在外部修改的,也就保证了新String对象的不可修改
    • 通过字符数组,StringBuffer,StringBuilder进行初始化时,就要执行value数组元素的拷贝,创建新数组,防止外部对value内容的改变
    • 通过byte数组进行初始化,需要指定编码,或使用默认编码(ISO-8859-1),否则无法正确解释字节内容
    • 通过Unicode代码点进行的初始化,可能会包含非BMP字符(int值大于65535),这时候字符串的长度可能会长于int数组的长度,(见本文前面增补字符处理部分)
        public String(String original) {
            this.value = original.value;
            this.hash = original.hash;
        }  
        public String(StringBuffer buffer) {
            synchronized(buffer) {
                this.value = Arrays.copyOf(buffer.getValue(), buffer.length());
            }
        }
        public String(StringBuilder builder) {
            this.value = Arrays.copyOf(builder.getValue(), builder.length());
        }  
        public String(char value[]) {
            this.value = Arrays.copyOf(value, value.length);
        }  
        public String(char value[], int offset, int count) {
            if (offset < 0) {
                throw new StringIndexOutOfBoundsException(offset);
            }
            if (count < 0) {
                throw new StringIndexOutOfBoundsException(count);
            }
            // Note: offset or count might be near -1>>>1.
            if (offset > value.length - count) {
                throw new StringIndexOutOfBoundsException(offset + count);
            }
            this.value = Arrays.copyOfRange(value, offset, offset+count);
        } 
        public String(byte bytes[], int offset, int length, Charset charset) {
            if (charset == null)
                throw new NullPointerException("charset");
            checkBounds(bytes, offset, length);
            this.value =  StringCoding.decode(charset, bytes, offset, length);
        } 
        public String(byte bytes[], int offset, int length) {
            checkBounds(bytes, offset, length);
            this.value = StringCoding.decode(bytes, offset, length);
        }  
        static char[] decode(byte[] ba, int off, int len) {
            String csn = Charset.defaultCharset().name();
            try {
                // use charset name decode() variant which provides caching.
                return decode(csn, ba, off, len);
            } catch (UnsupportedEncodingException x) {
                warnUnsupportedCharset(csn);
            }
            try {
                return decode("ISO-8859-1", ba, off, len);
            } catch (UnsupportedEncodingException x) {
                // If this code is hit during VM initialization, MessageUtils is
                // the only way we will be able to get any kind of error message.
                MessageUtils.err("ISO-8859-1 charset not available: "
                                 + x.toString());
                // If we can not find ISO-8859-1 (a required encoding) then things
                // are seriously wrong with the installation.
                System.exit(1);
                return null;
            }
        } 

    内部构造函数

    使用外部数组来初始化String内部数组只有保证传入的数组不可能被改变才能保证String的不可变性,例如用String初始化String对象时

    • 这种方法使用共享value数组的方法避免了数组的拷贝,提高了效率
    • 上面分析指出如果直接使用外部传入的数组不能保证String的不可变性,这个方法只在String的内部使用,不能由外部调用
    • 添加share参数,只是为了重载构造函数,share必须为true
    • 该函数只用在不能缩短String长度的函数中,如concat(str1,str2),如果用在缩短String长度的函数如subString中会造成内存泄漏
        String(char[] value, boolean share) {
            // assert share : "unshared not supported";
            this.value = value;
        }  
        public String concat(String str) {
            int otherLen = str.length();
            if (otherLen == 0) {
                return this;
            }
            int len = value.length;
            char buf[] = Arrays.copyOf(value, len + otherLen);
            str.getChars(buf, len);
            return new String(buf, true);
        }  
            // 使用了Arrays.copyof方法来构造新的数组,拷贝元素,而不是共用数组
        public String substring(int beginIndex) {
            if (beginIndex < 0) {
                throw new StringIndexOutOfBoundsException(beginIndex);
            }
            int subLen = value.length - beginIndex;
            if (subLen < 0) {
                throw new StringIndexOutOfBoundsException(subLen);
            }
            return (beginIndex == 0) ? this : new String(value, beginIndex, subLen);
        }  

    如果String(value,share)可以在外部使用,就可以改变字符串内容

    public class Demo {
        public static void main(String[] args) {
            char[] arr = new char[] {'h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd'};
            String s = new String(arr,true);
            arr[0] = 'a';
            System.out.println(s);
        }
    } 

    aLongString 已经不用了,但是由于其与aPart共享value数组,所以不能被回收,造成内存泄漏

         public String subTest(){
            String aLongString = "...a very long string..."; 
            String aPart = aLongString.substring(20, 40);
            return aPart;
        }

    主要方法

    其他主要方法

    length() 返回字符串长度
    
    isEmpty() 返回字符串是否为空
    
    charAt(int index) 返回字符串中第(index+1)个字符
    
    char[] toCharArray() 转化成字符数组
    
    trim() 去掉两端空格
    
    toUpperCase() 转化为大写
    
    toLowerCase() 转化为小写
    
    String concat(String str) //拼接字符串
    
    String replace(char oldChar, char newChar) //将字符串中的oldChar字符换成newChar字符
    
    //以上两个方法都使用了String(char[] value, boolean share);
    
    boolean matches(String regex) //判断字符串是否匹配给定的regex正则表达式
    
    boolean contains(CharSequence s) //判断字符串是否包含字符序列s
    
    String[] split(String regex, int limit) 按照字符regex将字符串分成limit份。
    
    String[] split(String regex)
    

    重载的valueOf方法

    可以看到主要是调用构造函数或者是调用对应类型的toString完成到字符串的转换

        public static String valueOf(boolean b) {
            return b ? "true" : "false";
        }
        public static String valueOf(char c) {
            char data[] = {c};
            return new String(data, true);
        }
        public static String valueOf(int i) {
            return Integer.toString(i);
        }
        public static String valueOf(long l) {
            return Long.toString(l);
        }
        public static String valueOf(float f) {
            return Float.toString(f);
        }
        public static String valueOf(double d) {
            return Double.toString(d);
        }  
        public static String valueOf(char data[], int offset, int count) {
            return new String(data, offset, count);
        } 
        public static String copyValueOf(char data[], int offset, int count) {
            // All public String constructors now copy the data.
            return new String(data, offset, count);
        } 

    字符串查找算法 indexOf

    可以看到String的字符串匹配算法使用的是朴素的匹配算法,即前向匹配,当遇到不匹配字符时,主串从下一个字符开始,字串从开始位置开始
    其他相关字符串匹配算法

        static int indexOf(char[] source, int sourceOffset, int sourceCount,
                char[] target, int targetOffset, int targetCount,
                int fromIndex) {
            if (fromIndex >= sourceCount) {
                return (targetCount == 0 ? sourceCount : -1);
            }
            if (fromIndex < 0) {
                fromIndex = 0;
            }
            if (targetCount == 0) {
                return fromIndex;
            }
            char first = target[targetOffset];
            int max = sourceOffset + (sourceCount - targetCount);
            for (int i = sourceOffset + fromIndex; i <= max; i++) {
                /* Look for first character. */
                if (source[i] != first) {
                    while (++i <= max && source[i] != first);
                }
                /* Found first character, now look at the rest of v2 */
                if (i <= max) {
                    int j = i + 1;
                    int end = j + targetCount - 1;
                    for (int k = targetOffset + 1; j < end && source[j]
                            == target[k]; j++, k++);
                    if (j == end) {
                        /* Found whole string. */
                        return i - sourceOffset;
                    }
                }
            }
            return -1;
        }  

    编码问题 getBytes

    • 字符串最终都是使用机器码以字节存储的,当我们将字符串转换为字节的时候也需要给定编码,同一个字符不同的编码就对应不同的字节
    • 如不指定编码,就会使用默认的编码ISO-8859-1进行编码
    • 编码时为了避免平台编码的干扰,应当指定确定的编码
        String s = "你好,世界!";
        byte[] bytes = s.getBytes("utf-8");  
    
        public byte[] getBytes(String charsetName)
                throws UnsupportedEncodingException {
            if (charsetName == null) throw new NullPointerException();
            return StringCoding.encode(charsetName, value, 0, value.length);
        } 
        static byte[] encode(String charsetName, char[] ca, int off, int len)
            throws UnsupportedEncodingException
        {
            StringEncoder se = deref(encoder);
            String csn = (charsetName == null) ? "ISO-8859-1" : charsetName;
            if ((se == null) || !(csn.equals(se.requestedCharsetName())
                                  || csn.equals(se.charsetName()))) {
                se = null;
                try {
                    Charset cs = lookupCharset(csn);
                    if (cs != null)
                        se = new StringEncoder(cs, csn);
                } catch (IllegalCharsetNameException x) {}
                if (se == null)
                    throw new UnsupportedEncodingException (csn);
                set(encoder, se);
            }
            return se.encode(ca, off, len);
        } 

    比较方法

    • 所有比较方法都是比较对应的字符数组的内容,后两个比较方法用来进行区段比较
    • 在进行数组比较时,如果可以通过长度进行初步判断,一般可以提高效率
        boolean equals(Object anObject);
        boolean contentEquals(StringBuffer sb);
        boolean contentEquals(CharSequence cs);
        boolean equalsIgnoreCase(String anotherString);
        int compareTo(String anotherString);
        int compareToIgnoreCase(String str);
        boolean regionMatches(int toffset, String other, int ooffset,int len)  //局部匹配
        boolean regionMatches(boolean ignoreCase, int toffset,String other, int ooffset, int len)   //局部匹配  
    
        public boolean equals(Object anObject) {
            if (this == anObject) {
                return true;
            }
            if (anObject instanceof String) {
                String anotherString = (String) anObject;
                int n = value.length;
                if (n == anotherString.value.length) {
                    char v1[] = value;
                    char v2[] = anotherString.value;
                    int i = 0;
                    while (n-- != 0) {
                        if (v1[i] != v2[i])
                                return false;
                        i++;
                    }
                    return true;
                }
            }
            return false;
        }  

    替换函数 replace

    • 单字符替换会替换所有特定字符的出现
    • replace为普通(literal)替换,不用正则表达式
    • replaceFirst与replaceAll都使用了正则表达式
        public String replace(CharSequence target, CharSequence replacement) {
            return Pattern.compile(target.toString(), Pattern.LITERAL).matcher(
                    this).replaceAll(Matcher.quoteReplacement(replacement.toString()));
        } 
        public String replaceFirst(String regex, String replacement) {
            return Pattern.compile(regex).matcher(this).replaceFirst(replacement);
        }
        public String replaceAll(String regex, String replacement) {
            return Pattern.compile(regex).matcher(this).replaceAll(replacement);
        } 
        public String replace(char oldChar, char newChar) {
            if (oldChar != newChar) {
                int len = value.length;
                int i = -1;
                char[] val = value; /* avoid getfield opcode */
                while (++i < len) {
                    if (val[i] == oldChar) {
                        break;
                    }
                }
                if (i < len) {
                    char buf[] = new char[len];
                    for (int j = 0; j < i; j++) {
                        buf[j] = val[j];
                    }
                    while (i < len) {
                        char c = val[i];
                        buf[i] = (c == oldChar) ? newChar : c;
                        i++;
                    }
                    return new String(buf, true);
                }
            }
            return this;
        }

    常量池相关方法

    • 每当定义一个字符串字面量,字面量进行字符串连接,或者final的String字面量初始化的变量的连接的变量时都会检查常量池中是否有对应的字符串,如果有就不创建新的字符串,而是返回指向常量池对应字符串的引用
    • 所有通过new String(str)方式创建的对象都会存在与堆区,而非常量区
    • 普通变量的连接,由于不能在编译期确定下来,所以不会存储在常量区
    public native String intern();  

    运算符的重载

    • String对“+”运算符进行了重载,通过反编译我们看到重载是通过StringBuilder的append方法,及String的valueOf方法实现的
    • int值转String过程中(”“+i)这种方法实际为(new StringBuilder()).append(i).toString();,而另外两种都是调用Integer的静态方法Integer.toString完成
    // int转String的方法比较
    public class Demo {
        public static void main(String[] args) throws Exception {
            int i = 5;
            String i1 = "" + i;
            String i2 = String.valueOf(i);
            String i3 = Integer.toString(i);
        }
    } 
    // 原始代码
    public class Demo {
        public static void main(String[] args) throws Exception {
            String string="hollis";
            String string2 = string + "chuang";
        }
    } 
    //反编译代码
    public class Demo {
        public static void main(String[] args) throws Exception {
            String string = "hollis";
            String string2 = (new StringBuilder(String.valueOf(string))).append("chuang").toString();
        }
    }
    展开全文
  • 下面小编就为大家分享一篇java String源码和String常量池的全面解析,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
  • public final class String implements java.io.Serializable, Comparable<String>, CharSequence
  • String 源码解析,深入认识String

    千次阅读 多人点赞 2017-02-26 21:53:16
    String 源码浅析

    问题

    前些日子犯了一个很低级的错误,将集合A==B,然后将B拿过去使用,发现事情不对,集合A的元素也发生了变化。好尴尬啊,这就是对==号的理解不深导致的低级错误。正好上一篇写了一个Stringutils类。那么今天就让我们深入她,理解她吧。

    引入

    public class VeryGood {
    
    
    
        public static void main(String[] args) {
            String a = "a"+"b"+1;
            String b = "ab1";
            System.out.println(a == b);
    
    
            String a1 = new String("ab1");
            String b1 = "ab1";
            System.out.println(a1 == b1);
        }
    
    
    }

    他们的运行结果是什么呢?

    正确答案是:

    true
    false

    让我们看看经过编译器编译后的代码:

    //第一段代码
    public void stringTest() {
        String a = "ab1";
        String b = "ab1";
        System.out.println(a == b);
    }
    
    //第二段代码
    public void stringTest() {
        String a1 = new String("ab1");
        String b = "ab1";
        System.out.println(a1 == b);
    }

    也就是说第一段代码经过了编译期优化,原因是编译器发现”a”+”b”+1和”ab1”的效果是一样的,都是不可变量组成。但是为什么他们的内存地址会相同呢?一起看看String类的一些重要源码吧。

    String类

    String类被final所修饰,也就是说String对象是不可变量,并发程序最喜欢不可变量了。String类实现了Serializable, Comparable, CharSequence接口。

    Comparable接口有compareTo(String s)方法,CharSequence接口有length(),charAt(int index),subSequence(int start,int end)方法。

    String属性

    String类中包含一个不可变的char数组用来存放字符串,一个int型的变量hash用来存放计算后的哈希值。

    /**
     * The <code>String</code> class represents character strings. All
     * string literals in Java programs, such as <code>"abc"</code>, are
     * implemented as instances of this class.
     * <p>
     * Strings are constant; their values cannot be changed after they
     * are created. String buffers support mutable strings.
     * /

    String构造函数

    //不含参数的构造函数,一般没什么用,因为value是不可变量
    public String() {
        this.value = new char[0];
    }
    
    //参数为String类型
    public String(String original) {
        this.value = original.value;
        this.hash = original.hash;
    }
    
    //参数为char数组,使用java.utils包中的Arrays类复制
    public String(char value[]) {
        this.value = Arrays.copyOf(value, value.length);
    }
    
    //从bytes数组中的offset位置开始,将长度为length的字节,以charsetName格式编码,拷贝到value
    public String(byte bytes[], int offset, int length, String charsetName)
            throws UnsupportedEncodingException {
        if (charsetName == null)
            throw new NullPointerException("charsetName");
        checkBounds(bytes, offset, length);
        this.value = StringCoding.decode(charsetName, bytes, offset, length);
    }
    
    //调用public String(byte bytes[], int offset, int length, String charsetName)构造函数
    public String(byte bytes[], String charsetName)
            throws UnsupportedEncodingException {
        this(bytes, 0, bytes.length, charsetName);
    }

    String常用方法

    boolean equals(Object anObject)
    
    public boolean equals(Object anObject) {
        //如果引用的是同一个对象,返回真
        if (this == anObject) {
            return true;
        }
        //如果不是String类型的数据,返回假
        if (anObject instanceof String) {
            String anotherString = (String) anObject;
            int n = value.length;
            //如果char数组长度不相等,返回假
            if (n == anotherString.value.length) {
                char v1[] = value;
                char v2[] = anotherString.value;
                int i = 0;
                //从后往前单个字符判断,如果有不相等,返回假
                while (n-- != 0) {
                    if (v1[i] != v2[i])
                            return false;
                    i++;
                }
                //每个字符都相等,返回真
                return true;
            }
        }
        return false;
    }

    equals方法经常用得到,它用来判断两个对象从实际意义上是否相等,String对象判断规则:

    内存地址相同,则为真。

    如果对象类型不是String类型,则为假。否则继续判断。

    如果对象长度不相等,则为假。否则继续判断。

    从后往前,判断String类中char数组value的单个字符是否相等,有不相等则为假。如果一直相等直到第一个数,则返回真。

    由此可以看出,如果对两个超长的字符串进行比较还是非常费时间的。

    int compareTo(String anotherString)
    
    public int compareTo(String anotherString) {
        //自身对象字符串长度len1
        int len1 = value.length;
        //被比较对象字符串长度len2
        int len2 = anotherString.value.length;
        //取两个字符串长度的最小值lim
        int lim = Math.min(len1, len2);
        char v1[] = value;
        char v2[] = anotherString.value;
    
        int k = 0;
        //从value的第一个字符开始到最小长度lim处为止,如果字符不相等,返回自身(对象不相等处字符-被比较对象不相等字符)
        while (k < lim) {
            char c1 = v1[k];
            char c2 = v2[k];
            if (c1 != c2) {
                return c1 - c2;
            }
            k++;
        }
        //如果前面都相等,则返回(自身长度-被比较对象长度)
        return len1 - len2;
    }

    这个方法写的很巧妙,先从0开始判断字符大小。如果两个对象能比较字符的地方比较完了还相等,就直接返回自身长度减被比较对象长度,如果两个字符串长度相等,则返回的是0,巧妙地判断了三种情况。

    int hashCode()
    
    public int hashCode() {
        int h = hash;
        //如果hash没有被计算过,并且字符串不为空,则进行hashCode计算
        if (h == 0 && value.length > 0) {
            char val[] = value;
    
            //计算过程
            //s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
            for (int i = 0; i < value.length; i++) {
                h = 31 * h + val[i];
            }
            //hash赋值
            hash = h;
        }
        return h;
    }

    String类重写了hashCode方法,Object中的hashCode方法是一个Native调用。String类的hash采用多项式计算得来,我们完全可以通过不相同的字符串得出同样的hash,所以两个String对象的hashCode相同,并不代表两个String是一样的。

    boolean startsWith(String prefix,int toffset)
    
    public boolean startsWith(String prefix, int toffset) {
        char ta[] = value;
        int to = toffset;
        char pa[] = prefix.value;
        int po = 0;
        int pc = prefix.value.length;
        // Note: toffset might be near -1>>>1.
        //如果起始地址小于0或者(起始地址+所比较对象长度)大于自身对象长度,返回假
        if ((toffset < 0) || (toffset > value.length - pc)) {
            return false;
        }
        //从所比较对象的末尾开始比较
        while (--pc >= 0) {
            if (ta[to++] != pa[po++]) {
                return false;
            }
        }
        return true;
    }
    
    public boolean startsWith(String prefix) {
        return startsWith(prefix, 0);
    }
    
    public boolean endsWith(String suffix) {
        return startsWith(suffix, value.length - suffix.value.length);
    }

    起始比较和末尾比较都是比较经常用得到的方法,例如在判断一个字符串是不是http协议的,或者初步判断一个文件是不是mp3文件,都可以采用这个方法进行比较。

    String concat(String str)
    
    public String concat(String str) {
        int otherLen = str.length();
        //如果被添加的字符串为空,返回对象本身
        if (otherLen == 0) {
            return this;
        }
        int len = value.length;
        char buf[] = Arrays.copyOf(value, len + otherLen);
        str.getChars(buf, len);
        return new String(buf, true);
    }

    concat方法也是经常用的方法之一,它先判断被添加字符串是否为空来决定要不要创建新的对象。

    String replace(char oldChar,char newChar)
    
    public String replace(char oldChar, char newChar) {
        //新旧值先对比
        if (oldChar != newChar) {
            int len = value.length;
            int i = -1;
            char[] val = value; /* avoid getfield opcode */
    
            //找到旧值最开始出现的位置
            while (++i < len) {
                if (val[i] == oldChar) {
                    break;
                }
            }
            //从那个位置开始,直到末尾,用新值代替出现的旧值
            if (i < len) {
                char buf[] = new char[len];
                for (int j = 0; j < i; j++) {
                    buf[j] = val[j];
                }
                while (i < len) {
                    char c = val[i];
                    buf[i] = (c == oldChar) ? newChar : c;
                    i++;
                }
                return new String(buf, true);
            }
        }
        return this;
    }

    这个方法也有讨巧的地方,例如最开始先找出旧值出现的位置,这样节省了一部分对比的时间。replace(String oldStr,String newStr)方法通过正则表达式来判断。

    String trim()
    
    public String trim() {
        int len = value.length;
        int st = 0;
        char[] val = value;    /* avoid getfield opcode */
    
        //找到字符串前段没有空格的位置
        while ((st < len) && (val[st] <= ' ')) {
            st++;
        }
        //找到字符串末尾没有空格的位置
        while ((st < len) && (val[len - 1] <= ' ')) {
            len--;
        }
        //如果前后都没有出现空格,返回字符串本身
        return ((st > 0) || (len < value.length)) ? substring(st, len) : this;
    }

    trim方法用起来也6的飞起

    String intern()
    
    public native String intern();

    intern方法是Native调用,它的作用是在方法区中的常量池里通过equals方法寻找等值的对象,如果没有找到则在常量池中开辟一片空间存放字符串并返回该对应String的引用,否则直接返回常量池中已存在String对象的引用。

    将引言中第二段代码

    //String a = new String("ab1");
    //改为
    String a = new String("ab1").intern();

    则结果为为真,原因在于a所指向的地址来自于常量池,而b所指向的字符串常量默认会调用这个方法,所以a和b都指向了同一个地址空间。

    int hash32()
    
    private transient int hash32 = 0;
    int hash32() {
        int h = hash32;
        if (0 == h) {
           // harmless data race on hash32 here.
           h = sun.misc.Hashing.murmur3_32(HASHING_SEED, value, 0, value.length);
    
           // ensure result is not zero to avoid recalcing
           h = (0 != h) ? h : 1;
    
           hash32 = h;
        }
    
        return h;
    }

    在JDK1.7中,Hash相关集合类在String类作key的情况下,不再使用hashCode方式离散数据,而是采用hash32方法。这个方法默认使用系统当前时间,String类地址,System类地址等作为因子计算得到hash种子,通过hash种子在经过hash得到32位的int型数值。

    public int length() {
        return value.length;
    }
    public String toString() {
        return this;
    }
    public boolean isEmpty() {
        return value.length == 0;
    }
    public char charAt(int index) {
        if ((index < 0) || (index >= value.length)) {
            throw new StringIndexOutOfBoundsException(index);
        }
        return value[index];
    }

    以上是一些简单的常用方法。

    总结

    String对象是不可变类型,返回类型为String的String方法每次返回的都是新的String对象,除了某些方法的某些特定条件返回自身。

    String对象的三种比较方式:

    ==内存比较:直接对比两个引用所指向的内存值,精确简洁直接明了。

    equals字符串值比较:比较两个引用所指对象字面值是否相等。

    hashCode字符串数值化比较:将字符串数值化。两个引用的hashCode相同,不保证内存一定相同,不保证字面值一定相同。

    展开全文
  • 主要给大家介绍了关于java源码解析String类的compareTo(String otherString)方法的相关资料,文中通过示例代码介绍的非常详细,需要的朋友可以参考借鉴,下面随着小编来一起学习学习吧
  • Java String 源码浅析

    千次阅读 2016-08-19 19:44:37
     String a = "a"+"b"+1;  String b = "ab1";  System.out.println(a == b); } 大家猜一猜结果如何?如果你的结论是true。好吧,再来一段代码: public void stringTest(){  String a =

    String表示字符串,Java中所有字符串的字面值都是String类的实例,例如“ABC”。字符串是常量,在定义之后不能被改变,字符串缓冲区支持可变的字符串。因为 String 对象是不可变的,所以可以共享它们。例如:

    String str = "abc";
    

    相当于

    char data[] = {'a', 'b', 'c'};
    String str = new String(data);
    

    这里还有一些其他使用字符串的例子:

     System.out.println("abc");
     String cde = "cde";
     System.out.println("abc" + cde);
     String c = "abc".substring(2,3);
     String d = cde.substring(1, 2);
    

    String类提供了检查字符序列中单个字符的方法,比如有比较字符串,搜索字符串,提取子字符串,创建一个字符串的副本、字符串的大小写转换等。实例映射是基于Character类中指定的Unicode标准的。 Java语言提供了对字符串连接运算符的特别支持(+),该符号也可用于将其他类型转换成字符串。字符串的连接实际上是通过StringBuffer或者StringBuilderappend()方法来实现的,字符串的转换通过toString方法实现,该方法由 Object 类定义,并可被 Java 中的所有类继承。 除非另有说明,传递一个空参数在这类构造函数或方法会导致NullPointerException异常被抛出。String表示一个字符串通过UTF-16(unicode)格式,补充字符通过代理对(参见Character类的 Unicode Character Representations 获取更多的信息)表示。索引值参考字符编码单元,所以补充字符在String中占两个位置。

    定义
    属性
    构造方法

    使用字符数组、字符串构造一个String

     

    使用字节数组构造一个String

     

    使用StringBuffer和StringBuider构造一个String

     

    一个特殊的私有的构造方法

    其他方法

    getBytes

     

    比较方法

     

    hashCode

     

    substring

     

    replaceFirst、replaceAll、replace区别

     

    copyValueOf 和 valueOf

     

    intern

     

    String对“+”的重载

     

    String.valueOf和Integer.toString的区别

    参考资料

    一、定义

    public final class String implements java.io.Serializable, Comparable, CharSequence{}
    

    从该类的声明中我们可以看出String是final类型的,表示该类不能被继承,同时该类实现了三个接口:java.io.Serializable、 Comparable、 CharSequence

    二、属性

    private final char value[];
    

    这是一个字符数组,并且是final类型,他用于存储字符串内容,从fianl这个关键字中我们可以看出,String的内容一旦被初始化了是不能被更改的。 虽然有这样的例子: String s = “a”; s = “b” 但是,这并不是对s的修改,而是重新指向了新的字符串, 从这里我们也能知道,String其实就是用char[]实现的。

    private int hash;
    

    缓存字符串的hash Code,默认值为 0

    private static final long serialVersionUID = -6849794470754667710L;
    private static final ObjectStreamField[] serialPersistentFields = new ObjectStreamField[0];
    

    因为String实现了Serializable接口,所以支持序列化和反序列化支持。Java的序列化机制是通过在运行时判断类的serialVersionUID来验证版本一致性的。在进行反序列化时,JVM会把传来的字节流中的serialVersionUID与本地相应实体(类)的serialVersionUID进行比较,如果相同就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异常(InvalidCastException)。

    三、构造方法

    String类作为一个java.lang包中比较常用的类,自然有很多重载的构造方法.在这里介绍几种典型的构造方法:

    1.使用字符数组、字符串构造一个String

    我们知道,其实String就是使用字符数组(char[])实现的。所以我们可以使用一个字符数组来创建一个String,那么这里值得注意的是,当我们使用字符数组创建String的时候,会用到Arrays.copyOf方法和Arrays.copyOfRange方法。这两个方法是将原有的字符数组中的内容逐一的复制到String中的字符数组中。同样,我们也可以用一个String类型的对象来初始化一个String。这里将直接将源String中的valuehash两个属性直接赋值给目标String。因为String一旦定义之后是不可以改变的,所以也就不用担心改变源String的值会影响到目标String的值。

    当然,在使用字符数组来创建一个新的String对象的时候,不仅可以使用整个字符数组,也可以使用字符数组的一部分,只要多传入两个参数int offsetint count就可以了。

    2.使用字节数组构造一个String

    在Java中,String实例中保存有一个char[]字符数组,char[]字符数组是以unicode码来存储的,String 和 char 为内存形式,byte是网络传输或存储的序列化形式。所以在很多传输和存储的过程中需要将byte[]数组和String进行相互转化。所以,String提供了一系列重载的构造方法来将一个字符数组转化成String,提到byte[]和String之间的相互转换就不得不关注编码问题。String(byte[] bytes, Charset charset)是指通过charset来解码指定的byte数组,将其解码成unicode的char[]数组,够造成新的String。

    这里的bytes字节流是使用charset进行编码的,想要将他转换成unicode的char[]数组,而又保证不出现乱码,那就要指定其解码方式

    同样使用字节数组来构造String也有很多种形式,按照是否指定解码方式分的话可以分为两种:

    String(byte bytes[]) String(byte bytes[], int offset, int length)

     

    String(byte bytes[], Charset charset)

     

    String(byte bytes[], String charsetName)

     

    String(byte bytes[], int offset, int length, Charset charset)

     

    String(byte bytes[], int offset, int length, String charsetName)

    如果我们在使用byte[]构造String的时候,使用的是下面这四种构造方法(带有charsetName或者charset参数)的一种的话,那么就会使用StringCoding.decode方法进行解码,使用的解码的字符集就是我们指定的charsetName或者charset。 我们在使用byte[]构造String的时候,如果没有指明解码使用的字符集的话,那么StringCodingdecode方法首先调用系统的默认编码格式,如果没有指定编码格式则默认使用ISO-8859-1编码格式进行编码操作。主要体现代码如下:

      static char[] decode(byte[] ba, int off, int len) {
            String csn = Charset.defaultCharset().name();
            try {
                // use charset name decode() variant which provides caching.
                return decode(csn, ba, off, len);
            } catch (UnsupportedEncodingException x) {
                warnUnsupportedCharset(csn);
            }
            try {
                return decode("ISO-8859-1", ba, off, len);
            } catch (UnsupportedEncodingException x) {
                // If this code is hit during VM initialization, MessageUtils is
                // the only way we will be able to get any kind of error message.
                MessageUtils.err("ISO-8859-1 charset not available: "
                                 + x.toString());
                // If we can not find ISO-8859-1 (a required encoding) then things
                // are seriously wrong with the installation.
                System.exit(1);
                return null;
            }
        }
    

    3.使用StringBuffer和StringBuider构造一个String

    作为String的两个“兄弟”,StringBuffer和StringBuider也可以被当做构造String的参数。

        public String(StringBuffer buffer) {
            synchronized(buffer) {
                this.value = Arrays.copyOf(buffer.getValue(), buffer.length());
            }
        }
    
        public String(StringBuilder builder) {
            this.value = Arrays.copyOf(builder.getValue(), builder.length());
        }
    

    当然,这两个构造方法是很少用到的,至少我从来没有使用过,因为当我们有了StringBuffer或者StringBuilfer对象之后可以直接使用他们的toString方法来得到String。关于效率问题,Java的官方文档有提到说使用StringBuilder的toString方法会更快一些,原因是StringBuffer的toString方法是synchronized的,在牺牲了效率的情况下保证了线程安全。

     public String toString() {
        // Create a copy, don't share the array
        return new String(value, 0, count);
     }
    
    this.value = Arrays.copyOfRange(value, offset, offset+count);
    

    4.一个特殊的私有的构造方法

    String除了提供了很多公有的供程序员使用的构造方法以外,还提供了一个私有的构造方法(Java 7),我们看一下他是怎么样的:

    String(char[] value, boolean share) {
        // assert share : "unshared not supported";
        this.value = value;
    }
    

    从代码中我们可以看出,该方法和 String(char[] value)有两点区别,第一个,该方法多了一个参数: boolean share,其实这个参数在方法体中根本没被使用,也给了注释,目前不支持使用false,只使用true。那么可以断定,加入这个share的只是为了区分于String(char[] value)方法,不加这个参数就没办法定义这个函数,只有参数不能才能进行重载。那么,第二个区别就是具体的方法实现不同。我们前面提到过,String(char[] value)方法在创建String的时候会用到 会用到ArrayscopyOf方法将value中的内容逐一复制到String当中,而这个String(char[] value, boolean share)方法则是直接将value的引用赋值给String的value。那么也就是说,这个方法构造出来的String和参数传过来的char[] value共享同一个数组。 那么,为什么Java会提供这样一个方法呢? 首先,我们分析一下使用该构造函数的好处:

    首先,性能好,这个很简单,一个是直接给数组赋值(相当于直接将String的value的指针指向char[]数组),一个是逐一拷贝。当然是直接赋值快了。

     

    其次,共享内部数组节约内存

    但是,该方法之所以设置为私有,是因为一旦该方法设置为公有,在外面可以访问的话,那就破坏了字符串的不可变性。例如如下YY情形:

    char[] arr = new char[] {'h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd'};
    String s = new String(0, arr.length, arr); // "hello world"
    arr[0] = 'a'; // replace the first character with 'a'
    System.out.println(s); // aello world
    

    如果构造方法没有对arr进行拷贝,那么其他人就可以在字符串外部修改该数组,由于它们引用的是同一个数组,因此对arr的修改就相当于修改了字符串。

    所以,从安全性角度考虑,他也是安全的。对于调用他的方法来说,由于无论是原字符串还是新字符串,其value数组本身都是String对象的私有属性,从外部是无法访问的,因此对两个字符串来说都很安全。

    在Java 7 之有很多String里面的方法都使用这种“性能好的、节约内存的、安全”的构造函数。比如:substringreplaceconcatvalueOf等方法(实际上他们使用的是public String(char[], int, int)方法,原理和本方法相同,已经被本方法取代)。

    但是在Java 7中,substring已经不再使用这种“优秀”的方法了,为什么呢? 虽然这种方法有很多优点,但是他有一个致命的缺点,对于sun公司的程序员来说是一个零容忍的bug,那就是他很有可能造成内存泄露。 看一个例子,假设一个方法从某个地方(文件、数据库或网络)取得了一个很长的字符串,然后对其进行解析并提取其中的一小段内容,这种情况经常发生在网页抓取或进行日志分析的时候。下面是示例代码。

    String aLongString = "...a very long string..."; 
    String aPart = data.substring(20, 40);
    return aPart;
    

    在这里aLongString只是临时的,真正有用的是aPart,其长度只有20个字符,但是它的内部数组却是从aLongString那里共享的,因此虽然aLongString本身可以被回收,但它的内部数组却不能(如下图)。这就导致了内存泄漏。如果一个程序中这种情况经常发生有可能会导致严重的后果,如内存溢出,或性能下降。

    2aqQFnf

    新的实现虽然损失了性能,而且浪费了一些存储空间,但却保证了字符串的内部数组可以和字符串对象一起被回收,从而防止发生内存泄漏,因此新的substring比原来的更健壮。

    额、、、扯了好远,虽然substring方法已经为了其鲁棒性放弃使用这种share数组的方法,但是这种share数组的方法还是有一些其他方法在使用的,这是为什么呢?首先呢,这种方式构造对应有很多好处,其次呢,其他的方法不会将数组长度变短,也就不会有前面说的那种内存泄露的情况(内存泄露是指不用的内存没有办法被释放,比如说concat方法和replace方法,他们不会导致元数组中有大量空间不被使用,因为他们一个是拼接字符串,一个是替换字符串内容,不会将字符数组的长度变得很短!)。

    四、其他方法

    length() 返回字符串长度

     

    isEmpty() 返回字符串是否为空

     

    charAt(int index) 返回字符串中第(index+1)个字符

     

    char[] toCharArray() 转化成字符数组

     

    trim() 去掉全部空格

     

    toUpperCase() 转化为大写

     

    toLowerCase() 转化为小写

     

    String concat(String str) //拼接字符串

     

    String replace(char oldChar, char newChar) //将字符串中的oldChar字符换成newChar字符

     

    //以上两个方法都使用了String(char[] value, boolean share);

     

    boolean matches(String regex) //判断字符串是否匹配给定的regex正则表达式

     

    boolean contains(CharSequence s) //判断字符串是否包含字符序列s

     

    String[] split(String regex, int limit) 按照字符regex将字符串分成limit份。

     

    String[] split(String regex)

    String string = "h,o,l,l,i,s,c,h,u,a,n,g";
    String[] splitAll = string.split(",");
    String[] splitFive = string.split(",",5);
    splitAll =  [h, o, l, l, i, s, c, h, u, a, n, g]  
    splitFive =  [h, o, l, l, i,s,c,h,u,a,n,g]
    

    getBytes

    在创建String的时候,可以使用byte[]数组,将一个字节数组转换成字符串,同样,我们可以将一个字符串转换成字节数组,那么String提供了很多重载的getBytes方法。但是,值得注意的是,在使用这些方法的时候一定要注意编码问题。比如:

    String s = "你好,世界!"; 
    byte[] bytes = s.getBytes();
    

    这段代码在不同的平台上运行得到结果是不一样的。由于我们没有指定编码方式,所以在该方法对字符串进行编码的时候就会使用系统的默认编码方式,比如在中文操作系统中可能会使用GBK或者GB2312进行编码,在英文操作系统中有可能使用iso-8859-1进行编码。这样写出来的代码就和机器环境有很强的关联性了,所以,为了避免不必要的麻烦,我们要指定编码方式。如使用以下方式:

    String s = "你好,世界!"; 
    byte[] bytes = s.getBytes("utf-8");
    

    比较方法

    boolean equals(Object anObject);
    boolean contentEquals(StringBuffer sb);
    boolean contentEquals(CharSequence cs);
    boolean equalsIgnoreCase(String anotherString);
    int compareTo(String anotherString);
    int compareToIgnoreCase(String str);
    boolean regionMatches(int toffset, String other, int ooffset,int len)  //局部匹配
    boolean regionMatches(boolean ignoreCase, int toffset,String other, int ooffset, int len)   //局部匹配
    

    字符串有一系列方法用于比较两个字符串的关系。 前四个返回boolean的方法很容易理解,前三个比较就是比较String和要比较的目标对象的字符数组的内容,一样就返回true,不一样就返回false,核心代码如下:

     int n = value.length;
     while (n-- != 0) {
         if (v1[i] != v2[i])
             return false;
         i++;
     }
    

    v1 v2分别代表String的字符数组和目标对象的字符数组。 第四个和前三个唯一的区别就是他会将两个字符数组的内容都使用toUpperCase方法转换成大写再进行比较,以此来忽略大小写进行比较。相同则返回true,不想同则返回false

    在这里,看到这几个比较的方法代码,有很多编程的技巧我们应该学习。我们看equals方法:

    public boolean equals(Object anObject) {
            if (this == anObject) {
                return true;
            }
            if (anObject instanceof String) {
                String anotherString = (String) anObject;
                int n = value.length;
                if (n == anotherString.value.length) {
                    char v1[] = value;
                    char v2[] = anotherString.value;
                    int i = 0;
                    while (n-- != 0) {
                        if (v1[i] != v2[i])
                                return false;
                        i++;
                    }
                    return true;
                }
            }
            return false;
        }
    

    该方法首先判断this == anObject ?,也就是说判断要比较的对象和当前对象是不是同一个对象,如果是直接返回true,如不是再继续比较,然后在判断anObject是不是String类型的,如果不是,直接返回false,如果是再继续比较,到了能终于比较字符数组的时候,他还是先比较了两个数组的长度,不一样直接返回false,一样再逐一比较值。 虽然代码写的内容比较多,但是可以很大程度上提高比较的效率。值得学习~~!!!

    contentEquals有两个重载,StringBuffer需要考虑线程安全问题,再加锁之后调用contentEquals((CharSequence) sb)方法。contentEquals((CharSequence) sb)则分两种情况,一种是cs instanceof AbstractStringBuilder,另外一种是参数是String类型。具体比较方式几乎和equals方法类似,先做“宏观”比较,在做“微观”比较。

    下面这个是equalsIgnoreCase代码的实现:

        public boolean equalsIgnoreCase(String anotherString) {
            return (this == anotherString) ? true
                    : (anotherString != null)
                    && (anotherString.value.length == value.length)
                    && regionMatches(true, 0, anotherString, 0, value.length);
        }
    

    看到这段代码,眼前为之一亮。使用一个三目运算符和&&操作代替了多个if语句。

    hashCode

    hashCode的实现其实就是使用数学公式:

    s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
    

    s[i]是string的第i个字符,n是String的长度。那为什么这里用31,而不是其它数呢? 计算机的乘法涉及到移位计算。当一个数乘以2时,就直接拿该数左移一位即可!选择31原因是因为31是一个素数!

    所谓素数:

    质数又称素数。指在一个大于1的自然数中,除了1和此整数自身外,没法被其他自然数整除的数。

    素数在使用的时候有一个作用就是如果我用一个数字来乘以这个素数,那么最终的出来的结果只能被素数本身和被乘数还有1来整除!如:我们选择素数3来做系数,那么3*n只能被3和n或者1来整除,我们可以很容易的通过3n来计算出这个n来。这应该也是一个原因!

    在存储数据计算hash地址的时候,我们希望尽量减少有同样的hash地址,所谓“冲突”。如果使用相同hash地址的数据过多,那么这些数据所组成的hash链就更长,从而降低了查询效率!所以在选择系数的时候要选择尽量长的系数并且让乘法尽量不要溢出的系数,因为如果计算出来的hash地址越大,所谓的“冲突”就越少,查找起来效率也会提高。

    31可以 由i*31== (i<<5)-1来表示,现在很多虚拟机里面都有做相关优化,使用31的原因可能是为了更好的分配hash地址,并且31只占用5bits!

    在java乘法中如果数字相乘过大会导致溢出的问题,从而导致数据的丢失.

    而31则是素数(质数)而且不是很长的数字,最终它被选择为相乘的系数的原因不过与此!

    在Java中,整型数是32位的,也就是说最多有2^32= 4294967296个整数,将任意一个字符串,经过hashCode计算之后,得到的整数应该在这4294967296数之中。那么,最多有 4294967297个不同的字符串作hashCode之后,肯定有两个结果是一样的, hashCode可以保证相同的字符串的hash值肯定相同,但是,hash值相同并不一定是value值就相同。

    substring

    public String substring(int beginIndex) {
        if (beginIndex < 0) {
            throw new StringIndexOutOfBoundsException(beginIndex);
        }
        int subLen = value.length - beginIndex;
        if (subLen < 0) {
            throw new StringIndexOutOfBoundsException(subLen);
        }
        return (beginIndex == 0) ? this : new String(value, beginIndex, subLen);
    }
    

    前面我们介绍过,java 7 中的substring方法使用String(value, beginIndex, subLen)方法创建一个新的String并返回,这个方法会将原来的char[]中的值逐一复制到新的String中,两个数组并不是共享的,虽然这样做损失一些性能,但是有效地避免了内存泄露。

    replaceFirst、replaceAll、replace区别

    String replaceFirst(String regex, String replacement)
    String replaceAll(String regex, String replacement)
    String replace(CharSequence target, CharSequence replacement)
    

    1)replace的参数是char和CharSequence,即可以支持字符的替换,也支持字符串的替换 2)replaceAll和replaceFirst的参数是regex,即基于规则表达式的替换,比如,可以通过replaceAll(“\d”, “*”)把一个字符串所有的数字字符都换成星号; 相同点是都是全部替换,即把源字符串中的某一字符或字符串全部换成指定的字符或字符串, 如果只想替换第一次出现的,可以使用 replaceFirst(),这个方法也是基于规则表达式的替换,但与replaceAll()不同的是,只替换第一次出现的字符串; 另外,如果replaceAll()和replaceFirst()所用的参数据不是基于规则表达式的,则与replace()替换字符串的效果是一样的,即这两者也支持字符串的操作;

    copyValueOf 和 valueOf

    String的底层是由char[]实现的:通过一个char[]类型的value属性!早期的String构造器的实现呢,不会拷贝数组的,直接将参数的char[]数组作为String的value属性。然后test[0] = 'A';将导致字符串的变化。为了避免这个问题,提供了copyValueOf方法,每次都拷贝成新的字符数组来构造新的String对象。但是现在的String对象,在构造器中就通过拷贝新数组实现了,所以这两个方面在本质上已经没区别了。

    valueOf()有很多种形式的重载,包括:

      public static String valueOf(boolean b) {
          return b ? "true" : "false";
      }
    
      public static String valueOf(char c) {
           char data[] = {c};
           return new String(data, true);
      }
      public static String valueOf(int i) {
          return Integer.toString(i);
      }
    
      public static String valueOf(long l) {
         return Long.toString(l);
      }
    
     public static String valueOf(float f) {
         return Float.toString(f);
     }
    
     public static String valueOf(double d) {
        return Double.toString(d);
    }
    

    可以看到这些方法可以将六种基本数据类型的变量转换成String类型。

    intern()方法

    public native String intern();
    

    该方法返回一个字符串对象的内部化引用。 众所周知:String类维护一个初始为空的字符串的对象池,当intern方法被调用时,如果对象池中已经包含这一个相等的字符串对象则返回对象池中的实例,否则添加字符串到对象池并返回该字符串的引用。

    String对“+”的重载

    我们知道,Java是不支持重载运算符,String的“+”是java中唯一的一个重载运算符,那么java使如何实现这个加号的呢?我们先看一段代码:

    public static void main(String[] args) {
        String string="hollis";
        String string2 = string + "chuang";
    }
    

    然后我们将这段代码反编译:

    public static void main(String args[]){
       String string = "hollis";
       String string2 = (new StringBuilder(String.valueOf(string))).append("chuang").toString();
    }
    

    看了反编译之后的代码我们发现,其实String对“+”的支持其实就是使用了StringBuilder以及他的append、toString两个方法。

    String.valueOf和Integer.toString的区别

    接下来我们看以下这段代码,我们有三种方式将一个int类型的变量变成呢过String类型,那么他们有什么区别?

    1.int i = 5;
    2.String i1 = "" + i;
    3.String i2 = String.valueOf(i);
    4.String i3 = Integer.toString(i);
    

    1、第三行和第四行没有任何区别,因为String.valueOf(i)也是调用Integer.toString(i)来实现的。 2、第二行代码其实是String i1 = (new StringBuilder()).append(i).toString();,首先创建一个StringBuilder对象,然后再调用append方法,再调用toString方法。


    展开全文
  • java jdk源码解析 :open_book: ​程序猿探险记 Kong子:“学而不思则罔,思而不学则殆。” :memo: ​目录 Java基础 Java如何将一个Java对象序列化到文件里 ...订阅Java基础、JDK源码解析系列文章。

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 174,184
精华内容 69,673
关键字:

javastring源码解析

java 订阅