因为String可以说是java中用的最多的一种类型,但是毕业三年还没看过源码,实在是惭愧,今天特意静下心来自己看看String的源码。
String源码分析
继承结构
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {}
- String是一个final类,所以不能被继承
- 实现Serializable,可以被序列化
- 实现Comparable,可以用于比较大小(按顺序比较单个字符的ASCII码)
- 实现CharSequence,表示是一个有序字符的序列,(因为String的本质是一个char类型数组)
成员
// String底层实现是一个不可变char型数组
private final char value[];
// 存储字符串哈希值
private int hash; // Default to 0
// 实现序列化的标识
private static final long serialVersionUID = -6849794470754667710L;
private final char value[]这是String字符串的本质,是一个字符数组,被final修饰,是不可变的。
构造方法
/** 01
* 默认将""空字符串的value赋值给实例对象的value,也是空字符
* 相当于深拷贝了空字符串""
*/
public String() {
this.value = "".value;
}
/** 02
* 这是一个有参构造函数,参数为一个String对象
* 将形参的value和hash赋值给实例对象作为初始化
* 相当于深拷贝了一个形参String对象
*/
public String(String original) {
this.value = original.value;
this.hash = original.hash;
}
/** 03
* 这是一个有参构造函数,参数为一个char字符数组
* 意义就是通过字符数组去构建一个新的String对象
*/
public String(char value[]) {
this.value = Arrays.copyOf(value, value.length);
}
/** 04
* 这是一个有参构造函数,参数为char字符数组,offset(起始位置,偏移量),count(个数)
* 作用就是在char数组的基础上,从offset位置开始计数count个,构成一个新的String的字符串
* 意义就类似于截取count个长度的字符集合构成一个新的String对象
*/
public String(char value[], int offset, int count) {
if (offset < 0) { //如果起始位置小于0,抛异常
throw new StringIndexOutOfBoundsException(offset);
}
if (count <= 0) {
if (count < 0) { //如果个数小于0,抛异常
throw new StringIndexOutOfBoundsException(count);
}
if (offset <= value.length) { //在count = 0的前提下,如果offset<=len,则返回""
this.value = "".value;
return;
}
}
// Note: offset or count might be near -1>>>1.
//如果起始位置>字符数组长度 - 个数,则无法截取到count个字符,抛异常
if (offset > value.length - count) {
throw new StringIndexOutOfBoundsException(offset + count);
}
//重点,从offset开始,截取到offset+count位置(不包括offset+count位置)
this.value = Arrays.copyOfRange(value, offset, offset+count);
}
/** 05
* 这是一个有参构造函数,参数为int字符数组,offset(起始位置,偏移量),count(个数)
* 作用跟04构造函数差不多,但是传入的不是char字符数组,而是int数组。
* 而int数组的元素则是字符对应的ASCII整数值
* 例子:new String(new int[]{97,98,99},0,3); output: abc
*/
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);
}
//以上都是为了处理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));
}
//上面关于BMP什么的,我暂时也没看懂,猜想关于验证int数据的正确性,通过上面的测试就进入下面的算法
// Pass 2: Allocate and fill in char[]
final char[] v = new char[n];
for (int i = offset, j = 0; i < end; i++, j++) { //从offset开始,到offset + count
int c = codePoints[i];
if (Character.isBmpCodePoint(c))
v[j] = (char)c; //将Int类型显式缩窄转换为char类型
else
Character.toSurrogates(c, v, j++);
}
this.value = v; //最后将得到的v赋值给String对象的value,完成初始化
}
/** 06
* 这是一个有参构造函数,参数为byte数组,offset(起始位置,偏移量),长度,和字符编码格式
* 就是传入一个byte数组,从offset开始截取length个长度,其字符编码格式为charsetName,如UTF-8
* 例子:new String(bytes, 2, 3, "UTF-8");
*/
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);
}
/** 07
* 类似06
*/
public String(byte bytes[], int offset, int length, Charset charset) {
if (charset == null)
throw new NullPointerException("charset");
checkBounds(bytes, offset, length);
this.value = StringCoding.decode(charset, bytes, offset, length);
}
/** 08
* 这是一个有参构造函数,参数为byte数组和字符集编码
* 用charsetName的方式构建byte数组成一个String对象
*/
public String(byte bytes[], String charsetName)
throws UnsupportedEncodingException {
this(bytes, 0, bytes.length, charsetName);
}
/** 09
* 类似08
*/
public String(byte bytes[], Charset charset) {
this(bytes, 0, bytes.length, charset);
}
/** 10
* 这是一个有参构造函数,参数为byte数组,offset(起始位置,偏移量),length(个数)
* 通过使用平台的默认字符集解码指定的 byte 子数组,构造一个新的 String。
*
*/
public String(byte bytes[], int offset, int length) {
checkBounds(bytes, offset, length);
this.value = StringCoding.decode(bytes, offset, length);
}
/** 11
* 这是一个有参构造函数,参数为byte数组
* 通过使用平台默认字符集编码解码传入的byte数组,构造成一个String对象,不需要截取
*
*/
public String(byte bytes[]) {
this(bytes, 0, bytes.length);
}
/** 12
* 有参构造函数,参数为StringBuffer类型
* 就是将StringBuffer构建成一个新的String,比较特别的就是这个方法有synchronized锁
* 同一时间只允许一个线程对这个buffer构建成String对象
*/
public String(StringBuffer buffer) {
synchronized(buffer) {
this.value = Arrays.copyOf(buffer.getValue(), buffer.length()); //使用拷贝的方式
}
}
/** 13
* 有参构造函数,参数为StringBuilder
* 同12差不多,只不过是StringBuilder的版本,差别就是没有实现线程安全
*/
public String(StringBuilder builder) {
this.value = Arrays.copyOf(builder.getValue(), builder.length());
}
/** 14
* 这个构造函数比较特殊,有用的参数只有char数组value,是一个不对外公开的构造函数,没有访问修饰符
* 加入这个share的只是为了区分于String(char[] value)方法,用于重载,功能类似于03,我也在03表示过疑惑。
* 为什么提供这个方法呢,因为性能好,不需要拷贝。为什么不对外提供呢?因为对外提供会打破value为不变数组的限制。
* 如果对外提供这个方法让String与外部的value产生关联,如果修改外不的value,会影响String的value。所以不能
* 对外提供
*/
String(char[] value, boolean share) {
// assert share : "unshared not supported";
this.value = value;
}
对外方法
// 返回数组长度,也就是String长度
public int length() {
return value.length;
}
// 数组长度为0则说明String是空字符串
public boolean isEmpty() {
return value.length == 0;
}
// 返回字符串指定位置的字符,也就是返回底层数组指定下标的字符
public char charAt(int index) {
if ((index < 0) || (index >= value.length)) {
throw new StringIndexOutOfBoundsException(index);
}
return value[index];
}
// 返回String对象指定位置字符的ASSIC码,也就是返回底层数组指定下标的元素的ASSIC码(int类型)
public int codePointAt(int index) {
if ((index < 0) || (index >= value.length)) {
throw new StringIndexOutOfBoundsException(index);
}
return Character.codePointAtImpl(value, index, value.length);
}
// 返回String对象指定位置前一个字符的ASSIC码,也就是返回底层数组指定下标前一个的元素的ASSIC码(int类型)
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);
}
/**
* 方法返回的是代码点个数,是实际上的字符个数,功能类似于length()
* 对于正常的String来说,length方法和codePointCount没有区别,都是返回字符个数。
* 但当String是Unicode类型时则有区别了。
* 例如:String str = “/uD835/uDD6B” (即 'Z' ), length() = 2 ,codePointCount() = 1
*/
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);
}
/**
* 也是相对Unicode字符集而言的,从index索引位置算起,偏移codePointOffset个位置,返回偏移后的位置是多少
* 例如,index = 2 ,codePointOffset = 3 ,maybe返回 5
*/
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);
}
/**
* 这是一个不对外的方法,是给String内部调用的,因为它是没有访问修饰符的,只允许同一包下的类访问
* 参数:dst[]是目标数组,dstBegin是目标数组的偏移量,既要复制过去的起始位置(从目标数组的什么位置覆盖)
* 作用就是将String的字符数组value整个复制到dst字符数组中,在dst数组的dstBegin位置开始拷贝
*
*/
void getChars(char dst[], int dstBegin) {
System.arraycopy(value, 0, dst, dstBegin, value.length);
}
/**
* 得到char字符数组,原理是getChars() 方法将一个字符串的字符复制到目标字符数组中。
* 参数:srcBegin是原始字符串的起始位置,srcEnd是原始字符串要复制的字符末尾的后一个位置(既复制区域不包括srcEnd)
* dst[]是目标字符数组,dstBegin是目标字符的复制偏移量,复制的字符从目标字符数组的dstBegin位置开始覆盖。
*/
public void getChars(int srcBegin, int srcEnd, char dst[], int dstBegin) {
if (srcBegin < 0) { //如果srcBegin小于,抛异常
throw new StringIndexOutOfBoundsException(srcBegin);
}
* if (srcEnd > value.length) { //如果srcEnd大于字符串的长度,抛异常
throw new StringIndexOutOfBoundsException(srcEnd);
}
if (srcBegin > srcEnd) { //如果原始字符串其实位置大于末尾位置,抛异常
throw new StringIndexOutOfBoundsException(srcEnd - srcBegin);
}
System.arraycopy(value, srcBegin, dst, dstBegin, srcEnd - srcBegin);
}
/**
* 获得charsetName编码格式的bytes数组
*/
public byte[] getBytes(String charsetName)
throws UnsupportedEncodingException {
if (charsetName == null) throw new NullPointerException();
return StringCoding.encode(charsetName, value, 0, value.length);
}
/**
* 与上个方法类似,但charsetName和charset的区别,我还没搞定,搞懂来再更新
*/
public byte[] getBytes(Charset charset) {
if (charset == null) throw new NullPointerException();
return StringCoding.encode(charset, value, 0, value.length);
}
/**
* 使用平台默认的编码格式获得bytes数组
*/
public byte[] getBytes() {
return StringCoding.encode(value, 0, value.length);
}
写一些常见的方法吧
// 按某一个字符串分割,返回String 数组
public String[] split(String regex) {
return split(regex, 0);
}
public String[] split(String regex, int limit) {
/* fastpath if the regex is a
(1)one-char String and this character is not one of the
RegEx's meta characters ".$|()[{^?*+\\", or
(2)two-char String and the first char is the backslash and
the second is not the ascii digit or ascii letter.
*/
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))
{
int off = 0;
int next = 0;
boolean limited = limit > 0;
ArrayList<String> list = new ArrayList<>();
while ((next = indexOf(ch, off)) != -1) {
if (!limited || list.size() < limit - 1) {
list.add(substring(off, next));
off = next + 1;
} else { // last one
//assert (list.size() == limit - 1);
list.add(substring(off, value.length));
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);
}
return Pattern.compile(regex).split(this, limit);
}
// 转为小写
public String toLowerCase() {
return toLowerCase(Locale.getDefault());
}
public String toLowerCase(Locale locale) {
if (locale == null) {
throw new NullPointerException();
}
int firstUpper;
final int len = value.length;
/* Now check if there are any characters that need to be changed. */
scan: {
for (firstUpper = 0 ; firstUpper < len; ) {
char c = value[firstUpper];
if ((c >= Character.MIN_HIGH_SURROGATE)
&& (c <= Character.MAX_HIGH_SURROGATE)) {
int supplChar = codePointAt(firstUpper);
if (supplChar != Character.toLowerCase(supplChar)) {
break scan;
}
firstUpper += Character.charCount(supplChar);
} else {
if (c != Character.toLowerCase(c)) {
break scan;
}
firstUpper++;
}
}
return this;
}
char[] result = new char[len];
int resultOffset = 0; /* result may grow, so i+resultOffset
* is the write location in result */
/* Just copy the first few lowerCase characters. */
System.arraycopy(value, 0, result, 0, firstUpper);
String lang = locale.getLanguage();
boolean localeDependent =
(lang == "tr" || lang == "az" || lang == "lt");
char[] lowerCharArray;
int lowerChar;
int srcChar;
int srcCount;
for (int i = firstUpper; i < len; i += srcCount) {
srcChar = (int)value[i];
if ((char)srcChar >= Character.MIN_HIGH_SURROGATE
&& (char)srcChar <= Character.MAX_HIGH_SURROGATE) {
srcChar = codePointAt(i);
srcCount = Character.charCount(srcChar);
} else {
srcCount = 1;
}
if (localeDependent ||
srcChar == '\u03A3' || // GREEK CAPITAL LETTER SIGMA
srcChar == '\u0130') { // LATIN CAPITAL LETTER I WITH DOT ABOVE
lowerChar = ConditionalSpecialCasing.toLowerCaseEx(this, i, locale);
} else {
lowerChar = Character.toLowerCase(srcChar);
}
if ((lowerChar == Character.ERROR)
|| (lowerChar >= Character.MIN_SUPPLEMENTARY_CODE_POINT)) {
if (lowerChar == Character.ERROR) {
lowerCharArray =
ConditionalSpecialCasing.toLowerCaseCharArray(this, i, locale);
} else if (srcCount == 2) {
resultOffset += Character.toChars(lowerChar, result, i + resultOffset) - srcCount;
continue;
} else {
lowerCharArray = Character.toChars(lowerChar);
}
/* Grow result if needed */
int mapLen = lowerCharArray.length;
if (mapLen > srcCount) {
char[] result2 = new char[result.length + mapLen - srcCount];
System.arraycopy(result, 0, result2, 0, i + resultOffset);
result = result2;
}
for (int x = 0; x < mapLen; ++x) {
result[i + resultOffset + x] = lowerCharArray[x];
}
resultOffset += (mapLen - srcCount);
} else {
result[i + resultOffset] = (char)lowerChar;
}
}
return new String(result, 0, len + resultOffset);
}
// 去头和尾空格
public String trim() {
int len = value.length;//尾部指针标志,须-1
int st = 0;//头指针
char[] val = value; /* avoid getfield opcode */
while ((st < len) && (val[st] <= ' ')) {
st++;
}
while ((st < len) && (val[len - 1] <= ' ')) {
len--;
}
return ((st > 0) || (len < value.length)) ? substring(st, len) : this;
}
String中唯一一个native函数
public native String intern();
String类中唯一的一条本地方法
比如str.intern(),作用就是去字符串常量池中寻找str字符串,如果有则返回str在常量池中的引用,如果没有则在常量池中创建str对象
相关问题
1,String类为何为不可变类型?
- 从String的定义,我们可以知道,String类是被final修饰的,是一个不可被继承的类
- String底层实际上是一个私有的不可变数组,也没有对外公开的改变数组中值的方法
- 从其他的方法中,我们也可以看到,如果是对字符串进行修改,其本质是new了一个新的String的对象
但是
可以通过反射修改String底层数组中的元素的值
2,为什么String已经有一个compareTo方法了,还需要一个静态内部类再实现compare?
因为String继承了comparable接口,所以可以重写compareTo(String str)方法,注意参数是String类型的。里面实现的方式不忽略大小写的比较方式。如果想要一个忽略大小写的比较方式,就需要一个比较器来实现,这时String提供一个内部比较器给我们用,我们就不用重写一个单独的类做String的比较器了
3,为什么在忽略大小写比较的时候,通常都会大小写都会比较一次?
也就是说,通常会同位置的字符会全部转为大写比较一次,又转为小写比较一次?这是为什么,不是转换为大写或小写比较一次既可吗?
同时比较了UpperCase和LowerCase,是为了兼容Georgian字符。
https://www.cnblogs.com/yanyichao/p/4493039.html