java.lang.string
说明:上一章我们阅读了java所有类的父类object,这一章学习下经常用到的string类。
1.String申明
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {}
这也是一个用 final 声明的常量类,不能被任何类所继承,而且只要一个String对象被创建, 包含在这个对象中的字符序列是不可改变的, 包括该类后续的所有方法都是不能修改该对象的,直至该对象被销毁,平时用到的string对象重新赋值,其实都是新建了一个string对象,把原来对象的指针指向新的对象地址。
- 实现了 Serializable接口,这是一个序列化标志接口。
- 还实现了 Comparable 接口,用于比较两个字符串的大小(按顺序比较单个字符的ASCII码),后面会有具体方法实现。
- 最后实现了 CharSequence 接口,表示是一个有序字符的集合,相应的方法后面也会介绍。
2.String属性
/**用来存储字符串 */
private final char value[];
/** 缓存字符串的哈希码 */
private int hash; // Default to 0
/** 实现序列化的标识 */
private static final long serialVersionUID = -6849794470754667710L;
3.String构造方法
String的构造方法有很多,先来看看比较常用的
3.1 String()
public String() {
this.value = "".value;
}
无参构造方法
String str =new String();
其实是生成了一个空串,不是null。
3.2 String(String original)
public String(String original) {
this.value = original.value;
this.hash = original.hash;
}
String st=new String("str");生成了一个新的对象。
3.3 其他构造方法
//接收一个char类型的数组,但是方法中使用了数组的copyOf方法,复制
public String(char value[]) {
this.value = Arrays.copyOf(value, value.length);
}
public String(char value[], int offset, int count) {
//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) {
//count=0,返回一个""
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);
}
//线程安全的,将一个StringBuffer转化为String
public String(StringBuffer buffer) {
synchronized(buffer) {
this.value = Arrays.copyOf(buffer.getValue(), buffer.length());
}
}
//将一个StringBuffer转化为String
public String(StringBuilder builder) {
this.value = Arrays.copyOf(builder.getValue(), builder.length());
}
还有其他很多构造方法就不一一说明了。
4.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);
}
用于边界检查字节数组的公共专用实用方法
5.length()
public int length() {
return value.length;
}
public boolean isEmpty() {
return value.length == 0;
}
返回字符串长度,字符串长度是否为0,"".length()长度为0,所以此方法不能用于判断空字符串。
5.charAt(int index)
public char charAt(int index) {
if ((index < 0) || (index >= value.length)) {
throw new StringIndexOutOfBoundsException(index);
}
return value[index];
}
很明显是返回传入下标所在的字符。
6.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;
}
String 类重写了object的 equals 方法,需要比较的是组成字符串的每一个字符是否相同,如果都相同则返回true,否则返回false。
7.equalsIgnoreCase(String anotherString)
public boolean equalsIgnoreCase(String anotherString) {
return (this == anotherString) ? true
: (anotherString != null)
&& (anotherString.value.length == value.length)
&& regionMatches(true, 0, anotherString, 0, value.length);
}
比较两个字符串是否相等,不区分大小写。
8.compareTo(String anotherString)
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;
}
源码也很好理解,该方法是按字母顺序比较两个字符串,是基于字符串中每个字符的 Unicode 值。当两个字符串某个位置的字符不同时,返回的是这一位置的字符 Unicode 值之差,当两个字符串都相同时,返回两个字符串长度之差。
9.compareToIgnoreCase(String str)
public int compareToIgnoreCase(String str) {
return CASE_INSENSITIVE_ORDER.compare(this, str);
}
public static final Comparator<String> CASE_INSENSITIVE_ORDER
= new 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; }
}
compareToIgnoreCase() 方法在 compareTo 方法的基础上忽略大小写,我们知道大写字母是比小写字母的Unicode值小32的,底层实现是先都转换成大写比较,然后都转换成小写进行比较。
10.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;
}
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,则比较字符时忽略大小写。
- toffset -- 此字符串中子区域的起始偏移量。
- other -- 字符串参数。
- ooffset -- 字符串参数中子区域的起始偏移量。
- len -- 要比较的字符数。
11.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;
// 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;
}
public boolean startsWith(String prefix) {
return startsWith(prefix, 0);
}
startsWith() 方法用于检测字符串是否以指定的前缀开始。
- prefix -- 前缀。
- toffset -- 字符串中开始查找的位置。
12.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 算法很简单,主要就是中间的 for 循环,计算公式如下:
s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
s 数组即源码中的 val 数组,也就是构成字符串的字符数组。这里有个数字 31 ,为什么选择31作为乘积因子,而且没有用一个常量来声明?主要原因有两个:
①、31是一个不大不小的质数,是作为 hashCode 乘子的优选质数之一。
②、31可以被 JVM 优化,31 * i = (i << 5) - i。因为移位运算比乘法运行更快更省性能。
13.indexOf(int ch)
public int indexOf(int ch) {
return indexOf(ch, 0);//从第一个字符开始搜索
}
public int indexOf(int ch, int fromIndex) {
final int max = value.length;//max等于字符的长度
if (fromIndex < 0) {//指定索引的位置如果小于0,默认从 0 开始搜索
fromIndex = 0;
} else if (fromIndex >= max) {
//如果指定索引值大于等于字符的长度(因为是数组,下标最多只能是max-1),直接返回-1
return -1;
}
if (ch < Character.MIN_SUPPLEMENTARY_CODE_POINT) {//一个char占用两个字节,如果ch小于2的16次方(65536),绝大多数字符都在此范围内
final char[] value = this.value;
for (int i = fromIndex; i < max; i++) {//for循环依次判断字符串每个字符是否和指定字符相等
if (value[i] == ch) {
return i;//存在相等的字符,返回第一次出现该字符的索引位置,并终止循环
}
}
return -1;//不存在相等的字符,则返回 -1
} else {//当字符大于 65536时,处理的少数情况,该方法会首先判断是否是有效字符,然后依次进行比较
return indexOfSupplementary(ch, fromIndex);
}
}
indexOf(int ch) ch是表示字符的 Unicode 值 indexOf(String str) 从前往后返回第一个字符所在索引。
lastIndexOf(int ch) ch是表示字符的 Unicode 值 lastIndexOf(String str) 从后往前面走的第一个字符所在索引。
14.substring(int beginIndex)
public String substring(int beginIndex) {
if (beginIndex < 0) {//如果索引小于0,直接抛出异常
throw new StringIndexOutOfBoundsException(beginIndex);
}
int subLen = value.length - beginIndex;//subLen等于字符串长度减去索引
if (subLen < 0) {//如果subLen小于0,也是直接抛出异常
throw new StringIndexOutOfBoundsException(subLen);
}
//1、如果索引值beginIdex == 0,直接返回原字符串
//2、如果不等于0,则返回从beginIndex开始,一直到结尾
return (beginIndex == 0) ? this : new String(value, beginIndex, subLen);
}
substring(int beginIndex) 返回一个从索引 beginIndex 开始后面所有的字符串。
substring(int beginIndex, int endIndex):返回一个从索引 beginIndex 开始,到 endIndex 结尾的子字符串。
15.concat(String str)
public String concat(String str) {
int otherLen = str.length();
if (otherLen == 0) {
return this;
}
int len = value.length;
char buf[] = Arrays.copyOf(value, len + otherLen);
str.getChars(buf, len);
return new String(buf, true);
}
该方法通过 Arrays 工具类的copyOf方法创建一个新的字符数组,长度为原字符串和要拼接的字符串之和,前面填充原字符串,后面为空。接着在通过 getChars 方法将要拼接的字符串放入新字符串后面为空的位置。getChars为本地方法。最后new一个新的字符串为拼接结果。
注意:返回值是 new String(buf, true),也就是重新通过 new 关键字创建了一个新的字符串,原字符串是不变的。这也可以看出一个String对象被创建, 包含在这个对象中的字符序列是不可改变的。
16.replace(char oldChar, char newChar)
public String replace(char oldChar, char newChar) {
if (oldChar != newChar) {
int len = value.length;
int i = -1;
char[] val = value; /* avoid getfield opcode */
while (++i < len) {
if (val[i] == oldChar) {
break;
}
}
if (i < len) {
char buf[] = new char[len];
for (int j = 0; j < i; j++) {
buf[j] = val[j];
}
while (i < len) {
char c = val[i];
buf[i] = (c == oldChar) ? newChar : c;
i++;
}
return new String(buf, true);
}
}
return this;
}
第一步,判断oldchar存在于字符数组中,存在跳出while循环,并得到改字符第一次出现的位置,
如果不存在,i的值会==len,下面的判断,就不会再进行了,也就是说,这个字符串中不包含要替换的字符,返回原来的字符串。
第二步,从开始位置一直往后查找到和替换字符相同的字符,用新的字符代替,最后返回一个新的字符串。
17.contains(CharSequence s)
public boolean contains(CharSequence s) {
return indexOf(s.toString()) > -1;
}
indexOf 找到字符串对应的下标,如果大于-1即包含,反之则不包含。
18.contains(CharSequence s)
public String[] split(String regex, int limit) {
char ch = 0;
if (((regex.value.length == 1 && //判断参数长度是否为1
".$|()[{^?*+\\".indexOf(ch = regex.charAt(0)) == -1) || //判断参数不在特殊符号".$|()[{^?*+\\"中
(regex.length() == 2 && //判断参数长度是否为2
regex.charAt(0) == '\\' && \\第一位为转义符"\\"
(((ch = regex.charAt(1))-'0')|('9'-ch)) < 0 && //第二位不是0-9之间 '0'转换为int为48 '9'转换为int为57
((ch-'a')|('z'-ch)) < 0 && //判断不在 a-z之间
((ch-'A')|('Z'-ch)) < 0)) && //判断不在A-Z之间
(ch < Character.MIN_HIGH_SURROGATE ||
ch > Character.MAX_LOW_SURROGATE)) //判断分隔符不在特殊符号中
{
int off = 0;//当前索引
int next = 0;//下一个分割符出现的索引
boolean limited = limit > 0;//只分割前limit份还是全部分割,limit=0代表全部分割
ArrayList<String> list = new ArrayList<>();//创建一个集合,用于存放切割好的子串
while ((next = indexOf(ch, off)) != -1) {//判断是否包含下个分隔符,如果有则进入循环
if (!limited || list.size() < limit - 1) {//判断是全部分割或当前分割次数小于总分割次数
list.add(substring(off, next));//切割当前索引到下一个分隔符之间的字符串并添加到list中
off = next + 1; //继续切割下一下子串
} else {
list.add(substring(off, value.length));//切割当前索引到字符串结尾的子字符串并添加到list
off = value.length;//将当前索引置为字符串长度
break;//结束循环
}
}
if (off == 0) //如果找不到分隔符则返回只有本字符串的数组
return new String[]{this};
if (!limited || list.size() < limit)//如果是全部分割,或者没有达到分割数,则追加最后一项
list.add(substring(off, value.length));
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);//集合转数组并返回
}
return Pattern.compile(regex).split(this, limit);//其它情况用正则的切割规则去切割
}
、