首页 > Java开发 > Java字符串类型

Java字符串类型

由于日常见到的大部分文本都是基于字符串来表示和处理的,所以Java提供了强大的API支持,方便程序编写人员对文本的处理。下面就来研究一下String类的源代码。

首先来看一下字符串常见的几个问题:

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

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

一个,因为Javac在做编译时已经对这些字符串进行了合并操作,预先做了优化处理。

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

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

4、字符串比较

 

[java] view plaincopyprint?在CODE上查看代码片派生到我的代码片

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

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

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

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

下面来看一下JDK 7中String类的两个重要的变量:

 

[java] view plaincopyprint?在CODE上查看代码片派生到我的代码片

  1. public final class String implements java.io.Serializable, Comparable<String>, CharSequence {
  2.     /** The value is used for character storage. */
  3.     private final char value[];
  4. }

在JDK 6中其实还有另外的两个变量offset和count:

 

 

[java] view plaincopyprint?在CODE上查看代码片派生到我的代码片

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

以上的两段代码可以看出:

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

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

3> 创建String对象的时候,String对象还使用字符数组(char[])来存储我们的字符串, 在Java中String类其实就是对字符数组的封装

 

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

 

[java] view plaincopyprint?在CODE上查看代码片派生到我的代码片

  1. public String(String original) {
  2.         this.value = original.value;
  3.         this.hash = original.hash;
  4. }

使用如上的构造函数使用original.value的形式来创建一个字符串,但是通常不建议这样做,可能会创建两个字符串对象,消耗太大。继续来看其他的一些常用构造函数。

 

 

[java] view plaincopyprint?在CODE上查看代码片派生到我的代码片

  1. public String(char value[]) {
  2.        this.value = Arrays.copyOf(value, value.length);
  3.    }
  4.    public String(char value[], int offset, int count) {
  5.        if (offset < 0) {
  6.            throw new StringIndexOutOfBoundsException(offset);
  7.        }
  8.        if (count < 0) {
  9.            throw new StringIndexOutOfBoundsException(count);
  10.        }
  11.        // Note: offset or count might be near -1>>>1.
  12.        if (offset > value.length - count) {
  13.            throw new StringIndexOutOfBoundsException(offset + count);
  14.        }
  15.        this.value = Arrays.copyOfRange(value, offset, offset+count);
  16.    }
  17.    public String(int[] codePoints, int offset, int count) {
  18.        if (offset < 0) {
  19.            throw new StringIndexOutOfBoundsException(offset);
  20.        }
  21.        if (count < 0) {
  22.            throw new StringIndexOutOfBoundsException(count);
  23.        }
  24.        // Note: offset or count might be near -1>>>1.
  25.        if (offset > codePoints.length - count) {
  26.            throw new StringIndexOutOfBoundsException(offset + count);
  27.        }
  28.        final int end = offset + count;
  29.        // Pass 1: Compute precise size of char[]
  30.        int n = count;
  31.        for (int i = offset; i < end; i++) {
  32.            int c = codePoints[i];
  33.            if (Character.isBmpCodePoint(c))
  34.                continue;
  35.            else if (Character.isValidCodePoint(c))
  36.                n++;
  37.            else throw new IllegalArgumentException(Integer.toString(c));
  38.        }
  39.        // Pass 2: Allocate and fill in char[]
  40.        final char[] v = new char[n];
  41.        for (int i = offset, j = 0; i < end; i++, j++) {
  42.            int c = codePoints[i];
  43.            if (Character.isBmpCodePoint(c))
  44.                v[j] = (char)c;
  45.            else
  46.                Character.toSurrogates(c, v, j++);
  47.        }
  48.        this.value = v;
  49.    }

编写一个测试程序,如下:

[java] view plaincopyprint?在CODE上查看代码片派生到我的代码片

  1. char data[] = {'a', 'b', 'c'};
  2.       String str1 = new String(data);
  3.       String str2 = new String(data,0,2);
  4.       char data1[] = {0x4E2D, 0x56FD};
  5.       String str3 = new String(data1);
  6.       System.out.println(str1);//abc
  7.       System.out.println(str2);//ab
  8.       System.out.println(str3);//中国

还有以byte[]数组为参数创建字符串的构造函数,其中最主要的两个如下:

[java] view plaincopyprint?在CODE上查看代码片派生到我的代码片

  1. public String(byte bytes[], int offset, int length) {
  2.        checkBounds(bytes, offset, length);
  3.        char[] v  = StringCoding.decode(bytes, offset, length);
  4.        this.offset = 0;
  5.        this.count = v.length;
  6.        this.value = v;
  7.    }
  8.    public String(byte bytes[], int offset, int length, String charsetName)
  9.        throws UnsupportedEncodingException
  10.    {
  11.        if (charsetName == null)
  12.            throw new NullPointerException("charsetName");
  13.        checkBounds(bytes, offset, length);
  14.        char[] v = StringCoding.decode(charsetName, bytes, offset, length);
  15.        this.offset = 0;
  16.        this.count = v.length;
  17.        this.value = v;
  18.    }
  19.    public String(byte bytes[], int offset, int length, Charset charset) {
  20.        if (charset == null)
  21.            throw new NullPointerException("charset");
  22.        checkBounds(bytes, offset, length);
  23.        char[] v = StringCoding.decode(charset, bytes, offset, length);
  24.        this.offset = 0;
  25.        this.count = v.length;
  26.        this.value = v;
  27.    }

编写测试程序如下:

[java] view plaincopyprint?在CODE上查看代码片派生到我的代码片

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

其他的一些使用byte数组创建字符串的构造函数其实最终都是调用如上两个构造函数。另外还提供了StringBuffer和StringBuilder转String字符串的构造函数,如下:

[java] view plaincopyprint?在CODE上查看代码片派生到我的代码片

  1. public String(StringBuffer buffer) {
  2.         synchronized(buffer) {
  3.             this.value = Arrays.copyOf(buffer.getValue(), buffer.length());
  4.         }
  5.     }
  6.     public String(StringBuilder builder) {
  7.         this.value = Arrays.copyOf(builder.getValue(), builder.length());
  8.     }

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

[java] view plaincopyprint?在CODE上查看代码片派生到我的代码片

  1. /**
  2.      * Returns the length of this string.
  3.      * The length is equal to the number of <a href="Character.html#unicode">Unicode
  4.      * code units in the string.
  5.      */
  6.     public int length() {
  7.         return value.length;
  8.     }

如上的方法是返回字符串的长度,但是需要注意的是:

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

 

[java] view plaincopyprint?在CODE上查看代码片派生到我的代码片

  1. public int codePointCount(int beginIndex, int endIndex) {
  2.        if (beginIndex < 0 || endIndex > value.length || beginIndex > endIndex) {
  3.            throw new IndexOutOfBoundsException();
  4.        }
  5.        return Character.codePointCountImpl(value, beginIndex, endIndex - beginIndex);
  6.    }
[java] view plaincopyprint?在CODE上查看代码片派生到我的代码片

  1. static int codePointCountImpl(char[] a, int offset, int count) {
  2.         int endIndex = offset + count;
  3.         int n = count;
  4.         for (int i = offset; i < endIndex; ) {
  5.             if (isHighSurrogate(a[i++]) && i < endIndex && isLowSurrogate(a[i])) {
  6.                 n--;
  7.                 i++;
  8.             }
  9.         }
  10.         return n;
  11.     }

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

 

 

[java] view plaincopyprint?在CODE上查看代码片派生到我的代码片

  1. public String substring(int beginIndex, int endIndex) {
  2.         if (beginIndex < 0) {
  3.             throw new StringIndexOutOfBoundsException(beginIndex);
  4.         }
  5.         if (endIndex > value.length) {
  6.             throw new StringIndexOutOfBoundsException(endIndex);
  7.         }
  8.         int subLen = endIndex - beginIndex;// 获取截取的长度
  9.         if (subLen < 0) {
  10.             throw new StringIndexOutOfBoundsException(subLen);
  11.         }
  12.         return ((beginIndex == 0) && (endIndex == value.length)) ? this
  13.                 : new String(value, beginIndex, subLen);
  14. }

可以看到,如果是截取部分的字符串,这时候会重新构造一个新的对象(new String())并返回。像replace, replaceAll, toLowerCase等方法处理都非常类似,这也就保证了字符串的不变性。

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

[java] view plaincopyprint?在CODE上查看代码片派生到我的代码片

  1. public native String intern();

这是一个本地的方法,当调用 intern 方法时,如果缓存池已经包含一个等于此 String 对象的字符串,则返回池中的字符串。否则,将此 String 对象添加到池中,并返回此 String 对象的引用。

[java] view plaincopyprint?在CODE上查看代码片派生到我的代码片

  1. String param = "abc";
  2.        String newStr = new String("abc");
  3.        String param2 = new String("abc");
  4.        newStr.intern();
  5.        param2 = param2.intern();    //param2指向intern返回的常量池中的引用
  6.        System.out.println(param == newStr);    //false
  7.        System.out.println(param == param2);    //true

可以看到,使用intern()方法后,字符串变量的值如果不存在缓冲区中将会被缓存


本文固定链接: http://www.devba.com/index.php/archives/4779.html | 开发吧

报歉!评论已关闭.