javaSE---string

目录

字符串类型

定义字符串

String

1、String类被final修饰,这个类不能被继承​

2、String的底层实现

常量池

1.Class文件常量池《在磁盘上》

2.运行时常量池《在方法区》

3.字符串常量池《在堆区》

哈希表

底层实现

直接赋值:创建一次对象

 使用new关键字:创建两次对象

 +拼接

手动入池---intern()方法

StringBuilder

StringBuffer

区别


字符串类型

c语言中没有字符串类型,而java中有字符串类型

c语言中字符串的结束标志是'\0',而java中字符串没有结束标志

定义字符串

直接赋值

String str="qwe";

使用关键字new,调用构造方法

String dest=new String("uiy");

 字符数组转化为字符串,调用构造方法

char[]str3={'r','t','o'};
String str4=new String(str3);

String

1、String类被final修饰,这个类不能被继承

2、String的底层实现

常量池

1.Class文件常量池《在磁盘上》

Class文件中除了包含类的版本、字段、方法、接口等描述信息外,还有一项信息就是常量池用于存放编译器生成的各种字面量和符号引用

字面量:例如 int a=10,10就是字面量

符号引用包括:类和接口的全限定名 、 字段的名称和描述符、 方法的名称和描述符

2.运行时常量池《在方法区》

是方法区的一部分,编译好的字节码文件通过java虚拟机先加载到内存,Class文件常量池中各种字面量和符号引用也将加载到运行时常量池中

3.字符串常量池《在堆区》

主要存放字符串常量本质是一个哈希表

哈希表

一种数据结构,按照某一种映射关系(设计哈希函数)来存放元素

下列使用除留余数法构造哈希函数,使用链地址法解决冲突

底层实现

public class text {
    public static void main(String[] args) {
        String str1="hello";
        String str2=new String("hello");
    }
}

打开String的源码

 发现String包含有许多的构造方法,并且使用成员value和hash

 value:该值用于字符存储

hash:缓存字符串的哈希代码,默认0

直接赋值:创建一次对象

public class text {
    public static void main(String[] args) {
        String str1="hello";
    }
}

程序编译之后,生成.class文件,.class文件常量池中存放字面值和符号引用,hello存放在里面

程序运行:.class文件通过JVM的类加载器加载到内存,JVM的解析器对加载到内存的java类进行解析

当.class文件通过JVM的类加载器加载到内存时,class文件常量池信息会被加载到运行时常量池。"Hello"会在堆区中创建一个对象,同时会在字符串常量池(堆上)存放一个它的引用

 如果这时有一个"hi"对象,和"hello"的存储位置冲突,那么next域存放冲突结点的地址,否则为NULL

 然后,主函数的str1变量开始在栈上创建,虚拟机会去字符串常量池中找是否有equals(“Hello”)的String,如果相等就把在字符串池中“Hello”的引用复制给str1。如果找不到相等的字符串,就会在堆中新建一个对象,再把引用赋给str1。

当用字面量赋值的方法创建字符串时,无论创建多少次,只要字符串的值相同,它们所指向的都是堆中的同一个对象。(指向同一个字符串)

 public static void main(String[] args) {
        String str1="hello";
        String str="hello";
        System.out.println(str==str1);
    }

 结果:true,两个都是0x666

 使用new关键字:创建两次对象

 当利用new关键字去创建字符串时,前面加载的过程是一样的,只是在运行时无论字符串常量池中有没有与当前值相等的对象引用,都会在堆中新开辟一块内存,创建一个对象

  public static void main(String[] args) {
        String str1="hello";
        String str2=new String("hello");
    }

str1中,Hello会在堆区中创建一个对象,同时会在字符串常量池存放一个它的引用

str2中,会在堆上new一个String,虚拟机会去字符串常量池中找是否有equals(“Hello”)的String,如果相等就把在字符串池中“Hello”的引用的value复制给创建的String的value

value定义就是对数组的引用,存放的是数组的地址

 栈上创建str2,引用创建的String类

 +拼接

 public static void main(String[] args) {
        String str1="hello";
        String str="hel"+"lo";
        System.out.println(str==str1);
    }

结果:true

   String str="hel"+"lo";两个常量的拼接,在编译时已经转化为了"hello"

public static void main(String[] args) {
        String str1="hello";
        String str2="hel";
        String str=str2+"lo";
        System.out.println(str==str1);
    }

结果:false

编译期间str2是变量,会创建hel对象,str2引用这个对象, 会创建lo对象,然后str指向了两个对象相拼接的对象

手动入池---intern()方法

 public static void main(String[] args) {
        String str1="11";
      String str2=new String("1")+new String("1");
        System.out.println(str2==str1);
    }

结果:false

   String str2=new String("1")+new String("1");字符串常量池没有1,先将1加载到字符串常量池中,并会在堆区new String两个对象

 两个对象拼接产生新的数组"11",字符串拼接底层优化为StingBuilder,产生新的对象StingBuilder,StingBuilder的val指向了"11"这个数组,并调用StingBuilder.toString方法产生新的String类,这个新的String类的val指向了"11"这个数组

 在这个过程中,拼接产生的”11“没有入字符串常量池

 public static void main(String[] args) {
        String str2=new String("1")+new String("1");
        str2.intern();
        String str1="11";
        System.out.println(str2==str1);
    }

结果true

  str2.intern():str2指向的对象被手动入池:11在字符串常量池中

  String str1="11";发现11在字符串常量池中,得到和str2一样的地址

StringBuilder

在一个没有字符串拼接的代码中,不会调用StringBuilder

 public static void main(String[] args) {
        String str = "123";
        System.out.println(str);
    }

  但是:如果出现字符串拼接,那么会被优化为StringBuider

  public static void main(String[] args) {
        String str="123";
        str+="890";
        System.out.println(str);
    }

 StringBuider的toString方法:new String返回

那么:代码相当于

 public static void main(String[] args) {
        String str = "123";
        //第一步:创建StringBuilder对象,调用无参数的构造方法
        StringBuilder s = new StringBuilder();
        //第二步:加载123
        s.append("123");

        str += "890";
        //第三步:加载”890“
        s.append("890");

        System.out.println(str);
        //第四步:调用StringBuider的toString方法进行类型转化
        String ss = s.toString();
        System.out.println(ss);
    }

StringBuffer

  StringBuffer的方法相比于 StringBuilder多了个synchronized(意为:同步的)保证线程安全

所谓同步就是指你要等待一个过程(调用,任务,事件等)执行完毕后才能进行下一步操作

多线程处理可以同时运行多个过程,单线程只能等一个过程结束了才能执行下一个过程

区别

1.String是不可以更改的,而StringBuffer和StringBuilder的内容可以更改

 public static void main(String[] args) {
        String s = "789";
        s = "90";
    }

 实际上创建了两个对象:0x777和0x888,s指向了新的对象

 String类是被final修饰的,字符串的所有方法都不改变字符串本身,而是返回一个新的字符串

但是StringBuffer和StringBuilder的内容可以更改,所有方法都改变本身

 2.StringBuffer和StringBuilder有许多相似的方法

 3.StringBuffer保证线程安全,适用于多线程,StringBuilder不保证线程安全,适用于单线程,相对来说StringBuilder效率升高

 StringBuffer的方法相比于 StringBuilder多了个synchronized(意为:同步的)保证线程安全

 

参与评论 您还未登录,请先 登录 后发表或查看评论

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

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:游动-白 设计师:我叫白小胖 返回首页

打赏作者

two 倩

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值