带你看清 Java 字符串的世界……

Java技术栈

共 4530字,需浏览 10分钟

 · 2021-04-18

点击关注公众号,Java干货及时送达

前言

Java 基本类型可谓是 Java 世界里使用最频繁的数据类型了。除此之外,有种数据类型你也一定会遇到,它在 Java 世界里使用也相当频繁。它就是字符串!

听到字符串,你是不是想起了字符这种类型。不过在 Java 里,字符和字符串是两种不同的类型。

字符串的定义与形式

字符类型你应该比较熟悉,通过关键词 char 来申明一个字符。

值只能是一个英文字符或者一个中文字符或者是 Unicode 编码,用单引号包住。如下:

// char 用 ascii 字符赋值
char charWithAscii = '.';

char charWithZh = '牛';

char a = '\u0041';// 值为 A

而字符串,顾名思义,就是多个字符连接而成的串。通过关键词 String  来声明一个字符串。

值可以是 null 或者空字符串或者单字符串或者多字符串,用双引号包住。如下:

// null 字符串,未指定地址
String nullStr = null;

// 空字符串,包含 0 个字符
String  blankStr = "";

// 包含一个字符
String oneCharStr = "A";

// 包含多个字符,打印 蜗牛666 A
String multiCharStr = "蜗牛666 \u0041";

你会发现字符串的值可以是 null,因为字符串类型 String 是引用类型,值为 null,就说明值不存在,也就是这个变量不指向任何对象。这也是它和基本类型 char 的区别所在。

那作为引用类型,String 就可以通过 new 的方式声明,比如:

String newStr = new String("蜗牛666");

另外,你也会发现字符串比字符存储了更丰富的数据,实际上,一个字符串可以存储 0 到任意多个字符。只要把数据内容用双引号包起来就好。

不过,如果你的数据内容本身就有双引号,会发生什么呢?

没错,连编译都过不去!编译器会提示你字符串非法行尾,因为编译器判断的时候,会把中间引号当成字符串结尾,导致第三个引号开始的字符串语法出错。

那此时就要用到转义字符了,这个 case 里可以通过反斜杠 \ 转义中间的引号。

String str = "蜗牛666\"";

这样就不会报错了。

字符串的存储方式

我们知道,程序在运行时,会针对不同类型的变量数据做运算,最终输出结果。那其实运算过程中的变量数据都是存在栈里边,根据栈先进后出的特点,完成程序的运行逻辑。而对 Java 这种面向对象编程的语言,对象的信息就没放栈里边,而存到了堆里边,栈只存对象的引用地址。

另外,有些数据要求是不可变的,Java 会分配一块常量池出来。

比如 String 的场景。

String str1 = "蜗牛666";

String str2 = "蜗牛666";

String newStr1 = new String("蜗牛666");
String newStr2 = new String("蜗牛666");

str1 和 str2 就存储在常量池中。而常量池中的数据只有一份,因此 str1 和 str2 其实是指向同一块内存空间。

newStr1 和 newStr2 是通过 new 语法创建的对象,在创建的过程中,Java 会先去常量池中查找是否已有 蜗牛666 对象,如果没有则在常量池中创建一个 蜗牛666 对象,然后在堆中创建一个 蜗牛666 的拷贝对象。

所以 new String("xxx"); 这行代码会产生几个对象?

答案是一个或两个。如果常量池中原来没有 xxx,就是两个。如果有就是一个。

字符串的特点

字符串最大的特点就是不可变性。前边也有提过,字符串在常量池会有一份,常量这个信息就说明字符串具备不可变性了。

我们看下实例,你猜下以下程序会输出什么:

String strChange = "蜗牛666";

System.out.println(strChange);

strChange = "蜗牛888";

System.out.println(strChange);

都是 蜗牛666?因为字符串不可变嘛

事实上不是:

蜗牛666
蜗牛888

难道 蜗牛666 被改成 蜗牛888 了?

实际上不是,蜗牛666蜗牛888 都在,只是 strChange 的指向变了。

程序在执行 String strChange = "蜗牛666"; 时,JVM 虚拟机先在常量池创建字符串 蜗牛666 ,然后把变量 strChange 指向它。

然后在执行 strChange = "蜗牛888"; 时,JVM 虚拟机先在常量池创建字符串 蜗牛888 ,然后把变量 strChange指向它。

所以你会发现,刚开始的字符串 蜗牛666 还在,只是变量 strChange 不再指向它了。

因此,字符串的不可变特性,是指字符串内容不可变

另外,字符串的不可变特性,也带来了两个好处。

一个是 String 对象可以缓存哈希码。在 String 类的源码中,你可以看到这么一个属性。

/** Cache the hash code for the string */
private int hash; // Default to 0

hash 的值是基于字符串的每个字符计算得出。那字符串的不可变特性,就能保证 hash 的唯一性,因此可以缓存起来,被频繁使用。这也是性能优化的一种手段。

另外一个就是字符串的不可变特性保证了很多场景下的安全。

很多 Java 类库都会选择 String 作为参数,像文件路径 path 这些。如果 String 会经常改变,那就有各种安全隐患。

字符串的常用场景

比较

和基本类型相比,字符串也有比较的能力。比如下面的比较方式。

String equalChar = "蜗牛";

String euqalCharCompare = "蜗牛";

System.out.println(equalChar == euqalCharCompare);

直接常量定义的方式,没有问题,会输出 true 。但如果通过 new 的方式定义那就容易出错了,比如以下的代码,你知道输出什么么?

String equalMethod = new String("蜗牛");

String euqalMethodCompare = new String("蜗牛");

System.out.println(equalMethod == euqalMethodCompare);

会输出 false ,而这是不符合我们预期的。为什么会这样呢?

因为 == 对于引用类型而言,比较的是引用的地址。而上边两个字符串都是 new 出来的新对象。引用地址自然不同。

那如果想只比较内容怎么做呢?可以使用 Java String 自带的 equals 方法!

System.out.println(equalMethod.equals(euqalMethodCompare));

此时就能正常输出 true 了。

拼接

最简单的拼接就是用 + 连接符,比如以下代码。

/**
 * 字符串连接
 *
 * @author 蜗牛
 * @from 来源:蜗牛互联网
 */

public class StringConnect {

    public static void main(String[] args) {

        String name = "蜗牛";

        String age = "18";

        String profile = name + " " + age;

        System.out.println(profile);


    }
}

运行代码会输出:

蜗牛 18

我们可以通过反编译看下,这段代码发生了什么,输入命令:

javap -c StringConnect

我们看到有如下输出:

你会发现,加号连接符实际上是 Java 编译器的优化,底层是用了 StringBuilder 这个类,它的 append 方法就起了拼接的效果。

如果拼接的字符串数量有限,相对固定,建议用加号连接符,这样一行代码搞定!

如果拼接的字符串有相关的逻辑,比如循环拼接,字符串数量不太固定,那建议用 StringBuilder 这个工具类。

另外, StringBuilder 是线程不安全的,如果你是多线程开发环境,为了保证程序不出错,可以用它的兄弟类 StringBuffer ,这个类方法和 StringBuilder 一致,只是增加了线程安全的能力。






关注Java技术栈看更多干货



获取 Spring Boot 实战笔记!
浏览 13
点赞
评论
收藏
分享

手机扫一扫分享

举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

举报