Java基础——String和String Pool

1、String的两种形式

    String x = "a"; 
    String y = new String("ab");

变量x指向String Pool中的"a"的引用,即String Pool中存在"a";
变量y指向堆中的String 对象,堆中的String对象指向String Pool中的"ab"


2、不可变性

在JDK8及其之前,final char value[];
在JDK9及其之后,final byte[] value + encoding-flag;

  • 更改底层存储结构的动机:研究发现大部分字符串都是拉丁字符,这些字符只要1个字节即可满足存储空间需求,JDK8及其之前使用的char[]数组,每个字符使用2个字节存储,浪费空间;

  • 优化方式:将char[]变成byte[],同时为了兼容中文等这种每个字符占用2个字节的情况,加入一个编码标识;当使用ISO8859-1/Latin-1编码时,采用byte[];当使用其他编码方式,如UTF-16编码时,采用char[];

  • 不变性的深入理解:当对一个已经存在字符串常量池中的字符串进行修改(拼接/截取/部分替换…)时,都是会在字符串常量池中新建一个字符串,原有的字符串不变;

3、String拼接

某些情况下,较多的字符串进行拼接时,使用StringBuffer(线程安全)和StringBuilder(非线程安全)的appen()方法,效率比直接"+"高很多;

  • 原因:"+“底层实际会new StringBuiler(),以及new String(),每一个”+"都会进行这些操作;直接使用StirngBuilder()的append()则只会new 一次StringBuilder();

  • 对使用StringBuilder拼接的性能优化:基本思路,减少数组copy次数,
    可以在new StringBuilder()的时候,在确定拼接的字符串的大概长度时,直接指定初始的char[]容量,减少扩容的次数,StringBuilder底层采用的是char[]存放元素;

3.1、“+”进行拼接的底层原理

1String s1 = "a" + "b";  // 等同于"ab"
     String s2 = "ab";
  • 常量之间的拼接,通过编译期优化,导致s1和s2指向的都是Stirng Pool中"ab"的引用
2String s3 = "a"; 
    String s4 = s3 + "b";
    String s5 = "ab";
  • 存在变量的拼接,s4指向的引用生成的底层原理:
    首先new StringBuilder对象,通过该对象调用append()方法,最后通过该对象调用toString()方法;
    toString()方法,底层调用了new String(“ab”)方法, 将new的对象返回;

  • 结果:s4指向堆中new 的String对象,但该String对象不指向String Pool中的"ab";s5是直接指向String Pool中的"ab";

  • StringBuilder的toString()分析:底层类似new String(“xxx”),但是和一般的new String(“xxx”)会在String Pool中加入对应的"xxx"不同,该操作不会如此;

3.2、append()拼接

底层原理:通过char[]数组存放元素,将需要拼接的元素直接放在数组的空闲末尾,若数组越界则进行数组扩容;


4、字符串常量池

4.1、字符串常量池中不会存储相同内存的字符串;

实现方式:字符串常量池的底层实际是一个固定大小的Hashtable,所以当String Pool中的String比较多的时候,容易产生hash冲突,导致桶数组中的链表比较长,影响性能;

优化:不断的将Hashtable的默认容量变大;

4.2、String Pool存放的位置

JDK6及其之前,和静态变量一起存放在永久代(方法区的具体实现)
JDK7及其之后,和静态变量一起存放在Java堆中
JDK8及其之后,永久代该为元空间

  • 逻辑上字符串常量池和静态变量一直都在方法区中

  • 变化的动机:永久代的默认空间比较小,存放大量的字符串容易OOM;永久代的回收频率比较低;


5、intern()

判断调用该方法的字符串在String Pool中是否存在:

  • 若存在,返回字符串常量池中的引用地址;

  • 若不存在,Jdk6在String Pool中新建对应的字符串,返回该引用地址,jdk7以后在String Pool中不创建字符串对象,而是创建一个指向堆中某个存放该字符串的对象的地址;

原因分析:在Jdk7及其之后,String Pool已经被存放在了Java堆中,虽然String Pool中没有目标字符串,但是若堆中有对象存放这对应字符串,则直接返回堆中该对象的引用即可;

用于理解的示意图:

在这里插入图片描述
在这里插入图片描述


6、new String()的对象个数分析

new String("ab");

上面的方式创建了2个对象:

  • new关键字在堆中创建了一个对象;
  • 字符串常量池中有对象"ab"
String s = new String("ab") + new String("cd");

上面的方式创建了6个对象:

  • new StringBuilder()
  • new String(“ab”)
  • 字符串常量池中的"ab"
  • new String(“cd”)
  • 字符串常量池中的"cd"
  • toString()中的new String(“abcd”),注意该方法不会在字符串常量池中生成"abcd"

注意点:即通过上述方式可知,在字符串常量池中没有字符串"abcd",变量s指向toString()方法返回的堆中的String对象,但是String该对象不指向Stirng Pool;


7、String常用方法

  • 获取字符串指定下标处的字符:charAt(index);
  • 判断是否包含指定字符串:contains(string);
  • 判断是否以指定字符串作为后缀:endsWith(string);
  • 忽略大小写比较两个字符串是否相等:equalsIgnoreCase(string string);
  • 转换成字节数组:getBytes();
  • 从左到右将指定字符串替换成目标字符串:replace(target, replace);
  • 以指定正则表达式分割字符串:split(string);
  • 将字符串转换成字符数组:toCharArray();
  • 将其他数据类型转换成字符串:valueOf(string);底层调用了toString();
  • 获取指定字符串中第一次出现的下标:indexOf(string);
  • 从指定开始下标截取字符串的部分:subString(beginIndex);

参考:

  • 尚硅谷康师傅JVM视频
  • JAVA编程思想
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值