Java字符串String的那些事

简介: Java字符串String的那些事

引言

众所周知在java里面除了8种基本数据类型的话,还有一种特殊的类型String,这个类型是我们每天搬砖都基本上要使用它。

String 类型可能是 Java 中应用最频繁的引用类型,但它的性能问题却常常被忽略。高效的使用字符串,可以提升系统的整体性能。当然,要做到高效使用字符串,需要深入了解其特性。

String类

我们可以看下String类的源码:

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

从源码上我们是不是可以发现String类是被final关键字所修饰的,String类的数据是通过char[] 数组来存储的。数组也是被final修饰的所以String 对象是不可被更改的。接下来我们再看看String的一些方法:像concat、replace、substring等都是返回了一个新的new String感兴趣的可以去看看String的一些常见方法。当我们执行这些方法之后最原始的字符串是没有改变的,都是返回新的字符串。

 public static void main(String[] args) {
        String str = new String("java金融");
        String str1 = str.substring(0, 4);
        String str2 = str.concat("公众号");
        String str3 = str.replace("java金融", "关注:【java金融】");
        // 还有其他的方法
        System.out.println(str1);
        System.out.println(str2);
        System.out.println(str3);
        System.out.println(str);
    }

输出结果

java
java金融公众号
关注:【java金融】
java金融

所以我们只要记住一点:“String对象一旦被创建就是固定不变的了,
String对象的任何改变都不影响到原对象,相关的任何change操作都会生成新的对象”。

字符串常量池

JVM中,为了减少字符串对象的重复创建,维护了一块特殊的内存空间,这块内存就被称为全局字符串常量池(string pool也有叫做string literal pool)。

字符串常量池的位置

字符串常量池所在的位置也是跟不同的jdk版本有关系的。

  • JDK6及之前字符串常量池存放在方法区, 此时hotspot虚拟机对方法区的实现为永久代。
  • JDK7字符串常量池被从方法区拿到了堆中, 这里没有提到运行时常量池,也就是说字符串常量池被单独拿到堆,运行时常量池剩下的东西还在方法区, 也就是hotspot中的永久代。
  • JDK8 hotspot移除了永久代用元空间(Metaspace)取而代之, 这时候字符串常量池还在堆里只不过把方法区的实现从永久代变成了元空间(Metaspace) 。

String# intern

String::intern()是一个本地方法,它的作用是如果字符串常量池中已经包含一个等于此String对象的字符串,则返回代表池中这个字符串的String对象的引用;
否则,会将此String对象包含的字符串添加到常量池中,并且返回此String对象的引用。

上述定义出自《深入理解Java虚拟机:JVM高级特性与最佳实践(第3版)》我们知道了这个 String::intern()这个方法的作用下面来看几道并没有什么用的题目看看你是否都能够回答对?

        String str2 = new String("java") + new String("金融"); // 1
         str2.intern(); // 2
         String str1 = "java金融"; // 3
         System.out.println(str2 == str1);

这个代码在JDK6中输出结果是false,在jdk7输出是true
为何会因为不同的jdk版本输出结果不一样,因为不同版本字符串常量池的位置发生了变化。
下面来分析下为何会产生这种差异。
字符串虽然不属于基本数据类型但是它也可以想基本类型一样,直接通过字面量来赋值,同时也是可以通过new 来生成字符串对象。通过字面量赋值的方式和new 的方式 生成字符串还是有区别的。

  • 字面量赋值:通过字面量赋值(使用双引号声明出来的String)会先去常量池中查找是否已经有相同的字符串,如果已经存在栈中的引用直接指向该字符串,如果不存在就在常量中生成一个字符串再将栈中的引用指向该字符串。
  • new 的方式创建:而通过new的方式创建字符串时,就直接在堆中生成一个字符串的对象栈中的引用指向该对象。对于堆中的字符串对象,可以通过 intern() 方法来将字符串添加的常量池中,并返回指向该常量的引用。

jdk6 结果是false,是因为常量池是在永久代的Perm区和java堆是两个区域。所以两个区域的对象地址比较是不同的。
JDK7结果是true, 这个原因主要是从JDK 7及以后,HotSpot 将常量池从永久代移到了堆,正因为如此,JDK7 及以后的intern方法在实现上发生了比较大的改变,JDK7及以后,intern方法还是会先去查询常量池中是否有已经存在,如果存在,则返回常量池中的引用,这一点与之前没有区别,区别在于如果在常量池找不到对应的字符串则不会再将字符串拷贝到常量池,而只是在常量池中生成一个对原字符串的引用。所以为什么返回true 是因为执行完标号为1的时候常量池中没有"java金融"对象的,接下来标号为2的时候 会在常量池生成一个“java金融”的对象会直接存一个对堆中“java金融”的引用,标号为3:进行字面量赋值的时候常量池已经存在了所以直接返回该引用。所以都是指向堆中的字符串返回true
如果把3行代码放到第一行上面结果又不一样了,感兴趣的可以动手试一试并且分析下原因哦。

string 常见性能优化

使用+号拼接字符串

字符串拼接是我们平时在代码中使用最频繁的了。

  • +号拼接静态字符串
   String str = "关注"+"公众号:"+"java金融";

我们可以通过反编译查看下上述代码:

 public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=1, locals=2, args_size=1
         0: ldc           #2                  // String 关注公众号:java金融
         2: astore_1
         3: return
      LineNumberTable:
        line 11: 0
        line 12: 3
}

我们可以发现编译器直接帮我们优化了,直接生成了一个字符串“关注公众号:java金融” 并没有生成中间变量的String实例。如果我们上述代码稍微变化下

   public static void main(String[] args) {
        String str ="关注";
        String str1 = str + "公众号:java金融";
    }

 stack=2, locals=3, args_size=1
         0: ldc           #2                  // String 关注
         2: astore_1
         3: new           #3                  // class java/lang/StringBuilder
         6: dup
         7: invokespecial #4                  // Method java/lang/StringBuilder."<init>":()V
        10: aload_1
        11: invokevirtual #5                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        14: ldc           #6                  // String 公众号:java金融
        16: invokevirtual #5                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        19: invokevirtual #7                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
        22: astore_2
        23: return
      LineNumberTable:
        line 11: 0
        line 12: 3
        line 13: 23

从反编译代码中我们会发现生成了StringBuilder对象来进行追加。

  • 所以String + 拼接变量的时候底层是通过StringBuilder来实现的,我们循环操作拼接字符串的时候也应当使用StringBuilder替代+,否则的话每一次循环都会创建

一个StringBuilder 对象。

  • 对于静态字符串的拼接操作,Java在编译时会进行彻底的优化,会把多个拼接字符串在编译时合成一个单独的长字符串。

常见字符串经典面试题

关于字符串最常见的面试题,面试宝典常见的题目。
String s = new String("xyz") 创建了多少个实例?
一般的回答都会是2个,(一个是“xyz”,一个是指向“xyz”的引用对象s)
答案并没有那么简单哦,可以看看大佬的回答还是非常精彩的。
连接地址https://www.iteye.com/blog/rednaxelafx-774673(文末第一个参考地址)

结束

  • 由于自己才疏学浅,难免会有纰漏,假如你发现了错误的地方,还望留言给我指出来,我会对其加以修正。
  • 如果你觉得文章还不错,你的转发、分享、赞赏、点赞、留言就是对我最大的鼓励。
  • 感谢您的阅读,十分欢迎并感谢您的关注。

https://www.iteye.com/blog/rednaxelafx-774673
https://www.zhihu.com/question/36908414/answer/69724311
https://www.cnblogs.com/paddix/p/5326863.html
https://tech.meituan.com/2014/03/06/in-depth-understanding-string-intern.html
https://www.jianshu.com/p/6bee67a7f6ce
https://juejin.im/post/6844903741032759310

目录
相关文章
|
19天前
|
SQL Java 索引
java小工具util系列2:字符串工具
java小工具util系列2:字符串工具
135 83
|
7天前
|
存储 JavaScript Java
Java 中的 String Pool 简介
本文介绍了 Java 中 String 对象及其存储机制 String Pool 的基本概念,包括字符串引用、构造方法中的内存分配、字符串文字与对象的区别、手工引用、垃圾清理、性能优化,以及 Java 9 中的压缩字符串特性。文章详细解析了 String 对象的初始化、内存使用及优化方法,帮助开发者更好地理解和使用 Java 中的字符串。
Java 中的 String Pool 简介
|
13天前
|
缓存 安全 Java
java 为什么 String 在 java 中是不可变的?
本文探讨了Java中String为何设计为不可变类型,从字符串池的高效利用、哈希码缓存、支持其他对象的安全使用、增强安全性以及线程安全等方面阐述了不可变性的优势。文中还通过具体代码示例解释了这些优点的实际应用。
java 为什么 String 在 java 中是不可变的?
|
2月前
|
Java 测试技术 开发者
Java零基础-indexOf(String str)详解!
【10月更文挑战第14天】Java零基础教学篇,手把手实践教学!
122 65
|
1月前
|
存储 安全 Java
Java零基础-字符串详解
【10月更文挑战第18天】Java零基础教学篇,手把手实践教学!
105 60
|
19天前
|
Java 数据库
java小工具util系列1:日期和字符串转换工具
java小工具util系列1:日期和字符串转换工具
51 26
|
25天前
|
JSON Java 关系型数据库
Java更新数据库报错:Data truncation: Cannot create a JSON value from a string with CHARACTER SET 'binary'.
在Java中,使用mybatis-plus更新实体类对象到mysql,其中一个字段对应数据库中json数据类型,更新时报错:Data truncation: Cannot create a JSON value from a string with CHARACTER SET 'binary'.
37 4
Java更新数据库报错:Data truncation: Cannot create a JSON value from a string with CHARACTER SET 'binary'.
|
7天前
|
存储 Java
Java 11 的String是如何优化存储的?
本文介绍了Java中字符串存储优化的原理和实现。通过判断字符串是否全为拉丁字符,使用`byte`代替`char`存储,以节省空间。具体实现涉及`compress`和`toBytes`方法,前者用于尝试压缩字符串,后者则按常规方式存储。代码示例展示了如何根据配置决定使用哪种存储方式。
|
23天前
|
存储 缓存 安全
java 中操作字符串都有哪些类,它们之间有什么区别
Java中操作字符串的类主要有String、StringBuilder和StringBuffer。String是不可变的,每次操作都会生成新对象;StringBuilder和StringBuffer都是可变的,但StringBuilder是非线程安全的,而StringBuffer是线程安全的,因此性能略低。
42 8
|
23天前
|
Java
在Java中如何将基本数据类型转换为String
在Java中,可使用多种方法将基本数据类型(如int、char等)转换为String:1. 使用String.valueOf()方法;2. 利用+运算符与空字符串连接;3. 对于数字类型,也可使用Integer.toString()等特定类型的方法。这些方法简单高效,适用于不同场景。
47 7