1.概述
无论是哪种编程语言,对字符串的操作都是必不可少的。JAVA中为我们提供了三个操作字符串的类,分别是String、StringBuffer、StringBuilder,下面我们将会详细进行介绍。
String、StringBuffer、StringBuilder类都是在java.lang包中定义的。所有的应用程序都可以使用他们。所有的这些类都被声明为final,这就意味着这些类不能有子类。这使得对于通用的字符串操作
可以采用特定的优化用来提高性能,并且这三个类都实现了CharSequeue接口。
提示:所谓的String类型对象中的字符串不可改变是指创建了String实例后不能修改实例的值。但是在任何时候都可以修改String引用变量,使其指向其他String对象。
2. String类
2.1 构造函数:
String类有众多的构造函数。一般我们使用一个传入一个字符串的构造函数。当然,如果我们想创建一个空字符串,可以使用默认的构造函数。
其它构造函数列表如下:
2.2 其他方法
这里也不再赘述,要学会查询文档! 这里给出常用函数列表:
2.3 注意事项
1. String 字符串是支持+号连接的,并且在编译时,String会将+号去掉,用来简化拼接方式。
2. 当字符串与数字进行连接的时候,若字符串在数字之前,则将数字转换成字符串,然后进行连接;若数字在前,则先对数字进行计算,将计算的结果与字符串进行拼接。(仅限+号)
1 public class TestString { 2 3 public static void main(String[] args) { 4 String s1 = "abc"+ 5+2; //直接拼接 5 String s2 = 5+2+"abc"; //先计算后拼接 6 String s3 = "abc"+5*2;//对于出去+号之外的都要先计算再拼接 7 System.out.println(s1); //abc52 8 System.out.println(s2); //7abc 9 System.out.println(s3); //abc10 10 } 11 12 }
3.equals 与 == 的区别:equals 是用来比较两个字符串的值是否相等(只要值相等即可,并不要求引用相同),而==是比较两个字符串的引用地址是否是同一个地址(尽管值相等,但是引用地址不一定相等)
public class TestString { public static void main(String[] args) { String s1 = new String("xiaobai"); String s2 = new String("xiaobai"); System.out.println(s1.equals(s2)); //true 值相等 System.out.println(s1==s2); //false //不是相同的引用 } }
4.字符串的单一性(常量保存):字符串是不可变对象,而且是存储在常量区的,意思就是说,如果常量区中有某一个字符串,当我们再次声明它时,jvm并不会为我们再创建一个新的字符串,而是将已经存在的字符串的引用返回。
public class TestString { public static void main(String[] args) { String s1 = "xiaobai"; String s2 = "xiaobai"; System.out.println(s1.equals(s2)); //true 值相等 System.out.println(s1==s2); //true 常量单一 } }
3.StringBuffer类
3.1 概述
我们知道,String类对象一旦确定就不能再修改,因为String类中的字符存储数组是final的。java为我们提供了一个可以更改的字符串操作类:StringBuffer。
3.2 构造函数
同理,这里直接给出构造函数:
3.3 其他方法
3.4 注意事项
1.StringBuffer 不支持直接字面量赋值(也就是说不能直接把String对象引用赋值给StringBuffer),必须使用new 的形式。
public class TestString { public static void main(String[] args) { StringBuffer s1 = "xiaobai"; //不合法 StringBuffer sb = new StringBuffer("ssss"); //正确 } }
2. StringBuffer 中的append方法可以进行链式调用,并且返回结果是最后一次链式调用的结果
public class TestString { public static void main(String[] args) { StringBuffer sb = new StringBuffer("xiao") .append("bai").append("ni").append("hao"); System.out.println(sb); //xiaobainihao } }
3.StringBuffer是线程不安全的。
4.StringBuilder类
4.1 概述
我们上面有提到,StringBuffer不是同步的,也就是说是线程不安全的。StringBuffer的优势是在于得到更高的性能。若我们同时有多个线程修改这个字符串怎么保证线程安全?
当然,我们可以选择加锁同步。不过java已经为我们提供了一个类——StringBuilder 该类与StringBuffer基本相同,只不过他是线程安全的。
4.2 构造方法
4.3 其它常用方法
与StringBuffer 基本相同,这里不再进行展示。
4.4 注意事项
这里也没什么可说的,等想到了再回来补充。
5.String、StringBuffer、StringBuilder 比较
这三个类的比较无非是效率(运行速度/内存占用)和线程安全上,
首先我们来看一下速度:
下面这段代码分别是创建和拼接一万个次的执行时间: 可以看出 执行时间效率 StringBuilder > StringBuffer > String
造成这种情况的原因也很简单,因为String 是单一实例(final)每次都要创建新对象并修改引用,所以最慢。
而StringBuffer和StringBuilder的时间要比String短得多,原因也很简单,他只是在原有的字符串数组做添加操作,并不需要生成新对象和修改引用。
StringBuilder比StringBuffer快的原因是StringBuffer实现了线程安全(synchronized),每次操作要获得锁,导致费时较长(但是要比String快的多)。
1 public class TestString { 2 public static void main(String[] args) { 3 System.gc(); 4 long stringStart = System.currentTimeMillis(); 5 String s1 = new String(); 6 for(int i=0;i<100000;i++) { 7 s1 =s1 + i; 8 } 9 long stringEnd = System.currentTimeMillis(); 10 System.out.println("10w String instance coast: "+(stringEnd-stringStart)+" ms"); 11 12 long stringBufferStart = System.currentTimeMillis(); 13 StringBuffer s2 = new StringBuffer(); 14 for(int i=0;i<100000;i++) { 15 s2 = s2.append(i); 16 } 17 long stringBufferEnd = System.currentTimeMillis(); 18 System.out.println("10w StringBuffer instance coast: "+(stringBufferEnd-stringBufferStart)+" ms"); 19 20 long stringBuilderStart = System.currentTimeMillis(); 21 StringBuilder s3 = new StringBuilder(); 22 for(int i=0;i<100000;i++) { 23 s3 = s3.append(i); 24 } 25 long stringBuilderEnd = System.currentTimeMillis(); 26 System.out.println("10w StringBuilder instance coast: "+(stringBuilderEnd-stringBuilderStart)+" ms"); 27 System.gc(); 28 } 29 }
然后再来看一下线程安全:
由于String不支持修改,所以也就没有线程是否安全的说法(如果非要说有,那就是创建新对象时修改原来的引用地址为新地址,那一定是线程不安全的)。
我们通过多线程程序测试:
我分别在StringBuffer 和 StringBuilder 中循环了1000 次 ,每次拼接一个字母A ,同时开启三条线程进行这个操作。
可以知道,如果线程安全 我们将会得到一个 3 * 1000 = 3000 长度的结果(一条线程拼接1000个 三条刚好3000个),通过结果我们可以看出,StringBuffer 是线程安全的,而SrtingBuilder的结果不足3000 说明发生了线程
不安全的操作。(最直接的办法还是去jdk源码看一下,StringBuffer基本上每一个方法都有synchronized修饰)。
1 public class TestString { 2 public static void main(String[] args) throws InterruptedException { 3 /** 4 * @author xiaobai 5 * 测试StringBuffer与StringBuilder的线程安全性 6 * 实现两个自己的线程类 7 * 分别创建三条线程 并发执行 8 * 等待线程全部执行完,统计结果 比较 9 * 10 * 若线程安全 应该是 3 * 1000 = 3000 11 */ 12 Thread t1 = new Thread(new MyStringBufferTest()); 13 Thread t2 = new Thread(new MyStringBufferTest()); 14 Thread t3 = new Thread(new MyStringBufferTest()); 15 16 Thread t4 = new Thread(new MyStringBuilderTest()); 17 Thread t5 = new Thread(new MyStringBuilderTest()); 18 Thread t6 = new Thread(new MyStringBuilderTest()); 19 20 t1.start(); t2.start(); t3.start(); t4.start(); t5.start(); t6.start(); 21 22 while(Thread.activeCount()>1); //等待线程执行完 23 24 System.out.println("StringBuilder : "+MyStringBuilderTest.sbd.length()); //不一定是多少 25 System.out.println("StringBuffer : "+MyStringBufferTest.sbf.length()); // 一定是 3000 26 27 } 28 } 29 30 class MyStringBufferTest implements Runnable{ 31 static StringBuffer sbf = new StringBuffer(); 32 /** 33 * 进行1000 次拼接字符操作 34 * 中途sleep 10ms 使效果更好 35 */ 36 @Override 37 public void run() { 38 for(int i=0;i<1000;i++) { 39 sbf.append("A"); 40 try { 41 Thread.sleep(10); 42 } catch (InterruptedException e) { 43 // TODO Auto-generated catch block 44 e.printStackTrace(); 45 } 46 } 47 } 48 } 49 50 class MyStringBuilderTest implements Runnable{ 51 static StringBuilder sbd = new StringBuilder(); 52 /** 53 * 进行1000 次拼接字符操作 54 * 中途sleep 10ms 使效果更好 55 */ 56 @Override 57 public void run() { 58 for(int i=0;i<1000;i++) { 59 sbd.append("A"); 60 try { 61 Thread.sleep(10); 62 } catch (InterruptedException e) { 63 // TODO Auto-generated catch block 64 e.printStackTrace(); 65 } 66 } 67 } 68 }
结果:
比较结论:
效率上 : 由快到慢 StringBuilder > StringBuffer > String
线程安全性: String 不安全 StringBuilder 不安全 StringBuffer 安全