彻底明白字符串(String)的使用

    首先我们先了解一下字符串,字符串在生成时就写入物理地址,定为不可更改,我们可以把它当作是final形的变量,既然是不可更改的那么我们对于字符串的任何修改将不是原对象,而是产生新的对象。Sun这样设计也许是为了在其性能有所提高。既然每次对字符串操作其结果将是新的字符串,那么频繁调用,既不是很浪费。Java设计者可能想到这一点,所以加了字符串池。也许有的人对于字符串池并不感到熟悉,那你就把它当成是一个ArrayList吧!它用来存储你使用到的所有字符串,追加字符串时,如果有则返加字串符池中的字符串引用,没有则创建一个。这样做是为了避免大量的字符串操作而产生浪费。

    好了,我们来看一下下面几个程序:
    String s = "abc";//这里用了几个字符串对象

    当程序遇到"abc"时,会首先查找字符串池有没有"abc",没有则在字符串池中创建"abc",然后将引用赋给了s。

    我们再来看这一句:
    String s = new String("abc");//这里用了几个字符串对象

    当程序遇到"abc"后,在字符串池创建并返回了引用。但是这里又用到了new关键字,new是在内存当中创建一个对象,并将内存地址赋给了s。也就是在这里一共创建了两个对象,一个是字符串池的引用,另一个是内存地址的引用。
    那么这两句话有什么区别吗!当然有区别了,一个是字符串池引用,一个是内存地址。从速度上说当然是访问内存快了。但是它是在内存中创建了一个对象。于利于弊,个人看着使用。

    关于字符串重载:

    在Java中是不允许使用符号重载的,(在C++就可以)好像是Java的设计者说这是一个不好的设计。于是在Java中去了这个功能。但是在 String中却使用了+的重载符。因为字符串在生成时都是定为不可改变,所以在重载多个字符串时将会产生大量的字符,所以在后来的虚拟机中对于字符串重载使用了StringBuider进行优化。(至于什么版本的进行了优化,嘿嘿,偶不知道。怎么优化,下面有介绍。)

    重载时还分有三种情况:

    第一:变量重载   
    第二,字符串常量重载。变量是指字符串所赋值的对象。常量就是指以""的未分配的字符串。而常量重载时虚拟机会为你新建一个 StringBuilder作为临时的字符链接。把所有字符串链接完成,用toString()方法返回新的字符串。而常量字符串则是直接交由字符串池处理。一切操作只在字符串池中,也就是说新生成的字符串也是在字符串池中的引用。
    第三种就是对象与常量一起用了。那虚拟会以第一种方式处理。

    关于==与equals():
    ==是检测两个对象的地址是否相等。equals()则是检测两个对象值是否相等。
    另有一点,""也是一个字符串,跟new String("")一样。可以用new String("").intern()==""来检测是否同一个引用。
    我们再来做一下练习:
    首先,定义一堆变量:

String _abc = "abc";
String _2_abc = "abc";
String _def = "def";
String _abcdef = "abcdef";
String _new_abc = new String("abc");
String _new_abcdef = new String("abcdef");
String _abc_def = _abc + _def;
String _abc_add_def = "abc" + "def";
String _new_abc_def = _new_abc + _def;

System.out.println("_abc == _2_abc -> " + (_abc == _2_abc)); // true
System.out.println("_abc==_new_abc -> " + (_abc == _new_abc)); // false
System.out.println("_abc==_new_abc.intern() -> " +
                   (_abc == _new_abc.intern())); // true

    第一个:第一个数在字符串池中创建了"abc",第二个数生成时,先在字符串池中查找有没有"abc",有则返回这个字符串的引用,所用两个都是同一个引用。
    第二个:_new_abc在生成时先查找字符串池是否有"abc",之后又在内存中创建了"abc",并把内存地址给_new_abc,所以这两个对象当然不相同,因为一个是内存地址,一个是字符串池引用。
    第三个:用到了intern()此方法返回的是一个新字符串,其对象是指向字符串池的引用。因为先前_abc在字符串池中也创建了"abc"字符串,所以返回的是与_abc相同的引用。
   
    好了,小试了牛刀,再看下面的程序:

System.out.println("_abc_def==_new_abc_def -> "
                   + (_abc_def == _new_abc_def)); // false
System.out.println("_abcdef==abc add def -> "
                   + (_abcdef == "abc" + "def")); // true
System.out.println("_abcdef==_abc_def -> "
                   + (_abcdef == _abc_def)); // false

    第一个因为是变量与常量重载,所以虚拟为你开了一个StringBuilder作为临时链接字符串。其返回的是一个new出来的字符串,也就是其地址是内存地址。所以为false。
    第二个因为是常量重载,所以生成的对象是字符串池中的引用。与原先的_abcdef是同一个地址。
    第三个跟第一个同理。

    再看以下这一句:

System.out.println("_abcdef==_abc_def.intern() -> " +
                   (_abcdef == _abc_def.intern())); // true

    _abcdef是字符池的引用,让_abc_def也指向字符池。所以两个对象都是同一个引用。
    再看以下:

System.out.println("_abcdef==_abc_add_def -> " +
                   (_abcdef == _abc_add_def)); // true
System.out.println("_new_abcdef==_abc_add_def -> " +
                   (_new_abcdef == _abc_add_def)); //false
System.out.println("_new_abcdef==_new_abc_def -> " +
                   (_new_abcdef == _new_abc_def)); //false

    第一个:_abc_add_def是字符串池"abc"与"def"的常量重载。返回其结果也是字符串池的引用。   
    第二个:_new_abcdef是new出来的内存地址与_abc_add_def的字符串池引用当然是不同的对象。
    第三个:_new_abcdef是new出来的对象,而_new_abc_def也是重载后new出来的对象。但它们之间却是不相同的。也就是说内存不会像字符串池那样给我们找字符串引用。(呵呵,弱智说明)
   
    我们来做一下测试:

int time = 60;
System.out.println("游戏时间" + time + "帧数" + 60);

    这里用几个字符串4个5个6个7个,也许你会觉得是7个,但是这里共用了5个字符串,为什么,因为虚拟机给你做了优化,在字符串重载时使用了StringBuilder,也就是说上面的程序创建了四个字符串,并用append()方法将四个字符串链接,最后toString()返回一个新的字符串,共是五个字符串。
    也许你会想,既然虚拟机会自动为我们优化,那不是怎么用都行。其实不然,虚拟机没有想像中的那么聪明,甚至说它很笨拙。看下面程序:

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

    这里首先将i转化为String,再把s+i链接成一个新的字符串。也许你会说那虚拟机不就生成了StringBuilder将s与i链接起来再 toString()返回给s。说得没错。但有一点不对,那就是不止一个Stringbuilder,而是10个,在每一次的循环中都会创建一次 StringBuilder,处理完后又释放了。
    既是这样。那怎样优化呢。可以看下面这个例子:

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

    如果你想用sb.append(i+" ");那虚拟就会认为你需要StringBuilder链接,于是又为你创建了一个StringBuilder。
    最后做一个小测试。

public class Test {
    public static void main(String[] args) {
        Hello();
    }

    static class TestString {
        String string;
        public TestString(String s) {
            string = s;
        }

        public String toString() {
            return string;
        }
    }


    public static void Hello() {
        TestString ts = new TestString("123");
        System.out.println("ts = " + ts);
        setString(ts);
        System.out.println("ts = " + ts);
        String string = "123";
        System.out.println("string = " + string);
        setString(string);
        System.out.println("string = " + string);
    }

    private static void setString(TestString ts) {
        ts.string = "321";
    }

    private static void setString(String s) {
        s = "321";
    }
}

    输出结果是多少呢?就留给各位自己解决了。嘿嘿。。
    如果有什么地方不对,欢迎拍砖。。。

 

转载自: http://www.j2megame.cn
作者:  hubluesky