剖析java中的String之__拼接

izard999 2011-08-22 02:25:19
加精
博客地址:http://blog.csdn.net/izard999/article/details/6708433

网上剖析String的不少,关于其他的String的知识我就不累赘去说了!

本文只解释下我在面试中遇到的String拼接的问题以及最近看到了网上的一道机试题跟这个有关系, 所以就想把自己对String拼接的理解分享给大家!

去华为面试的时候, 第一笔试题就让我费神去想了, 回来在机子上运行结果, 发现自己当时答错了, 于是就狠下心来花了点时间研究这个:
String s = null;  
s += "abc";
System.out.println(s);


答案是nullabc!

就这三行代码, 我问了不下于50个人, 有资深的人也有新手的, 在不运行的情况下全答错了。! 可见现在学java的人有很多人都是速成的,而且这种原理级而又看似不怎么实用的东西几乎没什么人去研究, 但是后面说的机试如果能知道String拼接的原理的话。将很容易就解决!

很早的时候我就知道String拼接中间会产生StringBuilder对象(JDK1.5之前产生StringBuffer),但是当时也没有去深究内部, 导致在华为笔试此题就错了!

运行时, 两个字符串str1, str2的拼接首先会调用 String.valueOf(obj),这个Obj为str1,而String.valueOf(Obj)中的实现是return obj == null ? "null" : obj.toString(), 然后产生StringBuilder, 调用的StringBuilder(str1)构造方法, 把StringBuilder初始化,长度为str1.length()+16,并且调用append(str1)! 接下来调用StringBuilder.append(str2), 把第二个字符串拼接进去, 然后调用StringBuilder.toString返回结果!

所以那道题答案的由来就是StringBuilder.append("null").append("abc").toString();

大家看了我以上的分析以后, 再碰到诸如此类的面试题应该不会再出错了!


那么了解String拼接有什么用呢?

在做多线程的时候, 往往会用到一个同步监视器对象去同步一个代码块中的代码synchronized(Obj), 对同一个对象才会互斥,不是同一个对象就不会互斥!

这里有个机试题,

现有程序同时启动了4个线程去调用TestDo.doSome(key, value)方法,由于TestDo.doSome(key, value)方法内的代码是先暂停1秒,然后再输出以秒为单位的当前时间值,所以,会打印出4个相同的时间值,如下所示:
4:4:1258199615
1:1:1258199615
3:3:1258199615
1:2:1258199615
请修改代码,如果有几个线程调用TestDo.doSome(key, value)方法时,传递进去的key相等(equals比较为true),则这几个线程应互斥排队输出结果,即当有两个线程的key都是"1"时,它们中的一个要比另外其他线程晚1秒输出结果,如下所示:
4:4:1258199615
1:1:1258199615
3:3:1258199615
1:2:1258199616
总之,当每个线程中指定的key相等时,这些相等key的线程应每隔一秒依次输出时间值(要用互斥),如果key不同,则并行执行(相互之间不互斥)。原始代码如下:

package syn;  

//不能改动此Test类
public class Test extends Thread{

private TestDo testDo;
private String key;
private String value;

public Test(String key,String key2,String value){
this.testDo = TestDo.getInstance();
/*常量"1"和"1"是同一个对象,下面这行代码就是要用"1"+""的方式产生新的对象,
以实现内容没有改变,仍然相等(都还为"1"),但对象却不再是同一个的效果*/
this.key = key+key2;
this.value = value;
}


public static void main(String[] args) throws InterruptedException{
Test a = new Test("1","","1");
Test b = new Test("1","","2");
Test c = new Test("3","","3");
Test d = new Test("4","","4");
System.out.println("begin:"+(System.currentTimeMillis()/1000));
a.start();
b.start();
c.start();
d.start();
}

public void run(){
testDo.doSome(key, value);
}
}

class TestDo {

private TestDo() {}
private static TestDo _instance = new TestDo();
public static TestDo getInstance() {
return _instance;
}

public void doSome(Object key, String value) {

// 以大括号内的是需要局部同步的代码,不能改动!
{
try {
Thread.sleep(1000);
System.out.println(key+":"+value + ":"
+ (System.currentTimeMillis() / 1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

}


此题解题的思路有很多种,不可或缺的步骤就是在doSome方法内部用synchronized(o)把那个写了注释的代码块同步, 有些人肯定会说:

我直接synchronized(key),不就完了么.? 这类人肯定是新手级别的了!

上面说了,synchronized(Obj), 对同一个对象才会互斥,不是同一个对象就不会互斥! 大家请看下Test类中的构造方法里面对key做了什么处理?

this.key = key + key2;

关于字符串的拼接, 如果是两个常量的拼接, 那么你无论拼接多少下都是同一个对象, 这个是编译时 编译器自动去优化的(想知道具体原理的自己去网上搜下).

String a = "a" + "b";  
String b = "a" + "b";
System.out.println(a == b);


这段代码输出true没有问题

但是一旦涉及到变量了, 我在上面标红加粗的运行时, 此时拼接字符串就会产生StringBuilder, 然而拼接完返回的字符串是怎么返回的呢?

在StringBuilder.toString()中的实现是new String(char value[], int offset, int count), 既然是创建String返回的, 那么调用一次toString,就是一个不同的对象

String a = "a";  
String b = "b";
String s1 = a + b;
String s2 = a + b;
System.out.println(s1 == s2);


所以在那道机试题中, 就不能直接用synchronized(key)去同步了, 如果你完完全全很耐心的看完本文, 那么应该知道如何用synchronized(key)同步那段代码了!

不错, 就是修改Test构造方法中的 this.key = key + key2;为this.key = key;

因为字符串不涉及到拼接的时候, 只要不new, 多少都是指向同一个对象!

当然这道多线程的题你也可以把那个key丢到集合里面去,用集合去的contains(obj)去判断,如果集合中存在, 就取集合中的, 否则往集合中添加,但是记住一定要使用并发包下面的集合, 否则可能会抛出ConcurrentModificationException
...全文
2386 151 打赏 收藏 转发到动态 举报
写回复
用AI写文章
151 条回复
切换为时间正序
请发表友善的回复…
发表回复
acertang 2011-09-01
  • 打赏
  • 举报
回复
mark
izard999 2011-08-29
  • 打赏
  • 举报
回复
[Quote=引用 139 楼 bao110908 的回复:]

Java code
22: ldc #74; //String 1
24: astore_1
25: ldc #76; //String
27: astore_2
28: new #78; //class java/lang/StringBuilder
31: dup
32: aload_1……
[/Quote]
我重新写了个类:

package test;

public class T {
public static void main(String[] args) {
String a1 = "1";
String b1 = "";
String s1 = a1 + b1;
String s2 = a1 + b1;
System.out.println(s1 == s2);
}
}

编译完的字节码

Compiled from "T.java"
public class test.T extends java.lang.Object{
public test.T();
Code:
0: aload_0
1: invokespecial #8; //Method java/lang/Object."<init>":()V
4: return

public static void main(java.lang.String[]);
Code:
0: ldc #16; //String 1
2: astore_1
3: ldc #18; //String
5: astore_2
6: new #20; //class java/lang/StringBuilder
9: dup
10: aload_1
11: invokestatic #22; //Method java/lang/String.valueOf:(Ljava/lang/Object;)Ljava/lang/String;
14: invokespecial #28; //Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
17: aload_2
18: invokevirtual #31; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
21: invokevirtual #35; //Method java/lang/StringBuilder.toString:()Ljava/lang/String;
24: astore_3
25: new #20; //class java/lang/StringBuilder
28: dup
29: aload_1
30: invokestatic #22; //Method java/lang/String.valueOf:(Ljava/lang/Object;)Ljava/lang/String;
33: invokespecial #28; //Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
36: aload_2
37: invokevirtual #31; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
40: invokevirtual #35; //Method java/lang/StringBuilder.toString:()Ljava/lang/String;
43: astore 4
45: getstatic #39; //Field java/lang/System.out:Ljava/io/PrintStream;
48: aload_3
49: aload 4
51: if_acmpne 58
54: iconst_1
55: goto 59
58: iconst_0
59: invokevirtual #45; //Method java/io/PrintStream.println:(Z)V
62: return

}

现在问题是咱俩同样的代码编译出了不同的字节码, 这个很奇怪呢, 会不会跟环境有关系。?
  • 打赏
  • 举报
回复
用于String的+ +=是JAVA中仅有的两个重载过的操作符,而JAVA并不允许程序员重载任何操作符
gzdiablo 2011-08-28
  • 打赏
  • 举报
回复
难怪+=性能那么差,原来用了StringBuilder。

如果还像LZ说的那样StringBuilder的默认长度仅仅是+16。那么写这个内核的家伙应该拖出去打屁股。
  • 打赏
  • 举报
回复
主要是+=特殊处理吧,如果是S=S+"ABC"呢?答案是编译报错。所以还是+= JAVA的特殊处理,重载成了StringBuffer.还有就是如果S="A"+"B",S是一个新的对象,所以我认为LZ这句话“因为字符串不涉及到拼接的时候, 只要不new, 多少都是指向同一个对象!
”有点问题。
夜雨醉清风 2011-08-28
  • 打赏
  • 举报
回复
顶顶顶
t5951088 2011-08-27
  • 打赏
  • 举报
回复
学习了,新手长知识了
eshengcn 2011-08-27
  • 打赏
  • 举报
回复
一般越简单的问题,越容易答错
yjcqbbs 2011-08-27
  • 打赏
  • 举报
回复
好东西~~~
song_wei_jun 2011-08-26
  • 打赏
  • 举报
回复
sun应该把这玩意改了
PCbinary_001 2011-08-26
  • 打赏
  • 举报
回复
顶了 好文啊。。
天地有雪 2011-08-26
  • 打赏
  • 举报
回复
强淫啊 强淫
wanqqw21 2011-08-26
  • 打赏
  • 举报
回复
好东西 支持 ~
public static String valueOf(Object obj)
{
return obj != null ? obj.toString() : "null";
}

public static String valueOf(char ac[])
{
return new String(ac);
  • 打赏
  • 举报
回复
   22:    ldc    #74; //String 1
24: astore_1
25: ldc #76; //String
27: astore_2
28: new #78; //class java/lang/StringBuilder
31: dup
32: aload_1
33: invokestatic #80; //Method java/lang/String.valueOf:(Ljava/lang/Object;)Ljava/lang/String;
36: invokespecial #84; //Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
39: aload_2
40: invokevirtual #85; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
43: invokevirtual #89; //Method java/lang/StringBuilder.toString:()Ljava/lang/String;
46: astore_3
47: new #78; //class java/lang/StringBuilder
50: dup
51: aload_1
52: invokestatic #80; //Method java/lang/String.valueOf:(Ljava/lang/Object;)Ljava/lang/String;
55: invokespecial #84; //Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
58: aload_2
59: invokevirtual #85; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
62: invokevirtual #89; //Method java/lang/StringBuilder.toString:()Ljava/lang/String;


你这里的 const 都到 #74 了,行号也是从 22 开始的,说明前面还有一些代码。

我的测试代码仅仅是这样的:

public class Test {

public static void main(String[] args) {
String a1 = "1";
String b1 = "";
String s1 = a1 + b1;
String s2 = a1 + b1;
}
}
lostkids 2011-08-26
  • 打赏
  • 举报
回复
学习学习
曹西 2011-08-26
  • 打赏
  • 举报
回复
绝对要顶起,学习了
izard999 2011-08-26
  • 打赏
  • 举报
回复
[Quote=引用 128 楼 zangxt 的回复:]

引用 125 楼 izard999 的回复:

引用 105 楼 bao110908 的回复:

你对第一个的解释是错误的,并不是先用 String.valueOf 转成 "null" 之后再由 StringBuilder 去拼接的。

转换是在 StringBuilder 内部处理的。

StringBuilder#append(String) 方法:

Java code……
[/Quote]
是啊, java - version显示
java version "1.6.0_20"
Java(TM) SE Runtime Environment (build 1.6.0_20-b02)
Java HotSpot(TM) Server VM (build 16.3-b01, mixed mode)

奇怪了哎
teemai 2011-08-26
  • 打赏
  • 举报
回复
支持!!
izard999 2011-08-26
  • 打赏
  • 举报
回复
[Quote=引用 129 楼 huang_1106 的回复:]

其实我觉得关于前面那三行代码,主要问题就是String s = null;显示出来时是""还是"null"的问题吧。。没必要想的这么复杂
[/Quote]
你是说print的时候.? 显示null的原因还是因为String.valueOf给你返回"null"
gukuitian 2011-08-26
  • 打赏
  • 举报
回复
首页了。。。
加载更多回复(126)

62,614

社区成员

发帖
与我相关
我的任务
社区描述
Java 2 Standard Edition
社区管理员
  • Java SE
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧