前言
点赞在看,养成习惯。
点赞收藏,人生辉煌。
点击关注【微信搜索公众号:编程背锅侠】第一时间获得最新文章。
最近花了两天时间,整理了一下String的源码。这个整理并不全面但是也涵盖了大部分Spring源码中的方法。后续如果有时间还会将剩余的未整理的方法更新到这篇文章中。方便以后的复习和面试使用。如果文章中有地方有问题还请指出。
简述
字符串广泛应用 在 Java 编程中,在 Java 中字符串属于对象,Java 提供了String 类来创建和操作字符串。字符串缓冲区支持可变字符串。因为String对象是不可变的,因此可以共享它们。
String类代表字符串,Java程序中的所有字符串字面值如"abc"都是这个类的实例对象。String 类是不可改变的,所以你一旦创建了 String 对象,那它的值就无法改变了。如果需要对字符串做很多修改,那么应该选择使用StringBuilder或者StringBuffer。
最简单的创建字符串的方式:String qc = "qiu chan"编译器会使用该值创建一个 对象。我们也可以使用关键字New创建String对象。
String类型的常量池比较特殊。它的主要使用方法有两种:
直接使用双引号声明出来的String对象会直接存储在常量池中。如果不是用双引号声明的String对象,可以使用String提供的intern方法。intern 方法会从字符串常量池中查询当前字符串是否存在,若不存在就会将当前字符串放入常量池中。
继承/实现关系
public final class String
implements java.io.Serializable, Comparable
<String>, CharSequence
{
}
String是final修饰的不能够被继承和修改。
源码
String的底层使用的是char数组用于存储。
private final char value
[];
缓存字符串的哈希码默认值为0
private int hash
;
无参数构造函数
public String() {
this.value
= "".value
;
}
解析:初始化一个新创建的String对象,使其代表一个空字符序列。 注意,由于String是不可变的,所以不需要使用这个构造函数。
参数为字符串的构造函数
public String(String original
) {
this.value
= original
.value
;
this.hash
= original
.hash
;
}
解析:初始化一个新创建的String对象,使其代表与参数相同的字符序列。换句话说,新创建的字符串是参数字符串的副本。除非需要参数字符串的显式拷贝,否则不需要使用这个构造函数,因为String是不可变的。
参数为char数组的构造函数
public String(char value[]) {
this.value = Arrays.copyOf(value, value.length);
}
解析:分配一个新的String,使其代表当前字符数组参数中包含的字符序列。使用Arrays.copyOf方法进行字符数组的内容被复制。字符数组的后续修改不会影响新创建的字符串。
参数为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;
}
}
if (offset
> value
.length
- count
) {
throw new StringIndexOutOfBoundsException(offset
+ count
);
}
this.value
= Arrays
.copyOfRange(value
, offset
, offset
+count
);
}
解析:分配一个新的Sting,来源于给定的char数组中的字符。offset参数是子数组中第一个字符的索引,count参数指定子数组的长度。子数组被被复制以后,对字符数组的修改不会影响新创建的字符串。
参数为StringBuffer的构造方法
public String(StringBuffer buffer
) {
synchronized(buffer
) {
this.value
= Arrays
.copyOf(buffer
.getValue(), buffer
.length());
}
}
解析:分配一个新的字符串,该字符串包含当前字符串缓冲区参数中包含的字符序列。Arrays.copyOf方法进行字符串缓冲区中内容的复制。这里对StringBuffer进行了加锁,然后再进行拷贝操作。这里对其进行加锁正是为了保证在多线程环境下只能有一个线程去操作StringBuffer对象。
参数为StringBuilder的构造方法
public String(StringBuilder builder
) {
this.value
= Arrays
.copyOf(builder
.getValue(), builder
.length());
}
解析:参数是StringBuilder,这个是线程不安全的,但是性能相对于StringBuffer有很大的提升,源码的注释中说通过toString方法从字符串构建器中获取字符串可能会运行得更快,通常是首选。
length方法
public int length() {
return value
.length
;
}
解析:返回此字符串的长度。查看源码发现,这个value是一个char数组,本质获取的是字符串对应的char数组的长度。
isEmpty方法
public boolean isEmpty() {
return value
.length
== 0;
}
@Test
public void test_string_isEmpty(){
System
.out
.println(" ".isEmpty());
System
.out
.println("".isEmpty());
}
解析:判断给定的字符串是否为空,底层实现是根据char数组的长度是否为0进行判断。
charAt方法
public char charAt(int index
) {
if ((index
< 0) || (index
>= value
.length
)) {
throw new StringIndexOutOfBoundsException(index
);
}
return value
[index
];
}
解析:根据给定的索引获取当前的指定位置的char字符。如果给定的索引否小于0,或者给定的索引是大于这个字符串对应的char数组的长度抛出角标越界异常。index是从0开始到length-1结束。序列的第一个char值在索引0处,下一个在索引1处,依此类推,与数组索引一样。
getChars方法
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
);
}
@Test
public void test_string_codePointAt(){
String h
= "ahelloworld";
char[] data
= new char[4];
h
.getChars(2, 6, data
, 0);
System
.out
.println(data
);
}
解析:将字符串中的字符复制到目标字符数组中。索引包含srcBegin,不包含srcEnd。
equals方法
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;
}
解析:这个方法重写了Object中的equals方法。方法中的将此字符串与指定对象进行比较。接下来附赠一个手写的String字符串equals方法。
手写equals方法
private boolean mineEquals(String srcObject
, Object anObject
){
if (srcObject
== anObject
){
return true;
}
if (anObject
instanceof String){
String ans
= (String
) anObject
;
char[] srcChar
= srcObject
.toCharArray();
char[] anChar
= ans
.toCharArray();
int n
= srcChar
.length
;
if (n
== anChar
.length
){
int i
= 0;
while (n
-- != 0){
if (srcChar
[i
] != anChar
[i
])
return false;
i
++;
}
return true;
}
}
return false;
}
@Test
public void test_string_mine(){
String s
= new String("aaa");
System
.out
.println(s
.equals(s
));
boolean b
= mineEquals(s
, s
);
System
.out
.println(b
);
}
equalsIgnoreCase方法
public boolean equalsIgnoreCase(String anotherString
) {
return (this == anotherString
) ? true
: (anotherString
!= null
)
&& (anotherString
.value
.length
== value
.length
)
&& regionMatches(true, 0, anotherString
, 0, value
.length
);
}
解析:将此字符串与另一个字符串进行比较,而忽略大小写注意事项。regionMatches方法的源码很有趣的,源码里面有一个while循环,先进行未忽略大小的判断,然后进行忽略大小的判断,在忽略大小的判断中,先进行的是大写的转换进行比较,但是可能会失败【这种字体Georgian alphabet】。所以在大写转换以后的比较失败,进行一次小写的转换比较。
startsWith方法
public boolean startsWith(String prefix
) {
return startsWith(prefix
, 0);
}
endsWith方法
public boolean endsWith(String suffix
) {
return startsWith(suffix
, value
.length
- suffix
.value
.length
);
}
startsWith和endsWith最终的实现方法
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
;
if ((toffset
< 0) || (toffset
> value
.length
- pc
)) {
return false;
}
while (--pc
>= 0) {
if (ta
[to
++] != pa
[po
++]) {
return false;
}
}
return true;
}
substring方法
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
);
}
解析:返回一个字符串,该字符串是该字符串的子字符串。子字符串以指定索引处的字符开头【包含】,并且扩展到该字符串的末尾。
substring方法
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
);
}
解析:返回一个字符串,该字符串是该字符串的子字符串。子字符串从指定的beginIndex开始【包含】,并且扩展到索引endIndex-1处的字符【不包含】。
concat方法
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);
}
将指定的字符串连接到该字符串的末尾。字符串拼接。
format方法
public static String
format(String format
, Object
... args
) {
return new Formatter().format(format
, args
).toString();
}
@Test
public void test_start(){
System
.out
.println(String
.format("ha %s hh %s a %s h", "-a-", "-b-", "-c-"));
}
trim方法
public String
trim() {
int len
= value
.length
;
int st
= 0;
char[] val
= value
;
while ((st
< len
) && (val
[st
] <= ' ')) {
st
++;
}
while ((st
< len
) && (val
[len
- 1] <= ' ')) {
len
--;
}
return ((st
> 0) || (len
< value
.length
)) ? substring(st
, len
) : this;
}
返回一个字符串,其值就是这个字符串,并去掉任何首部和尾部的空白。
join方法
public static String
join(CharSequence delimiter
, CharSequence
... elements
) {
Objects
.requireNonNull(delimiter
);
Objects
.requireNonNull(elements
);
StringJoiner joiner
= new StringJoiner(delimiter
);
for (CharSequence cs
: elements
) {
joiner
.add(cs
);
}
return joiner
.toString();
}
public StringJoiner
add(CharSequence newElement
) {
prepareBuilder().append(newElement
);
return this;
}
private StringBuilder
prepareBuilder() {
if (value
!= null
) {
value
.append(delimiter
);
} else {
value
= new StringBuilder().append(prefix
);
}
return value
;
}
@Override
public StringBuilder
append(CharSequence s
) {
super.append(s
);
return this;
}
@Override
public AbstractStringBuilder
append(CharSequence s
) {
if (s
== null
)
return appendNull();
if (s
instanceof String)
return this.append((String
)s
);
if (s
instanceof AbstractStringBuilder)
return this.append((AbstractStringBuilder
)s
);
return this.append(s
, 0, s
.length());
}
将给定的字符串以给定的分割符分割并返回分隔后的字符串。
replace方法
public String
replace(CharSequence target
, CharSequence replacement
) {
return Pattern
.compile(target
.toString(), Pattern
.LITERAL
).matcher(
this).replaceAll(Matcher
.quoteReplacement(replacement
.toString()));
}
解析:用指定的字符串替换这个字符串中与之匹配的每个子字符串。替换从字符串的开头到结尾,例如,在字符串 "aaa "中用 "b "替换 "aa "将导致 "ba "而不是 “ab”。
replaceAll方法
public String
replaceAll(String regex
, String replacement
) {
return Pattern
.compile(regex
).matcher(this).replaceAll(replacement
);
}
问题:replace和replaceAll方法的区别是啥?
replaceAll支持正则表达式。
针对char的replace方法
public String
replace(char oldChar
, char newChar
) {
if (oldChar
!= newChar
) {
int len
= value
.length
;
int i
= -1;
char[] val
= value
;
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;
}
案例
@Test
public void test_matches(){
String a
= "adddfdefe";
System
.out
.println(a
.replace('d', 'b'));
}
仿写replace方法参数针对
char
仿写
public String
replace(String source
, char oldChar
, char newChar
) {
char[] value
= source
.toCharArray();
if (oldChar
!= newChar
) {
int len
= value
.length
;
int i
= -1;
char[] val
= value
;
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
);
}
}
return new String(value
);
}
intern方法
public native String
intern();
这是一个native方法。调用String#intern方法时,如果池中已经包含一个由equals方法确定的等于此String对象的字符串,则返回来自池的字符串。否则,将此String对象添加到池中,并返回这个String的引用。
ArrayList系列文章
第一篇:ArrayList中的构造方法源码在面试中被问到了…抱歉没准备好!!!告辞 第二篇:面试官让我讲ArrayList中add、addAll方法的源码…我下次再来 第三篇:工作两年还没看过ArrayList中remove、removeAll、clear方法源码的都来报道吧 第四篇: 乱披风锤法锤炼ArrayList源码中的get、set、contains、isEmpty方法!!!肝起来 第五篇: 满屏飘红,操作ArrayList的Iterator方法时竟然给我报ConcurrentModificationException异常,撸ta