• JDK源码分析之String篇


    http://www.cnblogs.com/huntfor/p/3909059.html


    ------------------------------String在内存中的存储情况(一下内容摘自参考资料1)-----------------------------------

    前提:先了解下什么是声明,什么时候才算是产生了对象实例

    其中x并未看到内存分配,变量在使用前必须先声明,再赋值,然后才可以使用。java基础数据类型会用对应的默认值进行初始化

     

    一、首先看看Java虚拟机JVM的内存块及其变量、对象内存空间是怎么存储分配的

           1、栈:存放基本数据类型及对象变量的引用,对象本身不存放于栈中而是存放于堆中

                 1)、基础类型 byte (8位)、boolean (1位)、char (16位)、int (32位)、short (16位)、float (32位)、double (64位)、long (64位)

                 2)、java代码作用域中定义一个变量时,则java就在栈中为这个变量分配内存空间,当该变量退出该作用域时,java会自动释放该变量所占的空间

           2、堆:new操作符的对象

                 1)、new创建的对象和数组

                 2)、在堆中分配的内存,由Java虚拟机的自动垃圾回收器来管理

           3、静态域:static定义的静态成员变量

           4、常量池:存放常量

    二、Java String类型

            Java中String不是基本数据类型,而是一种特殊的类。String代表的是不可变的字符序列,为不可变对象,一旦被创建,就不能修改它的值,对于已经存在的String对象的修改都是重新创建一个新的对象,然后把新的值保存进去

    三 、String实例代码分析

    复制代码
     1 package terry.java.base;
     2 
     3 public class StringTest {
     4      public static void main(String[] args) {
     5       String a = "hello";
     6       String b = "hello";
     7       
     8       String newA = new String("hello");
     9       String newB = new String("hello");
    10       
    11       System.out.println("****** Testing Object == ******");
    12       System.out.println("a==b ? :" + (a==b));
    13       System.out.println("newA==newB ? :" +(newA==newB));
    14       System.out.println("a==newA ? :" + (a==newA));
    15       
    16       System.out.println("***** Testing String Object intern method******");
    17       System.out.println("a.intern()==b.intern() ? : " + (a.intern()==b.intern()));
    18       System.out.println("newA.intern()==newB.intern() ? :" + (newA.intern()==newB.intern()));
    19       System.out.println("a.intern()==newA.intern() ? :" + (a.intern()==newA.intern()));
    20       System.out.println("a=a.intern() ? :" + (a==a.intern()));
    21       System.out.println("newA==newA.intern() ? : " + (newA==newA.intern()));
    22       
    23       System.out.println("****** Testing String Object equals method******");
    24       System.out.println("equals() method :" + a.equals(newA));
    25       
    26       String c = "hel";
    27       String d = "lo";
    28       final String finalc = "hel";
    29       final String finalgetc = getc();
    30       
    31       System.out.println("****** Testing Object splice ******");
    32       System.out.println("a=="hel"+"lo" ? :" + (a=="hel"+"lo"));
    33       System.out.println("a==c+d ? : " + (a==c+d));
    34       System.out.println("a==c+"lo" ? : " + (a==c+"lo"));
    35       System.out.println("a==finalc+"lo" ? :" + (a==finalc+"lo"));
    36       System.out.println("a==finalgetc+"lo" ? :" + (a==finalgetc+"lo"));
    37       
    38      }
    39      private static String getc(){
    40       return "hel";
    41      } 
    42 }
    复制代码

     

    Run As Java Application -- 输出结果:

    复制代码
     1 ****** Testing Object == ******
     2 a==b ? :true
     3 newA==newB ? :false
     4 a==newA ? :false
     5 ***** Testing String Object intern method******
     6 a.intern()==b.intern() ? : true
     7 newA.intern()==newB.intern() ? :true
     8 a.intern()==newA.intern() ? :true
     9 a==a.intern() ? :true
    10 newA==newA.intern() ? : false
    11 ****** Testing String Object equals method******
    12 equals() method :true
    13 ****** Testing Object splice ******
    14 a=="hel"+"lo" ? :true
    15 a==c+d ? : false
    16 a==c+"lo" ? : false
    17 a==finalc+"lo" ? :true
    18 a==finalgetc+"lo" ? :false
    复制代码

     

    内存分析:

    上述各个变量及引用在JVM分配的内存情况

    String类型对象实例直接赋值和new操作符产生的结果在JVM内存分配过程是不同的,如下注释说明

    String常量+的拼接 及 String常量与引用实例+的拼接 的区别 

    关于String对象的intern()方法的说明

    一个初始时为空的字符串池,它由类 String 私有地维护

    当调用 intern 方法时,如果池已经包含一个等于此 String 对象的字符串(该对象由 equals(Object) 方法确定),则返回池中的字符串。否则,将此String 对象添加到池中,并且返回此String 对象的引用,因此a.intern(),b.intern(),newA.intern(),newB.intern()隐含的各自在栈中分配了各自的内存区域,同时都将栈中的应用全部指向了String pool常量池中的同一块区域"hello"

     

    -----------------------------------------------Thinking in Java 读书笔记--------------------------------------------

    《Java编程思想<第四版>》-第13章

    本章第一小节标题为“不可变String”,第一句话为“String对象时不可变的”。String类中任何一个看起来会修改String值的方法,实际上都是创建了一个全新的String对象,以包含修改后的字符串内容,原来的String对象丝毫未动。

    给Java初学者举个栗子,有基础的直接跳过:

    1 String s = "ABCabc";
    2 System.out.println("s = " + s);
    3  
    4 s = "123456";
    5 System.out.println("s = " + s);

     

    打印结果是:

    s = ABCabc
    s = 123456

     

    貌似s的值被改变了,改变的只是s的这个引用,本来s该引用指向了常量池中的“ABCabc”,后来指向了常量池中的“123456”。结合上面的内存模型,常量池中有两个常量“ABCabc”、“123456”,栈中只有一个引用s,本来这个s指向“ABCabc”,后来被赋值给了“123456”,图就不画了,还不明白,就别往下看了,看一点更基础的比较好。

     

    来看下String的源代码:

    复制代码
    public final class String
        implements java.io.Serializable, Comparable<String>, CharSequence {
        /** The value is used for character storage. */汉化一下:这个value是用来装char的.明白了吧,String其实是char数组的包装类。
        //而这个数组是final类型的,不可能指向别的对象,但是可以改,这个请耐心看下去
        private final char value[];
        /** Cache the hash code for the string */缓存hashCode
        private int hash; // Default to 0
    }
    复制代码

    源码中String的本质是一个final类型的char数组,既然是final类型,那个该数组引用value就不允许再指向其他对象了,因此只从类的设计角度讲:如果jdk源码中并没有提供对value本身的修改,那么理论上来讲String是不可变的。

     

    StringBuilder的故事

    大家都知道String的有两个关系很亲近的小伙伴:StringBuffer、StringBuilder,其中的区别大家可以看源代码,StringBuffer对每个方法(除了构造函数)都用了同步,StringBuilder就内向多了。

    大家还知道java中是不支持运算符重载的,但是有且仅有两个例外:+、+=对String进行的重载。

    +、+=被重载了,+用来连接String操作:就是说 “a" + "b" + ”c“= ”abc“;

    上文书说道:

    String是不可变的,即是说String a = "a" ; a + "b" 之后,a还是”a“,这个”ab“实际上又生成了一个新的String对象。将这个赋值语句剖析一下:

    "a" + "b" + "c":先计算前两个  "a" + "b":这时内存中其实有四个字符串,”a“,"b","c","ab",然后再+”c“,这是内存中有五个String:”a“,"b","c","ab",”abc“。

    真的是这样吗?

    可以通过javap来反编译上面的赋值语句,会发现,在编译本条语句时,编译器会自作主张的引入了StringBuilder,并调用了StringBuilder.append方法,这样就不用再生成多余的字符串了;

    因此在用StringBuilder进行append操作时候,千万不要使用append("a" + "b")这样的操作,因为酱,编译器会为你另外的创建一个StringBuilder对象来处理括号里的字符串操作。

     

    toString的故事,无意识的递归:

    toString方法里面谨慎返回this,可能因此无限递归

    this遇到+“”时候,会将this转换成String,怎么转换呢?通过调用toString方法的好了,无限递归,栈溢出。

     

    String真的不可变吗?

    从上文可知String的成员变量是private final 的,也就是初始化之后不可改变。那么在这几个成员中, value比较特殊,因为他是一个引用变量,而不是真正的对象。value是final修饰的,也就是说final不能再指向其他数组对象,那么我能改变value指向的数组吗? 比如将数组中的某个位置上的字符变为下划线“_”。 至少在我们自己写的普通代码中不能够做到,因为我们根本不能够访问到这个value引用,更不能通过这个引用去修改数组。 那么用什么方式可以访问私有成员呢? 没错,用反射, 可以反射出String对象中的value属性, 进而改变通过获得的value引用改变数组的结构。下面是实例代码: 

     

    复制代码
        //创建字符串"Hello World", 并赋给引用s
        String s = "Hello World"; 
         
        System.out.println("s = " + s); //Hello World
         
        //获取String类中的value字段
        Field valueFieldOfString = String.class.getDeclaredField("value");
         
        //改变value属性的访问权限
        valueFieldOfString.setAccessible(true);
         
        //获取s对象上的value属性的值
        char[] value = (char[]) valueFieldOfString.get(s);
         
        //改变value所引用的数组中的第5个字符
        value[5] = '_';
         
        System.out.println("s = " + s);  //Hello_World
    }
    复制代码

     

    打印结果为: s = Hello World
    s = Hello_World

    在这个过程中,s始终引用的同一个String对象,但是再反射前后,这个String对象发生了变化, 也就是说,通过反射是可以修改所谓的“不可变”对象的。但是一般我们不这么做。这个反射的实例还可以说明一个问题:如果一个对象,他组合的其他对象的状态是可以改变的,那么这个对象很可能不是不可变对象。例如一个Car对象,它组合了一个Wheel对象,虽然这个Wheel对象声明成了private final 的,但是这个Wheel对象内部的状态可以改变, 那么就不能很好的保证Car对象不可变。 

     

    常用方法:

        public char charAt(int index) {
            if ((index < 0) || (index >= value.length)) {
                throw new StringIndexOutOfBoundsException(index);
            }
            return value[index];//先判断越界,然后直接返回合法值
        }
        public boolean isEmpty() {
            return value.length == 0;
        }
        public int length() {
            return value.length;
        }

     从源码的实现来看,length() == 0 和 isEmpty()效率是一样的。

    复制代码
        public boolean equals(Object anObject) {
            if (this == anObject) {
                return true;
            }//重写了Object的equals方法,判断字符串内容
            if (anObject instanceof String) {
                String anotherString = (String) anObject;
                int n = value.length;
                if (n == anotherString.value.length) {
                    char v1[] = value;
                    char v2[] = anotherString.value;
                    int i = 0;
                    while (n-- != 0) {
                        if (v1[i] != v2[i])
                                return false;
                        i++;
                    }
                    return true;
                }
            }
            return false;
        }
    复制代码
        public boolean contentEquals(StringBuffer sb) {
            synchronized (sb) {
                return contentEquals((CharSequence) sb);
            }
        }
    复制代码
        public boolean contentEquals(CharSequence cs) {
            if (value.length != cs.length())
                return false;
            // Argument is a StringBuffer, StringBuilder
            if (cs instanceof AbstractStringBuilder) {
                char v1[] = value;
                char v2[] = ((AbstractStringBuilder) cs).getValue();
                int i = 0;
                int n = value.length;
                while (n-- != 0) {
                    if (v1[i] != v2[i])
                        return false;
                    i++;
                }
                return true;
            }
            // Argument is a String
            if (cs.equals(this))
                return true;
            // Argument is a generic CharSequence
            char v1[] = value;
            int i = 0;
            int n = value.length;
            while (n-- != 0) {
                if (v1[i] != cs.charAt(i))
                    return false;
                i++;
            }
            return true;
        }
    复制代码

     contentEquals(charSequence cs)参数可以是StringBuilder、StringBuffer、String以及CharSequence,contentEquals(StringBuilder sb)只是为了保证sb的一致性,在外面加了互斥锁。equals方法也只是contentEquals的一种情况的实现,完全可以用contentEquals(cs)来取代。

    复制代码
        public String concat(String str) {
            int otherLen = str.length();
            if (otherLen == 0) {
                return this;
            }
            int len = value.length;
            char buf[] = Arrays.copyOf(value, len + otherLen);
            str.getChars(buf, len);
            return new String(buf, true);//返回了新的string
        }
    复制代码

    concat方法可以看出,最后返回的其实是一个新new的string。并不对原value内容进行改动。

    同样的:

    复制代码
     public String substring(int beginIndex) {
            if (beginIndex < 0) {
                throw new StringIndexOutOfBoundsException(beginIndex);
            }
            int subLen = value.length - beginIndex;
            if (subLen < 0) {
                throw new StringIndexOutOfBoundsException(subLen);
            }
            return (beginIndex == 0) ? this : new String(value, beginIndex, subLen);
        }
    复制代码

    这两个方法很好用

    复制代码
        public boolean startsWith(String prefix, int toffset) {
            char ta[] = value;
            int to = toffset;
            char pa[] = prefix.value;
            int po = 0;
            int pc = prefix.value.length;
            // Note: toffset might be near -1>>>1.
            if ((toffset < 0) || (toffset > value.length - pc)) {
                return false;
            }
            while (--pc >= 0) {
                if (ta[to++] != pa[po++]) {
                    return false;
                }
            }
            return true;
        }
    复制代码
        public boolean endsWith(String suffix) {
            return startsWith(suffix, value.length - suffix.value.length);
        }

     endsWith是用startsWith实现的,只看名字有点搞笑。


  • 相关阅读:
    java并发计算的几种基本使用示例
    axios、ajax和xhr前端发送测试
    Spring注解
    Android菜鸟教程笔记
    普通二叉树操作
    MyBatis
    mysql的select语句总结与索引使用
    sys.argv的意义[转]
    硬件小白学习之路(1)稳压芯片LM431
    FPGA小白学习之路(6)串口波特率问题的处理
  • 原文地址:https://www.cnblogs.com/leeeee/p/7276048.html
Copyright © 2020-2023  润新知