String常用方法解析

3 篇文章 0 订阅

String的常用方法解析

String的方法很多,这里只列出常用的几种方法进行查看解析,研究底层 jdk的实现和他的代码编写。

startsWith 和 endsWith

startsWith主要是为了判断该String a 对象是是 String b开头。
例如

String a = "ABCD";
String b = "AB";
System.out.println(a.startsWith(b));

返回的一定是一个true,startsWith 还有一个重写的方法,加了一个int 参数,这个代表他是从下标多少开始进行查找判断的。
endsWith和startsWith恰恰相反,他是结束的字符是否匹配,他没有重写方法,通过查看底层源码我们可以知道startsWith(String prefix) 和 endsWith(String suffix) 这两个方法都调用了一个方法,就是startsWith重写的方法startsWith(String prefix, int toffset)。

public boolean startsWith(String prefix) {
   return startsWith(prefix, 0);
}
public boolean endsWith(String suffix) {
   return startsWith(suffix, value.length - suffix.value.length);
}

为什么他们最终都调用了startsWith(String prefix, int toffset)??
startsWith我们可以理解,代码重用,而endsWith我们也可以想象的到,我们只需要判断他是否是这个结尾,也就是说我们只需要将偏移量设置正确,只留下字符串a的最后几位即可(这几位是字符串b的长度),这样写,可以代码复用,减少代码的冗余。我们可以在看看startsWith(String prefix, int toffset)是怎么写的

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;
    // 判断偏移量是否低于0或者大于value的长度
    if ((toffset < 0) || (toffset > value.length - pc)) {
        return false;
    }
    while (--pc >= 0) {
        if (ta[to++] != pa[po++]) {
            return false;
        }
    }
    return true;
}

这是一个很简单的算法实现。判断字符串a是否以字符串b开始或结束。需要掌握。

charAt

这个方法是获取指定下标的字符,很简单的一个方法,但是我之前一篇博客提到了一个UNICODE。如果这个UNICODE需要获取到怎么获取?String里面提供了这几个方法
codePointAt
codePointBefore
codePointCount
offsetByCodePoints
因为平时使用了不多就不多复述了。

getChars

这个方法相信有些同学会有些陌生。首先说下这个方法的作用,是将当前字符串复制到指定的字符数组中。他有两个实现

// 这个实现主要是String的其他方法调用,String的实例是调用不了的。
void getChars(char dst[], int dstBegin) {
    System.arraycopy(value, 0, dst, dstBegin, value.length);
}
// 主要是这个方法。
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);
}

我们可以看见他最终调用了什么—System.arraycopy 这个方法,而这个方法有什么特点呢?他也是一个本地方法,也就说native方法,这个方法是由JVM来实现的,也就是说这个方法的效率很高。所以懂了吧,String只是对其进行了封装判断输入,真正执行的还是System.arraycopy 。

getBytes

其实这个方法也没什么好说的,就是获取这个方法的字节数组,底层主要还是jdk的编码,想了解请看java.nio.charset.CharsetEncoder类。

equals

这个方法也是常用的方法,在java里面 == 不只是比较值,还要比较他们的内存。也就是说我们如果要比较两个字符串是否相等,就不可避免的要使用这个函数

public boolean equals(Object anObject) {
    if (this == anObject) {
        return true;
    }
    // 判断这个对象是否是属于String实例
    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;
}

equalsIgnoreCase 和 regionMatches

忽略大小写判断,底层实现也很有意思,看源码

	public boolean equalsIgnoreCase(String anotherString) {
	// 首先判断了这个String对象是否是本身,
        return (this == anotherString) ? true
        // 不是的话进行这一步判断是否为null,并且长度是否相同
                : (anotherString != null)
                && (anotherString.value.length == value.length)
                // 前面判断合理之后主要就是调用了这个方法
                && regionMatches(true, 0, anotherString, 0, value.length);
    }

regionMatches这个方法功能很强大,主要有五个参数,
1.boolean类型,true代表忽略大小写,false反之
2.本身这个字符串的偏移量
3.需要比较的字符串
4.比较字符串的偏移量
5.比较的长度

	public boolean regionMatches(boolean ignoreCase, int toffset,
            String other, int ooffset, int len) {
        char ta[] = value;
        int to = toffset;
        char pa[] = other.value;
        int po = ooffset;
        // 判断ooffset , toffset 或者len 是否有效 
        if ((ooffset < 0) || (toffset < 0)
                || (toffset > (long)value.length - len)
                || (ooffset > (long)other.value.length - len)) {
            return false;
        }
        while (len-- > 0) {
            char c1 = ta[to++];
            char c2 = pa[po++];
            // 判断是否相等,相等进入下一个字符判断,不相等判断是不是要忽略大小写
            if (c1 == c2) {
                continue;
            }
            if (ignoreCase) {
                // 如果字符不匹配我们就需要判断是否忽略大小写
                // 首先将字符串转换为大小,如果结果匹配,就应该进行下一个字符的匹配
                char u1 = Character.toUpperCase(c1);
                char u2 = Character.toUpperCase(c2);
                if (u1 == u2) {
                    continue;
                }
                // 不过, 转换为大小格鲁吉亚字母可能无法正常的判断,
                // 其中有关于大小写转换的奇怪规则
                // 所以我们还需要最后一次进行判断后退出
                if (Character.toLowerCase(u1) == Character.toLowerCase(u2)) {
                    continue;
                }
            }
            return false;
        }
        return true;
    }

contentEquals

这个方法有两个实现分别是StringBuffer参数类型的和CharSequence类型,
其中StringBuffer类型的contentEquals主要还是调用了CharSequence类型的contentEquals方法。

	public boolean contentEquals(StringBuffer sb) {
        return contentEquals((CharSequence)sb);
    }
    public boolean contentEquals(CharSequence cs) {
        // 参数是 StringBuffer, StringBuilder类型
        if (cs instanceof AbstractStringBuilder) {
            if (cs instanceof StringBuffer) {
                synchronized(cs) {
                   return nonSyncContentEquals((AbstractStringBuilder)cs);
                }
            } else {
                return nonSyncContentEquals((AbstractStringBuilder)cs);
            }
        }
        // Argument is a String
        if (cs instanceof String) {
            return equals(cs);
        }
        // 参数是通用CharSequence
        char v1[] = value;
        int n = v1.length;
        if (n != cs.length()) {
            return false;
        }
        for (int i = 0; i < n; i++) {
            if (v1[i] != cs.charAt(i)) {
                return false;
            }
        }
        return true;
    }
    private boolean nonSyncContentEquals(AbstractStringBuilder sb) {
        char v1[] = value;
        char v2[] = sb.getValue();
        int n = v1.length;
        if (n != sb.length()) {
            return false;
        }
        for (int i = 0; i < n; i++) {
            if (v1[i] != v2[i]) {
                return false;
            }
        }
        return true;
    }

可以这么说一般情况下我们是使用equals方法就行了,但是如果遇见其他的字符串类型匹配就需要调用这个方法。StringBuffer 就需要进行同步操作了

compareTo

String 继承了Comparable接口,这个接口对实现它的每个类的对象强加了总体排序。此排序称为类的自然排序,而该类的compareTo方法被称为其自然比较方法,简单来说,实现这个接口,就是写一个比较方法。
将此对象与指定对象进行比较。返回负整数,零或正整数,因为此对象小于,等于或大于指定的对象。

	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;
    }

indexOf

获取当前字符或者UNICODE码代表的字符位置。
他有这些重载的方法
public int indexOf(int ch)
public int indexOf(int ch, int fromIndex)
public int indexOf(String str)
public int indexOf(String str, int fromIndex)
我们用的更多应该是第三种,但是我还是提一下其他几种

	public int indexOf(int ch) {
	// 调用了重载另一个方法indexOf(int ch, int fromIndex) 
        return indexOf(ch, 0);
    }
    public int indexOf(int ch, int fromIndex) {
        final int max = value.length;
        if (fromIndex < 0) {
            fromIndex = 0;
        } else if (fromIndex >= max) {
            // 判断起始位置是否大于字符串长度.
            return -1;
        }
		// 主要是对这个ch进行判断,判断他是否能够被char类型所容纳,
        // 主要还是UNICODE的标准已经超出了char所能容纳的大小
        if (ch < Character.MIN_SUPPLEMENTARY_CODE_POINT) {
            final char[] value = this.value;
            for (int i = fromIndex; i < max; i++) {
                if (value[i] == ch) {
                    return i;
                }
            }
            return -1;
        } else {
        // 这里调用的一个私有方法,就是因为char类型已经不能容纳
            return indexOfSupplementary(ch, fromIndex);
        }
    }
    private int indexOfSupplementary(int ch, int fromIndex) {
    // 对这个ch进行校验,查看是否是UNICODE的代码点
        if (Character.isValidCodePoint(ch)) {
            final char[] value = this.value;
            // 高位
            final char hi = Character.highSurrogate(ch);
            // 低位
            final char lo = Character.lowSurrogate(ch);
            final int max = value.length - 1;
            for (int i = fromIndex; i < max; i++) {
                if (value[i] == hi && value[i + 1] == lo) {
                    return i;
                }
            }
        }
        return -1;
    }

然后是另外两个String的indexOf方法

	public int indexOf(String str) {
	// 调用了重载另一个方法indexOf(String str, int fromIndex) 
        return indexOf(str, 0);
    }
    public int indexOf(String str, int fromIndex) {
    // 使用了一个静态方法
        return indexOf(value, 0, value.length,
                str.value, 0, str.value.length, fromIndex);
    }
    // 六个参数的意义 
    // 1、源数据,就是本事的value值
    // 2、源数据偏移量
    // 3、源数据检索的字符个数
    // 4、子字符数组
    // 5、子字符数组偏移量
    // 6、子字符数组检索个数
    // 7、开始搜索的索引
    static int indexOf(char[] source, int sourceOffset, int sourceCount,
            char[] target, int targetOffset, int targetCount,
            int fromIndex) {
        if (fromIndex >= sourceCount) {
            return (targetCount == 0 ? sourceCount : -1);
        }
        if (fromIndex < 0) {
            fromIndex = 0;
        }
        if (targetCount == 0) {
            return fromIndex;
        }

        char first = target[targetOffset];
        int max = sourceOffset + (sourceCount - targetCount);

        for (int i = sourceOffset + fromIndex; i <= max; i++) {
            /// 查找第一个字符
            if (source[i] != first) {
                while (++i <= max && source[i] != first);
            }

            // 找到第一个字符后查找后面的字符
            if (i <= max) {
                int j = i + 1;
                int end = j + targetCount - 1;
                for (int k = targetOffset + 1; j < end && source[j]
                        == target[k]; j++, k++);

                if (j == end) {
                    // 没有找到
                    return i - sourceOffset;
                }
            }
        }
        return -1;
    }

为什么要设计成这样,主要是因为字符串类型,在String类会多次用到这样的操作,所以封装成一个静态的方法供其他的类进行调用,比如AbstractStringBuilder就使用了这个静态的indexOf的方法。String和StringBuffer共享的代码以进行搜索。 source是正在搜索的字符数组,target是正在搜索的字符串

lastIndexOf

这个和index大致相同,只是搜索方向不一致,这个从末尾搜索。就不多复述

substring

这个方法更需要理解java的设计了,通过构造函数实现了这个功能。

	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);
    }

也就是说每次截取字符串就代表new了一个新的对象。只需要对参数进行校验即可。

concat

这个方法就有意思了,连接两个字符串。String是不能变的字符常量,怎么才能让他变的高效?
jdk的设计很巧妙,他调用了两次System.arraycopy方法进行复制来操作。也就是说他只通过调用本地方法来完成这个功能,JVM效率是很高的,你可以自己手写一个连接方法进行测试,不调用System.arraycopy的情况下。

	public String concat(String str) {
        int otherLen = str.length();
        if (otherLen == 0) {
            return this;
        }
        int len = value.length;
        // Arrays.copyOf底层也是System.arraycopy
        char buf[] = Arrays.copyOf(value, len + otherLen);
        // 这个方法前面讲过,里面调用了System.arraycopy
        str.getChars(buf, len);
        return new String(buf, true);
    }

replace replaceFirst replaceAll

public String replace(char oldChar, char newChar) 一般就是将一个字符替换成另外一个字符,他会生成一个副本,将他替换然后new一个新的String对象,返回。
public String replace(CharSequence target, CharSequence replacement) 本质上与上一个方法是一致的,底层实现不一样,参数不一样,用的少。
public String replaceFirst(String regex, String replacement) 他使用的正则的方式匹配对应字符,进行替换,不过他只替换第一个匹配到的字符
public String replaceAll(String regex, String replacement) 他使用的正则的方式匹配对应字符,进行替换,他替换的是所有匹配到的字符。
除了第一个replace方法之外,其余replace方法都是要了 Pattern 类中的方法进行正则匹配,然后通过Matcher进行替换,主要实现在Matcher 类中。

split

这个方法需要注意的是,他的分隔不是单纯的以字符串分隔,他还可以通过正则来进行切分。

join

这是个静态方法,也就是说我们只需要通过String类来调用,而不是实例,也就是说我们可以将它当成一个工具类的方法,这个方法的作用就是连接字符串,第一个参数是一个CharSequence类型的字符串,我们需要将第二个参数的值加在这后面,主要第二个参数有两种情况
1.他是一个可变字符序列
2.他是一个Iterable的迭代器。
要合理利用这两种,底层实现是一样的,只有参数不一样。

toLowerCase 和 toUpperCase

这两个方法就是转大写和小写,但是源码有个点你可以看看,平时肯定没接触到

	scan: {
            for (firstLower = 0 ; firstLower < len; ) {
                int c = (int)value[firstLower];
                int srcCount;
                if ((c >= Character.MIN_HIGH_SURROGATE)
                        && (c <= Character.MAX_HIGH_SURROGATE)) {
                    c = codePointAt(firstLower);
                    srcCount = Character.charCount(c);
                } else {
                    srcCount = 1;
                }
                int upperCaseChar = Character.toUpperCaseEx(c);
                if ((upperCaseChar == Character.ERROR)
                        || (c != upperCaseChar)) {
                    break scan;
                }
                firstLower += srcCount;
            }
            return this;
        }

提问这个 scan是什么??这段源码在toUpperCase 里。

trim

消除字符串两边的空格。

toCharArray

返回一个字符数组副本。

format

将字符串转换为指定格式

valueOf

这个有许多的重载方法,就是将这些数据类型转换为String类型,他们使用了什么,可以自行查看

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值