• String 进阶


    字符串拼接

    // 常量与常量的拼接结果放在常量池
    // 常量池中不会存在相同的常量
    String str1 = "a" + "b";
    System.out.println(str1 == "ab");
    
    • 1
    • 2
    • 3
    • 4
    // 拼接时有一个为变量,则结果会放在堆中。
    // 变量拼接的原理是 StringBuilder append 最后toString 
    // 查看字节码指令就可以看到详细过程
    // 就是在堆空间中 new String
    String a = "a";
    String str2 = a + "b";
    System.out.println(str2 == "ab");
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    // 拼接结果调用 intern 方法,则主动将常量池中还没有的字符串对象放入字符串常量池,并返回对应地址
    // 如果常量池中有对应字符串对象,则返回已有的字符串对象地址
    String b = "b";
    String str3 = ("a" + b).intern();
    System.out.println(str3 == "ab");
    
    • 1
    • 2
    • 3
    • 4
    • 5
    // final 修饰的变量,在编译时就会进行赋值确定
    String str4 = a + b;
    System.out.println(str4 == "ab");// false
    final String c = "c";
    final String d = "d";
    String str5 = c + d;
    System.out.println(str5 == "cd");// true
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    使用 StringBulider 进行拼接

    int i = 0;
    String str = "";
    while (i < 1000){
        str += "a";
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    循环中每次 str + “a” 都会创建一个 StringBuilder ,然后toString。效率极低。

    int i = 0;
    StringBuilder sb = new StringBuilder();
    while (i < 1000){
        sb.append("a");
    }
    String str = sb.toString();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    只 new 了一个StringBuilder,且只 toString 一次。

    进一步优化

    int i = 0;
    StringBuilder sb = new StringBuilder(1000);
    while (i < 1000){
        sb.append("a");
    }
    String str = sb.toString();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    优化方式和ArrayList一样。如果我们能大概确定要生成的字符串长度,我们可以初始化 StringBuilder 的底层 char[] 数组的长度,避免超过长度时的扩容操作。

    new String 会创建几个对象?

    String s = new String(“ab”) 会创建两个对象。一个在堆空间,一个在字符串常量池。此时 s 的地址为指向堆空间的字符串对象。

    具体可以在 idea 中下载 jclasslib 插件,查看字节码的方式来解释
    在这里插入图片描述

    引申

    new String("a") + new String("b") // 创建了几个对象?
    1. new String("a") 创建两个对象,堆和常量池
    2. new String("b") 创建两个对象,堆和常量池
    3. 两个非常量相加,会创建一个 StringBuilder 对象使用其 append 方法,最后 toString
    4. toString 方法会 new 一个 String 对象(但其不会在常量池生成对象"ab"
    • 1
    • 2
    • 3
    • 4
    • 5

    intern 方法的使用

    public native String intern();
    
    • 1

    intern 是一个 native 方法,如果当前常量池没有当前字符串对应相等(equals 为 true)的字符串,则将对象放入字符串常量池,并返回对应地址。如果常量池中有对应字符串对象,则返回已有的字符串对象地址

    示例

    String a = new String("a");
    a.intern();
    System.out.println(a == "a"); //jdk6 false jdk1.7+ false
    
    String b = new String("b") + new String("b");
    b.intern();
    System.out.println(b == "bb");//jdk6 false jdk1.7+ true
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    对象 a 指向的时堆中的字符串对象地址,“a” 放在常量池的,所以为 false

    new String(“b”) + new String(“b”) 不会在常量池创建对象 “ab”,其主要问题是,jdk1.7+ 环境中,在调用 intern 方法的时候,不是在常量池中创建一个新的对象 “ab”,而是将当前堆中 new 的 “ab” 的引用赋值给了常量池的引用,导致堆和常量池中的引用都指向了同一个地址。基于此特性,下面的示例结果就能够解释了。

    String c = new String("c") + new String("c");
    String cc = "cc";
    c.intern(); // 当前 "cc" 在常量池已经存在,且和堆中的对象引用不同
    System.out.println(c == cc);// jdk1.7+ false
    
    
    String d = new String("d") + new String("d");
    d.intern();// 此时 "dd" 在常量池还不存在,基于上面的解释,此时堆和常量池中的"dd"对象的引用是一致的
    String dd = "dd";
    System.out.println(d == dd);//  jdk1.7+  true
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    intern 使用技巧

    大量的 String 对象使用的时候,比如:String 数组或大的集合中存放 String 对象,可以对String对象先调用 intern 方法返回常量池引用后存放。这样的好处是,最后这些大量的引用都引用的常量池的对象,堆中的对象可以正常 GC 释放。此方式特别在有大量重复字符串对象的时候能节省大量的空间。

  • 相关阅读:
    绝地求生大吃鸡攻略,让你成为顶级战士!
    基于SpringBoot+Vue的校园招聘管理系统(Java毕业设计)
    vim的超详细使用方法
    小白高效自学-网络安全(黑客技术)
    NSSCTF2nd与羊城杯部分记录
    [附源码]SSM计算机毕业设计医学季节性疾病筛查系统JAVA
    Kernel Memory 入门系列: RAG 简介
    CTFHub技能树web之XSS
    生产依赖与开发依赖区别: 前端程序没有区别,后端程序有点区别
    【杂记-浅谈OSPF协议之Hello、DD、LSR、LSU、LSAck报文】
  • 原文地址:https://blog.csdn.net/forlinkext/article/details/133377569