String解析

前言

本文的初就是记录一些知识点,方便自己未来的学习,本文主要是描述java中String常用或不常用的特性,就是都简单介绍下😂,有个大概的认识。

String不可变性

不可变性就是变量一旦初始化后就不会发生变化了,基本数据类型的话值不能改变,引用类型的话就是不能指向其他新引用且指向对象的状态也不能发生改变。
这个时候可能会发生歧义,有些人会把final跟不可变性当做同一概念,将他们划等号。final修饰的变量不能指向新的引用,但引用指向的对象的状态是可发生改变的,这就是final跟不可变性的区别。严格来说只有基本类型被final修饰后才是常量。
而String就具有不可变性,String的不可变性其实是通过final来实现的,下面是java1.8 String的源码。

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    /** The value is used for character storage. */
    private final char value[];

    /** Cache the hash code for the string */
    private int hash; // Default to 0

在java1.7之后,String由两个成员变量组成,其中的核心就是value[],其中的所有字符都是属于String这个对象的,同时他自己也是一个引用,指向的是一个数据对象。虽然value修饰为final,但它是私有的且没有提供任何可以获取或修改该对象的方法,从而保证了String的不可变性。
其实String的不可变性有着重要的意义,它是实现字符串池的基础,同时它还可以加快字符串处理速度、使多线程安全、避免安全问题等优势。
但还是有办法改变String的不可变性,那就是通过反射来实现,以下就是实现代码:

		String str = "wujian_wujian";
        System.err.println(str);
        Field strField = String.class.getDeclaredField("value");
        strField.setAccessible(true);
        char[] chars = (char[])strField.get(str);
        chars[6] = '~';
        System.err.println(str);

输出结果:

wujian_wujian
wujian~wujian

String的存储

常量池有多种(Class文件常量池、运行时常量池,全局字符串常量池,以及基本类型包装类对象常量池),常量池主要存储两大常量,字面量与符号引用,字面量的话主要就是字符串字面量,整型字面量和声明为final的常量等,而符号引用主要就是字段与方法的名称和描述符以及类与接口的全限定名。
在jdk1.6及以前的时候常量池存储在方法区,方法区的实现即永久代,在jdk1.7的时候字符串常量池从方法区移到了堆中,而运行池常量池还存储在方法区中,到了jdk1.8永久代被元空间给代替了,之前存储的在方法区的常量池还在方法区,只不过实现变成了元空间(堆外内存),而字符串常量池还在堆中。
至于永久代为何要向元空间转换,原因有很多,如:

  • 永久代的存在会给GC过程带来很多不必要的复杂度同时回收效率低
  • 字符串存在永久代中,容易出现内存泄露与性能问题
  • 永久代的大小不易分配,太小容易出现永久代溢出,太大则容易导致老年代溢出
  • …………………………

String的创建与intern函数

String的创建有两种方式,一个是字面量形式,另一种则是new创建对象的形式。

  • 字面量形式
    通过字面量来创建字符串的时候,jvm判断字符串常量池是否已经存在该字符串,如存在则直接将变量指向该对象,如不存在则在常量池中生成该对象同时将变量指向该对象。字符串常量池运用的就是享元模式。
  • 创建字符串对象的形式
    通过new来创建对象时,不管三七二十一在堆中创建一个对象,如果字符串常量池没有该字面量的对象则也创建一个。

当调用 intern 方法时,如果字符串常量池中已经存在该字面量,那么返回池中的字符串引用;否则将此字符串添加到字符串常量池中,并返回字符串的引用。intern()的实现上jdk1.6,与jdk1.7还是有一定区别的。
在jdk1.6中,intern()方法会把首次遇到的字符串实例复制到永久代中,返回的也是永久代中这个字符串实例的引用,而其他创建的新的字符串实例在Java堆中。
在jdk1.7以后,intern()实现不会再复制实例,只是在常量池中记录首次出现的实例引用。
所以intern()也可以手动入池

实例

示例一

		String str1 = new String("wu");
		System.out.println(str1.intern()==str1);
		System.out.println(str1.intern()=="wu");
		str1=str1+"jian";
		System.out.println(str1.intern()==str1);
		String str2 = "wujian";
		System.out.println(str1.intern()==str2);
		System.out.println(str1==str2);

		false
		true
		true
		true
		true

当new一个String字符串的时候会在内存中开辟一片新空间存放新对象,同时将"wu"字符串放到常量池里面去,相当于创建了两个对象。而intern()会在常量池中记录首次出现的实例引用,所以第一个为false而第二个为true,接下来str1=str1+"jian"将会创建一个新的字符串实例,而这个字符串实例与intern()返回的引用是同一个,所以第三个也为true。String str2 = “wujian”;此时在常量池已经有了"wujian"这个字符串,将会直接把字符串的地址赋值给str2,所以第四个和第五个都是true。

将String str2 = “wujian”;移动到str=str1+“jian”;前面去
示例二

		String str1 = new String("wu");
		System.out.println(str1.intern()==str1);
		System.out.println(str1.intern()=="wu");
		String str2 = "wujian";
		str1=str1+"jian";
		System.out.println(str1.intern()==str1);
		System.out.println(str1.intern()==str2);
		System.out.println(str1==str2);

		false
		true
		false
		true
		false

我们发现示例一跟示例二的结果有很大的区别,原因如下:
第一个和第二个结果前面已经说过,对于第三个是因为先执行String str2 = “wujian”;然后在常量池中放入了"wujian"然后将地址赋值给str2,接下来执行str1=str1+“jian”;的时候将会创建一个新的对象,而这个对象是跟常量池里面的这个是不一样的,而str1.intern()将会返回首次出现的实例引用,所以第三个为false,第四个为true,而str1与str2是两个不同的对象,所以为false。

实例三

		String str1 = new String("ja")+"va";
		System.out.println(str1.intern()==str1);
		String str2 = new String("wu")+"jian";
		System.out.println(str2.intern()==str2);

		false
		true

对于第一个返回’false’是因为"java"这个字符串在new String(“ja”)+"va"之前已经出现过了,字符串常量池已经有了它的引用了,不符合首次出现的原则了,而"wujian"这个字符串是首次出现的,因此为’true’。

实例四

		String str1 = new String("aaa")+"a";
        System.err.println("aaaa"==str1.intern());
        System.err.println(str1=="aaaa");
        System.err.println("--------------------------------");
        String str2 = new String("bbb")+"b";
        System.err.println(str2.intern()=="bbbb");
        System.err.println(str2=="bbbb");
        System.err.println("--------------------------------");
        String str3 = new String("cccc");
        System.err.println(str3.intern()==str3);
        System.err.println(str3=="cccc");
        
		true
		false
		--------------------------------
		true
		true
		--------------------------------
		false
		false

new String(xx) 如果常量池没有xx则在常量池创建一个 且在 堆里面创建一个
str1 = str1+str2仅在堆里面创建一个
str=“xx”,如果常量池没有xx则在常量池中创建xx对象,如果在此之前使用过intern()方法 则常量池里存储的为堆中xx的地址
所以第一个"aaaa"= =str1.intern()先将字面量"aaaa"存到了常量池,str1为堆里面的对象,则为true,false。第二个str2.intern()==“bbbb”,str2只在堆中创建一个对象,然后intern()将堆里面的对象放入到常量池,这为true,true。第三个str3在堆和常量池里都创建了一个对象,则intern()直接返回常量池中的对象,则false,false。

实例五

		String str1 = "aa";
        String str2 = "bb";
        String str3 = "aa"+"bb";
        String str4 = str1+str2;
        final String str5="cc";
        String str6 ="aa"+str5;
        String str7="aacc";
        System.err.println((str3==str4)+":"+(str3=="aabb")+":"+(str4=="aabb"));
        System.err.println((str6==str7)+":"+(str6=="aacc")+":"+(str7=="aacc"));

		false:true:false
		true:true:true

str3为字面量"aa"与字面量"bb"的组合所以等价于"aabb",而str4为变量直接的组合,因为其在编译期不能被确定,会在运行期创建新对象,所以与"aabb"不等价,而str7为字面量"aa"与常量"cc"的组合,其值在编译的时候就能够被确定所以等价"aacc"。
在str4=str1+str2这个一步中,可以拆分成以下几步:

  • 调用 String 类的静态方法 String.valueOf() 将 str1 转换为字符串
  • jvm在堆中创建一个StringBuilder对象,同时用str1对应字符串进行初始化
  • 调用StringBuilder对象的append方法完成与身str2所对应字符串对象进行合并
  • 调用StringBuilder的toString()方法在堆中创建一个String对象
  • 将创建好的对象地址赋值给变量str4
    这里一共有五个对象,分别为常量池中的aa,bb,aabb 堆中的aabb与Stringbuilder对象。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值