Java的字符串的 深入理解.

直接上代码,非常的清楚,相信能看懂.

/**
 * @author hackbase   Java字符串的操作详细介绍,深入的理解 .
 * String 类是被final修饰的,在java里面被final修饰的类是不能被继承的。
 * 并且它的成员方法也是不能被继承的.使用string创建的量是字符串常量,是不能
 * 被改变的
 * String 对象一断被创建是固定不变的。对String对象进行的任何操作都不会影响
 * 到原来的对象,相关的任何改变都会生成新的 对象.
 * <p>
 * <p>
 * //字符串常量池;
 * 我们知道字符串的分配和其他对象分配一样,是需要消耗高昂的时间和空间的,而且字符串我们使用的非常多。
 * JVM为了提高性能和减少内存的开销,在实例化字符串的 时候进行了一些优化:
 * 使用字符串常量池。
 * 每当我们创建字符串常量时,JVM会首先检查字符串常量池,
 * 如果该字符串已经存在常量池中,那么就直接返回常量池中的实例引用。
 * 如果字符串不存在常量池中,就会实例化该字符串并且将其放到常量池中。
 * 由于String字符串的不可变性我们可以十分肯定常量池中一定不存在两个相同的字符串(这点对理解上面至关重要)
 * <p>
 * Java中的常量池,实际上分为两种形态:静态常量池和运行时常量池。
 * 所谓静态常量池,即*.class文件中的常量池,class文件中的常量池不仅仅包含字符串(数字)字面量,
 * 还包含类、方法的信息,占用class文件绝大部分空间。
 * 而运行时常量池,则是jvm虚拟机在完成类装载操作后,
 * 将class文件中的常量池载入到内存中,并保存在方法区中,我们常说的常量池,就是指方法区中的运行时常量池
 */
public class StringDemo {
    /**
     * 但是我们使用new关键创建的 字符串对象存储在计算机的堆空间里面,他们和字符串常量池所示两个不同的地方
     * 因此是不相等的。但是在java里面根本就不存在两个完全一模一样的字符串对象.因此可以这样的 理解,堆中的因该是引用
     * 字符串常量池.可以理解为堆里面是一个内存地址,该地址指向指向常量池的内存空间
     * 通过上面的图我们可以非常清晰的认识他们之间的关系。所以我们修改内存中的值,他变化的是所有。来看看下面的代码.
     */

    String str5 = "hello world";

    //str1,str2都指向了常量池的同一个  hello world对象。因此他们是相等的,我们可以看看他们的Hascode就知道了。
    String str4 = "hello world";

    public static void main(String[] args) {
        String str1 = "hello world";
        String str2 = "hello world";


        //hascode就是对象的内存地址十进制的表示.可以看到他们的内存地址是一样的,他们指向的同一个内存空间
        //这也是Java虚拟机的功劳,大大的节约了节约了内存空间,同时提高了数据的访问速度.
        System.out.println(str1.hashCode());
        System.out.println(str2.hashCode());
        System.out.println(str1 == str2);
        System.out.println(str1.hashCode() == str2.hashCode());

        String str3 = "hello world";
        System.out.println(str3 == str2);


        //在来看看下面的代码.
        String str4 = "hello world";

        String str5 = "hello world";


        System.out.println(str4 == str5);

        //从结果可以看出的就是返回输出的结果为false,为什么了,str4是存储在字符串常量池里面.
        //然而下面的代码,创建了另个字符串对象,一个是new关键字创建的对象存储在堆里面,一个是i字符串常量str5,
        //存储在栈里面.但是在java里面更笨就不存在两个完全一模一样的对象,因此,堆中的对象因该是引用栈中的字符串对象


        /**
         * 总结:
         * String str5=new String("hello world");
         * 虽然str5的内容在堆中创建,但是内部value还是指向JVM的常量池的helloworld
         */


        //在来看看下面几个例子.
        String str6 = "aaa";
        String str7 = "aaa";
        System.out.println(str6 == str7);
        //true

        /**
         * 从上面结果可以看到,结果为true
         * 当执行String str1="aaa"时,JVM首先会去字符串池中查找是否存在"aaa"这个对象,
         * 如果不存在,则在字符串池中创建"aaa"这个对象,然后将池中"aaa"这个对象的引用地址返回给字符串常量str1,
         * 这样str1会指向池中"aaa"这个字符串对象;如果存在,则不创建任何对象,
         * 直接将池中"aaa"这个对象的地址返回,赋给字符串常量。当创建字符串对象str2时,
         * 字符串池中已经存在"aaa"这个对象,直接把对象"aaa"的引用地址返回给str2,这样str2指向了池中"aaa"这个对象,
         * 也就是说str1和str2指向了同一个对象,因此语句System.out.println(str1 == str2)输出:true
         */


        //使用new关键字创建一个对象.
        String test1 = "aaa";
        String test2 = "aaa";
        System.out.println(test1 == test2);
        //false

        /**
         * 采用new关键字新建一个字符串对象时,JVM首先在字符串池中查找有没有"aaa"这个字符串对象,
         * 如果有,则不在池中再去创建"aaa"这个对象了,直接在堆中创建一个"aaa"字符串对象,
         * 然后将堆中的这个"aaa"对象的地址返回赋给引用str3,这样,str3就指向了堆中创建的这个"aaa"字符串对象;
         * 如果没有,则首先在字符串池中创建一个"aaa"字符串对象,然后再在堆中创建一个"aaa"字符串对象,
         * 然后将堆中这个"aaa"字符串对象的地址返回赋给str3引用,这样,str3指向了堆中创建的这个"aaa"字符串对象。
         * 当执行String str4=new String("aaa")时, 因为采用new关键字创建对象时,每次new出来的都是一个新的对象,
         * 也即是说引用str3和str4指向的是两个不同的对象,因此语句System.out.println(str3 == str4)输出:false。
         * 在这里就明白了使用new关键字是创建了两个对象,一个在栈里面,一个在堆里面.
         */


        /**
         * 编译器确定.
         */
        String test3 = "hello world";
        String test4 = "hello world";
        String test5 = "hello world";

        String test6 = "hello" + "world";

        System.out.println("----------------------->>>>>>>>>>>>>>>");

        System.out.println(test3 == test4);
        System.out.println(test5 == test4);
        System.out.println(test6 == test3);


        /**
         * 程序代码里面的test4,test3都是字符串常量,因此编译期就确定了,所以test3==test4。但是hello  和
         * world都是字符串常量,当他们链接在一起的时候,java虚拟机就会创建一个新的空间来存放该字符串
         * 对象.在这里我们要区别等号==和equals的区别,后者侧重的就是内存地址的比较,但是前者不止是这些,同时
         * 还是比较数值的比较.因此就是不相等的。
         *
         */


        /**
         * 我们来看看编译期是无法确定的 ,还是来看看代码吧。
         */
        String data = "abc";
        String data2 = "def";
        String data3 = data + data2;


        System.out.println(data3 == "abcdef");


        /**
         * 下面,我们就上面的代码来分析一下,上面的代码的结果为假,因为data3指向堆中的"abcdef"对象,
         * 然而字符串常量存储在栈里面,准确的的说是在常量池里面,所以结果为false  java虚拟机在堆String="abc"
         * 放在常量池里面,是在编译时期做的. String   data3=data+data2;是在运行是确定的。new对象也是在运行
         * 的时候才确定的。+运算符会在堆中建立来两个String对象,这两个对象的值分别是"abc"和"def"也就是说从字符串池中复制这两个值,然后在堆中创建两个对象,
         * 然后再建立对象str3,然后将"abcdef"的堆地址赋给str3。
         */


        /**
         * 来看看编译期间的优化,来看看下面的代码.
         *
         */

        String s0 = "a1";
        String s1 = "a" + 1;


        System.out.println("-----------------test6-------------->>>>>");
        System.out.println(s0 == s1);


        String s2 = "atrue";
        String s3 = "a" + "true";

        System.out.println(s2 == s3);


        String s4 = "a3.14";
        String s5 = "a" + "3.14";

        System.out.println(s4 == s5);

        /**
         * 上面的代码计算的记过为true.程序的编译期间,java虚拟机将常量的字符串的+链接优化后的值,
         * 比如拿"a" + 1来说,经编译器优化后在class中就已经是a1,在编译期间字符串常量值就饿确定下来了
         * 所以上面的结果为true.
         */

        /**
         *
         * 来看看编译期间无法确定,来看看下面的代码.
         */

        String s6 = "ab";
        String s7 = "a";
        String S8 = "b" + s7;


        System.out.println("---------------test7---------->>>>>");
        System.out.println(s6 == S8);


        /**
         * 来分析下代码,
         *java虚拟机对于字符串的 引用,由于在字符串的+链接之中,有字符串的引用存在,然而 引用的值是在编译
         * 期间是无法确定的. String S8="b"+s7;是不能在编译期间被优化的。只能在运行期间来动态的分配。把那个且
         * 将链接后的新地址付给S8,因此上面的结果为false.
         */


        /**
         * 总结.
         * 字符串+实现的 字符串的拼接是在编译期间确定的.拼接后的字符串存放在字符串常量池里面.然而字符串引用
         * 的+的拼接运算是在运行期间确定的.新创建的 字符串存放在堆里面.对于直接相加的字符串效率是很高的.
         * 因为在编译期就确定它的值,也就是形如也就是说形如"I"+"love"+"java"; 的字符串相加,
         * 在编译期间便被优化成了"Ilovejava",对于间接相加的字符串(包含字符串的引用),形如s1+s2+s3; 效率要比直接相加低,
         * 因为在编译器不会对引用变量进行优化。
         *
         */


        /**
         * 最后来点总结.
         * String使用private final char value[]来实现字符串的存储,
         * 也就是说String对象创建之后,就不能再修改此对象中存储的字符串内容,
         * 就是因为如此,才说String类型是不可变的(immutable)。程序员不能对已有的不可变对象进行修改。
         * 我们自己也可以创建不可变对象,只要在接口中不提供修改数据的方法就可以。
         * 然而,String类对象确实有编辑字符串的功能,比如replace()。
         * 这些编辑功能是通过创建一个新的对象来实现的,而不是对原有对象进行修改
         * 比如来看看下面的代码.
         */


        System.out.println("-------test8------------->>>");

        String sp = "hello world";
        String ts = sp.replace("world", "hacking");
        System.out.println(ts);


        /**
         * 创建字符串的方式分为一下几种.分别如下;
         * 1.使用引号来创建字符串.
         * 2.使用new关键字来创建字符串.
         * 结合上面的例子,我们总结如下.
         * 1.单独使用引号创建的字符串都是字符串常量存储在String pool里面
         * 2.使用new String("")创建的对象会存储到heap中,是运行期新创建的;是存储在堆里面的。
         * new创建字符串首先要查看字符串常量池中是否有相同的字符串,如果有就会拷贝一份到堆里面,然后返回
         * 一份堆中的地址,注意,此时不需要从堆中复制到池中,否则,
         * 将使得堆中的字符串永远是池中的子集,导致浪费池的空间)!
         *
         * 3.(3)使用只包含常量的字符串连接符如"aa" + "aa"创建的也是常量,编译期就能确定,
         * 已经确定存储到String Pool中;
         * (4)使用包含变量的字符串连接符如"aa" + s1创建的对象是运行期才创建的,存储在heap中;
         *   (5)使用new关键字一定创建了一个对象.只有通过new方法保证创建了一个对象.
         */


        /**
         *
         *  7.关于equals和==
         *
         * (1)对于==,如果作用于基本数据类型的变量(byte,short,char,int,long,float,double,boolean ),
         * 则直接比较其存储的"值"是否相等;如果作用于引用类型的变量(String),
         * 则比较的是所指向的对象的地址(即是否指向同一个对象)。
         *
         * (2)equals方法是基类Object中的方法,因此对于所有的继承于Object的类都会有该方法。
         * 在Object类中,equals方法是用来比较两个对象的引用是否相等,即是否指向同一个对象。
         * 说通俗一定就是比较两个对象内存地址时候相等的 。
         *
         * (3)对于equals方法,注意:equals方法不能作用于基本数据类型的变量。如果没有对equals方法进行重写,
         * 则比较的是引用类型的变量所指向的对象的地址;而String类对equals方法进行了重写,
         * 用来比较指向的字符串对象所存储的字符串是否相等。其他的一些类诸如Double,Date,Integer等,
         * 都对equals方法进行了重写用来比较指向的对象所存储的内容是否相等。
         *
         */


        /**
         *
         * 下面来看看字符串的相加的 操作.下面来看一个简单的例子.来看看代码.
         */


        System.out.println("---------test--------------->>>>");
        String r = "aa";
        String t = "bb";
        String b = "xx" + "yy " + r + "zz" + "mm" + t;
        System.out.println(b);

        System.out.println("----------test9------------->>>>");


        /**
         * String的不可变性导致了,使用+进行连接字符串后的代价,来看看下面的代码.
         */
        String n = null;
        for (int i = 0; i < 100; i++) {
            n += 'a';
        }
        System.out.println(t);

        /**
         * 由此得出结论
         * 由此得出结论:当使用+进行多个字符串连接时,实际上是产生了一个StringBuilder对象和一个String对象。
         */


        /**
         * 代码分析.
         * 每做一次 + 就产生个StringBuilder对象,然后append后就扔掉。
         * 下次循环再到达时重新产生个StringBuilder对象,然后 append 字符串,如此循环直至结束。
         * 如果我们直接采用 StringBuilder 对象进行 append 的话,
         * 我们可以节省 N - 1 次创建和销毁对象的时间。所以对于在循环中要进行字符串连接的应用,
         * 一般都是用StringBuffer或StringBulider对象来进行append操作
         */


        /**
         *
         * 最后来看看StribgBuild,StringBuffer
         * 1.String是不可改变的字符串对象.然而StringBuilder,StringBuffer是可变的字符串对象.
         * 2.2)是否多线程安全:String中的对象是不可变的,也就可以理解为常量,显然线程安全。StringBuffer 与 StringBuilder 中的方法和功能完全是等价的,只是StringBuffer 中的方法大都采用了synchronized 关键字进行修饰,因此是线程安全的,
         * 而 StringBuilder 没有这个修饰,可以被认为是非线程安全的。
         */

        /**
         *
         *

         (3)String、StringBuilder、StringBuffer三者的执行效率:
         StringBuilder > StringBuffer > String 当然这个是相对的,不一定在所有情况下都是这样。
         比如String str = "hello"+ "world"的效率就比
         StringBuilder st  = new StringBuilder().append("hello").append("world")要高。
         因此,这三个类是各有利弊,应当根据不同的情况来进行选择使用
         当字符串相加操作或者改动较少的情况下,建议使用 String str="hello"这种形式;
         当字符串相加操作较多的情况下,建议使用StringBuilder,如果采用了多线程,则使用StringBuffer
         */


        /**
         * String里面的final的用法和区别.
         *来看看下面的代码.
         *
         */


        System.out.println("-----test10----->>>>");
        final StringBuffer tr = new StringBuffer("1111");
        final StringBuffer td = new StringBuffer("2222");

        System.out.println(tr == td);



        final  StringBuffer  tw=new StringBuffer("111");
        tw.append("222");

        final StringBuffer  tn=new StringBuffer("111222");
        System.out.println(tw==tn);


        /**
         * 12.关于String str = new String("abc")创建了多少个对象?
         *
         * 这个问题在很多书籍上都有说到比如《Java程序员面试宝典》,
         * 包括很多国内大公司笔试面试题都会遇到,大部分网上流传的以及一些面试书籍上都说是2个对象,这种说法是片面的。
         *
         * 首先必须弄清楚创建对象的含义,创建是什么时候创建的?这段代码在运行期间会创建2个对象么?
         * 毫无疑问不可能,用javap -c反编译即可得到JVM执行的字节码内容:
         */


    }


}



猜你喜欢

转载自blog.csdn.net/hackerbaseing/article/details/89527979