本文已参与「新人创作礼」活动,一起开启掘金创作之路。
本篇文章首发在CSDN,所以图片上也有CSDN的水印,文章均为原创,并不存在侵权行为
String与StringBuilder与StringBuffer
String解析
前言
String是一个引用类型,不是基本类型,底层是由char数组组成的,我们说String对象是不可变的,一经创建无法再进行修改,那我们平常为什么又可以使用concat(String s)来进行字符串的拼接呢?
其实在源码中我们可以发现其实是新new了一个String对象,对它进行赋值,返回的是新的String对象,已经不是原来的对象了。
String中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); //从这里我们可以看出这里是新new了一个对象
}
而对于StringBuffer和StringBuilder来说,在执行它们的拼接方法之后,地址并没有改变,这是因为它们底层的char数组是可以进行扩容的,仍然是原来的地址。(下文我们会详细地介绍StringBuffer和StringBuilder)
String对象的继承体系
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence
- String被final修饰,意味着它不可以被继承
- 实现了Serializable接口,意味着它可以被序列化和反序列化
- 实现了Comparable接口,意味着可以进行字符串对象的比较
- 实现了CharSequence接口,表明这是一个字符串
成员变量
private final char value[]; //被final修饰,表示只可以被赋值一次
private int hash; // Default to 0,哈希值,默认为0
构造方法
//无参的构造方法
public String() {
this.value = "".value; //这里要注意:full并不是""
}
//传入一个String对象的构造方法,注意利用这个构造方法构造出来的对象和传入的对象并不指向同一块地址
public String(String original) {
this.value = original.value;
this.hash = original.hash; //赋值哈希值
}
//传入一个char数组构造String对象
public String(char value[]) {
this.value = Arrays.copyOf(value, value.length);
}
//传入一个byte数组构造String对象
public String(byte bytes[]) {
this(bytes, 0, bytes.length);
}
//传入一个StringBuffer构造String对象
public String(StringBuffer buffer) {
synchronized(buffer) {
this.value = Arrays.copyOf(buffer.getValue(), buffer.length());
}
}
//传入一个StringBuilder对象构造String对象
public String(StringBuilder builder) {
this.value = Arrays.copyOf(builder.getValue(), builder.length());
}
成员方法
int length()
返回String对象的长度
public int length() {
return value.length;
}
boolean isEmpty()
判断字符串是否为空字符串
public boolean isEmpty() {
return value.length == 0;
}
char charAt(int index)
获取索引处的char字符
public char charAt(int index) {
if ((index < 0) || (index >= value.length)) { //判断index是否非法
throw new StringIndexOutOfBoundsException(index);
}
return value[index];
}
byte[] getBytes()
将String对象转化为字节数组并返回
public byte[] getBytes() {
return StringCoding.encode(value, 0, value.length);
}
boolean equals(Object anObject)
判断传入的参数和自身是否相等(内容上相等)
String重写了Object的equals,将equals重写成了比较内容是否相等的方法(在Object中equals是比较地址的方法)
public boolean equals(Object anObject) {
if (this == anObject) { //地址上相等,内容一定相等,直接返回true
return true;
}
if (anObject instanceof String) { //判断是不是String类型(或其父类)
String anotherString = (String)anObject; //强转为String类型
int n = value.length; //到这里就说明是String类型的了,得到字符串长度
if (n == anotherString.value.length) { //长度相等才可以继续比较,不相等直接中断返回false
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) { //一个一个比较
if (v1[i] != v2[i])
return false; //不等返回false
i++;
}
return true; //可以执行到这里说明一定相等,返回true就好了
}
}
return false;
}
boolean 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);
}
int compareTo(String anotherString)
比较两个字符串的大小(字典序比较)
长度不等则底层char数组一个一个从前往后比较,直到一个char数组中的字符和另一个char数组中的对应位置字符不等,
如果直到一个char数组为空前面的对应位置的字符都相等则比较字符串长度
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; //前面都相等进行长度比较
}
compareToIgnoreCase(String str)
忽略大小写比较两个String对象的大小
public int compareToIgnoreCase(String str) {
return CASE_INSENSITIVE_ORDER.compare(this, str);
}
String substring(int beginIndex, int endIndex)
截取String对象(从下标为beginIndex到下标为endIndex – 1处,这里不包括endIndex处的字符)
从下面的源码中我们也可以看出所谓的截取字符串也是创建了一个新的String对象,这也再次印证了String字符串是不可变的这句话
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); //从这里可以看出截取返回的String是新new的
}
int hashcode()
因为String没有继承类,所以其父类就是Object,Object中的hashcode()方法返回的是地址,而String重写了hashcode()返回的是内容的hash,也就是说,如果两个String对象内容一样,则使用hashcode()返回的值也一定相等
这里的hashcode()使用的是内容作为哈希,我觉得应该是为了之后的键值对映射,为了使内容相等的String键可以映射到一块相同的索引处,如果不重写hashcode()则内容相同的String不会映射到同一块地址。在选取基数时选取质数为基数哈希冲突更小,而至于为什么选取31作为基数呢?我觉得是为了哈希冲突的概率更小吧。总之,一切为了减小哈希冲突的概率
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;
}
常量池
在java中==比较的是地址
当我们使用双引号直接创建字符串时,其实会先创建字符串,再把字符串放入常量池中,当接下来如果再使用双引号创建相同的字符串时,会直接从常量池中取出,而不会重新创建,节省了空间。如下的结果也说明了常量池的存在
而如果使用new创建的字符串(即使创建的是常量池中的字符串,也不会从常量池中取),自己会在堆中新创建一个
那么尽然是new的,肯定和原来内容相等的字符串通过==比较出的结果肯定不一致,如下
最后,我们说说String中利用+号拼接字符串的原理
对于两个常量的拼接就是使用双引号创建的字符串
//字符串常量拼接
public class Main {
public static void main(String[] args) {
String s1 = "abc";
String s2 = new String("abc");
String s3 = "abc" + "edf"; //两个常量使用+号拼接
System.out.println(s1 == s2);
}
}
//反编译之后
public class Main {
public Main() {
}
public static void main(String[] args) {
String s1 = "abc";
String s2 = new String("abc");
String s3 = "abcedf"; //这里可以看出本质是使用双引号创建的
System.out.println(s1 == s2);
}
}
对于两个变量的拼接,本质是new了一个StringBuilder对象
//源代码
public class Main {
public static void main(String[] args) {
String s1 = "abc";
String s2 = new String("abc");
String s3 = s1 + s2; //两个变量使用+号拼接
}
}
//反编译
public class Main {
public Main() {
}
public static void main(String[] args) {
String s1 = "abc";
String s2 = new String("abc");
(new StringBuilder()).append(s1).append(s2).toString();//本质是new了一个StringBuilder(可变字符串)进行拼接
}
}
因为StringBuffer与StringBuilder的源码极其类似,我们把这两个类放在一起讲
StringBuffer与StringBuilder解析
前言
我们说StringBuffer和StringBuilder是可变字符串,其底层是一个char数组,那么我们是如何来实现这样一个可变字符串类呢?我们一起来看一看吧!
继承体系
//这是StringBuffer的继承体系
public final class StringBuffer
extends AbstractStringBuilder
implements java.io.Serializable, CharSequence
//这是StringBuilder的继承体系
public final class StringBuilder
extends AbstractStringBuilder
implements java.io.Serializable, CharSequence
- StringBuffer和StringBuilder被final修饰,意味着它不可以被继承
- 继承了AbstractStringBuilder父类,具有了一些成员变量,也具有append(),insert(),delete()等方法
- 实现了Serializable接口,表明可以序列化和反序列化
- 实现了CharSequence,表明这是一个字符串
成员变量
在StringBuffer与StringBuilder只有一个记录版本的成员变量,对于我们理解这两个类没有太大关系,那么我们到它的父类看看吧!
下面是它的父类AbstractStringBuilder的成员变量
//注意:这里未被final修饰,表名可以被修改
char[] value; //底层的char数组,是动态的,当容量不足时,会对它进行扩容
int count; //实际存储的字符数量
构造方法
StringBuffer的构造方法
//无参构造,字符数组长度为16
public StringBuffer() {
super(16);
}
//传入一个容量参数,指定字符数组的长度
public StringBuffer(int capacity) {
super(capacity);
}
//传入一个String对象,开辟的数组长度是传入的String对象的长度+16
public StringBuffer(String str) {
super(str.length() + 16);
append(str); //这里使用了StringBuilder重写的append方法,但加上了synchronized其本质也是调用了父类的append方法
}
StringBuilder的构造方法
//无参构造,字符数组长度为16
public StringBuilder() {
super(16);
}
//传入一个容量参数,指定字符数组的长度
public StringBuilder(int capacity) {
super(capacity);
}
//传入一个String对象,开辟的数组长度是传入的String对象的长度+16
public StringBuilder(String str) {
super(str.length() + 16);
append(str); //这里使用了StringBuilder重写的append方法,其本质也是调用了父类的append方法
}
既然StringBuffer与StringBuilder的构造方法基本上都是调用父类AbstractStringBuilder的构造方法,那我们来看一下父类AbstractStringBuilder的构造方法吧!
AbstractStringBuilder的构造方法
//无参构造
AbstractStringBuilder() {
}
//字符数组的长度为指定长度
AbstractStringBuilder(int capacity) {
value = new char[capacity];
}
成员方法
append()
StringBuffer的append(方法)
进行拼接(可以拼接String,StringBuffer,StringBuilder,boolean,int,float),返回类型都是StringBuffer
我们可以看到这些拼接方法其实都是调用父类AbstractStringBuilder的拼接方法,但是在StringBuffer中这些方法都使用synchronized修饰,表明是线程安全的。
//拼接Object
@Override
public synchronized StringBuffer append(Object obj) {
toStringCache = null;
super.append(String.valueOf(obj));
return this;
}
//拼接String
@Override
public synchronized StringBuffer append(String str) {
toStringCache = null;
super.append(str);
return this;
}
//拼接StringBuffer,这个拼接是子类自己定义的,但是本质还是调用父类的拼接方法
public synchronized StringBuffer append(StringBuffer sb) {
toStringCache = null;
super.append(sb);
return this;
}
//拼接AbstractStringBuilder(可以接收StringBuilder)
@Override
synchronized StringBuffer append(AbstractStringBuilder asb) {
toStringCache = null;
super.append(asb);
return this;
}
//拼接字符数组
@Override
public synchronized StringBuffer append(char[] str) {
toStringCache = null;
super.append(str);
return this;
}
//拼接boolean类型的
@Override
public synchronized StringBuffer append(boolean b) {
toStringCache = null;
super.append(b);
return this;
}
//拼接字符类型的
@Override
public synchronized StringBuffer append(char c) {
toStringCache = null;
super.append(c);
return this;
}
StringBuilder的append()方法
与StringBuffer不同的是,这些方法没有使用synchronized修饰,表明这些方法不是线程同步的,但是其内部还是调用的是父类AbstractStringBuilder的拼接方法。
//拼接Object
@Override
public StringBuilder append(Object obj) {
return append(String.valueOf(obj));
}
//拼接String
@Override
public StringBuilder append(String str) {
super.append(str);
return this;
}
//拼接StringBuffer
@Override
public StringBuilder append(StringBuffer sb) {
super.append(sb);
return this;
}
//拼接字符数组
@Override
public StringBuilder append(char[] str) {
super.append(str);
return this;
}
//拼接boolean
@Override
public StringBuilder append(boolean b) {
super.append(b);
return this;
}
//拼接字符
@Override
public StringBuilder append(char c) {
super.append(c);
return this;
}
既然StringBuffer与StringBuilder的拼接其实都是调用父类AbstractStringBuilder的拼接方法,那我们来看看父类的拼接方法吧!
AbstractStringBuilder的append()方法
append()方法内部基本都是确保容量(确保容量方法里面有扩容方法),然后追加。
public AbstractStringBuilder append(Object obj) {
return append(String.valueOf(obj));
}
public AbstractStringBuilder append(String str) {
if (str == null)
return appendNull();
int len = str.length();
ensureCapacityInternal(count + len); //确保容量
str.getChars(0, len, value, count);
count += len;
return this;
}
public AbstractStringBuilder append(StringBuffer sb) {
if (sb == null)
return appendNull();
int len = sb.length();
ensureCapacityInternal(count + len); //确保容量
sb.getChars(0, len, value, count);
count += len;
return this;
}
AbstractStringBuilder append(AbstractStringBuilder asb) {
if (asb == null)
return appendNull();
int len = asb.length();
ensureCapacityInternal(count + len); //确保容量
asb.getChars(0, len, value, count);
count += len;
return this;
}
public AbstractStringBuilder append(boolean b) {
if (b) {
ensureCapacityInternal(count + 4); //确保容量
value[count++] = 't';
value[count++] = 'r';
value[count++] = 'u';
value[count++] = 'e';
} else {
ensureCapacityInternal(count + 5); //确保容量
value[count++] = 'f';
value[count++] = 'a';
value[count++] = 'l';
value[count++] = 's';
value[count++] = 'e';
}
return this;
}
AbstractStringBuilder中的扩容方法
private void ensureCapacityInternal(int minimumCapacity) {
// overflow-conscious code
if (minimumCapacity - value.length > 0) { //容量不够,进行扩容
value = Arrays.copyOf(value,
newCapacity(minimumCapacity));
}
}
private int newCapacity(int minCapacity) {
// overflow-conscious code
int newCapacity = (value.length << 1) + 2; //扩容为原来的两倍加2
if (newCapacity - minCapacity < 0) { //扩容之后还是不够,以最小需要的容量为准
newCapacity = minCapacity;
}
return (newCapacity <= 0 || MAX_ARRAY_SIZE - newCapacity < 0)//扩容之后的容量小于等于0或大于最大容量
? hugeCapacity(minCapacity) //执行hugeCapacity方法
: newCapacity; //直接返回
}
private int hugeCapacity(int minCapacity) {
if (Integer.MAX_VALUE - minCapacity < 0) { // overflow
throw new OutOfMemoryError();
}
return (minCapacity > MAX_ARRAY_SIZE) //最小需要的容量大于最大容量返回最小需要的容量
? minCapacity : MAX_ARRAY_SIZE; //最小需要的容量小于最大容量返回最大容量
}
delete(int start, int end)方法
StringBuffer的delete方法
移除起始位置从下标start开始,end终止(不包括end)的字符
@Override
public synchronized StringBuffer delete(int start, int end) {
toStringCache = null;
super.delete(start, end);
return this;
}
StringBuilder的delete方法
移除起始位置从下标start开始,end终止(不包括end)的字符
@Override
public StringBuilder delete(int start, int end) {
super.delete(start, end);
return this;
}
本质都是调用了父类AbstractBuilder的delete方法
AbstractBuilder的delete方法
public AbstractStringBuilder delete(int start, int end) {
if (start < 0) //其实索引小于0,抛异常
throw new StringIndexOutOfBoundsException(start);
if (end > count) //删除的终止处大于数组长度,将end赋值为数组长度
end = count;
if (start > end) //起始索引大于终止索引,抛异常
throw new StringIndexOutOfBoundsException();
int len = end - start;
if (len > 0) {
System.arraycopy(value, start+len, value, start, count-end); //调用arraycopy进行拷贝完成删除操作
count -= len;
}
return this;
}
replace(int start, int end, String str)方法
StringBuffer的replace方法
@Override public synchronized StringBuffer replace(int start, int end, String str) {
toStringCache = null;
super.replace(start, end, str);
return this;
}
StringBuilder的replace方法
@Override public StringBuilder replace(int start, int end, String str) {
super.replace(start, end, str);
return this;
}
都是调用了父类AbstractBuilder的replace方法
AbstractBuilder的replace(int start, int end, String str)
public AbstractStringBuilder replace(int start, int end, String str) {
if (start < 0)
throw new StringIndexOutOfBoundsException(start);
if (start > count)
throw new StringIndexOutOfBoundsException("start > length()");
if (start > end)
throw new StringIndexOutOfBoundsException("start > end");
if (end > count)
end = count;
int len = str.length();
int newCount = count + len - (end - start);
ensureCapacityInternal(newCount); //确保容量
System.arraycopy(value, end, value, start + len, count - end); //进行赋值
str.getChars(value, start);
count = newCount;
return this;
}
toString方法
StringBuffer的toString方法
将StringBuffer对象转化为String对象
@Override
public synchronized String toString() {
if (toStringCache == null) {
toStringCache = Arrays.copyOfRange(value, 0, count);
}
return new String(toStringCache, true);
}
StringBuilder的toString方法
将StringBuilder转化为String对象
@Override
public String toString() {
// Create a copy, don't share the array
return new String(value, 0, count);
}
总结
-
String是不可变字符串,底层char数组使用final修饰。String的优点,使用线程池,可以节省空间。
-
StringBuffer与StringBuilder是可变字符串(底层char数组长度可变),两者的主要区别是StringBuffer的方法都加上了synchronized修饰,表明StringBuffer是线程安全的,而StringBuilder是线程不安全的。
-
StringBuff与StringBu内部的许多方法其实都是调用父类AbstractStringBuilder的方法,在进行频繁的字符串拼接时,建议使用StringBuilder与StringBuilder会比String更快些。
-
转化
在使用构造方法时转化,其中一个可以传入另外两种类对象的的任意一个(当然也可以穿入同属于同一种类的对象)
在创建完之后转化,StringBuffer和StringBuilder转化为String——>使用toString()方法
虽然String不可以转化为StringBu或StringBuilder,但是String可以使用+号拼接StringBuffer和StringBuider
StringBuffer可以使用append方法拼接String或StringBuilder
StringBuilder可以使用append方法拼接String或StringBuffer
今天的文章String与StringBuffer与StringBuilder解析分享到此就结束了,感谢您的阅读。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/21434.html