【一线大厂Java面试题解析+后端开发学习笔记+最新架构讲解视频+实战项目源码讲义】
**开源地址:https://docs.qq.com/doc/DSmxTbFJ1cmN1R2dB **
5)参数为StringBuffer
String.java中的相关代码:
public String(StringBuffer buffer) {
// 同步锁,锁对象为buffer
synchronized(buffer) {
this.value = Arrays.copyOf(buffer.getValue(), buffer.length());
}
}
根据buffer中保存的字符串的值创建一个新对象并保存新对象,因为StringBuffer中字符串长度可变。因为StringBuffer需要保证线程安全,所以加锁。
6)参数为StringBuilder
String.java中的相关代码:
public String(StringBuilder builder) {
this.value = Arrays.copyOf(builder.getValue(), builder.length());
}
根据builder中保存的字符串的值创建一个新对象并保存新对象,因为StringBuilder中字符串长度可变。因为StringBuilder不需要保证线程安全,所以不加锁。
获取字符串长度。
String.java中的相关代码:
public int length() {
return value.length;
}
返回字符数组的长度。
判断字符串是否为空。
String.java中的相关代码:
public boolean isEmpty() {
return value.length == 0;
}
通过比较字符数组长度进行判断。
获取字符串中指定位置的字符。
String.java中的相关代码:
public char charAt(int index) {
// 若指定的位置超过了数组的边界,则抛出异常
if ((index < 0) || (index >= value.length)) {
throw new StringIndexOutOfBoundsException(index);
}
// 满足条件,返回字符数组中指定位置的字符
return value[index];
}
指定字符集,获取字符串对应的字节数组。
String.java中的相关代码:
public byte[] getBytes(Charset charset) {
// 若指定的字符集为空,则抛出异常
if (charset == null) throw new NullPointerException();
// 满足条件,根据字符集将字符数组编码成字节数组并返回
return StringCoding.encode(charset, value, 0, value.length);
}
比较两个String对象是否相等。
String.java中的相关代码:
public boolean equals(Object anObject) {
// 若两个对象为同一个对象,则返回true
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) {
// 若发现有不相等的字符,则返回false
if (v1[i] != v2[i])
return false;
i++;
}
// 若全部相等,则返回true;
return true;
}
}
// 待比较的对象不是String类型,返回false
return false;
}
比较两个字符串谁包含的字符范围更大
String.java中的相关代码:
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++;
}
// 若两个字符串长度不相等,且最小长度内的按位比较,两者都相等
// 则返回两个字符串的长度之差 eg: abcdef和abc。
return len1 - len2;
}
在忽略字符大小写的情况下,比较两个字符串谁包含的字符范围更大
String.java中的相关代码:
public int compareToIgnoreCase(String str) {
return CASE_INSENSITIVE_ORDER.compare(this, str);
}
调用了全局变量CASE_INSENSITIVE_ORDER的compare方法。
CASE_INSENSITIVE_ORDER是一个CaseInsensitiveComparator类型的对象。
CaseInsensitiveComparator是String的静态内部类。
String.java中的相关代码:
private static class CaseInsensitiveComparator
implements Comparator<String>, java.io.Serializable {
// 用于序列化
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++) {
// 获取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) {
// 返回两个字符的差值
return c1 - c2;
}
}
}
}
// 若两个字符串长度不相等,且最小长度内的按位比较,两者都相等
// 则返回两个字符串的长度之差 eg: abcdef和abc。
return n1 - n2;
}
// 返回全局变量,用于替换反序列化的对象
private Object readResolve() { return CASE_INSENSITIVE_ORDER; }
}
为什么两个字符大写形式比较不相等,还要比较两个字符的小写形式?
官方说法是因为格鲁吉亚字母的存储规则和英文字母不同。
判断两个从不同起始位置开始的字符串,在一定的长度内的值是否相等
String.java中的相关代码:
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;
// 若不满足边界条件,则返回false
// 采用减法形式比较,因为相加后的结果可能超过最大值-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) {
// 将二者变成大写形式
char u1 = Character.toUpperCase(c1);
char u2 = Character.toUpperCase(c2);
// 若相等,则跳过本次循环
if (u1 == u2) {
continue;
}
// 若二者的大写形式相等,则跳过本次循环
if (Character.toLowerCase(u1) == Character.toLowerCase(u2)) {
continue;
}
}
// 若还是不相等,则返回false
return false;
}
// 最后返回true
return true;
}
忽略大小写的情况下比较两个字符串是否相等
String.java中的相关代码:
public boolean equalsIgnoreCase(String anotherString) {
return (this == anotherString) ? true
: (anotherString != null)
&& (anotherString.value.length == value.length)
&& regionMatches(true, 0, anotherString, 0, value.length);
}
若两个字符串为同一个对象,则返回true。
否则需要同时满足三个条件:1)另一个字符串不为空。2)两个字符串长度相等。3)忽略大小写时,两个字符串对应位置的字符相等。
判断一个字符串从头开始的连续长度内的字符是否和指定字符串的内容相等。
String.java中的相关代码:
public boolean startsWith(String prefix) {
return startsWith(prefix, 0);
}
调用了重载的方法。
判断一个字符串从指定位置开始的连续长度内的字符是否和指定的字符串中的字符匹配。
String.java中的相关代码:
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;
// 若不满足边界条件,则返回false
if ((toffset < 0) || (toffset > value.length - pc)) {
return false;
}
// 循环比较
while (--pc >= 0) {
// 若不相等,则返回false
if (ta[to++] != pa[po++]) {
return false;
}
}
// 全部相等,返回true
return true;
}
判断一个字符串从末尾向前的连续长度内的字符是否和指定字符串的内容相等。
String.java中的相关代码:
public boolean endsWith(String suffix) {
// 用两个字符串的长度做差,得出起始位置
return startsWith(suffix, value.length - suffix.value.length);
}
求一个字符串的哈希值。
String.java中的相关代码:
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;
}
计算公式为s[0]*31^(n-1) + s[1]*31^(n-2) + … + s[n-1],n为字符串的长度。
获取字符串中指定的字符首次出现的位置。
String.java中的相关代码:
public int indexOf(int ch) {
return indexOf(ch, 0);
}
调用了重载方法。
获取字符串中指定起始位置开始,指定的字符首次出现的位置。
String.java中的相关代码:
public int indexOf(int ch, int fromIndex) {
// 获取字符串长度
final int max = value.length;
// 若起始位置小于0
if (fromIndex < 0) {
// 则默认起始位置为0
fromIndex = 0;
// 或起始位置超过字符串的长度
} else if (fromIndex >= max) {
// 返回-1,说明指定的字符在字符串中没有出现
return -1;
}
// 若字符ch为BMP字符,即占用空间为两个字节
// 详解在1)处
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;
}
}
// // 返回-1,说明指定的字符在字符串中没有出现
return -1;
} else { //若ch为增补字符集中的字符。
// 详解在2)处
return indexOfSupplementary(ch, fromIndex);
}
}
1)UTF-16编码方式下,为每16bit表示一个字符,一共可以表示65536种字符,这种占用两个字节的字符称为BMP字符。但是Unicode编码方式为了表示更多的字符,同时还要用16bit表示,于是引入了Surrogates,即从UTF-16的65536种字符中选择2048种,让这些字符两个为一组来表示超出65536的字符。进一步将这2048种字符分成High Surrogates和Low Surrogates两部分,每部分1024种字符,一共可以表示1048576种字符。
UTF-16编码下的High Surrogates和Low Surrogates通过一定的规则计算,可以得到16bit的Unicode编码下的字符。
2)indexOfSupplementary方法
String.java中的相关代码:
private int indexOfSupplementary(int ch, int fromIndex) {
if (Character.isValidCodePoint(ch)) {
final char[] value = this.value;
// 获取High Surrogate
final char hi = Character.highSurrogate(ch);
// 获取Low Surrogates
final char lo = Character.lowSurrogate(ch);
// 获取字符串长度
final int max = value.length - 1;
// 循环比较
for (int i = fromIndex; i < max; i++) {
// 若High Surrogates和Low Surrogates都相等,则返回对应位置
if (value[i] == hi && value[i + 1] == lo) {
return i;
}
}
}
// 返回-1,说明指定的字符在字符串中没有出现
return -1;
}
从指定的起始位置开始,对字符串进行截取。
String.java中的相关代码:
public String substring(int beginIndex) {
// 若起始位置小于0
if (beginIndex < 0) {
throw new StringIndexOutOfBoundsException(beginIndex);
}
// 计算截取的字符串长度
int subLen = value.length - beginIndex;
// 若长度小于0
if (subLen < 0) {
throw new StringIndexOutOfBoundsException(subLen);
}
// 若起始位置为0,说明截取的字符串为全部,直接返回
// 若起始位置大于0,创建新的字符串,返回
return (beginIndex == 0) ? this : new String(value, beginIndex, subLen);
}
调用String的构造方法,将字符数组中指定位置和长度的字符变成字符串
String.java中的相关代码:
public String(char value[], int offset, int count) {
// 若起始位置小于0
if (offset < 0) {
throw new StringIndexOutOfBoundsException(offset);
}
// 若指定长度小于等于0
if (count <= 0) {
// 若指定长度小于0
if (count < 0) {
throw new StringIndexOutOfBoundsException(count);
}
// 若起始位置小于等于字符数组的长度,同时指定长度为0
if (offset <= value.length) {
// 字符串为””
this.value = "".value;
// 返回
return;
}
}
// 若终止位置超出了字符数组的长度
if (offset > value.length - count) {
throw new StringIndexOutOfBoundsException(offset + count);
}
// 对字符数组指定范围的字符进行复制,并保存
this.value = Arrays.copyOfRange(value, offset, offset+count);
}
将字符串中的某一种字符替换成另一种字符
String.java中的相关代码:
public String replace(char oldChar, char newChar) {
// 若要替换的字符和替换后的字符不是相同的字符
if (oldChar != newChar) {
// 获取字符串长度
int len = value.length;
// 用于记录首个需要替换字符的位置
int i = -1;
// 获取字符串对应的字符数组
char[] val = value;
// 遍历查找字符串中是否有需要替换的字符
// 同时记录首个需要替换的字符的位置
while (++i < len) {
// 若i位置的字符为需要替换的字符
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];
// 若i位置字符为待替换的字符,则替换
buf[i] = (c == oldChar) ? newChar : c;
i++;
}
// 根据新创建的字符数组的内容,创建新的字符串,返回
return new String(buf, true);
}
}
// 若要替换的字符和替换后的字符一样,则不需要替换,直接返回
return this;
}