zoukankan      html  css  js  c++  java
  • JDK1.8源码之String

    一、String类型   

    引用博文连接:  https://blog.csdn.net/ylyg050518/article/details/52352993

    一、成员变量

    1 //用于存储字符串
    2 private final char value[];
    3 
    4 //缓存String的hash值
    5 private int hash; // Default to 0
    6 
    7 /** use serialVersionUID from JDK 1.0.2 for interoperability */
    8 private static final long serialVersionUID = -6849794470754667710L;

    二、构造方法

    //不含参数的构造函数,一般没什么用,因为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);
    }

    三、常用方法

    1,int hashCode()

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

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

    Object的hashCode方法,

    1 public native int hashCode();

    源码翻译:

    返回该对象的哈希码值。支持此方法是为了提高哈希表(例如 java.util.Hashtable 提供的哈希表)的性能。 
    hashCode 的常规协定是: 
    
    在 Java 应用程序执行期间,在对同一对象多次调用 hashCode 方法时,必须一致地返回相同的整数,前提是将对象进行 equals 比较时所用的信息没有被修改。从某一应用程序的一次执行到同一应用程序的另一次执行,该整数无需保持一致。 
    如果根据 equals(Object) 方法,两个对象是相等的,那么对这两个对象中的每个对象调用 hashCode 方法都必须生成相同的整数结果。 
    如果根据 equals(java.lang.Object) 方法,两个对象不相等,那么对这两个对象中的任一对象上调用 hashCode 方法不 要求一定生成不同的整数结果。但是,程序员应该意识到,为不相等的对象生成不同整数结果可以提高哈希表的性能。 
    
    实际上,由 Object 类定义的 hashCode 方法确实会针对不同的对象返回不同的整数。(这一般是通过将该对象的内部地址转换成一个整数来实现的,但是 JavaTM 编程语言不需要这种实现技巧。)

    2,boolean equals(Object anObject)  

     1 public boolean equals(Object anObject) {
     2     //如果引用的是同一个对象,返回真
     3     if (this == anObject) {
     4         return true;
     5     }
     6     //如果不是String类型的数据,返回假
     7     if (anObject instanceof String) {
     8         String anotherString = (String) anObject;
     9         int n = value.length;
    10         //如果char数组长度不相等,返回假
    11         if (n == anotherString.value.length) {
    12             char v1[] = value;
    13             char v2[] = anotherString.value;
    14             int i = 0;
    15             //从后往前单个字符判断,如果有不相等,返回假
    16             while (n-- != 0) {
    17                 if (v1[i] != v2[i])
    18                         return false;
    19                 i++;
    20             }
    21             //每个字符都相等,返回真
    22             return true;
    23         }
    24     }
    25     return false;
    26 }
    1. 内存地址相同,则为真。
    2. 如果对象类型不是String类型,则为假。否则继续判断。
    3. 如果对象长度不相等,则为假。否则继续判断。
    4. 从后往前,判断String类中char数组value的单个字符是否相等,有不相等则为假。如果一直相等直到第一个数,则返回真。

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

    3,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;
    }

    equals方法是重写了Object的equals方法,而compareTo是因为String implements  Comparable<String>,重写了Comparable的compareTo方法。

    boolean startsWith(String prefix,int toffset)

     1 public boolean startsWith(String prefix, int toffset) {
     2     char ta[] = value;
     3     int to = toffset;
     4     char pa[] = prefix.value;
     5     int po = 0;
     6     int pc = prefix.value.length;
     7     // Note: toffset might be near -1>>>1.
     8     //如果起始地址小于0或者(起始地址+所比较对象长度)大于自身对象长度,返回假
     9     if ((toffset < 0) || (toffset > value.length - pc)) {
    10         return false;
    11     }
    12     //从所比较对象的末尾开始比较
    13     while (--pc >= 0) {
    14         if (ta[to++] != pa[po++]) {
    15             return false;
    16         }
    17     }
    18     return true;
    19 }
    20 
    21 public boolean startsWith(String prefix) {
    22     return startsWith(prefix, 0);
    23 }
    24 
    25 public boolean endsWith(String suffix) {
    26     return startsWith(suffix, value.length - suffix.value.length);
    27 }

    4,String concat(String str)

     1 public String concat(String str) {
     2     int otherLen = str.length();
     3     //如果被添加的字符串为空,返回对象本身
     4     if (otherLen == 0) {
     5         return this;
     6     }
     7     int len = value.length;
     8     //复制指定的数组,截取或用 null 字符填充(如有必要),以使副本具有指定的长度。
    10     char buf[] = Arrays.copyOf(value, len + otherLen);
    11     str.getChars(buf, len);
    12     return new String(buf, true);
    13 }

    5,String trim()

     1 public String trim() {
     2     int len = value.length;
     3     int st = 0;
     4     char[] val = value;    /* avoid getfield opcode */
     5 
     6     //找到字符串前段没有空格的位置
     7     while ((st < len) && (val[st] <= ' ')) {
     8         st++;
     9     }
    10     //找到字符串末尾没有空格的位置
    11     while ((st < len) && (val[len - 1] <= ' ')) {
    12         len--;
    13     }
    14     //如果前后都没有出现空格,返回字符串本身
    15     return ((st > 0) || (len < value.length)) ? substring(st, len) : this;
    16 }
    其中的val[st] <= ' ',比较的是字符在字典位置,String trim()只能去掉字符串的前后空格,中间空格去不掉!
    可以通过
    char[] toCharArray() 得到字符串的字符数组
    
    
     1  public static void main(String[] args) {
     2         String str = "  klp m ";
     3         //得到字符串字符数组
     4         char[] val = str.toCharArray();
     5 
     6         System.out.println("字符是"+val[4]+"!");
     7         System.out.println("字符字典位置"+Integer.valueOf(val[4])+"!");
     8         System.out.println("字符是"+val[0]+"!");
     9         System.out.println("字符字典位置"+Integer.valueOf(val[1])+"!");
    10     }

    结果:

    1 字符是p!
    2 字符字典位置112!
    3 字符是 !
    4 字符字典位置32!

    6,String intern()

    1  public native String intern();

    源码解释:

    返回字符串对象的规范化表示形式。 
    一个初始为空的字符串池,它由类 String 私有地维护。 
    
    当调用 intern 方法时,如果池已经包含一个等于此 String 对象的字符串(用 equals(Object) 方法确定),则返回池中的字符串。
    否则,将此 String 对象添加到池中,并返回此 String 对象的引用。 它遵循以下规则:对于任意两个字符串 s 和 t,当且仅当 s.equals(t) 为
    true 时,s.intern() == t.intern() 才为 true。 所有字面值字符串和字符串赋值常量表达式都使用 intern 方法进行操作。字符串字面值在 Java Language Specification 的 §3.10.5 定义。 返回: 一个字符串,内容与此字符串相同,但一定取自具有唯一字符串的池。

    详细使用方法及解释参考链接:  https://blog.csdn.net/wjzhang5514/article/details/70209403

     1 public static void main(String[] args) {
     2         String s1 = "HelloWorld";
     3         String s2 = new String("HelloWorld");
     4         String s3 = "Hello";
     5         String s4 = "World";
     6         String s5 = "Hello" + "World";
     7         String s6 = s3 + s4;
     8 
     9         System.out.println(s1 == s2);
    10         System.out.println(s1 == s5);
    11         System.out.println(s1 == s6);
    12         System.out.println(s1 == s6.intern());
    13         System.out.println(s2 == s2.intern());
    14     }

    结果:

    1 false
    2 true
    3 false
    4 true
    5 false

    解释:

    s1 创建的 HelloWorld 存在于方法区中的常量池其中的字符串池,而 s2 创建的 HelloWorld 存在于堆中,故第一条 false 。
    
    s5 的创建是先进行右边表达式的字符串连接,然后才对 s5 进行赋值。赋值的时候会搜索池中是否有 HelloWorld 字符串,
    若有则把 s5 引用指向该字符串,若没有则在池中新增该字符串。显然 s5 的创建是用了 s1 已经创建好的字面量,故
    true 。 第三个比较容易弄错,s6 = s3 + s4; 其实相当于 s6 = new String(s3 + s4); s6 是存放于堆中的,不是字面量。所以 s1 不等于 s6 。 第四个 s6.intern() 首先获取了 s6 的 HelloWorld 字符串,然后在字符串池中查找该字符串,找到了 s1 的 HelloWorld 并返回。
    这里如果事前字符串池中没有 HelloWorld 字符串,那么还是会在字符串池中创建一个 HelloWorld 字符串再返回。总之返回的不是堆中的 s6 那个字符串。 第四条也能解释为什么第五条是
    false 。s2是堆中的 HelloWorld,s2.intern() 是字符串池中的 HelloWorld 。String s6 = (s3 + s4).intern();

    String s7 = (s3 + s4).intern(); 则s7 存储的 HelloWorld 是存放字符串池中!!!

    四、关于String类不是可变类

    所谓不可变类,就是创建该类的实例后,该实例的属性是不可改变的,java提供的包装类和java.lang.String类都是不可变类。当创建它们的实例后,其实例的属性是不可改变的。但是需要注意的是:

    String s="abc"; s="def";

    s是字符串对象的”abc”引用,即引用是可以变化的,跟对象实例的属性变化没有什么关系,

     String类被设计成不可变的原因

    1,字符串常量池的需要

    字符串常量池(String pool, String intern pool, String保留池) 是Java方法区中一个特殊的存储区域, 当创建一个String对象时,假如此字符串值已经存在于常量池中,

    则不会创建一个新的对象,而是引用已经存在的对象。 如下面的代码所示,将会在字符串常量池中只创建一个实际String对象. 
    代码如下:

    1 String s1 = "abcd"; 
    2 String s2 = "abcd"; 

    假若字符串对象允许改变,那么将会导致各种逻辑错误,比如改变一个对象会影响到另一个独立对象. 严格来说,这种常量池的思想,是一种优化手段.

    1 String s1= "ab" + "cd"; 
    2 String s2= "abc" + "d"; 

    也许这个问题违反新手的直觉, 但是考虑到现代编译器会进行常规的优化, 所以他们都会指向常量池中的同一个对象. 或者,你可以用 jd-gui 之类的工具查看一下编译后的class文件. 

    2,允许String对象缓存HashCode

    Java中String对象的哈希码被频繁地使用, 比如在hashMap 等容器中。

    字符串不变性保证了hash码的唯一性,因此可以放心地进行缓存.这也是一种性能优化手段,意味着不必每次都去计算新的哈希码.

    3, 安全性

    String被许多的Java类(库)用来当做参数,例如 网络连接地址URL,文件路径path,还有反射机制所需要的String参数等, 假若String不是固定不变的,将会引起各种安全隐患。 
    假如有如下的代码:

    1 boolean connect(string s){
    2     if (!isSecure(s)) {
    3 throw new SecurityException();
    4 }
    5     // 如果在其他地方可以修改String,那么此处就会引起各种预料不到的问题/错误
    6     causeProblem(s);
    7 }

    4,线程安全 

    因为字符串是不可变的,所以是多线程安全的,同一个字符串实例可以被多个线程共享。这样便不用因为线程安全问题而使用同步。字符串自己便是线程安全的。

    总体来说, String不可变的原因包括 设计考虑,效率优化问题,以及安全性这三大方面.

     ----------------------------------------------------若有不正之处,欢迎大家指正,不胜感激!!!!!

  • 相关阅读:
    Overloaded的方法是否可以改变返回值的类型
    parseXXX的用法
    java的类型转换问题。int a = 123456;short b = (short)a;System.out.println(b);为什么结果是-7616?
    UVA 10405 Longest Common Subsequence(简单DP)
    POJ 1001 Exponentiation(大数处理)
    POJ 2318 TOYS(计算几何)(二分)
    POJ 1265 Area (计算几何)(Pick定理)
    POJ 3371 Flesch Reading Ease (模拟题)
    POJ 3687 Labeling Balls(拓扑序列)
    POJ 1094 Sorting It All Out(拓扑序列)
  • 原文地址:https://www.cnblogs.com/sqy123/p/9120334.html
Copyright © 2011-2022 走看看