【String类】10分钟彻底搞懂Java中的String类!!

1. 引言

1.1. String 类的简要介绍

在Java中,String类型是我们最常用的类型之一。那么,String是什么呢?根据Java官方文档,String类代表字符串,Java程序中的所有字符串文字(例如“abc”)都被视为此类的实例。实际上,Java中的String是一个不可变的对象,这意味着一旦创建了字符串,就不能改变它。

String s1 ="hello";

在这个例子中,我们创建了一个新的String对象“hello”。现在,无论我们做什么,“hello”这个对象都不会改变。如果我们对s1赋一个新值,例如:

s1 = "hello world";

我们并没有改变原来的“hello”对象,而是创建了一个全新的String对象“hello world”,并让s1指向它。原来的“hello”对象仍然存在于内存中,除非没有任何引用指向它,它才会被垃圾回收器回收。

这就是为什么String被设计为不可变的,因为它们经常被使用,所以不可变性确保了字符串的安全性和效率。但这也意味着如果需要修改字符串,可能需要花费额外的内存和CPU资源。

2. String 基础

2.1 String的创建

在Java中,创建String对象有多种方法,但每种方法都有其特定的用途和注意事项。

2.1.1 字符串字面量

最常见的创建String对象的方法是使用字符串字面量,如下:

String s1 ="Hello,world!";

在这种情况下,Java会查看字符串池,如果池中存在“Hello World”这个对象,就会返回对这个对象的引用;否则,它会在池中创建一个新的对象,并返回对新对象的引用。

2.1.2 new关键字

我们还可以使用new关键字来创建一个新的String对象,如下:

当我们使用new关键字创建String对象时Java总是会在堆上创建一个新的对象,并忽略字符串池。

值得注意的是,第一种方法在性能和内存利用率上通常比第二种更好,因为它可以重用字符串池中的对象。

2.2 String的不可变性

在Java中,String类的一个关键特性是其不可变性。一旦创建了一个String对象,那么这个对象就不能被改变。这听起来可能会令人困惑,因为我们似乎可以通过某些方法来“改变”字符串。例如:

String s = "Hello";
s = s + "World";

看起来我们虽然“改变”了字符串s,但实际上我妈们做的是创建了一个全新的字符串对象赋值给s。原先的“Hello”字符串仍然存在,只不过我们已经没有引用指向它了。

那么,如果你需要一个可以修改的字符串怎么办?这时就可以使用StringBuilder或StringBuffer类。这两个类代表的都是可变字符串,适合在需要频繁修改字符串内容的场景中使用。

3.深入String

3.1 String pool的概念和工作方式

在Java中,所有的字符串字面量,例如“Hello”,都是在一个特殊的区域内存储的,我们通常称这个区域为String pool。这种机制的存在有利于减少内存占用并提高性能。

看看下面的代码:

String s1 = "Hello";
String s2 = "Hello";

这段代码中,我们看似创建了两个字符串,当实际上,由于String pool的存在,只创建了一个。字符串“Hello”在内存中只有一份,s1和s2都指向它。这是因为字符串字面量会自动被Java放入String pool中。

但如果我们用new关键字创建字符串,情况就会不同:

String s3 = new String("Hello");

这段代码会创建一个新的String对象,并且这个对象不会被放到String pool中。因此,s3指向的对象与s1和s2指向的对象不同,即使它们的内容相同。

如果我们希望把一个用new创建的字符串放到String pool中,可以使用intern方法:

String s4 = new String("Hello").intern();

现在,s4会指向String pool中的“Hello”字符串。

3.2 String的比较:equal()   vs   ==

在Java中比较两个字符串的内容是否相同,我们应该使用equals()方法,而不是==。这个原则的背后有一些重要的理由,我感觉这应该是每一个学习Java的开发者都需要理解的知识点。

首先,我们要明白一点:在Java中,字符串是对象。这意味着每一个字符串都是String类的一个实例。

如果我们使用==来表示两个字符串,我们实际上是在比较这两个字符串对象的内存地址是否相同,也就是说,我们在比较它们是否是同一个对象。这可能不是我们想要的,来看看下面的例子你就懂了:

String s1 = "Hello";
String s2 = "Hello";
String s3 = new String("Hello");

System.out.println(s1==s2);//输出true
System.out.println(s1==s3);//输出false

在这个例子中,s1和s2是指向String pool中的同一个对象,所以s1==s2是true。但s3是一个新对象,虽然它的内容与s1和s2相同,但它是一个不同的对象,所以s1==s3是false。

但是,如果我们使用equals()方法来比较字符串,我们是在比较它们的内容是否相同,而不是它们是否是同一个对象。

System.out.println(s1.equals(s2));//输出true
System.out.println(s1.equals(s3));//输出false

这个例子显示,无论我们使用哪种方式创建字符串,只要它们的内容相同,equals()方法就会返回true。

所以,记住:在Java中比较字符串时,我们应该使用equals()方法,而不是==。这是一种更安全,也更加直观的方式。

3.3 String的连接和效率问题

在Java中,我们可以使用“+”运算符来连接两个或者多个字符串。这看起来很方便,但是这个操作的效率问题是值得我们注意的。

首先,我们要明白,String在Java中是不可变的,这意味着,一旦一个String对象被创建,我们就不能更改它。如果我们想要改变一个字符串,Java实际上会创建一个新的String对象。

看看下面的例子:

String s = "Hello";
s = s + "World";

在这个例子中,第二行代码实际上创建了一个新的String对象,然后将s指向这个新对象。

因此,如果我们在循环中连接字符串,效率会非常低。每次连接操作,都会创建一个新的String对象,这就涉及到内存分配与垃圾回收,这都是非常耗时的操作。看看下面的例子:

String s = "";
for(int i = 0; i<1000;i++){
s = s + i;
}

在这个例子中,我们创建了1000个String对象,而我们实际上只需要最后一个。

为了解决这个问题,Java提供了一个StringBuilder类,它是可变的,我们可以使用它来更高效的进行字符串连接。看看下面的例子:

StringBuilder sb = new StringBuilder();
for(int i = 0;i<1000;i++){
 sb.append(i);
}
String s = sb.toString

在这个例子中,我们只创建了一个StringBuilder对象和一个String对象,大大提高了效率。

所以,如果我们需要在循环中连接字符串,我们应该使用StringBuilder,这是一个非常重要的Java最佳实践。

4. String与其他类

4.1 String与StringBuffer和StringBuilder的对比

在Java中,除了String之外,我们还会经常使用到StringBuffer和StringBuilder。这三者都用与处理字符串,但是它们在性能和线程安全性方面有着显著的差异。

4.1.1.可变性

首先,我们需要理解String是不可变的,也就是说,当你创建一个String对象后,它的内容就不能改变。这是由于String的内部字符数组被声明为final,所以无法修改。相反,StringBuffer和StringBuilder都是可变的,它们内部的字符数组可以动态的改变。

例如,假设我们有以下代码:

String str = "Hello";
str = str + "world";

实际上,上述的代码并没有修改原来的“Hello”字符串,而是创建了一个新的String对象“HelloWorld”,而如果我们使用StringBuffer和StringBuilder,情况则会完全不同:

StringBuilder builder = new StringBuilder("Hello");
builder.append("world");

这段代码只创建了一个StringBuilder对象,并且通过append()方法在原有的字符数组上添加了“world”。

4.1.2.性能

由于String是不可变的,所以每次修改String都会产生新的对象,这在大量字符串操作时会带来性能问题。相反,StringBuffer和StringBuilder可以动态改变,所以它们在大量的字符串操作时更高效。

4.1.3.线程安全

StringBuffer是线程安全的,它的大部分方法都是synchronized的,所以在多线程环境下也可以安全使用。但是,这种线程安全也是有代价的,它在性能上比StringBuilder慢。

相反,StringBuilder不是线程安全的,所以它的性能比StringBuffer快。如果你的代码只在单线程环境下执行,那么使用StringBuilder是一个更好地选择。

5. Java String的内存优化

当我们在Java程序中创建字符串时,Java的String interning机制可以帮助我们节省内存。这是一种特定的优化策略,它尝试复用在内存中已存在的String对象,而不是每次都创建新对象。

要理解String interning,我们首先要了解Java的字符串常量池。字符串常量池是Java堆内存的一部分,用于存储由字面量创建的String对象。Java虚拟机(JVM)会尝试在这个常量池中复用String对象。

例如:

String str1 = "Hello";
String str2 = "Hello";

在这个例子中,虽然我们似乎创建了两个不同的String对象,但实际上,由于String interning,这两个引用变量实际上指向的是常量池中的同一个对象。

但是,如果使用new关键字显式创建String对象,那么Java就会在堆内存中创建一个新的对象,如下所示:

String str1 = new String("Hello");
String str2 = new String("Hello");

在这个例子中,str1和str2是指向堆内存中的两个不同对象,即使它们的内容相同。

如果你希望强制的将使用new创建的String对象添加到字符串常量池,你可以使用intern()方法,如下所示:

String str1 = new String("Hello").intern();
String str2 = new String("Hello").intern();

在这个例子中,str1和str2实际上指向的是字符串常量池中的同一个对象,即使我们使用了new关键字。这是因为intern()方法会检查字符串常量池中是否存在内容相同的String对象。如果存在 ,它就会返回那个对象的引用;如果不存在,它就会将当前String对象添加到常量池,并返回这个对象的引用。

通过理解和使用String interning,我们可以更有效的在Java程序中使用和管理字符串,优化我们的内存使用。

6.结论

在使用Java String类时,遵循一些最佳实践可以帮助我们编写出更高效,更加可维护的代码

6.1 使用equals()方法进行字符串比较

在Java中,我们应该始终使用equals()方法来比较字符串的内容是否相等,而不是使用==运算符。因为==运算符比较的是两个字符串对象的内存地址,而不是它们的内容。

6.2 避免使用 + 进行字符串的连接 

在循环或者大量的字符串连接操作中,我们应该避免使用 + 运算符,因为这会在每次连接时都创建一个新的String对象,效率非常低。相反,我们应该使用StringBuffer和StringBuilder。

6.3 字符串常量应该定义为public static final

由于String是不可变的,所以我们可以将字符串常量定义为public static final。这样,这个常量就可以在程序的任何地方使用,而不需要每次使用时都创建一个新的对象。

7.面试问题与答案

7.1 String是不可变的,这意味着什么?

答:在Java中,字符串是不可变的,这意味着一旦创建String对象,就不能更改其内容。每次使用+,substring()等方法时,都会创建一个新的String对象。这是因为String类被设计为final,所有的成员方法也不会改变其值。

7.2 为什么要设计String类为不可变类?

答:String的不可变性为Java提供了一些优势。例如,String是不可变的,所以它是线程安全的,可以在多个线程共享。

7.3 ==和equals()在比较字符串时有何不同?

答:==运算符比较的是两个字符串对象的内存地址,而equals()方法比较的是两个字符串的内容。在大多数情况下,我们应该使用equal()方法来比较字符串是否相等。

7.4 String类的intern方法的作用是什么?

答:intern()方法用于返回字符串对象的规范表示形式。它会首先检查字符串常量池中是否已经存在等于此String对象的字符串。如果存在,那么返回常量池中的字符串。如果不存在,此String对象会被添加到字符串常量池中,并返回此String对象的引用。

7.5 什么是StringBuffer和StringBuilder,它们和String有什么区别?

答:StringBuffer和StringBuilder类也用于处理字符串,但它们和String的主要区别在于它们是可变的。也就是说,你可以再原位置上修改StringBuffer和StringBuilder的内容,而不会创建新的对象。StringBuilder在大多数实现中比StringBuffer更快,因为它是非同步的。

7.6 解释一下Java中的String Pool?

答:在Java中,String Pool是JVM的特定区域,用于存储所有的字符串字面量。当你创建一个新的字符串字面量时,JVM会首先检查String Pool中是否已经存在相同的字符串。如果存在,JVM就会返回对该字符串的引用,而不是创建新的字符串。如果不存在,JVM就会在String Pool中创建一个新的字符串。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值