Java 7源码分析第5篇 - Java字符串类型

由于日常见到的大部分文本都是基于字符串的,所以Java也提供了强大的API支持,大大方便了字符串的处理。下面就来具体看一下Java源代码是如何来处理这些字符串的。

首先来看如下的一题:

1、Strings=newString("xyz");创建了几个String 对象?

两个或一个,”xyz”对应一个对象,这个对象放在字符串常量缓冲区,常量”xyz”不管出现多少遍,都是缓冲区中的那一个,类似与缓存Byte数值的-128~127数值。NewString每写一遍,就创建一个新的对象,那个常量”xyz”对象的内容来创建出一个新String对象。如果以前就用过’xyz’,这句代表就不会创建”xyz”自己了,直接从缓冲区拿。
所以,凡是通过构造器创建的对象都会进行内存分配,所以他就不会指向缓存池中已有的对象而指向新的对象,这样就会造成缓存池中存在多个值相同的字符串对象,浪费资源。所以一般要直接为字符串指定值即可。
这里需要介绍一下缓存池:为了节省内存,提高资源的复用,jvm引入了常量池这个概念,它属于方法区的一部分,作用之一就是存放编译期间生产的各种字面量和符号引用。方法区的垃圾回收行为是比较少出现的,该区中的对象基本不会被回收,可以理解成是永久存在的。

2、Strings="a"+"b"+"c"+"d";创建了几个String对象?
一个,因为Javac在做编译时已经对这些字符串进行了合并操作,做了优化处理。

3、String name = "ab"; name = name + "c";两条语句总共创建了多少个字符串对象?

创建了两个对象,这两个对象都会放到缓存池中,只是name的引用由"ab"改变为"abc"了。我们在这样用的时候,还需要考虑其他问题,如这个程序会造成内在泄漏,因为缓存池中的在缓存池中的字符串是不会被垃圾回收机制回收的,基本都是常驻内存,所以过多使用String类,可能会出现内存溢出。

4、

        String s1 = "a";
        String s2 = s1 + "b";
        String s3 = "ab"; 
        System.out.println(s2 == s3);//false

可以看到s2与s3的引用并不相同。由于s2字符串在编译时并不能进行确定,所以首先进入缓存池中的有s1和s3,随后会创建一个新的s2字符串对象,两者当然不一样了。

如果程序的字符串连接表达式中没有使用变量或者调用方法,那么该字符串变量的值就能够在编译期间确定下来,并且将该字符换缓存在缓冲区中,同时让该变量指向该字符串;否则将无法利用缓冲区,因为使用了变量和调用了方法之后的字符串变量的值只能在运行期间才能确定连接式的值,也就无法在编译期间确定字符串变量的值,从而无法将字符串变量增加到缓冲区并加以利用。

所以如何有字符串拼接之类的操作,建议使用线程安全的StringBuilder类型或StringBuffer类。

public final class String implements java.io.Serializable, Comparable<String>, CharSequence{
    private final char value[]; //用来存储字符串转换而来的字符数组
    private final int offset; //字符串起始字符在字符数组的位置
    private final int count; //字符串分解成字符数组后字符的数目
}

从上面代码,我们知道:

1> String类是被final修饰的,从安全角度来说,通过final修饰后的String类是不能被其他类继承的,在最大程度上的保护了该类,从效率上来说,提高了该类的效率,因为final修饰后会比没有用final修饰的快。

2> value[], offet, count也是被final修饰的,这时候三个变量的值必须在编译期间就被确定下来,并且在运行期间不能再被修改了。因此,每次我们每次进行字符串修改、拼接的时候,并不能直接修改当前String对象的值,只能重新创建一个新的对象。

3>我们创建String对象的时候,String对象还使用字符数组(char[])来存储我们的字符串的。

下面来看一下String类的源代码,首先来看String类最常用的一个构造函数,如下:

public String(String original) {
        int size = original.count;
        char[] originalValue = original.value;
        char[] v;
        if (originalValue.length > size) {
            // The array representing the String is bigger than the new
            // String itself.  Perhaps this constructor is being called
            // in order to trim the baggage(行李), so make a copy of the array.
            int off = original.offset;
            v = Arrays.copyOfRange(originalValue, off, off+size);
        } else {
            // The array representing the String is the same
            // size as the String, so no point in making a copy.
            v = originalValue;
        }
        this.offset = 0;
        this.count = size;
        this.value = v;
    }
使用如上的构造函数也就允许我们通过String x=new String("字符串")的形式来创建一个字符串,但是通常不建议这样做,前面讲到过,可能会创建两个字符串对象,消耗太大。继续来看其他的一些常用构造函数

    public String(char value[]) {// 传入字符数组返回字符串
        int size = value.length;
        this.offset = 0;
        this.count = size;
        this.value = Arrays.copyOf(value, size);// copy the newly string
    }
    // 指定范围内的字符数组组成字符串
    public String(char value[], int offset, int count) {
        if (offset < 0) {
            throw new StringIndexOutOfBoundsException(offset);
        }
        if (count < 0) {
            throw new StringIndexOutOfBoundsException(count);
        }
        // Note: offset or count might be near -1>>>1.
        if (offset > value.length - count) {
            throw new StringIndexOutOfBoundsException(offset + count);
        }
        this.offset = 0;
        this.count = count;
        this.value = Arrays.copyOfRange(value, offset, offset+count);
    }

    /**
     * Allocates a new {@code String} that contains characters from a subarray
     * of the Unicode code point array
     * argument. 
     */
    public String(int[] codePoints, int offset, int count) {// 
        if (offset < 0) {
            throw new StringIndexOutOfBoundsException(offset);
        }
        if (count < 0) {
            throw new StringIndexOutOfBoundsException(count);
        }
        // Note: offset or count might be near -1>>>1.
        if (offset > codePoints.length - count) {
            throw new StringIndexOutOfBoundsException(offset + count);
        }
        final int end = offset + count;
        // Pass 1: Compute precise size of char[]
        int n = count;
        for (int i = offset; i < end; i++) {
            int c = codePoints[i];
            if (Character.isBmpCodePoint(c))
                continue;
            else if (Character.isValidCodePoint(c))
                n++;
            else throw new IllegalArgumentException(Integer.toString(c));
        }
        // Pass 2: Allocate and fill in char[]
        final char[] v = new char[n];

        for (int i = offset, j = 0; i < end; i++, j++) {
            int c = codePoints[i];
            if (Character.isBmpCodePoint(c))
                v[j] = (char) c;
            else
                Character.toSurrogates(c, v, j++);
        }
        this.value  = v;
        this.count  = n;
        this.offset = 0;
    }
编写一个测试程序,如下:

        char data[] = {'a', 'b', 'c'};  
        String str1 = new String(data);  
        String str2 = new String(data,0,2);  
        
        char data1[] = {0x4E2D, 0x56FD};  
        String str3 = new String(data1);
        System.out.println(str1);//abc
        System.out.println(str2);//ab
        System.out.println(str3);//中国
还有以byte[]数组为参数创建字符串的构造函数,其中最主要的两个如下:
    public String(byte bytes[], int offset, int length) {
    checkBounds(bytes, offset, length);
    char[] v = StringCoding.decode(bytes, offset, length);
    this.offset = 0;
    this.count = v.length;
    this.value = v;
  }
    public String(byte bytes[], int offset, int length, String charsetName)
        throws UnsupportedEncodingException
    {
        if (charsetName == null)
            throw new NullPointerException("charsetName");
        checkBounds(bytes, offset, length);
        char[] v = StringCoding.decode(charsetName, bytes, offset, length);
        this.offset = 0;
        this.count = v.length;
        this.value = v;
    }
    public String(byte bytes[], int offset, int length, Charset charset) {
        if (charset == null)
            throw new NullPointerException("charset");
        checkBounds(bytes, offset, length);
        char[] v = StringCoding.decode(charset, bytes, offset, length);
        this.offset = 0;
        this.count = v.length;
        this.value = v;
    }

编写测试程序如下:

byte[] ascBytes = {(byte)0x61, (byte)0x62, (byte)0x63}; // ASCII的'a','b','c'字符
System.out.println(new String(ascBytes,0,2));//abc
System.out.println(new String(ascBytes,0,2,Charset.forName("ISO-8859-1")));//abc
System.out.println(new String(ascBytes,0,2,"ISO-8859-1"));//abc

其他的一些使用byte数组创建字符串的构造函数其实最终都是调用如上两个构造函数。另外还提供了StringBuffer和StringBuilder转String字符串的构造函数,非常简单,这里不做介绍。

接下来就看一看String常用的API了。

public int length() {
        return count;
    }
返回字符串的长度,但是需要注意的是:

String类中的length()方法也是对字符串进行char统计,也就是计算代码单元数量(代码单元有可能大于正真的字符数量,如果字符串中有增补字符的话)。如果要计算代码点数量,必须用str.codePointCount(0,str.length())方法。

由此可见,用char类型来处理字符串在有些情况下是不准确的,我们最好使用字符串类型中处理代码点的方法,不要使用处理char类型(一个代码单元)的方法。


下面还有一个方法需要讲解一下:

public native String intern();
这是一个本地的方法,当调用 intern 方法时,如果缓存池已经包含一个等于此 String 对象的字符串,则返回池中的字符串。否则,将此 String 对象添加到池中,并返回此 String 对象的引用。
String param = "abc";
       String newStr = new String("abc");
       String param2 = new String("abc");
       newStr.intern();
       param2 = param2.intern();    //param2指向intern返回的常量池中的引用
       System.out.println(param == newStr);    //false
       System.out.println(param == param2);    //true
可以看到,使用intern()方法后,字符串变量的值如果不存在缓冲区中将会被缓存




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值