String相关学习

概念

String在java中是对char数组的延伸和封装,它主要有三部分:

  • char数组

  • 偏移量

  • String长度

char数组表示String的内容 他是String对象所表示字符串的超集,String的真是内容还需要由偏移量长度在这个char数组中进行定位和截取

特点

  • 不变性

    一个String对象一旦生成就不可改变,作用在于被多线程共享和访问时,省略同步和锁等待的时间,提高性能

  • 针对常量池的优化

    当两个String对象拥有相同的值得时候,他们只引用常量池中的同一个拷贝

  • 类final的定义

    String类作为final类不可以有任何子类,这是对系统安全性的保护,使用final定义 ,有助于虚拟机内联所有final方法,提高效率

subString()方法的内存泄漏

public String subString(int beginIndex,int endIndex)

这个方法在jdk1.6中存在严重的内存泄漏,查看源代码:

public String substring(int beginIndex, int endIndex) {
    if (beginIndex < 0) {
        throw new StringIndexOutOfBoundsException(beginIndex);
    }
    if (endIndex > count) {
        throw new StringIndexOutOfBoundsException(endIndex);
    }
    if (beginIndex > endIndex) {
        throw new StringIndexOutOfBoundsException(endIndex - beginIndex);
    }
    return ((beginIndex == 0) && (endIndex == count)) ? this :
        new String(offset + beginIndex, endIndex - beginIndex, value);
    }

这个方法的最后返回了一个包私有的构造函数

// Package private constructor which shares value array for speed.
    String(int offset, int count, char value[]) {
    this.value = value;
    this.offset = offset;
    this.count = count;
    }

这种把原来字符串中char数组完全复制到被截取字符创中的做法,虽然可以高效快速实现字符串共享,但是当原来字符串很大,我们需要截取的字符串很小时,子字符串虽然看上去只有那么几个,但是他包含了原来字符串所有的内容,极大的浪费了空间,这就是典型的以空间换时间。

通过一个例子可以亲自试验一下:

public class SubStringTest {

    static class HugeStr{

        private String str = new String(new char[100000]);

        public String getSubString(int begin,int end){
            return str.substring(begin, end);
        }
    }

    static class ImprovedHugeStr{
        private String str = new String(new char[100000]);

        public String getSubString(int begin,int end){
            return new String(str.substring(begin, end));
        }
    }

    public static void main(String[] args) {
        List<String> handle = new ArrayList<String>();

        for(int i = 0;i < 100000;i ++){
//          HugeStr h = new HugeStr();
            ImprovedHugeStr h = new ImprovedHugeStr();
            handle.add(h.getSubString(1, 5));
        }
        System.out.println("end");
    }
}

使用HugeStr时会看到内存溢出是由于调用了

// Package private constructor which shares value array for speed.
    String(int offset, int count, char value[]) {

这个构造函数,但实际使用时,应用程序用不到。

使用ImprovedHugeStr没有内存溢出,他没有使用内存泄漏的String构造函数重新生成对象,使得由subString()返回的存在内存泄漏的对象失去了强引用,从而被垃圾回收器回收掉。

我们通过查看调用关系可以看到所有使用了这个构造函数的方法,他们都有可能造成内存泄漏:

这里写图片描述

字符串的分割

  • String类的split()方法
  • StringTokenizer类进行分割
  • 自定义更优化的分割方法

    对于最原始的split方法

public String[] split(String regex)

我们可以通过自定义正则表达式来规定分割的方法。为了比较这几种字符串分割的优劣,首先,我们先来 构造一个字符串:

String orgStr = null;
StringBuffer sb = new StringBuffer();

for(int i = 0;i < 1000; i ++){
    sb.append(i);
    sb.append(";");
}
orgStr = sb.toString();

对于split做一万次分割:

long current = System.currentTimeMillis();
for(int i = 0;i < 10000; i ++){
    orgStr.split(";");
}
System.out.println(System.currentTimeMillis() - current);

用时:510ms

使用StringTokenizer类:

long current = System.currentTimeMillis();
StringTokenizer st = new StringTokenizer(orgStr,";");
for(int i = 0;i < 10000;i++){
    while(st.hasMoreTokens()){
        st.nextToken();
    }
    st = new StringTokenizer(orgStr,";");
}
System.out.println(System.currentTimeMillis() - current);

耗時:460ms
在StringTokenizer類中,通過hasMoreTokens()可以判斷是否還有子串需要分割,nextToken()能夠獲取下一個分割的子字符串,我們及時構造了一萬次StringTokenizer對象,仍然比split方法要快。

第三種,需要用到兩個String類的方法indexOf()和subString(),下面是我們自定義的方法:

long current = System.currentTimeMillis();
String tmp = orgStr;
for(int i = 0;i < 10000;i ++){
    while(true){
        String splitStr = null;
        int j = tmp.indexOf(';');//找到分隔符的位置
        if( j < 0){  //沒有分隔符存在
            break;
        }
        splitStr = tmp.substring(0,j);//找到分隔符,截取子字符串
        tmp = tmp.substring(j+1);//剩下待處理字符串
    }
    tmp = orgStr;
}
System.out.println(System.currentTimeMillis() - current);

這種方法耗時才172ms,明顯更快,綜上,split性能最差,能有StringTokenizer就不要用split,當時間性能很重要的時候,可以選用第三種。

高效的charAt()

这个方法也具有很高的效率,他采用了数组的随机地址访问:

public char charAt(int index) {
   if ((index < 0) || (index >= count)) {
       throw new StringIndexOutOfBoundsException(index);
   }
   return value[index + offset];
}

它能够根据索引直接找到对应的字符。
我们经常会判断一个字符串的开头或者结尾是一个什么样的字符串,通常的做法是使用以下两个方法:

public boolean startsWith(String prefix);
public boolean endsWith(String suffix);

即使他们是JAVA内置的函数,其效率也没有charAt方法效率高,以下是我们自己封装的一个判断方法

public static boolean endWith(String str){
    int len = str.length();
    if(str.charAt(len - 1) == 'a'
            && str.charAt(len - 2) == 'b' 
            && str.charAt(len - 3) == 'c'){
        return true;
    }
    return false;
}

public static boolean startWith(String str){
    if(str.charAt(0) == 'a'
            && str.charAt(1) == 'b'
            && str.charAt(2) == 'c'){
        return true;
    }
  return false;
}

适应这两个方法时,耗时7ms,使用startsWith和endsWith 耗时16ms,所以当性能成为影响系统的主要因素的时候,最好选用charAt。

StringBuffer和StringBuilder的异同

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值