byte数组转string_「面试专题」讲透必考点String,带配套视频

25e778479648b00bdbfe5b220f30e48c.png

前言

String 是我们实际开发中使用频率非常高的类,Java 可以通过 String 类来创建和操作字符串,使用频率越高的类,我们就越容易忽视它,因为见的多所以熟悉,因为熟悉所以认为它很简单,其实只是了解到皮毛,并没有真正掌握,而 String 又是面试的高频考点,所以我们有必要将 String 这个类深入研究,彻底搞定,本节课就为大家详细讲解 String 的核心机制以及实际使用。

String 三大核心:

1、不变性:String 是只读字符串,是一个典型的 immutable 对象,对它进行任何操作,其实都是创建一个新的对象,再把引用指向该对象。不变模式的主要作用在于当一个对象需要被多线程共享并频繁访问时,可以保证数据的一致性。

2、常量池优化:String 对象创建之后,会在字符串常量池中进行缓存,如果下次创建同样的对象时,会直接返回缓存的引用。

3、final:使用 final 来定义 String 类,表示 String 类不能被继承,提高了系统的安全性。

考点分析

String 不是基本数据类型

这是很基础的东西,但是很多初学者却容易忽视,Java 的 8 种基本数据类型中不包括 String,基本数据类型中用来描述文本数据的是 char,但是它只能表示单个字符,比如 'a','好' 之类的,如果要描述一段文本,就需要用多个 char 类型的变量,也就是一个 char 类型数组,比如“你好” 就是长度为2的数组 char[] chars = {'你','好'};

但是使用数组过于麻烦,所以就有了 String,String 底层就是一个 char 类型的数组,只是使用的时候开发者不需要直接操作底层数组,用更加简便的方式即可完成对字符串的使用。

String 实例化

String 对象的实例化有两种方式:

1、直接赋值

String str = "Hello";

2、通过构造函数,可以直接将 String 值传入,也可以直接将 char 数组传入。

String str = new String("Hello");char[] chars = {'你','好'};String str2 = new String(chars);

看到这里大家可能会感到疑惑,String str = "Hello",本身就已经是一个 String 了,为什么还要再次调用 String 构造函数,把 str 传入,然后再生成一个 String,这不是多此一举了吗?

这样设计 String 类,一定有它的道理,"Hello" 和 new String("Hello") 的区别在于存储区域不同,"Hello" 存储在字符串常量池中,new String("Hello") 存储在堆内存中,我们通过下面这段代码来比较二者的区别。

String str1 = "Hello";String str2 = "Hello";System.out.println(str1 == str2);String str3 = new String("World");String str4 = new String("World");System.out.println(str3 == str4);

上述代码非常简单,用直接赋值的方式创建了 String 对象 str1 和 str2,并且值相等,又用构造函数的方式创建了 str3 和 str4,值也相等。

然后用 == 分别判断 str1 和 str2 、str3 和 str4 是否相等,这里并不是比较值是否相等,而是比较它们的内存地址是否相等,结果如下图所示。

0f94c7c8f08817005d27f45b296f442a.png

通过结果我们可以得知,str1 和 str2 指向同一块内存区域,而 str3 和 str4 指向不同的内存区域,这是为什么呢?

因为 str = "Hello" 是直接赋值的方式,"Hello" 是存储在字符串常量池中的,在创建 "Hello" 的时候会首先在字符串常量池中寻找是否已经存在 "Hello",如果存在,则直接将其引用赋给 str,如果不存在则创建 "Hello",再将其引用赋给 str。

所以 str1 = "Hello",会在字符串常量池中创建 "Hello",并将其引用赋给 str1,str2 = "Hello",会在字符串常量池中找到 "Hello",并将其引用赋给 str2,所以 str1 和 str2 指向同一块内存地址,str1 == str2 结果为 true。

7098b976f060ab9c7e9a9001be8c4315.png

而使用构造函数的方式则完全不同,String 对象存储在堆内存中,且不会去寻找是否已经存在值相等的对象,而是每创建一个对象,都会在堆内存中开辟一块新的内存空间来保存,所以 str3 = new String("World"),它的创建过程是先在堆内存中开辟空间存储 "World",然后再将该地址赋给 str3,str4 = new String("World") 是同样的过程,那么 str3 和 str4 所指向的内存地址肯定不同,str3 == str4 结果为 false。

c6f3e680d3a82bc9089332979b6d4c66.png

equals 方法

通过上面的例子我们知道,== 是比较内存地址的,我们一般对于字符串的比较都是判断其值是否相等,而非内存地址,那么对于 String 对象,我们如何来判断值是否相等呢?可以使用 equals 方法来完成。

首先来说说 equals 方法的出处,它是 Object 类中定义的方法,源码如下所示。

public boolean equals(Object obj) { return (this == obj);}

可以看到这个方法非常简单粗暴,用 == 比较两个引用对象所指向的内存地址是否一致,和直接使用 == 判断是一样的,这样并不能对值进行判断,所以 String 类在继承的基础上对 equals 方法进行了重写,如下所示。

public boolean equals(Object anObject) { if (this == anObject) { return true; } if (anObject instanceof String) { String aString = (String)anObject; if (coder() == aString.coder()) { return isLatin1() ? StringLatin1.equals(value, aString.value) : StringUTF16.equals(value, aString.value); } } return false;}//StringLatin1.equalspublic static boolean equals(byte[] value, byte[] other) { if (value.length == other.length) { for (int i = 0; i < value.length; i++) { if (value[i] != other[i]) { return false; } } return true; } return false;}//StringUTF16.equalspublic static boolean equals(byte[] value, byte[] other) { if (value.length == other.length) { int len = value.length >> 1; for (int i = 0; i < len; i++) { if (getChar(value, i) != getChar(other, i)) { return false; } } return true; } return false;}

在 String 对 equals 方法重写的逻辑里,将 == 判断改为了值的判断,即将 String 转为 byte 数组,然后依次比较两个 byte 数组中的每一个值是否相等,如果两个数组完全一致,则返回 true,否则返回 false。

String 不可变

实际上 String 类在存储字符串时,会将字符串的值保存在 byte 类型的数组中,我们知道数组一旦创建,其长度就是不可改变的。既然长度不可改变,也就意味着 byte 类型所存储的字符串值不可修改。一旦修改,就会重新创建一个 String 对象,用新对象的 byte 数组来存储修改之后的字符串。即如果我们修改了 String 对象的值,它就已经不是之前的对象了,而是一个新的对象,如下所示。

String str1 = new String("Hello");String str2 = str1;System.out.println(str2 == str1);str1 += " World";System.out.println(str2 == str1);

运行结果如下图所示。

0f94c7c8f08817005d27f45b296f442a.png

intern 方法

当调用某个字符串对象的 intern 方法时,会去字符串常量池中寻找,如果已经存在一个等于该 String 对象的字符串(equals 方法判断),则返回该字符串,否则将这个 String 对象添加到字符串常量池中,并返回它的引用,代码如下所示。

String str1 = "Hello World";String str2 = new String("Hello World");System.out.println(str1 == str2);System.out.println(str1 == str2.intern());

运行结果如下图所示。

7f699f3fbe5dc714008aa82a3a9bc9dd.png

"Hello World" 保存在字符串常量池中,new String("Hello World") 保存在堆内存中,所以引用肯定不相等,但是 new String("Hello World").intern() 就是在字符串常量池中的 "Hello World"。

String 常用方法

4064c96d81adf2be7a2b25d70f47acbd.png
a7147944b40ec03723012cd3a50218dc.png

具体使用如下所示。

char[] array = {'J','a','v','a',',','H','e','l','l','o',',','W','o','r','l','d'};String str = new String(array);System.out.println(str);System.out.println("str长度:"+str.length());System.out.println("str是否为空:"+str.isEmpty());System.out.println("下标为2的字符是:"+str.charAt(2));System.out.println("H的下标是:"+str.indexOf('H'));String str2 = "Hello";System.out.println("str和str2是否相等:"+str.equals(str2));String str3 = "HELLO";System.out.println("str2和str3忽略大小写是否相等:"+str2.equalsIgnoreCase(str3));System.out.println("str是否以Java开头:"+str.startsWith("Java"));System.out.println("str是否以Java结尾:"+str.endsWith("Java"));System.out.println("从2开始截取str:"+str.substring(2));System.out.println("从2到6截取str:"+str.substring(2, 6));System.out.println("将str中的World替换为Java:"+str.replaceAll("World
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值