string(二)成员方法

1、长度

public int length() {
        return value.length;
    }

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

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

 public int codePointCount(int beginIndex, int endIndex) {
        if (beginIndex < 0 || endIndex > value.length || beginIndex > endIndex) {
            throw new IndexOutOfBoundsException();
        }
        return Character.codePointCountImpl(value, beginIndex, endIndex - beginIndex);
    }

static int codePointCountImpl(char[] a, int offset, int count) {
        int endIndex = offset + count;
        int n = count;
        for (int i = offset; i < endIndex; ) {
            if (isHighSurrogate(a[i++]) && i < endIndex && isLowSurrogate(a[i])) {
                n--;
                i++;
            }
        }
        return n;
    }

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

2、截取字符串字串

//最常用,用户自己给开始位置和结束位置
public String substring(int beginIndex, int endIndex) {
        if (beginIndex < 0) {
            throw new StringIndexOutOfBoundsException(beginIndex);
        }
        if (endIndex > value.length) {
            throw new StringIndexOutOfBoundsException(endIndex);
        }
        int subLen = endIndex - beginIndex;
        if (subLen < 0) {
            throw new StringIndexOutOfBoundsException(subLen);
        }
        return ((beginIndex == 0) && (endIndex == value.length)) ? this
                : new String(value, beginIndex, subLen);
    }
//用户只给开始位置,结束位置默认为最后
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);
    }

如果没变就返回本来的String,否则返回了一个新的字符串,串中的内容与原串中要截取的子字符串相同。不会出现JDK1.6版本的char[] value 数组被共享了。而截取字串的操作是通过偏移量offset和长度count来实现的。这样就会造成内存泄漏问题,例如需要多次截取大字符串中的很小一部分时,导致无法释放内存并且造成了内存的巨大浪费,最终程序运行会抛出异常:

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space

3、分割字符串

(1)用传统的split()

//第一个会调第二个
public String[] split(String regex) {
        return split(regex, 0);
    }
//第二个,limit为你期望的数组的长度,如果为0,则全部分割
public String[] split(String regex, int limit) {
        char ch = 0;
        //判断分隔符是否正确
        if (((regex.value.length == 1 &&        //不去要转义的
             ".$|()[{^?*+\\".indexOf(ch = regex.charAt(0)) == -1) ||
             (regex.length() == 2 &&
              regex.charAt(0) == '\\' &&        //需要用\\转义的".$|()[{^?*+\\"这几个符号都要转义
              (((ch = regex.charAt(1))-'0')|('9'-ch)) < 0 &&
              ((ch-'a')|('z'-ch)) < 0 &&
              ((ch-'A')|('Z'-ch)) < 0)) &&
            (ch < Character.MIN_HIGH_SURROGATE ||
             ch > Character.MAX_LOW_SURROGATE))
        {    //例子:aa,bb,cc,dd自己想想下怎么分
            int off = 0;    //当前分隔符的位置+1
            int next = 0;    //下一个分隔符的位置:indexOf(分隔符, 从指定位置开始)
            boolean limited = limit > 0;
            ArrayList<String> list = new ArrayList<>();
            while ((next = indexOf(ch, off)) != -1) {    //循环分割,加入到list列表中
                if (!limited || list.size() < limit - 1) {      //
                    list.add(substring(off, next));    //off开始位置和next结束位置
                    off = next + 1;
                } else {    //assert (list.size() == limit - 1);
                    list.add(substring(off, value.length));    //最后那个达不到期望的limit值,后面所有的为一个加载到list中,并打断
                    off = value.length;
                    break;
                }
            }
            // If no match was found, return this
            if (off == 0)
                return new String[]{this};

            // Add remaining segment
            if (!limited || list.size() < limit)
                list.add(substring(off, value.length));

            // Construct result
            int resultSize = list.size();
            if (limit == 0)
                while (resultSize > 0 && list.get(resultSize - 1).length() == 0)
                    resultSize--;
            String[] result = new String[resultSize];
            return list.subList(0, resultSize).toArray(result);    //将list转化为String[] result并返回
        }
        //除上面的外就返回正则表达式的分割
        return Pattern.compile(regex).split(this, limit);
    }
//上面函数调用的indexOf函数:从指定索引开始,向后搜索与分隔符ch一样的字符,并返回该位置
public int indexOf(int ch, int fromIndex) {
        final int max = value.length;
        if (fromIndex < 0) {             //索引小于0置为0,大于长度置为-1
            fromIndex = 0;
        } else if (fromIndex >= max) {
            return -1;
        }

        if (ch < Character.MIN_SUPPLEMENTARY_CODE_POINT) {
            final char[] value = this.value;
            for (int i = fromIndex; i < max; i++) {        //从指定索引开始,向后搜索与分隔符ch一样的字符,并返回该位置
                if (value[i] == ch) {
                    return i;
                }
            }
            return -1;
        } else {
            return indexOfSupplementary(ch, fromIndex);    //超过范围,就是增补的,就按增补的算,这个只需要知道即可
        }
    }

(2)StringTokenizer类实现分割,效率优于splite()方法

StringTokenizer strtk=new StringTokenizer(str,";");
while(strtk.hasMoreTokens()){
    strtk.nextToken();
}

StringTokenizer类在java.util中,有兴趣的可以阅读一下。

(3)虽然subString()会造成内存泄漏,但是由于使用了空间换时间的方法,所以查找的速度还是很快的。

//循环下面代码
int j=str.indexOf(';');
str.substring(0,j);
str=str.substring(j+1);

4、字符串的hashcode()

    public int hashCode() {
        int h = hash;
        //该类在定义hash变量是值为0,所以空字符串的value.length=0的,就不会进入if中,则h=hash值为0。
        if (h == 0 && value.length > 0) {
            char val[] = value;
             //这个就是String计算他的hashCode方法
            for (int i = 0; i < value.length; i++) {
                h = 31 * h + val[i];
            }
            hash = h;
        }
        return h;
    }

字符串的hashCode()计算的方法为:s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]

所以说,当字符串一样的时候,他们的hashCode方法是一样的。

5、字符串的equals()

    public boolean equals(Object anObject) {
        if (this == anObject) {//比较是不是同一个
            return true;
        }
        if (anObject instanceof String) {//看是不是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;
    }

比较两个字符串中字符的序列组成是否相等,也就是比较字符串中的内容。如果不覆写这个方法,默认继承的是Object中的equals()方法,代码如下:

    public boolean equals(Object obj) {
        return (this == obj);
    }

使用“==”符号来比较两个引用类型的值,则比较的是引用地址,对于String类型只比较字符串时不准确,所以一定要进行覆写。

6、缓存字符串

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()方法后,字符串变量的值如果不存在缓冲区中将会被缓存。

7、字符串比较大小

public int compareTo(String anotherString) {
        int len1 = value.length;
        int len2 = anotherString.value.length;
        int lim = Math.min(len1, len2);
        char v1[] = value;
        char v2[] = anotherString.value;

        int k = 0;
        while (k < lim) {
            char c1 = v1[k];
            char c2 = v2[k];
            if (c1 != c2) {
                return c1 - c2;
            }
            k++;
        }
        return len1 - len2;
    }

正数为大于,负数为小于,0为一样

8、搜索是否以什么开始,和以什么结尾

//第一种调用第二种
public boolean startsWith(String prefix) {
        return startsWith(prefix, 0);
    }
//prefix为以它开始,toffset为字符串从那个地方开始算起,在以prefix开始进行比较
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++]) {//pa为prefix串从po=0开始,ta为字符串,从to=toffset开始
                return false;
            }
        }
        return true;
    }
//以suffix结尾也是用第二个方法,只不过,是从总长度刚好减去suffix长度的地方开始比较
public boolean endsWith(String suffix) {
        return startsWith(suffix, value.length - suffix.value.length);
    }

9、从后往前查找,但是以Unicode编码进行查找的

//第一个调用第二个
public int lastIndexOf(int ch) {
        return lastIndexOf(ch, value.length - 1);
    }
//注意ch为Unicode编码不是char类型,而fromIndex为索引,表示意思是从那个地方开始倒着查找
public int lastIndexOf(int ch, int fromIndex) {
        if (ch < Character.MIN_SUPPLEMENTARY_CODE_POINT) {
            // handle most cases here (ch is a BMP code point or a
            // negative value (invalid code point))
            final char[] value = this.value;
            int i = Math.min(fromIndex, value.length - 1);
            for (; i >= 0; i--) {
                if (value[i] == ch) {
                    return i;
                }
            }
            return -1;
        } else {
            return lastIndexOfSupplementary(ch, fromIndex);//增补字符
        }
    }

后面还有函数,后续补充

猜你喜欢

转载自blog.csdn.net/xiao1_1bing/article/details/81165131