读书人

辨析java中的String之__拼接

发布时间: 2011-12-20 22:26:40 作者: rapoo

剖析java中的String之__拼接
博客地址:http://blog.csdn.net/izard999/article/details/6708433

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

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

去华为面试的时候, 第一笔试题就让我费神去想了, 回来在机子上运行结果, 发现自己当时答错了, 于是就狠下心来花了点时间研究这个:

Java code
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不同,则并行执行(相互之间不互斥)。原始代码如下:

Java code
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;

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

Java code
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,就是一个不同的对象

Java code
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

[解决办法]
谢谢lz分享。
[解决办法]
先做个标记,有时间慢慢研究
[解决办法]
学习下是啊
[解决办法]
String s = null;
s += "abc";
System.out.println(s);

这个结果我还是知道的,嘿嘿。
[解决办法]
+=其实是java对其做了特殊的处理
[解决办法]
可惜我不是版主,无法帮楼主推荐。。要不我申请一个版主? 大家到时候支持一下? Java版的版主已经常年见不着人了

首先关于前半部分 string的 +=操作。 楼主没必要讲这么多。 直接用 javap 看一下字节码就懂


后半部分, 关于synchronized的互斥,可以看一下我博客,前些日子周末无聊,就写写,很多人都容易犯这个错
[解决办法]
同步对象还有很多陷阱
比如
Integer i = 10;
synchronized(i) {
i++; //此时i指向也变了
}
[解决办法]
给我点好处收买我,我就支持你

探讨

可惜我不是版主,无法帮楼主推荐。。要不我申请一个版主? 大家到时候支持一下? Java版的版主已经常年见不着人了

首先关于前半部分 string的 +=操作。 楼主没必要讲这么多。 直接用 javap 看一下字节码就懂


后半部分, 关于synchronized的互斥,可以看一下我博客,前些日子周末无聊,就写写,很多人都容易犯这个错

[解决办法]
顶啊!
[解决办法]
探讨

给我点好处收买我,我就支持你

引用:

可惜我不是版主,无法帮楼主推荐。。要不我申请一个版主? 大家到时候支持一下? Java版的版主已经常年见不着人了

首先关于前半部分 string的 +=操作。 楼主没必要讲这么多。 直接用 javap 看一下字节码就懂


后半部分, 关于synchronized的互斥,可以看一下我博客,前些日子周……

[解决办法]
支持~ 现在java版比较冷清~
[解决办法]
java里String是比较不一样
[解决办法]
探讨


引用:

给我点好处收买我,我就支持你

引用:

可惜我不是版主,无法帮楼主推荐。。要不我申请一个版主? 大家到时候支持一下? Java版的版主已经常年见不着人了

首先关于前半部分 string的 +=操作。 楼主没必要讲这么多。 直接用 javap 看一下字节码就懂


后半部分, 关于synchroni……


[解决办法]
......问了30个人 全回答错了?..太夸张了吧...LZ还知道+=操作符重载是StringBuffer还回答错鸟..我看LZ是不知道 String s = null + "abc"的答案吧..
[解决办法]
谢谢分享!
[解决办法]
学习了
[解决办法]
华为这么早么?? 楼主哪个学校???
我们这面 26 27 才过来啊。。

面试几轮啊。。? 有没有考什么组成原理之类的???
[解决办法]
支持有实践的原创
[解决办法]
探讨
引用:

......问了30个人 全回答错了?..太夸张了吧...LZ还知道+=操作符重载是StringBuffer还回答错鸟..我看LZ是不知道 String s = null + "abc"的答案吧..

嗯, 以前没深入过, +的操作符重载生成StringBuilder, 回来跟源码看, 才知道死在String.valueOf()的实现上面, 嘿嘿……

[解决办法]
探讨

给我点好处收买我,我就支持你

引用:

可惜我不是版主,无法帮楼主推荐。。要不我申请一个版主? 大家到时候支持一下? Java版的版主已经常年见不着人了

首先关于前半部分 string的 +=操作。 楼主没必要讲这么多。 直接用 javap 看一下字节码就懂


后半部分, 关于synchronized的互斥,可以看一下我博客,前些日子周……

[解决办法]
貌似我 2B了 楼主已经工作了。。
[解决办法]
都很犀利啊
[解决办法]
复习下
[解决办法]
探讨
版主 版主在么.?这个文章申请推荐阿!

[解决办法]
帮顶一个
[解决办法]
学习了
[解决办法]
在用String类对象直接拼接时,JVM会创建一个临时的StringBuffer类对象,并调用其append()方法完成字符串的拼接,这是因为String类是不可变的,拼接操作不得不使用StringBuffer类(并且--JVM会将"You are nice."和"I love you so much."创建为两个新的String对象)。之后,再将这个临时StringBuffer对象转型为一个String,代价不菲!
[解决办法]
这个要顶
[解决办法]
很有帮助
[解决办法]
学习下。
[解决办法]
学习下~~~
[解决办法]
学C++ 表示有点汗
String s = null;
s += "abc";
System.out.println(s);

开始觉得 nullabc 应该很简单呀,
后来一看 怎么 null 没用引号,难道表示的 "";
..... 然后就 越看越糊涂了
[解决办法]
lz牛啊。这个问题简直太强大了。没想到还有这样的问题。学习了。另外给点分吧。我穷死了。。。。
[解决办法]
果断得顶。。
[解决办法]
探讨

学C++ 表示有点汗
String s = null;
s += "abc";
System.out.println(s);



开始觉得 nullabc 应该很简单呀,
后来一看 怎么 null 没用引号,难道表示的 "";
..... 然后就 越看越糊涂了


[解决办法]
感谢分享
[解决办法]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
[解决办法]
加了,学习学习!!
[解决办法]
学习了,新手长知识了.
[解决办法]
java不好使
[解决办法]
下面这个面试题我就做错了,看来学习东西还是必须要好好遵循:
What -> Why -> How
-----------------------------------------------
int i=0;
for(test('a');test('b')&&i<2;test('c')){
i++;
test('d');
}
-----------------------------------------------
public boolean test(char c){
System.out.pritn(c);
return true;
}
[解决办法]
很是受教
[解决办法]
顶 不错
[解决办法]
顶一个
[解决办法]
受教了
[解决办法]
彩笔看到这些后 脑袋大了~~~
[解决办法]
楼主好人,感谢楼主分享,学习了,争取早日到达楼主这样的境界!
[解决办法]
学习了
[解决办法]
这个真容易弄错~学习了
[解决办法]
不错,,,,
[解决办法]
学习!
[解决办法]
领教了
[解决办法]
接分。嘿嘿!
[解决办法]
嗯,很基础的知识却很有实用价值
[解决办法]
不错,第一题以前看到过被阴了。。。
[解决办法]
顶一个
[解决办法]
路过帮顶
[解决办法]
顶下···············
[解决办法]
学习的真够深入的

[解决办法]
等有空我好好研究下,还是很有学问的哦…………
[解决办法]
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();
}
}
}

}


---------------------------
关于以上代码只需这样修改:
private static String key;
synchronized(key),不就完了么
[解决办法]
学习了!!!!!!!!!!!1
[解决办法]
我只是实现了同一个KEY对象实现同步,但由于是key为static,所以4个类对象的key的值会都一样
[解决办法]

探讨
谢谢lz分享。

[解决办法]
学习。。。
[解决办法]
确实很容易弄错
[解决办法]
探讨

可惜我不是版主,无法帮楼主推荐。。要不我申请一个版主? 大家到时候支持一下? Java版的版主已经常年见不着人了

首先关于前半部分 string的 +=操作。 楼主没必要讲这么多。 直接用 javap 看一下字节码就懂


后半部分, 关于synchronized的互斥,可以看一下我博客,前些日子周末无聊,就写写,很多人都容易犯这个错

[解决办法]
你之前发了一个 java 关于字符串+ 重载的问题 我感觉有些问题
按你的解释如下代码

char[] str={1,2,3}
System.out.println("aa"+"bb"+str);
等同于 StringBuilder.append(aa).append("bb").append(str).toString();
那结果应该是 aabb123

但是结果确是 aabb+数组的地址

所以我感觉你说的是不对的

对于+ 操作

如果引用为 null 则把它转换为 “null”。否则,该转换的执行就像无参数调用该引用对象的toString() 方法一样
[解决办法]
我来模拟一下来证明你说的是错误的


char[] numbers={'1','2','3'};
String s = null;
s += "abc";
s +=numbers ;
System.out.println(s);

String i=null;
StringBuilder builder=new StringBuilder(String.valueOf(i));
builder.append("abc");
builder.append(numbers);
String temp=builder.toString();

System.out.println(temp);

上面是代码
nullabc[C@89ae9e
nullabc123

下面是结果 ,所以事实证明 +的执行 并不是你分析的那样


[解决办法]
还好String s = null;
s += "abc";
System.out.println(s); 这个我知道。。因为之前遇到过这个问题,结果在画面text中输出的是null字符串。。


[解决办法]
对于 char[]来说 ,StringBuilder 的append(char[]) String的valueof(char[]) 都给出了重载
所以都能以正确的形式表现。 但是对于 + 却没有

先去吃饭 求讲解
[解决办法]
虽然我不什么懂,但非常感谢楼主,MARK!
[解决办法]
5768
[解决办法]
我今天在读 java解惑凑巧就看到了 ,如果楼主手中有此书 可以看看 解惑12
[解决办法]
这个书上说调用的 toString() 方法啊 而不是 String.valueOf(),一直没有看字节码的习惯, 哈哈一会去看看
[解决办法]
好东西 支持 ~
public static String valueOf(Object obj)
{
return obj != null ? obj.toString() : "null";
}

public static String valueOf(char ac[])
{
return new String(ac);
}
[解决办法]
我只能说,这个面试官真变态。

读书人网 >J2SE开发

热点推荐