java字符串知识点,在java中,字符串是作为什么出现的
在上一篇文章中,我们深入分析了字符串的内存和它的一些特性。本文深入分析了与String相关的另外两个类,StringBuilder和StringBuffer。这两个类和String有什么关系?我们先来看下面这个类图:
从图中可以看出,StringBuilder和StringBuffer都继承了AbstractStringBuilder,而AbstractStringBuilder和String实现了通用接口CharSequence。
我们知道,一个字符串是由一系列字符组成的,字符串内部是基于一个char数组(jdk9之后的byte数组)实现的,而数组通常是一个连续的内存区域,所以数组的大小需要在数组初始化的时候指定。在上一篇文章中,我们已经知道String是不可变的,因为它的内部数组被声明为final。同时通过实例化新对象实现String的字符拼接、插入、删除等操作。与String相比,我们今天所知道的StringBuilder和StringBuffer是动态的。接下来,我们一起来认识一下这两个班级。
一、StringBuilder
在StringBuilder的父类AbstractStringBuilder中可以看到以下代码:
抽象类AbstractStringBuilder实现Appendable,CharSequence { /**
*该值用于字符存储。
*/
char[]值;/**
*计数是使用的字符数。
*/
int计数;
}复制代码StringBuilder像String一样基于char数组。不同的是,StringBuilder没有最终修饰,这意味着StringBuilder可以动态更改。接下来,我们来看看StringBuilder的无参数构造方法。代码如下:
/**
*构造一个不包含任何字符的字符串生成器和一个
*初始容量为16个字符。
*/
public StringBuilder(){ super(16);
}复制代码调用该方法中父类的构造方法,在AbstractStringBuilder中看到其构造方法如下:
/**
*创建指定容量的AbstractStringBuilder。
*/
AbstractStringBuilder(int capacity){
value=新字符[容量];
}复制代码的构造方法AbstractStringBuilder内部初始化一个有容量的数组。也就是说StringBuilder默认初始化一个容量为16的char[]数组。除了无参数构造之外,StringBuilder还提供了几种构造方法。源代码如下:
/**
*构造一个不包含任何字符的字符串生成器和一个
*由{@code capacity}参数指定的初始容量。
*
* @param capacity初始容量。
* @如果{@code capacity}抛出NegativeArraySizeException
*参数小于{@code 0}。
*/
public StringBuilder(int capacity){ super(capacity);
} /**
*构造一个字符串生成器,用
*指定的字符串。字符串生成器的初始容量为
* {@code 16}加上字符串参数的长度。
*
* @param str缓存的初始内容。
*/
public StringBuilder(String str){ super(str . length()16);
append(字符串);
} /**
*构造包含相同字符的字符串生成器
*作为指定的{@code CharSequence}。的初始容量
*字符串生成器是{@code 16}加上
* {@code CharSequence}参数。
*
* @param seq要复制的序列。
*/
public StringBuilder(char sequence seq){ this(seq . length()16);
追加(序列);
}复制代码这段代码的第一个方法用指定的容量初始化一个StringBuilder。另外两个构造方法可以分别通过传入String和CharSequence来初始化StringBuilder。这两种构造方法的容量会给传入的字符串增加16的长度。
1.StringBuilder的append操作与扩容
上一篇文章已经知道StringBuilder的append方法可以用来有效地拼接字符串。append方法是如何实现的?以append(String)为例,我们可以看到StringBuilder的append调用了父类的append方法。其实不仅仅是追加。几乎所有在StringBuilder类中操作字符串的方法都是由父类实现的。append方法源代码如下:
//StringBuilder
@覆盖
public StringBuilder append(String str){ super . append(str);还这个;
}
//AbstractStringBuilder
public AbstractStringBuilder append(String str){ if(str==null)return append null();int len=str . length();
ensureCapacityInternal(count len);
str.getChars(0,len,value,count);
count=len还这个;
}复制的代码首先在append方法的第一行检查Null,当等于null时调用appendNull方法。其源代码如下:
private AbstractStringBuilder appendNull(){ int c=count;
ensurecapacityininternal(C4);final char[]value=this . value;
值[c]= n ;
值[c]= u ;
值[c]= l ;
值[c]= l ;
count=c;还这个;
}在复制代码的appendNull方法中,首先调用ensureCapacityInternal来保证字符串数组的容量充值。下面将详细分析ensureCapacityInternal的方法。接下来,您可以看到字符“null”被添加到char[]数组值中。
如上所述,StringBuilder内部数组的默认容量是16。所以拼接字符串的时候首先要保证char[]数组有足够的容量。因此,在appendNull方法和append方法中,调用ensureCapacityInternal方法来检查char[]数组是否有足够的容量。如果容量不足,将扩展阵列。ensureCapacityInternal的源代码如下:
private void ensureCapacityInternal(int minimum capacity){//溢出感知代码
if(minimum capacity-value . length 0)
expandd capacity(minimum capacity);
}将代码复制到这里。如果拼接字符串的长度大于字符串数组的长度,将调用expandCapacity进行扩展。
void expandCapacity(int minimum capacity){ int new capacity=value . length * 2 ^ 2;if(new capacity-minimum capacity 0)
newCapacity=minimumCapacityif(new capacity 0){ if(minimum capacity 0)//溢出
抛出new out of memory error();
newCapacity=整数。MAX _ VALUE
}
value=Arrays.copyOf(value,new capacity);
}复制代码expandCapacity的逻辑也很简单。首先,将原始数组的长度乘以2,再加上2,计算出扩展后的数组长度。然后,如果newCapacity小于minimumCapacity,则将minimumCapacity值分配给newCapacity。因为这里有多个地方可以调用expandCapacity方法,所以添加了这段代码以确保安全性。
接下来的代码非常有趣。newCapacity和minimumCapacity还有可能小于0吗?即使minimumCapacity小于0,也会引发OutOfMemoryError异常。实际上这里小于0,因为越界了。我们知道,计算机中存储的一切都是二进制的,乘以2相当于左移一位。以byte为例。一个字节有8位,有符号数中最左边的位是符号位。正数的符号位是0,负数的符号位是1。那么一个字节所能代表的大小范围就是[-128~127],如果一个数大于127就越界了,也就是最左边的符号位会被左边的第二位代替,出现负数。当然不是byte而是int,但原理是一样的。
另外,在这个方法的最后一句,通过Arrays.copyOf进行数组复制,实际上,Arrays.copyof在上一篇文章中已经看到了。这里,我们来分析一下这个方法,看看源代码:
public static char[]copy of(char[]original,int new length){ char[]copy=new char[new length];
System.arraycopy(原始,0,副本,0,
Math.min(原始长度,新长度));返回副本;
}复制代码咦?复制关于方法中竟然也去实例化了一个对象!那不会影响性能吗?莫慌,看一下这里仅仅是实例化了一个新长度长度的空数组,对于数组的初始化其实仅仅是指针的移动而已,浪费的性能可谓微乎其微。接着这里通过System.arraycopy的当地的方法将原数组复制到了新的数组中。
2.StringBuilder的subString()方法toString()方法
StringBuilder中其实没有子链方法,子串的实现是在StringBuilder的父类AbstractStringBuilder中的。它的代码非常简单,源码如下:
public String substring(int start,int end){ if(start 0)throw new StringIndexOutOfBoundsException(start);if(结束计数)抛出新的StringIndexOutOfBoundsException(end);如果(开始和结束)抛出新的StringIndexOutOfBoundsException(end-start);返回新字符串(值,开始,结束-开始);
}复制代码在进行了合法判断之后,子串直接实例化了一个线对象并返回。这里和线的子链实现其实并没有多大差别。
而StringBuilder的转换对象为字符串方法的实现其实更简单,源码如下:
@覆盖
公共字符串toString() { //创建一个副本,不共享数组
返回新字符串(值,0,计数);
}复制代码这里直接实例化了一个线对象并将StringBuilder中的价值传入,我们来看下字符串(值,0,计数)这个构造方法:
public String(char value[],int offset,int count){ if(offset 0){ throw new StringIndexOutOfBoundsException(offset);
} if(count 0){ throw new StringIndexOutOfBoundsException(count);
} //注意:偏移量或计数可能接近-11。
if(偏移值。length-count){ throw new StringIndexOutOfBoundsException(offset count);
}这个。值=数组。复印费(值,偏移量,偏移量计数);
}复制代码可以看到,在线的这个构造方法中又通过Arrays.copyOfRange方法进行了数组拷贝,Arrays.copyOfRange的源码如下:
范围的公共静态char[]副本(char[]original,int from,int to){ int new length=to-from;if (newLength 0)抛出新的IllegalArgumentException(从""到);char[]copy=new char[新长度];
System.arraycopy(原始,来自,复制,0,
Math.min(original.length - from,新长度));返回副本;
}复制代码Arrays.copyOfRange与数组。复制Of类似,内部都是重新实例化了一个char[]数组,所以线构造方法中的这个值与传入进来的价值不是同一个对象。意味着StringBuilder在每次调用toString的时候生成的String对象内部的char[]数组并不是同一个!这里立一个Falg!
3.StringBuilder的其它方法
StringBuilder除了提供了附加方法、子串方法以及转换对象为字符串方法外还提供了还提供了插入(插入)删除(删除、删除字符)替换(替换)查找(索引)以及反转(反向)等一些列的字符串操作的方法。但由于实现都非常简单,这里就不再赘述了。
二、StringBuffer
在第一节已经知道,StringBuilder的方法几乎都是在它的父类AbstractStringBuilder中实现的。而字符串缓冲器同样继承了AbstractStringBuilder,这就意味着字符串缓冲器的功能其实跟StringBuilder并无太大差别。我们通过字符串缓冲器几个方法来看
/**
* toString返回的最后一个值的缓存。清除
*每当字符串缓冲器被修改时。
*/
private transient char[]tostring缓存;@覆盖
公共同步StringBuffer append(String str) {
toStringCache=nullsuper。append(字符串);还这个;
} /**
* @ throws StringIndexOutOfBoundsException { @ inherit doc }
* @从1.2开始
*/
@覆盖
公共同步字符串缓冲区删除(int start,int end) {
toStringCache=nullsuper.delete(开始,结束);还这个;
} /**
* @ throws StringIndexOutOfBoundsException { @ inherit doc }
* @从1.2开始
*/
@覆盖
public synchronized string buffer insert(int index,char[] str,int offset,int len)
{
toStringCache=nullsuper.insert(index,str,offset,len);还这个;
} @覆盖
公共同步字符串substring(int start){ return substring(start,count);
}
//.复制代码以查看synchronized关键字被添加到StringBuffer的方法中,这意味着StringBuffer的所有操作都是线程安全的。所以在多线程字符串操作的情况下应该首选StringBuffer。
另外,我们注意到在StringBuffer的方法中比StringBuilder多了一个toStringCache的成员变量。从源代码中我们可以看到toStringCache是一个char[]数组。它的注释是这样描述的:
我们再来看看StringBuffer中的方法,发现只要操作过StringBuffer中char[]数组的方法都被操作过,toStringCache就已经被设置为空了!但是,没有操作字符数组的方法不会清空它。此外,评论中还提到了toString方法,我们不妨看看StringBuffer中的toString。源代码如下:
@覆盖
公共同步字符串toString(){ if(toString cache==null){
tostring cache=arrays . copyofrage(value,0,count);
}返回新字符串(toStringCache,true);
}复制代码。在这个方法中,首先判断当toStringCache为null时,将由Arrays.copyOfRange方法进行赋值,上面已经分析过了。他将重新实例化一个char[]数组,并将原始数组赋给新数组。这有什么影响?仔细想想,不难发现多次调用StringBuffer的toString方法生成的String对象共享同一个字符数组——toString cache。这里有一点StringBuffer和StringBuilder的区别。至于为什么在StringBuffer中这样做,其实也没有明确的原因。可以参考StackOverRun。
055-79000中的一个答案:
三、 总结
本文到此结束。055-79000通过两篇文章深入分析String、StringBuilder和StringBuffer。这个内容其实很简单,只要花一点时间看源代码就很容易理解。当然,如果你还没有看过这部分源代码,相信这篇文章可以帮到你。不管怎样,我相信你看完这篇文章还是能有所收获的。了解这些知识可以帮助我们在开发中更好的选择字符串。同时,这个内容也是面试官经常问的。相信你看完这篇文章,回答面试官的问题会绰绰有余。
也就是说,前车之鉴,后车之鉴(2)多了解Java中字符串的细节。请多关注我们的其他相关文章!
郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。