java.lang.String
- 1.String类的结构图
- 2.String 类详细介绍
- 2.1 CaseInsensitiveComparator
- 2.2 String构造函数
- 2.2.1 String()
- 2.2.2 String(String original)
- 2.2.3 String(char value[])
- 2.2.4 String(char value[], int offset, int count)
- 2.2.5 String(int[] codePoints, int offset, int count)
- 2.2.6 String(byte ascii[], int hibyte, int offset, int count)和String(byte ascii[], int hibyte)
- 2.2.7 String(byte bytes[], int offset, int length, String charsetName)和String(byte bytes[], int offset, int length, Charset charset)
- 2.2.8 String(byte bytes[], String charsetName)和public String(byte bytes[], Charset charset)
- 2.2.9 String(byte bytes[], int offset, int length)
- 2.2.10 String(byte bytes[])
- 2.2.11 String(StringBuffer buffer)和String(StringBuilder builder)
- 2.3 checkBounds(byte[] bytes, int offset, int length)
- 2.4 length() *
- 2.5 isEmpty()
- 2.6 代码点
- 2.7 getChars()
- 2.8 getBytes()
- 2.9 equals(Object anObject) *
- 2.10 contentEquals(StringBuffer sb)
- 2.11 equalsIgnoreCase(String anotherString)
- 2.12 compareTo(String anotherString) *
- 2.13 regionMatches()
- 2.14 startsWith相关方法
- 2.15 hashCode()
- 2.16 indexOf()
开始整理java.lang.String的源码,String的类在实际开发中使用非常的普遍,基本每一位java程序员,都有使用过String类以及里面的各种方法。
1.String类的结构图
String类的构造方法相当之多,方法也很多,慢慢全都整理出来,为了便于记忆,会把重要的后面加上*以表示重要。
第一反应,我去,居然这么多方法,加油。
2.String 类详细介绍
2.1 CaseInsensitiveComparator
先看一下这部分的源码吧。忽略大小写比较器
private static class CaseInsensitiveComparator
implements Comparator<String>, java.io.Serializable {
// use serialVersionUID from JDK 1.2.2 for interoperability
private static final long serialVersionUID = 8575799808933029326L;
public int compare(String s1, String s2) {
int n1 = s1.length();
int n2 = s2.length();
int min = Math.min(n1, n2);
for (int i = 0; i < min; i++) {
char c1 = s1.charAt(i);
char c2 = s2.charAt(i);
if (c1 != c2) {
c1 = Character.toUpperCase(c1);
c2 = Character.toUpperCase(c2);
if (c1 != c2) {
c1 = Character.toLowerCase(c1);
c2 = Character.toLowerCase(c2);
if (c1 != c2) {
// No overflow because of numeric promotion
return c1 - c2;
}
}
}
}
return n1 - n2;
}
/** Replaces the de-serialized object. */
private Object readResolve() { return CASE_INSENSITIVE_ORDER; }
}
从源码上看CaseInsensitiveComparator这个方法重写了Comparator接口中的compare方法,compare在Comparator并没有实现,所以在String类中实现了这个排序的方法,下面来分析这部分实现过程。
public int compare(String s1, String s2) {
// 获取传入字符串s1的长度
int n1 = s1.length();
// 获取传入字符串s2的长度
int n2 = s2.length();
// 获取最小的长度
int min = Math.min(n1, n2);
// 用最小的长度作为最大值,进行循环
for (int i = 0; i < min; i++) {
// 返回s1的每一个字符的ASCII码
char c1 = s1.charAt(i);
// 返回s2的每一个字符的ASCII码
char c2 = s2.charAt(i);
// 判断字符是否相等
if (c1 != c2) {
// 将c1字符变成大写的
c1 = Character.toUpperCase(c1);
// 将c2字符变成大写的
c2 = Character.toUpperCase(c2);
// 比较是否相等
if (c1 != c2) {
// 将c1字符变成小写的
c1 = Character.toLowerCase(c1);
// 将c2字符变成小写的
c2 = Character.toLowerCase(c2);
// 比较是否相当
if (c1 != c2) {
// No overflow because of numeric promotion
return c1 - c2;
}
}
}
}
return n1 - n2;
}
这里面有一个小小的讨论点,就算是部分大小写的比较是否相当,那为什么变成大写之后比较不同仍然要再一次变成小写对比一次呢?
有些Unicode字符的小写不同,但大写形式相同。例如,希腊字母Sigma-它有两个小写形式(σ和只在单词末尾使用),但只有一个大写形式(Σ)。java为了适应所有的语言,所以会进行两次比较。
2.2 String构造函数
2.2.1 String()
无参构造方法,该构造方法会创建空的字符序列。
看一下源码
public String() {
this.value = "".value;
}
这个没啥好解释的,直接来个例子一看就懂。
String str = new String();
创建了一个空的字符串
2.2.2 String(String original)
参数为字符串类型,这个方法一般使用的也不是很多
看一下源码
public String(String original) {
this.value = original.value;
this.hash = original.hash;
}
这个方法除了初始化了String类中的value,同时获取了这个String的hashCode
写一个简单的应用例子
String str = new String("str");
创建一个"str"的字符串
2.2.3 String(char value[])
参数为 char 数组类型
源码:
public String(char value[]) {
this.value = Arrays.copyOf(value, value.length);
}
使用字符串数组来创建String时,会使用Arrays.copyOf方法,将原有的字符串数组中的内容逐一复制到String的字符串数组中。
写一个小例子
char[] ch = {65, 66};
String c = new String(ch);
System.out.println(c);
输出结果是:
AB
2.2.4 String(char value[], int offset, int count)
参数还是 char 数组类型,只不过规定了开始的位置和获取多少位数
源码:
public String(char value[], int offset, int count) {
if (offset < 0) {
throw new StringIndexOutOfBoundsException(offset);
}
if (count <= 0) {
if (count < 0) {
throw new StringIndexOutOfBoundsException(count);
}
if (offset <= value.length) {
this.value = "".value;
return;
}
}
// Note: offset or count might be near -1>>>1.
if (offset > value.length - count) {
throw new StringIndexOutOfBoundsException(offset + count);
}
this.value = Arrays.copyOfRange(value, offset, offset+count);
}
当开始位置小于9,获取位数小于0或者开始位置大于字符串长度-获取长度时,都会抛出StringIndexOutOfBoundsException异常。之后会调用Arrays.copyOfRange(value, offset, offset+count);方法,来将范围内的字符串的内容复制或到String的字符串中。
写个小例子:
char[] ch = {65, 66, 67, 68, 69, 70, 71, 72, 73};
String b = new String(ch);
System.out.println(b);
String c = new String(ch, 2, 2);
System.out.println(c);
输出结果为:
ABCDEFGHI
CD
感觉这个方法的应用真的非常小,这个方法以前居然从来没有注意过。
2.2.5 String(int[] codePoints, int offset, int count)
参数变成了int数组类型,只不过规定了开始的位置和获取多少位数
源码:
/**
* Allocates a new {@code String} that contains characters from a subarray
* of the <a href="Character.html#unicode">Unicode code point</a> array
* argument. The {@code offset} argument is the index of the first code
* point of the subarray and the {@code count} argument specifies the
* length of the subarray. The contents of the subarray are converted to
* {@code char}s; subsequent modification of the {@code int} array does not
* affect the newly created string.
*
* @param codePoints
* Array that is the source of Unicode code points
*
* @param offset
* The initial offset
*
* @param count
* The length
*
* @throws IllegalArgumentException
* If any invalid Unicode code point is found in {@code
* codePoints}
*
* @throws IndexOutOfBoundsException
* If the {@code offset} and {@code count} arguments index
* characters outside the bounds of the {@code codePoints} array
*
* @since 1.5
*/
public String(int[] codePoints, int offset, int count) {
if (offset < 0) {
throw new StringIndexOutOfBoundsException(offset);
}
if (count <= 0) {
if (count < 0) {
throw new StringIndexOutOfBoundsException(count);
}
if (offset <= codePoints.length) {
this.value = "".value;
return;
}
}
// 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;
}
看源码可以知道,这个方法是java1.5加进去的,忽略的看一下,发现这个方法和String(char value[], int offset, int count)的功能好像是一样的,那不一样体现在哪里呢?
首先我们来看两个方法的具体内容
1.Character.isBmpCodePoint(codePoint)
public static boolean isBmpCodePoint(int codePoint) {
return codePoint >>> 16 == 0;
// Optimized form of:
// codePoint >= MIN_VALUE && codePoint <= MAX_VALUE
// We consistently use logical shift (>>>) to facilitate
// additional runtime optimizations.
}
传入的数字按位右移16位,这代表了什么?
首先我们知道char是16bits的,代码点在U+0000 — U+FFFF之内到是可以用一个char完整的表示出一个字符。但代码点在U+FFFF之外的,一个char无论如何无法表示一个完整字符。这样用char类型来获取字符串中的那些代码点在U+FFFF之外的字符就会出现问题。
而java为了解决这样的问题,就出现了Unicode编码字符集的增补字符,而这些增补字符的处理逻辑与原来的不同,所以在用int[]传入的时候,要把符合U+0000 — U+FFFF之间的字符按照char来处理,其他的则按照新的方式来处理。
2.Character.isValidCodePoint(codePoint)
public static boolean isValidCodePoint(int codePoint) {
// Optimized form of:
// codePoint >= MIN_CODE_POINT && codePoint <= MAX_CODE_POINT
int plane = codePoint >>> 16;
return plane < ((MAX_CODE_POINT + 1) >>> 16);
}
所以这段代码则是判断传入的数字是否是增补码,就是在U+10000—U+10FFFF之间的字符。
看到这里基本就理解了String(int[] codePoints, int offset, int count)与String(char value[], int offset, int count)功能相同,只不过是增加了一些Unicode编码的范围。
2.2.6 String(byte ascii[], int hibyte, int offset, int count)和String(byte ascii[], int hibyte)
这两个方法目前已经过时了,暂时就不去学习了。
2.2.7 String(byte bytes[], int offset, int length, String charsetName)和String(byte bytes[], int offset, int length, Charset charset)
参数变成了byte数组类型,还是规定了开始的位置和获取多少位数,同时规定编码格式
源码:
public String(byte bytes[], int offset, int length, String charsetName)
throws UnsupportedEncodingException {
if (charsetName == null)
throw new NullPointerException("charsetName");
checkBounds(bytes, offset, length);
this.value = StringCoding.decode(charsetName, bytes, offset, length);
}
还有StringCoding.decode(String charsetName, byte[] ba, int off, int len)的源码
static char[] decode(String charsetName, byte[] ba, int off, int len)
throws UnsupportedEncodingException
{
StringDecoder sd = deref(decoder);
String csn = (charsetName == null) ? "ISO-8859-1" : charsetName;
if ((sd == null) || !(csn.equals(sd.requestedCharsetName())
|| csn.equals(sd.charsetName()))) {
sd = null;
try {
Charset cs = lookupCharset(csn);
if (cs != null)
sd = new StringDecoder(cs, csn);
} catch (IllegalCharsetNameException x) {}
if (sd == null)
throw new UnsupportedEncodingException(csn);
set(decoder, sd);
}
return sd.decode(ba, off, len);
}
我们可以看到传入的参数根据指定的编码格式进行解析,具体的解析方式将会在StringCoding这个类中具体去学习。
这里有一个很有意思的小现象,从源码中我们可以知道charsetName这个参数是不能为null的,如果为null则会抛出异常NullPointerException,但是在decode的源码中,charsetName如果为null则会被赋予一个缺省值"ISO-8859-1",这样看上去貌似在String(byte bytes[], int offset, int length, String charsetName)中去做是否为null的判断有些多余了。
String(byte bytes[], int offset, int length, Charset charset)方法则是直接传入了字符集,规则是一样的。
2.2.8 String(byte bytes[], String charsetName)和public String(byte bytes[], Charset charset)
源码:
public String(byte bytes[], String charsetName)
throws UnsupportedEncodingException {
this(bytes, 0, bytes.length, charsetName);
}
public String(byte bytes[], Charset charset) {
this(bytes, 0, bytes.length, charset);
}
就是调用了2.2.7的方法,缺省了截位,获取全部的内容。
2.2.9 String(byte bytes[], int offset, int length)
源码:
public String(byte bytes[], int offset, int length) {
checkBounds(bytes, offset, length);
this.value = StringCoding.decode(bytes, offset, length);
}
此方法,调用了2.2.7中的缺省了编码格式,默认会使用"ISO-8859-1"进行编码。
2.2.10 String(byte bytes[])
源码:
public String(byte bytes[]) {
this(bytes, 0, bytes.length);
}
参照2.2.9
2.2.11 String(StringBuffer buffer)和String(StringBuilder builder)
参数为StringBuffer 或 StringBuilder
源码:
public String(StringBuffer buffer) {
synchronized(buffer) {
this.value = Arrays.copyOf(buffer.getValue(), buffer.length());
}
}
public String(StringBuilder builder) {
this.value = Arrays.copyOf(builder.getValue(), builder.length());
}
感觉这个真的是很少用到了
2.3 checkBounds(byte[] bytes, int offset, int length)
这个方法有一个好听的名字叫做边界检查函数
源码:
private static void checkBounds(byte[] bytes, int offset, int length) {
if (length < 0)
throw new StringIndexOutOfBoundsException(length);
if (offset < 0)
throw new StringIndexOutOfBoundsException(offset);
if (offset > bytes.length - length)
throw new StringIndexOutOfBoundsException(offset + length);
}
代码非常的简洁,出现数组越界就会报错,然而有点尴尬,我接受的代码编程的规范思想是,能不抛出异常的情况,就不抛出异常,所以一般都会去判断字符串是不是可以截位才会去截位,对于这样的写法一般是不允许的,不过看着这个代码,感觉这麽写再捕获一下,逼格好高的啊。
2.4 length() *
返回一个字符串的长度
源码:
public int length() {
return value.length;
}
源码很好理解,也经常用到
2.5 isEmpty()
判读字符串长度是否为0
源码:
public boolean isEmpty() {
return value.length == 0;
}
源码中可以看出来,如果这个字符串是null的话,会抛出NullPointerException,只有字符串为""时才会返回ture。
此方法一般不经常使用,因为在StringUnit中重写了isEmpty(),这时的方法将支持null的判断,在代码中使用更加的灵活。
2.6 代码点
引入这个概念的原因是因为在早期java使用的时候,char采用UCS-2编码是一种淘汰的UTF-16编码,最多65536种形态,而现如今unicode拥有11万字符的需求,java只好对后来新增的unicode字符用2个char拼出1个unicode字符。这样就会导致String中char的数量不等于unicode字符的数量。所以需要引入代码点的概念,在精确统计字符数量等问题。
2.6.1 charAt(int index)
用于返回指定索引处的字符。索引范围为从 0 到 length() - 1
源码:
public char charAt(int index) {
if ((index < 0) || (index >= value.length)) {
throw new StringIndexOutOfBoundsException(index);
}
return value[index];
}
如果索引范围为超过 0 到 length() - 1,就会抛出异常StringIndexOutOfBoundsException
2.6.2 codePointAt(int index)
用于返回指定索引处的字符的ACSII码。索引范围为从 0 到 length() - 1
源码:
public int codePointAt(int index) {
if ((index < 0) || (index >= value.length)) {
throw new StringIndexOutOfBoundsException(index);
}
return Character.codePointAtImpl(value, index, value.length);
}
如果索引范围为超过 0 到 length() - 1,就会抛出异常StringIndexOutOfBoundsException
2.6.3 codePointBefore(int index)
用于返回指定索引处前一个的字符的ACSII码。索引范围为从 1 到 length()
源码:
public int codePointBefore(int index) {
int i = index - 1;
if ((i < 0) || (i >= value.length)) {
throw new StringIndexOutOfBoundsException(index);
}
return Character.codePointBeforeImpl(value, index, 0);
}
如果索引范围为超过 1 到 length(),就会抛出异常StringIndexOutOfBoundsException
2.6.4 codePointCount(int beginIndex, int endIndex)
用于返回指定索引处的字符长度。规定开始字符索引和结束字符索引
beginIndex–开始索引位置
endIndex–结束索引位置
源码:
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);
}
其实这个用法和length一样的,不同的就是这个能识别一些特殊的字符,也就是后来的unicode字符。
2.6.5 offsetByCodePoints(int index, int codePointOffset)
用于返回指定索引处的字符长度。规定开始字符索引和检索长度
index–开始索引位置
codePointOffset–获取字符长度
源码:
public int offsetByCodePoints(int index, int codePointOffset) {
if (index < 0 || index > value.length) {
throw new IndexOutOfBoundsException();
}
return Character.offsetByCodePointsImpl(value, 0, value.length,
index, codePointOffset);
}
对于编码在实际使用中,特殊的字符并没有经常遇到,所以对于这样的编码暂时不做深入的研究,等学习到Character类的时候,在做探讨。
2.7 getChars()
getChars() 方法将字符从字符串复制到目标字符数组,分为两种方法,分别是getChars(int srcBegin, int srcEnd, char dst[], int dstBegin)和getChars(char dst[], int dstBegin)。
2.7.1 getChars(char dst[], int dstBegin)
拷贝一个字符串到dst中,规定了开始位置。
源码:
void getChars(char dst[], int dstBegin) {
System.arraycopy(value, 0, dst, dstBegin, value.length);
}
注意这个方法不是public的。
2.7.2 getChars(int srcBegin, int srcEnd, char dst[], int dstBegin)
将字符从字符串复制到目标字符数组。
srcBegin – 字符串中要复制的第一个字符的索引。
srcEnd – 字符串中要复制的最后一个字符之后的索引。
dst – 目标数组。
dstBegin – 目标数组中的起始偏移量。
源码:
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来完成的,这个方法不是在java中实现的。
这里面有一个问题,就是起始偏移量是什么?
上述目标数组中的起始偏移量。也就是说dst数组中在第几个下标开始进行复制操作。
2.8 getBytes()
该方法是获得字符串的默认或者指定编码格式的字节数组。
2.8.1 getBytes()
String的getBytes()方法是得到一个操作系统默认的编码格式的字节数组
源码:
public byte[] getBytes() {
return StringCoding.encode(value, 0, value.length);
}
小例子:
public static void main(String[] args) {
// TODO Auto-generated method stub
String aa = "若勖";
System.out.println(aa.getBytes());
}
输出结果为:
[B@69222c14
至于这个编码有什么用呢?一般在做I/O,或者web交互的时候会需要以Byte的方式进行传输,这个时候就需要规定一个统一的规则,来进行转换。
2.8.2 getBytes(String charsetName) *
得到一个指定的编码格式的字节数组。
这个方法在实际应用中,会经常用到。
源码:
public byte[] getBytes(String charsetName)
throws UnsupportedEncodingException {
if (charsetName == null) throw new NullPointerException();
return StringCoding.encode(charsetName, value, 0, value.length);
}
根据指定的编码格式进行转换,这里写一个小例子:
public static void main(String[] args) throws UnsupportedEncodingException {
// TODO Auto-generated method stub
String aa = "若勖";
System.out.println(aa.getBytes("GBK"));
System.out.println(aa.getBytes("UTF-8"));
System.out.println(aa.getBytes("ISO8859-1"));
System.out.println(aa.getBytes("unicode"));
}
输出结果:
[B@606d8acf
[B@782830e
[B@33833882
[B@5680a178
可见使用不同的编码格式进行转换,会得到不同的字节数组。
2.8.3 getBytes(Charset charset)
不指定编码格式,直接指定编码名称所对应的字符集
源码:
public byte[] getBytes(Charset charset) {
if (charset == null) throw new NullPointerException();
return StringCoding.encode(charset, value, 0, value.length);
}
用法与getBytes(String charsetName) 相同,只是传入的参数为字符集而不是字符的名称
2.9 equals(Object anObject) *
比较字符串是否相等,这个方法实在是太常见了,应该没有人没有用到过这个方法了。
源码:
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
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;
}
从源码中可以看到,首先是去比较地址是否相同,如果相同则直接返回true,如果地址不相同,则去判断传入参数是不是String类型,是的话将其转换为String,然后获取字符串的长度,如果长度相同的话,则去比较每一位字符的char是否相当。如果都相当,则返回true。
注意:如果是null.equals()会抛出空指针。
2.10 contentEquals(StringBuffer sb)
contentEquals可以用来比较String对象内容序列的异同,但是与equals还是有一定的差异,主要差异体现在String的equals方法只有在另一个对象是String的情况下才可能返回true,
而contentEquals只要求另一个对象是CharSequence或其子类的对象。
源码:
public boolean contentEquals(StringBuffer sb) {
return contentEquals((CharSequence)sb);
}
public boolean contentEquals(CharSequence cs) {
// Argument is a 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);
}
// Argument is a generic 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;
}
三部分源码放在一次来学习,如果接受到的参数是StringBuffer或者StringBuilder的话,则会调用nonSyncContentEquals方法,先获取sb对应的char,然后在进行比较。
如果传入参数是String,则直接调用equals方法进行比较,如果传入参数是CharSequence,则直接用入参的字符与String的字符进行比较。
2.11 equalsIgnoreCase(String anotherString)
这个也是比较家族中的一个方法,他和equals的区别就是equalsIgnoreCase是不区分大小写的。
源码:
public boolean equalsIgnoreCase(String anotherString) {
return (this == anotherString) ? true
: (anotherString != null)
&& (anotherString.value.length == value.length)
&& regionMatches(true, 0, anotherString, 0, value.length);
}
第二部分
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;
// Note: toffset, ooffset, or len might be near -1>>>1.
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) {
// If characters don't match but case may be ignored,
// try converting both characters to uppercase.
// If the results match, then the comparison scan should
// continue.
char u1 = Character.toUpperCase(c1);
char u2 = Character.toUpperCase(c2);
if (u1 == u2) {
continue;
}
// Unfortunately, conversion to uppercase does not work properly
// for the Georgian alphabet, which has strange rules about case
// conversion. So we need to make one last check before
// exiting.
if (Character.toLowerCase(u1) == Character.toLowerCase(u2)) {
continue;
}
}
return false;
}
return true;
}
有点类似compare的方法,先比较大写再比较小写。
2.12 compareTo(String anotherString) *
这个方法也是经常用到,用于比较字符串的大小。
其实现实使用中,我一直觉得比较字符串的大小没有什么意义,一般不单独使用,会和steam流一起用于list的排序。
源码:
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;
}
源码也很好理解,逐位去比较每个字符的的ASCII码的大小。
2.13 regionMatches()
用于检测两个字符串在一个区域内是否相等。
共有两个方法
regionMatches(int toffset, String other, int ooffset, int len)
和regionMatches(boolean ignoreCase, int toffset, String other, int ooffset, int len)
区别在于是否需要区分大小写。
源码:
regionMatches(int toffset, String other, int ooffset, int len)
public boolean regionMatches(int toffset, String other, int ooffset,
int len) {
char ta[] = value;
int to = toffset;
char pa[] = other.value;
int po = ooffset;
// Note: toffset, ooffset, or len might be near -1>>>1.
if ((ooffset < 0) || (toffset < 0)
|| (toffset > (long)value.length - len)
|| (ooffset > (long)other.value.length - len)) {
return false;
}
while (len-- > 0) {
if (ta[to++] != pa[po++]) {
return false;
}
}
return true;
}
首先将需要比较的字符转换为字符数组,之后按照开始比较的位置,对每一个字符进行比较,发现有不相等的字符,则返回false
regionMatches(boolean ignoreCase, int toffset, String other, int ooffset, int len)
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;
// Note: toffset, ooffset, or len might be near -1>>>1.
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) {
// If characters don't match but case may be ignored,
// try converting both characters to uppercase.
// If the results match, then the comparison scan should
// continue.
char u1 = Character.toUpperCase(c1);
char u2 = Character.toUpperCase(c2);
if (u1 == u2) {
continue;
}
// Unfortunately, conversion to uppercase does not work properly
// for the Georgian alphabet, which has strange rules about case
// conversion. So we need to make one last check before
// exiting.
if (Character.toLowerCase(u1) == Character.toLowerCase(u2)) {
continue;
}
}
return false;
}
return true;
}
与上述方法类似,唯一的区别是当ignoreCase参数为true是,则是不区分大小写,会进行字符的大小写转换对比,方式参考compare。
2.14 startsWith相关方法
startsWith也是用于比较的字符串的方式,用于判断一个字符串是否以一特定的字符串为开始字符串,相关包含startsWith和endWith,endsWith则是startsWith的一种特殊情况。
源码:
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++]) {
return false;
}
}
return true;
}
从源码中可以看是,该方法存在一个toffset偏移量,就是说,他可以规定从第几个字符开始作为开始字符去匹配。
与改方法相关的方法还有两个,分别是:
startsWith(String prefix)默认的偏移量为0
public boolean startsWith(String prefix) {
return startsWith(prefix, 0);
}
和
endsWith(String suffix)
public boolean endsWith(String suffix) {
return startsWith(suffix, value.length - suffix.value.length);
}
偏移量为被比较字符串的长度-比较字符串的长度。
2.15 hashCode()
这部分重写了Object的hashCode
源码:
public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
char val[] = value;
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}
有大神这麽解释String重写的hashCode
在String类中有个私有实例字段hash表示该串的哈希值,在第一次调用hashCode方法时,字符串的哈希值被计算
并且赋值给hash字段,之后再调用hashCode方法便可以直接取hash字段返回。
String类中的hashCode计算方法还是比较简单的,就是以31为权,每一位为字符的ASCII值进行运算,用自然溢出
来等效取模。
哈希计算公式可以计为s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
关于为什么取31为权,可以参考StackOverflow上的这个问题
主要是因为31是一个奇质数,所以31*i=32*i-i=(i<<5)-i,这种位移与减法结合的计算相比一般的运算快很多。
字符串哈希可以做很多事情,通常是类似于字符串判等,判回文之类的。
但是仅仅依赖于哈希值来判断其实是不严谨的,除非能够保证不会有哈希冲突,通常这一点很难做到。
就拿jdk中String类的哈希方法来举例,字符串"gdejicbegh"与字符串"hgebcijedg"具有相同的hashCode()
返回值-801038016,并且它们具有reverse的关系。这个例子说明了用jdk中默认的hashCode方法判断字
符串相等或者字符串回文,都存在反例。
2.16 indexOf()
indexOf()总结来说,就是返回各种条件下的第一次出现的索引。indexOf()包含很多方法,下面来一一学习。
2.16.1 indexOf(int ch, int fromIndex)
indexOf的基础方法,很多方法都是他的特例。API中解释返回指定字符第一次出现的字符串内的索引,以指定的索引开始搜索。
源码:
public int indexOf(int ch, int fromIndex) {
final int max = value.length;
if (fromIndex < 0) {
fromIndex = 0;
} else if (fromIndex >= max) {
// Note: fromIndex might be near -1>>>1.
return -1;
}
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;
for (int i = fromIndex; i < max; i++) {
if (value[i] == ch) {
return i;
}
}
return -1;
} else {
return indexOfSupplementary(ch, fromIndex);
}
}
indexOfSupplementary(int ch, int fromIndex)源码:用于特殊字符的处理
private int indexOfSupplementary(int ch, int fromIndex) {
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;
}
从源码中可以看出,这个方法可以指定在第几索引处开始进行检索判断,返回ch出现的第一个位置。ch就是ASCII码,如果ch的值属于补码,则在indexOfSupplementary中做判断。
2.16.2 indexOf(int ch)
2.16.1方法中,规定了默认从0开始
源码:
public int indexOf(int ch) {
return indexOf(ch, 0);
}
2.16.3 lastIndexOf(int ch, int fromIndex)
返回指定字符的最后一次出现的字符串中的索引。 对于从0到0xFFFF(含)范围内的ch的值, ch的索引(以Unicode代码为单位)是最大的值k ,使得:
this.charAt(k) == ch
是真的。 对于ch其他值,它是最大值k ,使得:
this.codePointAt(k) == ch
是真的。 在任何一种情况下,如果此字符串中没有此类字符,则返回-1 。 String从最后一个字符开始String搜索。
源码:
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);
}
}
lastIndexOfSupplementary(ch, fromIndex)源码:
private int lastIndexOfSupplementary(int ch, int fromIndex) {
if (Character.isValidCodePoint(ch)) {
final char[] value = this.value;
char hi = Character.highSurrogate(ch);
char lo = Character.lowSurrogate(ch);
int i = Math.min(fromIndex, value.length - 2);
for (; i >= 0; i--) {
if (value[i] == hi && value[i + 1] == lo) {
return i;
}
}
}
return -1;
}
2.16.4 lastIndexOf(int ch)
2.16.3的缺省fromIndex 默认为字符串最大长度-1
源码:
public int lastIndexOf(int ch) {
return lastIndexOf(ch, value.length - 1);
}
2.16.5 indexOf() 对于字符串的处理
这部分主要有一个方法,indexOf(char[] source, int sourceOffset, int sourceCount, char[] target, int targetOffset, int targetCount, int fromIndex),剩下的三个方法都是这个方法的特殊情况的调用。
下面开始分析源码:
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++) {
/* Look for first character. */
if (source[i] != first) {
while (++i <= max && source[i] != first);
}
/* Found first character, now look at the rest of v2 */
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) {
/* Found whole string. */
return i - sourceOffset;
}
}
}
return -1;
}
首先来解释一下每个参数的含义,
source–源字符串数组
sourceOffset–源字符串数组的开始位置
sourceCount–源字符串数组获取数量
target–目标字符串数组
targetOffset–目标字符串数组的开始位置
targetCount–目标字符串数组获取数量
fromIndex–源字符串数组在第几索引开始
这段源码的逻辑就是首先获取目标字符串数组的第一个字符
未完待续、、、