• 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不可变的原因包括 设计考虑,效率优化问题,以及安全性这三大方面.

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

  • 相关阅读:
    linux Mint 安装apache2
    linux Mint 安装tomcat8
    linux Mint wine安装qq,桌面快捷键配置
    linux Mint mysql 安装
    卸载linux Mint自带jdk并安装最新jdk
    linux Mint截图软件 Shutter
    linux git安装及配置(包括更新)
    linux安装wine
    百度地图用ip获取当前位置的经纬度(高精度)
    mysql 索引和视图
  • 原文地址:https://www.cnblogs.com/sqy123/p/9120334.html
Copyright © 2020-2023  润新知