前言
来说说为什么要讲String、StringBuBuffer、StringBuilder,相信正在求职的小伙伴们一定对这个问题耳熟能详,一般这三个类在面试时百分之八十的面试官会拿这个问题,并且在实际开发中灵活运用和区分这三个类更是为我们的开发省了不少时间,所以把它们理解透彻会对你的java之路大有裨益。
String介绍
String类又称作不可变字符序列,本质就是一个字符串常量,是Java中基础且重要的类,用法广细节多。位于java.lang包中,Java程序默认就会导入java.lang包下的所有类。所以我们平时写的时候,就会发现并没有手动导入该包。String也是Immutable类(即不可变类)的典型实现,用final修饰的class,除了hash这个属性其它属性都声明为final。
Java字符串就是Unicode字符序列,底层是final char[]实现;例如字符串“Java”就是4个Unicode字符’J’、’a’、’v’、’a’组成的。Java没有内置的字符串类型,而是在标准Java类库中提供了一个预定义的类String,每个用双引号括起来的字符串都是String类的一个实例。所以例如拼接字符串时候会产生很多无用的中间对象,如果频繁的进行这样的操作对性能有所影响。部分源码参考:
public final class String implements Serializable, Comparable<String>, CharSequence {
private final char[] value;
}
关于String,这里就会涉及到一个经典的面试题。
String str1 = "hello" + " java";
String str2 = "hello java";
System.out.println(str1 == str2);//输出?
String str3 = "hello";
String str4 = " java";
String str5 = str3 + str4;
System.out.println(str2 == str5);//输出?
这里不饶弯子了,直接说明吧!
String str1 = “hello” + " java";编译器会做一个优化,直接在编译的时候将字符串进行拼接,相当于str1 = “hello java”;所以System.out.println(str1 == str2);//输出true;而String str5 = str3 + str4;编译的时候不知道变量中存储的是什么,所以没办法在编译的时候进行优化,所以System.out.println(str2 == str5);//输出false。
String是我们开发中经常会用到的,下面介绍一些常用的方法吧!
其实在开发中也经常会涉及到一些字符转换,下面提供一些常见的转换方式!
String相关转换
String转变为数字:
String s = "123.456 "; //要确保字符串为一个数值,否则会出异常
double d = Double.parseDouble(s);
float f = Float.parseFloat(s);
String s= "123";
System.out.println(Integer.parseInt(s));
字符转数字:
//char和int之间的转换
//首先将char转换成string
String str=String.valueOf('2');
Integer.valueof(str); //或者 Integer.PaseInt(str)
//Integer.valueof返回的是Integer对象,Integer.paseInt返回的是int
String类和byte[]转换
//Convert to byte[]
byte[] bytes = string.getBytes();
//Convert back to String
String s = new String(bytes);
String使用的陷阱:
String str = "";
//本质上使用StringBuilder拼接, 但是每次循环都会生成一个StringBuilder对象
long num1 = Runtime.getRuntime().freeMemory();//获取系统剩余内存空间
System.out.println(num1);
for (int i = 0; i < 5000; i++) {
str = str + i;//相当于产生了10000个对象,不可取。
}
long num2 = Runtime.getRuntime().freeMemory();//获取系统剩余内存空间
System.out.println(num2);
执行这段代码,你会发现代码的执行速度较慢和内存空间的剩余量急剧下降。以上这种情况在java中用什么解决呢?不要担心,你要相信java是强大,你所担心的问题是不存在的,在java的世界里还存在着两种可变字符序列,下面就来看看吧!
可变字符序列
可变字符序列有两大类,分别是:StringBuffer和StringBuilder。它们非常类似,均代表可变的字符序列。 这两个类都是抽象类AbstractStringBuilder的子类,方法几乎一模一样。下面来详细的介绍哈。
StringBuffer的本质是一个字符串变量,也是字符串缓冲区,它也是一个容器,专门用于对字符串进行操作,可以存储和操作字符串,即包含多个字符的字符串数据。合理正确的使用可以提高效率、简化书写、提高安全性。StringBuffer和StringBuilder用法上没有太大的区别,但是相较于StringBuilder有速度优势,线程安全问题在区分中重点介绍。
下面介绍哈可变字符序列常用的方法:
-
重载的public StringBuilder append(…)方法;可以为该StringBuilder 对象添加字符序列,仍然返回自身对象。
-
方法 public StringBuilder delete(int start,int end);可以删除从start开始到end-1为止的一段字符序列,仍然返回自身对象。所以可以链式调用
-
方法 public StringBuilder deleteCharAt(int index);移除此序列指定位置上的 char,仍然返回自身对象。
-
重载的public StringBuilder insert(…)方法;可以为该StringBuilder 对象在指定位置插入字符序列,仍然返回自身对象。
-
方法 public StringBuilder reverse();用于将字符序列逆序,仍然返回自身对象。
-
方法 public String toString();返回此序列中数据的字符串表示形式。
值得一提的是,可变字符序列类有很多方法都是可以进行链式调用,那么什么是链式调用呢?这里简单描述一下:就是一个对象调用一个方法时,方法的返回值就是该对象。经过我的描述,相信你去看哈源码,你就清楚它为什么是链式调用的。这里就举个简单例子吧。
了解了可变字符序列,上面String的使用陷阱我们就可以解决了。
/**使用StringBuilder进行字符串的拼接*/ StringBuilder sb1 = new StringBuilder(""); long num3 = Runtime.getRuntime().freeMemory(); long time3 = System.currentTimeMillis(); for (int i = 0; i < 5000; i++) { sb1.append(i); }
既然java中提供了三种表示字符串的类,那么它们之间有什么区别呢?
分析三者的异同
三者共同之处:都是final类,不允许被继承,主要是从性能和安全性上考虑的,因为这几个类都是经常被使用着,且考虑到防止其中的参数被参数修改影响到其他的应用。下面就来描述哈它们的区别吧!
StringBuffer是线程安全的,但是效率较低。可以不需要额外的同步用于多线程中。部分源码参考:
@Override
public synchronized StringBuffer append(Object obj) {
toStringCache = null;
super.append(String.valueOf(obj));
return this;
}
可以看到StringBuffer类中的append方法,加了关键字synchronized。
StringBuilder是非同步,运行于多线程中就需要使用着单独同步处理,但是速度就比StringBuffer快多了,所以我们通常就使用该类。
StringBuffer与StringBuilder两者共同之处:可以通过append、indert进行字符串的操作。
String实现了三个接口:Serializable、Comparable、CarSequence。而StringBuilder只实现了两个接口Serializable、CharSequence,所以相比之下String的实例可以通过compareTo方法进行比较,其他两个不可以。StringBuilder部分源码参考:
public final class StringBuffer
extends AbstractStringBuilder
implements java.io.Serializable, CharSequence
{
那这三者谁的运行速度快,谁的慢勒?在这方面运行速度快慢顺序为:StringBuilder > StringBuffer > String。前面已经说了,StringBuilder 为什么比StringBuffer 运行速度快,那为什么String是最慢的呢?因为String为字符串常量,而StringBuilder和StringBuffer均为字符串变量,即String对象一旦创建之后,存放在常量池中,该对象是不可更改的,但后两者的对象是变量,存放在堆中的,是可以更改的。以下面一段代码为例:
String str="123";
System.out.println(str);
str=str+"456";
System.out.println(str);
运行这段代码,会打印“123”和“123456”,表面上看起好像是str这个对象被更改了,其实并没有,底层JVM对于这几行代码是这样处理的:首先会在常量池中创建一个String对象“123”,然后在栈中声明一个变量str,并把常量池中的对象“123”的地址赋值给str变量,然后在第三行中,JVM又在常量池中创建了一个新的对象,值就是原来的str的值和“456”加起来,再赋值给新的str,而原来的str指向的“123”String对象就失去了引用,就会被JVM的垃圾回收机制(GC)给回收掉。
因此,Java中对String对象进行的操作实际上是一个不断创建新的对象并且将失去引用的对象回收的一个过程,比其他两个所做的事情要多,所以相比较而言,运行速度就很慢。
而StringBuilder和StringBuffer的对象是变量,是存在栈中的,真正的内容是存在堆中的,对变量进行操作就是直接对该对象的内容进行修改,并且不会创建新的对象和回收无引用的垃圾对象,所以速度要比String快很多。
所以像上面的字符拼接我们就可以使用下面的方法。
StringBuilder str=new StringBuilder().append("123").append("456");
System.out.println(strr.toString());
这样运行的速度就会快很多。你可以把下面的代码拿去测试哈,循环次数越大,差距就越明显。
public static void main(String[] args) {
String str = "";
long num1 = Runtime.getRuntime().freeMemory();//获取系统剩余内存空间
long time1 = System.currentTimeMillis();//获取系统的当前时间
for (int i = 0; i < 5000; i++) {
str = str + i;//相当于产生了10000个对象
}
long num2 = Runtime.getRuntime().freeMemory();
long time2 = System.currentTimeMillis();
System.out.println("String占用内存 : " + (num1 - num2));
System.out.println("String占用时间 : " + (time2 - time1));
/**使用StringBuilder进行字符串的拼接*/
StringBuilder sb = new StringBuilder("");
long num3 = Runtime.getRuntime().freeMemory();
long time3 = System.currentTimeMillis();
for (int i = 0; i < 5000; i++) {
sb.append(i);
}
long num4 = Runtime.getRuntime().freeMemory();
long time4 = System.currentTimeMillis();
System.out.println("StringBuilder占用内存 : " + (num3 - num4));
System.out.println("StringBuilder占用时间 : " + (time4 - time3));
}
总结
最后再来总结一下它们的特点和使用场景。
- StringBuffer是JDK1.0版本提供的类,线程安全,做线程同步检查, 但是效率较低;适用于多线程下在字符缓冲区进行大量操作的情况。
- StringBuilder是JDK1.5版本提供的类,线程不安全,不做线程同步检查,因此效率较高。 建议采用该类;适用于单线程下在字符缓冲区进行大量操作的情况。
- String是不可变字符序列,存放在常量池中,适用于少量的字符串操作的情况。
关注公众号,获取免费软件、资料,笔记等。