百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 编程字典 > 正文

Java笔记:深入理解String类(java中string类的方法)

toyiye 2024-08-08 00:14 7 浏览 0 评论

摘要

String是Java中最为常用的数据类型之一,也是面试中比较常被问到的基础知识点,本文来聊聊Java中的String。主要包括如下的五个内容:

  • String概览
  • “+”连接符解析
  • 字符串常量池
  • String.intern()方法解析
  • String、StringBuffer与StringBuilder

String概览

在Java中,所有类似“ABCabc”的字面值,都是String的实例;String类位于java.lang包下,是Java语言的核心类,提供了字符串的比较、查找、截取、大小写转换等操作;Java语言为“+”连接符以及对象转换为字符串提供了特殊支持,字符串对象可以使用“+”连接其他对象。String的部分源码如下:

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    /** The value is used for character storage. */
    private final char value[];
    /** Cache the hash code for the string */
    private int hash; // Default to 0
    ...
}

从上面的源码可以看出:

  1. String类被final关键字修饰,意味着String类时不可变类,不能被继承,并且其成员value也是final的,因此字符串一旦创建就不能再修改;
  2. String类实现了Serializable、CharSequence、Comparable接口;
  3. String实例的值是通过字符数组实现字符串存储的。

“+”连接符解析

“+”连接符的实现原理

Java语言为“+”连接符以及对象转换为字符串提供了特殊的支持。其中字符串连接是通过StringBuilder及其append方法实现的,对象转换字符串是通过toString方法实现的,toString方法由Object类实现,并可被Java中的所有类继承。用个简单的例子来验证“+”连接符的实现原理:

// 测试代码
public class Test {
    public static void main(String[] args) {
        int i = 2;
        String str = "abc";
        System.out.println(str + i);
    }
}

// 反编译后
public class Test {
    public static void main(String args[]) {
        byte byte0 = 10;      
        String s = "abc";      
        System.out.println((new StringBuilder()).append(s).append(byte0).toString());
    }
}

由反编译后的代码可以看出,Java使用“+”连接字符串对象时,JVM会创建一个StringBuilder对象,并调用其append方法将字符串连接,最后调用StringBuilder对象的toString方法返回拼接好的字符串。所以在实际代码编写中,使用“+”来拼接字符串和使用StringBuilder对象的append方法来拼接字符串对象是等价的。

“+”连接符的注意事项

“+”的效率

使用“+”连接符时,JVM会隐式创建StringBuilder对象,这种方式在大部分情况下并不会造成效率的损失,不过在进行大量循环拼接字符串时则需要注意。因为大量StringBuilder创建在堆内存中,必然会造成效率的损失,所以这种情况建议在循环体外创建一个StringBuilder对象调用append方法手动拼接。

字符串常量的优化

编译时可以解析为常量值还有一种特殊情况,当“+”两端均为编译器确定的字符串常量时,编译器会进行优化,直接将两个字符串拼接好。例如:

String s = "hello" + "world!";
// 反编译后
String s0 = "helloworld!";
/**
 * 编译期确定
 * 对于final修饰的变量,它在编译时被解析为常量值的一个本地拷贝存储到自己的常量池中或嵌入到它的字节码流中。
 * 所以此时的"a" + s1和"a" + "b"效果是一样的。故结果为true。
 */
String s0 = "ab"; 
final String s1 = "b"; 
String s2 = "a" + s1;  
System.out.println((s0 == s2)); // true

编译时不可以被解析为常量值

/**
 * 编译期无法确定
 * 这里面虽然将s1用final修饰了,但是由于其赋值是通过方法调用返回的,那么它的值只能在运行期间确定
 * 因此s0和s2指向的不是同一个对象,故上面程序的结果为false。
 */
String s0 = "ab"; 
final String s1 = getS1(); 
String s2 = "a" + s1; 
System.out.println((s0 == s2)); // false 
public String getS1() {  
    return "b";   
}

综上,“+”连接符对于直接相加的字符串常量效率很高,因为在编译期间便确定了它的值,也就是说形如"hello"+"java"; 的字符串相加,在编译期间便被优化成了"Ilovejava"。对于间接相加(即包含字符串引用,且编译期无法确定值的),形如s1+s2+s3; 效率要比直接相加低,因为在编译器不会对引用变量进行优化。

字符串常量池

字符串常量池介绍

在Java语言中的8种基本类型和String类型,JVM都为它们提供了一种常量池的概念,常量池就类似于一个Java系统级别提供的缓存。8种基本类型的常量池都是系统协调的,String类型的常量池比较特殊,它的主要使用方法有两种:

  • 直接使用双引号声明出来的String对象会直接存储在常量池中;
  • 如果不是双引号声明的String对象,可以使用String提供的intern方法。intern方法是个Native方法,会从字符串常量池中查询当前字符串是否存在,若不存在就会将当前字符串放入常量池中。

由于String字符串的不可变性,常量池中一定不存在两个相同的字符串。

内存区域

在HotSpot VM中字符串常量池是通过一个StringTable类实现的,它是一个Hash表,默认值大小长度是1009;这个StringTable在每个HotSpot VM的实例中只有一份,被所有的类共享;字符串常量由一个一个字符组成,放在了StringTable上。要注意的是,如果放进String Pool的String非常多,就会造成Hash冲突严重,从而导致链表会很长,而链表长了后直接会造成的影响就是当调用String.intern时性能会大幅下降(因为要一个一个找)。在JDK6及之前版本,字符串常量池是放在Perm Gen区(也就是方法区)中的,StringTable的长度是固定的1009;在JDK7版本中,字符串常量池被移到了堆中,StringTable的长度可以通过-XX:StringTableSize=66666参数指定。至于JDK7为什么把常量池移动到堆上实现,原因可能是由于方法区的内存空间太小且不方便扩展,而堆的内存空间比较大且扩展方便。

内存的分配

在JDK6及之前版本中,String Pool里放的都是字符串常量;在JDK7.0中,由于String.intern()发生了改变,因此String Pool中也可以存放放于堆内的字符串对象的引用。请看如下代码:

String s1 = "ABC";
String s2 = "ABC";
String s3 = new String("ABC");
System.out.println(s1 == s2); // true
System.out.println(s1 == s3); // false
System.out.println(s1.intern() == s3.intern()); // true

由于常量池中不存在两个相同的对象,所以s1和s2都是指向JVM字符串常量池中的"ABC"对象。new关键字一定会产生一个对象,并且这个对象存储在堆中。所以String s3 = new String(“ABC”);产生了两个对象:保存在栈中的s3和保存在堆中的String对象。当执行String s1 = "ABC"时,JVM首先会去字符串常量池中检查是否存在"ABC"对象,如果不存在,则在字符串常量池中创建"ABC"对象,并将"ABC"对象的地址返回给s1;如果存在,则不创建任何对象,直接将字符串常量池中"ABC"对象的地址返回给s1。由于s1,s2,s3的字符串值都是在常量池中的同一个引用,所以intern()方法的返回值是相等的。

String.intern()方法解析

String.intern()方法解析

先来看一下String.intern()方法的代码和注释:

/**
     * Returns a canonical representation for the string object.
     * <p>
     * A pool of strings, initially empty, is maintained privately by the
     * class {@code String}.
     * <p>
     * When the intern method is invoked, if the pool already contains a
     * string equal to this {@code String} object as determined by
     * the {@link #equals(Object)} method, then the string from the pool is
     * returned. Otherwise, this {@code String} object is added to the
     * pool and a reference to this {@code String} object is returned.
     * <p>
     * It follows that for any two strings {@code s} and {@code t},
     * {@code s.intern() == t.intern()} is {@code true}
     * if and only if {@code s.equals(t)} is {@code true}.
     * <p>
     * All literal strings and string-valued constant expressions are
     * interned. String literals are defined in section 3.10.5 of the
     * <cite>The Java? Language Specification</cite>.
     *
     * @return  a string that has the same contents as this string, but is
     *          guaranteed to be from a pool of unique strings.
     */
    public native String intern();

直接使用双引号声明出来的String对象会直接存储在字符串常量池中,如果不是用双引号声明的String对象,可以使用String提供的intern方法。intern 方法是一个native方法,intern方法会从字符串常量池中查询当前字符串是否存在,如果存在,就直接返回当前字符串;如果不存在就会将当前字符串放入常量池中,之后再返回。JDK1.7的改动将String常量池 从 Perm 区移动到了 Java Heap区String.intern() 方法时,如果存在堆中的对象,会直接保存对象的引用,而不会重新创建对象。

String.intern()的使用

来看看使用和不使用intern()的执行过程,在用new String("ABC")实例化String对象的时候,如果使用了intern方法,那么会先去字符串常量池中去查找是否有值为"ABC"的字符串,找到了就不会创建新的"ABC"字符串,找不到才会去创建新的"ABC"字符串;如果不使用intern方法,则没有去常量池查找的过程,会直接创建新的"ABC"字符串。可以看出二者的区别是:

  • 使用intern(),实际创建的对象数目是少于需要创建的对象数目的,因为会有常量池的字符串共享;但相应的,所需要的常量池的查询消耗会增加时间损耗;这体现出的是一种空间友好,不需要太多gc来回收空间;
  • 不使用intern(),实际需要多少对象,就会创建多少对象,因此会有大量的重复值的String对象出现;但相应的,少了查询的消耗,时间损耗会少一些;这体现出的是一种时间友好。

String、StringBuffer与StringBuilder

类图

主要区别

  • String是不可变字符序列,StringBuilder和StringBuffer是可变字符序列;
  • StringBuilder是非线程安全的,StringBuffer是线程安全的,其线程安全是通过在成员方法上添加synchronized关键字来实现的;
  • 执行效率上,StringBuilder > StringBuffer > String

总结

综上,我们再通过一个例子来测验以上的学习成果:

String s1 = "AB";
String s2 = new String("AB");
String s3 = "A";
String s4 = "B";
String s5 = "A" + "B";
String s6 = s3 + s4;
System.out.println(s1 == s2); // false
System.out.println(s1 == s2.intern()); // true
System.out.println(s1 == s5); // true
System.out.println(s1 == s6); // false
System.out.println(s1 == s6.intern()); // true

要理解此题目,需要搞清楚以下三点:

  1. 直接使用双引号声明出来的String对象会直接存储在常量池中;
  2. String对象的intern方法会得到字符串对象在常量池中对应的引用,如果常量池中没有对应的字符串,则该字符串将被添加到常量池中,然后返回常量池中字符串的引用;
  3. 字符串的+操作其本质是创建了StringBuilder对象进行append操作,然后将拼接后的StringBuilder对象用toString方法处理成String对象。

看一下以上的6个String对象在内存的分布情况:


【参考资料】https://tech.meituan.com/2014/03/06/in-depth-understanding-string-intern.htmlhttps://docs.oracle.com/javase/8/docs/api/https://blog.csdn.net/ifwinds/article/details/80849184

相关推荐

为何越来越多的编程语言使用JSON(为什么编程)

JSON是JavascriptObjectNotation的缩写,意思是Javascript对象表示法,是一种易于人类阅读和对编程友好的文本数据传递方法,是JavaScript语言规范定义的一个子...

何时在数据库中使用 JSON(数据库用json格式存储)

在本文中,您将了解何时应考虑将JSON数据类型添加到表中以及何时应避免使用它们。每天?分享?最新?软件?开发?,Devops,敏捷?,测试?以及?项目?管理?最新?,最热门?的?文章?,每天?花?...

MySQL 从零开始:05 数据类型(mysql数据类型有哪些,并举例)

前面的讲解中已经接触到了表的创建,表的创建是对字段的声明,比如:上述语句声明了字段的名称、类型、所占空间、默认值和是否可以为空等信息。其中的int、varchar、char和decimal都...

JSON对象花样进阶(json格式对象)

一、引言在现代Web开发中,JSON(JavaScriptObjectNotation)已经成为数据交换的标准格式。无论是从前端向后端发送数据,还是从后端接收数据,JSON都是不可或缺的一部分。...

深入理解 JSON 和 Form-data(json和formdata提交区别)

在讨论现代网络开发与API设计的语境下,理解客户端和服务器间如何有效且可靠地交换数据变得尤为关键。这里,特别值得关注的是两种主流数据格式:...

JSON 语法(json 语法 priority)

JSON语法是JavaScript语法的子集。JSON语法规则JSON语法是JavaScript对象表示法语法的子集。数据在名称/值对中数据由逗号分隔花括号保存对象方括号保存数组JS...

JSON语法详解(json的语法规则)

JSON语法规则JSON语法是JavaScript对象表示法语法的子集。数据在名称/值对中数据由逗号分隔大括号保存对象中括号保存数组注意:json的key是字符串,且必须是双引号,不能是单引号...

MySQL JSON数据类型操作(mysql的json)

概述mysql自5.7.8版本开始,就支持了json结构的数据存储和查询,这表明了mysql也在不断的学习和增加nosql数据库的有点。但mysql毕竟是关系型数据库,在处理json这种非结构化的数据...

JSON的数据模式(json数据格式示例)

像XML模式一样,JSON数据格式也有Schema,这是一个基于JSON格式的规范。JSON模式也以JSON格式编写。它用于验证JSON数据。JSON模式示例以下代码显示了基本的JSON模式。{"...

前端学习——JSON格式详解(后端json格式)

JSON(JavaScriptObjectNotation)是一种轻量级的数据交换格式。易于人阅读和编写。同时也易于机器解析和生成。它基于JavaScriptProgrammingLa...

什么是 JSON:详解 JSON 及其优势(什么叫json)

现在程序员还有谁不知道JSON吗?无论对于前端还是后端,JSON都是一种常见的数据格式。那么JSON到底是什么呢?JSON的定义...

PostgreSQL JSON 类型:处理结构化数据

PostgreSQL提供JSON类型,以存储结构化数据。JSON是一种开放的数据格式,可用于存储各种类型的值。什么是JSON类型?JSON类型表示JSON(JavaScriptO...

JavaScript:JSON、三种包装类(javascript 包)

JOSN:我们希望可以将一个对象在不同的语言中进行传递,以达到通信的目的,最佳方式就是将一个对象转换为字符串的形式JSON(JavaScriptObjectNotation)-JS的对象表示法...

Python数据分析 只要1分钟 教你玩转JSON 全程干货

Json简介:Json,全名JavaScriptObjectNotation,JSON(JavaScriptObjectNotation(记号、标记))是一种轻量级的数据交换格式。它基于J...

比较一下JSON与XML两种数据格式?(json和xml哪个好)

JSON(JavaScriptObjectNotation)和XML(eXtensibleMarkupLanguage)是在日常开发中比较常用的两种数据格式,它们主要的作用就是用来进行数据的传...

取消回复欢迎 发表评论:

请填写验证码