String、StringBuffer、StringBuilder

String

JVM体系结构图

在这里插入图片描述

Java栈、Java堆、方法区和常量池

Java栈[线程私有数据区]

 每个Java虚拟机线程都有自己的Java虚拟机栈,Java虚拟机栈用来存放栈帧,每个方法被
 
 执行的时候都会同时创建一个栈帧(Stack Frame)用于存储局部变量表、操作栈、动态
 
 链接、方法出口等信息。每一个方法被调用直至执行完成的过程,就对应着一个栈帧在虚
 
 拟机栈中从入栈到出栈的过程。

Java堆[线程共享数据区]

在虚拟机启动时创建,此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都
在这里分配。

方法区[线程共享数据区]

     方法区在虚拟机启动的时候被创建,它存储了每一个类的结构信息,例如运行时常量
     
池、字段和方法数据、构造函数和普通方法的字节码内容、还包括在类、实例、接口

初始化时用到的特殊方法。在JDK8之前永久代是方法区的一种实现,而JDK8元空间

替代了永久代,永久代被移除,也可以理解为元空间是方法区的一种实现。

常量池[线程共享数据区]

常量池常被分为两大类:静态常量池和运行时常量池。

静态常量池也就是Class文件中的常量池,存在于Class文件中。

运行时常量池(Runtime Constant Pool)是方法区的一部分,存放一些运行时常量数据。

字符串常量池

1. 字符串常量池存在运行时常量池之中(在JDK7之前存在运行时常量池之中,在JDK7已经将其转移到堆中)。

2. 字符串常量池的存在使JVM提高了性能和减少了内存开销。

3. 使用字符串常量池,每当我们使用字面量(String s=”1”;)创建字符串常量时,JVM会首

 先检查字符串常量池,如果该字符串已经存在常量池中,那么就将此字符串对象的地址

赋值给引用s(引用s在Java栈中)。如果字符串不存在常量池中,就会实例化该字符串并

且将其放到常量池中,并将此字符串对象的地址赋值给引用s(引用s在Java栈中)。
两种创建String对象的区别
方式一:直接赋值 String s = "hello"; 
方式二:调用构造器 String s2 = new String("hello"); 
图解

在这里插入图片描述


步骤:① String s = "hello";  JVM会首先检查字符串常量池,如果该字符串已经存在常量池

中,那么就将此字符串对象的地址赋值给引用s(引用s在Java栈中)。如果字符串不存在

常量池中,就会实例化该字符串并且将其放到常量池中,并将此字符串对象的地址赋值给

引用s(引用s在Java栈中)。


②使用字符串常量池,每当我们使用关键字new(String s=new String(”1”);)创建字符串

常量时,JVM会首先检查字符串常量池,如果该字符串已经存在常量池中,那么不再在字

符串常量池创建该字符串对象,而直接堆中复制该对象的副本,然后将堆中对象的地址赋

值给引用s,如果字符串不存在常量池中,就会实例化该字符串并且将其放到常量池中,然

后在堆中复制该对象的副本,然后将堆中对象的地址赋值给引用s。

在这里插入图片描述

        翻译为:“初始化一个新创建的字符串对象,以便它表示与参数相同的字符序列;换句
        
        话说,新创建的字符串是参数字符串的副本。除非需要显式的原始副本,否则使用此
        
        构造函数是不必要的,因为字符串是不可变的。
        
		由于String字符串的不可变性我们可以十分肯定常量池中一定不存在两个相同的字符串。
String源码

public final class String
 
    implements java.io.Serializable, Comparable<String>, CharSequence
 
{
 
    /** The value is used for character storage. */
 
    private final char value[];
 
 
 
    /** The offset is the first index of the storage that is used. */
 
    private final int offset;
 
 
 
    /** The count is the number of characters in the String. */
 
    private final int count;
 
 
 
    /** Cache the hash code for the string */
 
    private int hash; // Default to 0
 
 
 
    /** use serialVersionUID from JDK 1.0.2 for interoperability */
 
    private static final long serialVersionUID = -6849794470754667710L;
 
 
 
    ........
 
}
1. String类是用final修饰的,这意味着String不能被继承,而且所有的成员方法都默认为
final方法。

2.   
		value[] :char数组用于储存String的内容。
		
		 offset :存储的第一个索引。
		
		 count :字符串中的字符数。
		
		 hash :String实例化的hashcode的一个缓存,String的哈希码被频繁使用,将其缓存起来,每次使用就没必要再次去计算,这也是一种性能优化的手段。这也是String被设计为不可变的原因之一,

Srtring 在JVM 层解析

1.创建字符串形式
 首先形如声明为S ss是一个类S的引用变量ss(我们常常称之为句柄,后面JVM相关内容
 
 会讲到),而对象一般通过new创建。所以这里的ss仅仅是引用变量,并不是对象。


2. 
String s1=”1”;
String s2=new String(“1”);
  • 1、 String s1=”1”;
    在这里插入图片描述
3.  从图中可以看出,s1使用””引号(也是平时所说的字面量)创建字符串,在编译期的时

候就对常量池进行判断是否存在该字符串,如果存在则不创建直接返回对象的引用;如果

不存在,则先在常量池中创建该字符串实例再返回实例的引用给s1。注意:编译期的常量

池是静态常量池
 2.  再来看看s2,s2使用关键词new创建字符串,JVM会首先检查字符串常量池,如果该字

符串已经存在常量池中,那么不再在字符串常量池创建该字符串对象,而直接堆中复制该

对象的副本,然后将堆中对象的地址赋值给引用s2,如果字符串不存在常量池中,就会实

例化该字符串并且将其放到常量池中,然后在堆中复制该对象的副本,然后将堆中对象的

地址赋值给引用s2。注意:此时是运行期,那么字符串常量池是在运行时常量池的。
  • 2.“+”连接形式创建字符串
    (1)String s1=”1”+”2”+”3”;
    在这里插入图片描述
 使用包含常量的字符串连接创建是也是常量,编译期就能确定了,直接入字符串常量池,
 当然同样需要判断是否已经存在该字符串。

(2)String s2=”1”+”3”+new String(“1”)+”4”;
在这里插入图片描述

1. 当使用“+”连接字符串中含有变量时,也是在运行期才能确定的。首先连接操作最开始时
 
如果都是字符串常量,编译后将尽可能多的字符串常量连接在一起,形成新的字符串常量

参与后续的连接(可通过反编译工具jd-gui进行查看)。

2. 接下来的字符串连接是从左向右依次进行,对于不同的字符串,首先以最左边的字符串

为参数创建StringBuilder对象(可变字符串对象),然后依次对右边进行append操作,最

后将StringBuilder对象通过toString()方法转换成String对象(注意:中间的多个字符串常量

不会自动拼接)。

 3. 实际上的实现过程为:String s2=new StringBuilder(“13”).append(new 
String(“1”)).append(“4”).toString();

 4. 当使用+进行多个字符串连接时,实际上是产生了一个StringBuilder对象和一个String对
象。

(3)String s3=new String(“1”)+new String(“1”);
在这里插入图片描述

String的面试题

   @Test
    public void test12(){
        String []str=new String[]{"hhh","hhh"};
        for (int i = 0; i < str.length; i++) {
            str[i]="kkk";
        }
        for (int i = 0; i < str.length; i++) {
            System.out.println(str[i]);

        }
    }
    输出:kkk
		 kkk
 @Test
    public void test12(){
        String []str=new String[]{"hhh","hhh"};
		for (String s : str) {
            s="ggg";
        }
        //相当于遍历
        System.out.println(Arrays.toString(str));//[hhh, hhh]
        //遍历
        for (int i = 0; i < str.length; i++) {
            System.out.println(str[i]);//hhh	hhh
        }


StringBuffer与StringBuilder

1. 两者都没有重写equals() 比较的是地址值。

StringBuffer s = new StringBuffer("hello");

String s2 = "hello";

System.out.println(s==s2);  //编译报错
System.out.println(s.equals(s2));//f
System.out.println(s2.equals(s));//f 调用String的equals() 底层判断是不是同一类型  不是的话直接返回false
//不能只看到表面的equals().
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值