String总结

不变性

定义

如果一个对象创建完成后,不能改变它的状态,那么这个对象就是不可改变的,不改变对象的状态的意思是,不能改变对象内的成员变量,包括基本数据类型的值不能改变,引用类型的变量不能指向其他的对象,引用类型指向的对象的状态也不能改变

需求

1:内存管理需要,字符串属于基础数据类型,多次改变,堆内存占用较大
2:安全需要,java多个底层方法大部分需要调用String,如果可以改变安全不占用

实现

实例变量:

private final char value[]; 字符串由字节数组构成,private不提供方法调用,修改此数组,final数组不可改变,间接证明字符串的不变性,但是可以调用反射来修改此数组中的值
private int hash; 字符串hash,用以在hashmap中生成hash

方法:

所有String的方法中最后都是重新创建一个String对象

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

首先比较两者的引用是不是一个,在比较是不是String,在比较长度,最后再逐一比较,返回结果 

结论,String是final的,不可继承,并且不可变的,我们眼中看到改变就是引用中的地址发生了改变

内存分析

java中的常量池分为两种

1静态常量池:class文件中的常量池,包括字符串(数字)字面量,类,方法的信息,占用class文件绝大部分空间

2运行时常量池:jvm在完成类装载操作后,将class文件中的常量池载入到内存中,并保存在方法区,我们常说的常量池,就是指方法区中的运行时常量池,在1.7以后的jvm中方法区物理上是在堆中,常量池实在方法区

创建对象分析

String a = "cnmpm"; 

在栈中创建引用a,在常量池中寻找字符串"cnmpm",如果找不到则创建,找到了则返回其中的地址到a的栈中,同理其他直接创建的字符串,同此种方式相同

String c = new String("chenssy")

在栈中创建c的引用,在堆内存中肯定创建对象chenssy,此chenssy同上一个不同,此处是在堆内存在,上一个实在常量池中,在常量池中寻找chenssy,若存在则将常量池中的地址返回到堆中,否者创建此常量,此时栈中c存放的是堆中对象的地址。关系如下图



String str1 = "a"+"bc"

字面常量在编译器就优化为"abc",再进入常量池中进行判断

String str1 = "ab";String str2 = "cd"; String str3 = str1+str2;

 String str1 = "ab";                                       //1
        String str2 = "cd";                                       //2
        String str3 = str1+str2;                                  //3
        String str4 = "abcd";                                     //4
        System.out.println(str3 == str4);                         //5
        System.out.println(str3.intern() == str4);                //6\

1:常量池中判断ab,创建或者返回

2:常量池中判断cd,创建或者返回

3:运行期jvm在堆中创建一个StringBuilder类,同时利用str中的字符串进行初始化,调用append方法发添加str2中的字符串,接着调用StringBuilder的toString方法创建一个String对象,最后将刚生成的String对象的堆地址放在str3中

4:常量池中判断abcd,创建或者返回

5:比较str3和str4在栈中所包含的地址,因为str4中包含的是常量池中的地址,str3中包含的是堆内存中新创建的String的堆地址,所以两者不相同,所以返回false

6:调用str3的intern方法,此方法进入常量池中寻找str3的字面量,如果有则返回字面常量地址,如果无则在常量池中创建str3的引用地址,所以调用str3.inern的方法后进入常量池查找"abcd",因为str4已经在常量池中创建了abcd,所以当调用str3的intern方法时,返回的是str4在常量池中创建的地址,两者相同

final str1 = "1"

        String s0 = "ab";
        final String s1 = "b";
        String s2 = "a" + s1;
        System.out.println("===========test9============");
        System.out.println((s0 == s2)); //result = true

当对象用fial修饰时,他在编译时解析为常量值的一个本地拷贝到自己的常量池中或嵌入到他的字节码流中,这里的常量池指的这个方法所属的class文件中的常量池,所以当运行''a''+s1时可理解为"a"+"b",所以结果true。

intern

此方法的原义是进入常量池寻找String对象的字面量,如果有则返回字面量地址,如果无,则创建并返回,只是这里的创建和返回在jdk的版本中有所区别

1.6:在调用intern时,如果在常量池中没有找到此字面量则复制一个字面量,返回常量池中复制的字面量地址

1.7:在调用intern时,如果在常量池中没有找到此字面量则复制此对象的地址,返回此对象的地址

参考

以下总结是我参看上面的文章总结而来,如有不正之处,欢迎指正

 String s = "aa";
        String s1 = "aa";
        System.out.println(s.intern() == s);                   //true
        System.out.println(s.intern() == s1);                  //true
        System.out.println(s.intern() == s1.intern());         //true   

以上运行说明s和s1的栈中存放的是常量池中aa的地址

   String s = new String("aa");
        System.out.println(s == s.intern());         //false

以上说明,虚拟机栈中s所包含地址和s对象中的常量地址不同,说明s在堆中创建了一个对象,又在常量池中查找了aa的地址,然后将堆中对象的地址赋给栈

 /*    String s = new String("aa");
        String s1 = "aa";
        System.out.println(s == s.intern());         //false
        System.out.println(s.intern() == s1);        //true
        */

        String s = new String("aa");
        s.intern();
        String s1 = "aa";
        System.out.println(s == s.intern());         //false
        System.out.println(s.intern() == s1);       //true

两次改变intern的执行位置,就是为了证明在new String()对象时,也在常量池中创建了一个aa,所以intern在此处并无用处

      String s = new String("ds")+new String("af");
        System.out.println(s == "dsaf");                     //1
        System.out.println( s.intern()== "dsaf");            //2
        System.out.println(  s.intern() ==s);                //3

在字符串+时,此处具有对象相加,查看class文件

     Compiled from "test.java"
public class test {
  public test();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>"
()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: new           #2                  // class java/lang/StringBuilder
       3: dup
       4: invokespecial #3                  // Method java/lang/StringBuilder."
init>":()V
       7: new           #4                  // class java/lang/String
      10: dup
      11: ldc           #5                  // String ds
      13: invokespecial #6                  // Method java/lang/String."<init>"
(Ljava/lang/String;)V
      16: invokevirtual #7                  // Method java/lang/StringBuilder.a
pend:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      19: new           #4                  // class java/lang/String
      22: dup
      23: ldc           #8                  // String af
      25: invokespecial #6                  // Method java/lang/String."<init>"
(Ljava/lang/String;)V
      28: invokevirtual #7                  // Method java/lang/StringBuilder.a
pend:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      31: invokevirtual #9                  // Method java/lang/StringBuilder.t
String:()Ljava/lang/String;
      34: astore_1
      35: return
}

可知在他的底层利用new StringBuilder的append相加,最后利用toString返回,但是此处并没有利用最终生成的String的字面量进入常量池,下面进入分析

1:比较s和字符串dsaf的地址,此处在寻找dsaf的过程总就已经在常量池中新建dsaf

2:调用s的intern,返回的是1中dsaf字符串的地址,所以两者相同,返回true

3:s的地址是StringBuilder最终生成的String对象的地址,同字符串dsaf的地址不同返回false

    String s = new String("ds")+new String("af");
        s.intern();
        System.out.println(s == "dsaf");
        System.out.println( s.intern()== "dsaf");
        System.out.println(  s.intern() ==s);
此段代码同上一段代码的不同之处在于,先调用了s.intern(),这时将会进入常量池中寻找dsaf,发现并没有将s的对象地址放入了常量池,导致一下的的比较全为true,在上一段中我为什么说在s的定义中s最终并没有像常量池中插入dsaf,那是因为如果插入了不管我在什么时候比较s.intern == 结果都为false,现在调换了intern的位置,结果发生了改变正好说明了字符串在对象相加的过程中,并没有向常量池中插入字面量。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值