欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

J2SE入门(三) String深度解析

程序员文章站 2022-05-15 13:22:13
String可以说是Java中使用最多最频繁、最特殊的类,因为同时也是字面常量,而字面常量包括基本类型、String类型、空类型。 本文从String的不可变性,String创建时字面量和String对象的不同,字符串字面量常量池,字符串的内存结构,常用的String相关方法的描述 ......

j2se入门(三) string深度解析

string可以说是java中使用最多最频繁、最特殊的类,因为同时也是字面常量,而字面常量包括基本类型、string类型、空类型。

一. string的使用

1. string的不可变性

/**
 * the {@code string} class represents character strings. all
 * string literals in java programs, such as {@code "abc"}, are
 * implemented as instances of this class.
 * <p>
 * strings are constant; their values cannot be changed after they
 * are created. string buffers support mutable strings.
 * because string objects are immutable they can be shared. for example:
 * ...
 */

public final class string { 
    private final char value[];
}

string对象一旦在堆中创建出来,就无法再修改。因为string对象放在char数组中,该数组由final关键字修饰,不可变。

2. 定义一个字符串

/**
 * 定义一个字符串
 */
string str1 = "helloworld";
string str2 = "helloworld";
//也可以,但基本不这样写
string str3 = new string("helloworld");
system.out.println(str1 == str2);
system.out.println(str1 == str3);

//运行结果 true, false

上面三句代码怎么理解呢?这里需要先引入一个概念,字符串常量池

字符串常量池是一块特殊的独立内存空间,放在java heap中 { 在jdk7.0之前字符串常量池存放在permgen中,jdk7.0的时候移动到了java heap(在堆中仍然是独立的),jdk8.0的时候去掉了permgen,用metaspace进行取代 } ,java内存模型不是本章讨论的重点。

str1和str2引用的字符串字面量就在字符串常量池中,而str3引用的对象在java heap中。

怎么,还不太好理解?举个例子

工作一天,到下班时间了,准备看会儿金瓶.,算了,《三国演义》,打开小说网站,在线阅读;过了半个小时,女票回家了,看《三国演义》也是她想做的事儿,我看网址发给她,好,她也开始看了,再过半个小时,我爸回来了,他也是三国迷,但是他不喜欢在线看,因此在书店买了一本看。

上面提到的小说网站就是一个字符串常量池,包含了很多字符串字面量,如《三国演义》、《西游记》、《红楼梦》等,每个字符串字面量在常量池中保持独一份,无论谁进网站看《三国演义》都是同样的网址和同样的内容。

我和女票就是str1和str2,我们看的都是同一个网站的《三国演义》,不仅内容一样,引用的地址也一样(字符串常量池中保留了唯一的“helloworld”),因此str1 == str2 运行结果为true

而我爸就是str3,与我和女票都不一样,虽然看的内容也是《三国演义》,但是通过实体书籍来看,引用地址不一样,同时一本书籍不支持多个人同时看(字符串对象在java heap中,且每次new都会新建一个对象),因此str1 == str3 运行结果为false。

一个字符串字面量总是引用string类的同一个实例,因为被string.intern()方法限定了,同样我们可以调用该方法将堆中的string对象放到字符串常量池中,这样做可以提升内存使用效率,同时可以让所用使用者共享唯一的实例。

system.out.println(str1 == str3.intern());
//运行结果为true

那么该方法的实现逻辑是怎么样的呢,我们看一下源码

/** 
 * 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&trade; 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();

我们发现这是一个native方法,看一下注释,发现str3.intern()方法大致流程是:

当执行intern()时,会先判断字符串常量池中是否含有相同(通过equals方法)的字符串字面量,如果有直接返回字符串字面量;如果不含,则将该字符串对象添加到字符串常量池中,同时返回该对象在字符串常量池的引用。

返回的引用需要赋值才可,否则还是会指向堆中的地址,即:

string str4 = new string("hellochina");
system.out.println(str4.intern() == str4);//false
str4 = str4.intern();
string str5 = "hellochina";
string str6 = "hellozhonghua"
system.out.println(str4 == str5);//true

下面我们看一下内存结构
J2SE入门(三)  String深度解析

3. 再次赋值给已定义的字符串

str6 = "hellohuaxia";

我们开始已经说了string是由final关键字修饰,不可变,那么此时在内存中如何体现呢?

J2SE入门(三)  String深度解析

4. string 对 “+” 的处理

string str7 = "good good" + " study";
string str8 = "good good study";
system.out.println(str7 == str8);

通过编译工具后得到

string str7 = "good good study";
string str8 = "good good study";

因此我们可以发现编译器在编译期间就是进行变量合并,而不会在常量池中创建三个对象 “good good”,“ study”,"good good study"。str7 == str8 运行结果 true。

但如果这样

string str9 = "good good ";
string str10 = str9 + "study";
system.out.println(str8 == str10);//false

这时运行结果为false,通过string变量 + 字符常量方式得到的结果会在堆中,不在常量池中,当然可以通过intern()方法放进常量池中,同时不仅“+”如此,调用substring(),touppercase(),trim()等返回的都是string在堆中的地址。

5. string常用的方法

//str1 == "hello,world ";

//获取长度
str1.length()//12;

//截取位置2到5之间的字符串(包括位置2,不包括位置5,从0开始)
str1.substring(2,5);//"llo"

//判断是否含有字符串“ello”
str1.contains("ello");//true,通过indexof实现

//获取ello在str1中的开始位置
str1.indexof("ello");//1

//将字符串转化为字符串数据
str1.split(",");//["hello","world"]

//去掉字符串两侧空格
str1.trim();//"hello,world"

二. 总结

本文从string的不可变性,string创建时字面量和string对象的不同,字符串字面量常量池,字符串的内存结构,常用的string相关方法的描述,若有不对之处,请批评指正,望共同进步,谢谢!