Java源码:String类

1.分类

  1. 基本属性:String最终的属性就一个char[] value;
  2. 构造方法区域:比较复杂的就是通过unicode码和byte[]构造;
  3. 字符串比较:是否相等、大小(排序);
  4. 查询:indexOf、startsWith、endWith、contains
  5. 截取:subString、
  6. 工具方法:格式化打印、unicode 码点相关的位操作方法;

2. 基础

面向对象的封装思想为什么屏蔽了很多底层细节,String类作为我们使用频率最高的类,帮我们实现了很多功能,

2.1 不可变性

JDK中有很多不可变的类,所谓的不可变类不是表现在字符串声明为final,而是内部的value是不可以被修改的,所有对value的操作都是返回一个新的对象,比如BigDecimal,下面是一些错误的方法

String str = "  hello,你好 ";
str.trim();
str.replace(',',',');//这行是编译不过去的,中文的char是有问题的
System.out.println(str);

2.2 字符串常量池

JVM为了提升8种基本类型(short/int/long/double/float/char/boolean/byte)和String的效率,建立了常量池的概念。这些变量在编译为class文件后,放入到class文件的常量池部分。JVM运行时会建立字符串常量池,预编译的字符串会加入常量池,通过intern也可以将字符串加入常量池;

public class StringPoolTest {

	public static final String str1 = "a"; // a进入常量池,str1使用常量池引用

	public final String str2 = "b"; // b进入常量池,str2使用常量池引用

	public String str3 = "c"; // c进入常量池,str3 使用常量池引用

	public final String str4 = str2 + str3; // bc不进入常量池, 实际上是 str4 = new StringBuilder(str2).append(str3).toString();

	public static void main(String[] args) {
		String str5 = "d"; // d进入常量池
		final String str7 = "f"; // f 进入常量池
		final String str8 = str5 + "j"; //dj不进入常量池 实际上是str8 = new StringBuilder(str5).append(j).toString()
	}

}

使用 javap -v做反编译查看字节码的详细信息如下

 public class com.saillen.demo.lang.StringPoolTest
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:   
   ……
   #4 = String             #44            // c
   //这里很明确引入了StringBuilder,但是我们的代码中并没有import
   #6 = Class              #46            // java/lang/StringBuilder
   ……
   //表示d的引用值在#51定义
  #12 = String             #51            // d
  #13 = String             #52            // e
  #14 = String             #53            // f
  #15 = String             #54            // j
  ……
  //
  #17 = Utf8               str1
  #20 = String             #56            // a
  #21 = Utf8               str2
  #22 = Utf8               str3
  #23 = Utf8               str4
  #35 = Utf8               str5
  #36 = Utf8               str6
  #37 = Utf8               str7
  #38 = Utf8               str8
  ……
  #42 = Utf8               b
  #44 = Utf8               c
  #51 = Utf8               d
  #52 = Utf8               e
  #53 = Utf8               f
  #54 = Utf8               j
  #56 = Utf8               a

{
  
  //类的初始化指令
  public com.saillen.demo.lang.StringPoolTest();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=3, locals=1, args_size=1
         //加载b,str1的代码被优化掉了,因为没有被使用,还是很智能的
         //这里JDK8优化了无用的code,但是JDK7、6不一定
         //优化掉了但是常量池中存在,因为在其他类中可以使用str1,
         4: aload_0
         5: ldc           #2                  // String b
         7: putfield      #3                  // Field str2:Ljava/lang/String;
        //加载c
        10: aload_0
        11: ldc           #4                  // String c
        13: putfield      #5                  // Field str3:Ljava/lang/String;
        
        //str4 实际是,
        //先调用new StringBuilder()
        //然后调用append(str2).append(str3)
        //然后调用toString()
        //然后赋值给str4
        16: aload_0
        17: new           #6                  // class java/lang/StringBuilder
        20: dup
        21: invokespecial #7                  // Method java/lang/StringBuilder."<init>":()V
        //加载b做append(str2)
        24: ldc           #2                  // String b
        26: invokevirtual #9                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        //加载c做append(str3)
        29: aload_0
        30: getfield      #5                  // Field str3:Ljava/lang/String;
        33: invokevirtual #9                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        //toString给str4
        36: invokevirtual #10                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
        39: putfield      #11                 // Field str4:Ljava/lang/String;

	……
	//其他str5、str6、str7、str8同理

}
SourceFile: "StringPoolTest.java"

可以参考:http://blog.csdn.net/sugar_rainbow/article/details/68150249

NOTE:涉及到这种‘基本的’,‘JVM’层次的要注意JDK版本,还有有些结论不一定“正确”;

Unicode 和 UTF-16

  • Java使用unicode字符集表示内存中的每个字符(不仅仅是字母和汉字);
  • unicode标准中为每个字符确定了唯一的 码点(codePoint);
  • unicode标准将码点以16进制表示即\uxxxx形式;
  • unicode标准将码点分区域管理,称为平面(plane);
  • unicode将最前面的65536字符称为 基本平面(BMP);
  • unicode仅仅是一个标准,不管实际怎么存储的;
  • UTF-16仅仅是unicode的一种实现方案;

unicode的几个问题:

  • 码点如何表示:比如codePoint为1,那么1个字节就够了,但是如果是10万,那么1个字节肯定不够;
  • 如何存储数据?最简单 or 占用空间最小?

可以参考:http://blog.csdn.net/gjb724332682/article/details/43229075

问题:为什么1byte = 8bit ? 为什么1 byte 不用 4bit或者 16bit?

2.3Java中UTF-16

  • java使用utf-16存储和表示字符 – 在JVM中每个字符都是2个字节;
  • 2个字节能表示的codePoint 范围:256 * 256 = 65536,能表示BMP的内容 O~0xFFFF范围;
  • Charset类是char的包装类也提供了很多Unicode的操作方法,保存了最大和最小字符数量;
  • 如果字符比如汉字codePoint的值很大怎么办 - 用2个char也就是4个字节来表示;
  • 在class中为了减小文件大小,字符常量都是以UTF8编码的,但是JVM内存中是UTF16的,即在load的时候是有编码转换的;

到底几个字节?

  • char到底是几个字节,很明确char是2个字节,那么汉字为什么是2个字符,4个字节,如何定义?
  • char的实质存储的是int值,如果codePoint在2个字节内能表示下,那么就用一个char就够了,在显示打印的时候读取一个char然后找打对应的字符去打印;
  • 如果codePoint需要4个字节,2个字符,那么就读取两个连续的char来打印即可。
// 下面的代码段不要用IDE编辑会报错。
 char c = ''; //不要用IDE编辑。
 String s = ""; // char[]的长度为2;

2,4 Java中char和String到底存储的是什么

  • String:存储的是char[]数组;
  • char:存储的是字符的unicode的codePoint的int值;
  • java中编码转换的实质就是:读取目标编码的codePoint的值转换为unicode的codePoint值然后按照UTF-16的格式存储到内存或其他地方;

3 源码

3.1 属性

// 字符串是常量,字符串被创建后,字符串的值不可以被修改,
// final的不可修改
public final class String implements java.io.Serializable, Comparable<String>, CharSequence {

    /** 存储了原始字符 */
    private final char value[];

    /** 因为字符串的不可变性,所以可以缓存一个hash值 */
    private int hash; // Default to 0

    private static final long serialVersionUID = -6849794470754667710L;

    //序列化使用
    private static final ObjectStreamField[] serialPersistentFields =
        new ObjectStreamField[0];

    // 内部方法用来比较字符串大小
    public static final Comparator<String> CASE_INSENSITIVE_ORDER
                                         = new CaseInsensitiveComparator();

3.2 常用方法

    //判断长度就是char[]的长度
    public int length() {
        return value.length;
    }

    //是不是空串,空格不算空串;
    public boolean isEmpty() {
        return value.length == 0;
    }
    
    //第index个字符,要注意不在BMP内的字符取出的char可能无法转换为一个完整字符;
    public char charAt(int index) {
        if ((index < 0) || (index >= value.length)) {
            throw new StringIndexOutOfBoundsException(index);
        }
        return value[index];
    }

    
    //这里copy了一个新的char,防止String的不可变性被破坏
    void getChars(char dst[], int dstBegin) {
        System.arraycopy(value, 0, dst, dstBegin, value.length);
    }

    //只copy部分,还是arraycopy
    //做数组copy的时候还是多使用arraycopy
    public void getChars(int srcBegin, int srcEnd, char dst[], int dstBegin){
        if (srcBegin < 0) {
            throw new StringIndexOutOfBoundsException(srcBegin);
        }
        if (srcEnd > value.length) {
            throw new StringIndexOutOfBoundsException(srcEnd);
        }
        if (srcBegin > srcEnd) {
            throw new StringIndexOutOfBoundsException(srcEnd - srcBegin);
        }
        System.arraycopy(value, srcBegin, dst, dstBegin, srcEnd - srcBegin);
    }

3.3 相等性比较、hash

相等的比较思路最终都是:挨个循环比较char[],如果不一致则false

    //equals方法挨个比较char字符是否一样
    public boolean equals(Object anObject) {
        if (this == anObject) {
            return true;
        }
        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;
    }
    
     //可以看到是挨个字符计算一个最终的hash
    //如果计算了一遍了则不再计算
    //对于""的字符串,hashCode就是0
    public int hashCode() {
        int h = hash;
        if (h == 0 && value.length > 0) {
            char val[] = value;
			  //将unicode码参与到计算中
            for (int i = 0; i < value.length; i++) {
                h = 31 * h + val[i];
            }
            hash = h;
        }
        return h;
    }

3.4 多种valueOf方法

valueOf(Object obj)的时候不会有null的问题,可以替代我们的的obj.toString();

  public static String valueOf(Object obj) {
        return (obj == null) ? "null" : obj.toString();
    }

    public static String valueOf(char data[]) {
        return new String(data);
    }

    public static String valueOf(char data[], int offset, int count) {
        return new String(data, offset, count);
    }

    public static String copyValueOf(char data[], int offset, int count) {
        return new String(data, offset, count);
    }

    public static String copyValueOf(char data[]) {
        return new String(data);
    }

    public static String valueOf(boolean b) {
        return b ? "true" : "false";
    }

    public static String valueOf(char c) {
        char data[] = {c};
        return new String(data, true);
    }

    public static String valueOf(int i) {
        return Integer.toString(i);
    }

    public static String valueOf(long l) {
        return Long.toString(l);
    }

    public static String valueOf(float f) {
        return Float.toString(f);
    }

    public static String valueOf(double d) {
        return Double.toString(d);
    }


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章