java字符串知识点,在java中,字符串是作为什么出现的

  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的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。

留言与评论(共有 条评论)
   
验证码: