String

Easter79
• 阅读 745

一,String,StringBuffer,StringBuilder三者之间的关系

三个类的关系:StringBuffer和StringBuilder都继承自AbstractStringBuilder这个类,

而AbstractStringBuilder和String都继承自Object这个类(Object是所有java类的超类)

可以通过如下的部分源码看到:

String:

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    。。。。。
}

  StringBuffer:

public final class StringBuffer
    extends AbstractStringBuilder
    implements java.io.Serializable, CharSequence{
    。。。
}

  StringBuilder:

public final class StringBuilder
    extends AbstractStringBuilder
    implements java.io.Serializable, CharSequence{
    。。。。。
}

二,String是不可变类,而StringBuffer, StringBuilder是可变类

我们查看这三个类的源码,发现String类没有append()、delete()、insert()这三个成员方法,而StringBuffer和StringBuilder都有这些方法,StringBuffer和StringBuilder中的这三个方法都是通过system类的arraycopy方法来实现的,即将原数组复制到目标数组

1.String类对字符串的截取操作

public String substring(int beginIndex) {
        if (beginIndex < 0) {
            throw new StringIndexOutOfBoundsException(beginIndex);
        }
        int subLen = value.length - beginIndex;
        if (subLen < 0) {
            throw new StringIndexOutOfBoundsException(subLen);
        }
       //当对原来的字符串进行截取的时候(beginIndex >0),返回的结果是新建的对象
        return (beginIndex == 0) ? this : new String(value, beginIndex, subLen);
    }

当我们对字符串从第beginIndex(beginIndex >0) 个字符开始进行截取时,返回的结果是重新new出来的对象。所以,在对String类型的字符串进行大量“插入”和“删除”操作时会产生大量的临时变量。

2.StringBuffer与StringBuilder:

因为StringBuffer和StringBuilder都继承自这个抽象类,即AbstractStringBuilder类是StringBuffer和StringBuilder的共同父类。AbstractStringBuilder类的关键代码片段如下:

abstract class AbstractStringBuilder implements Appendable, CharSequence {
    /**
     * The value is used for character storage.
     */
    char[] value;//一个char类型的数组,非final类型,这一点与String类不同
 
    /**
     * This no-arg constructor is necessary for serialization of subclasses.
     */
    AbstractStringBuilder() {
    }
 
    /**
     * Creates an AbstractStringBuilder of the specified capacity.
     */
    AbstractStringBuilder(int capacity) {
        value = new char[capacity];//构建了长度为capacity大小的数组
    }
 
//其他代码省略……
……
}

StringBuffer:

public final class StringBuffer
    extends AbstractStringBuilder
    implements java.io.Serializable, CharSequence
{
   /**
     * Constructs a string buffer with no characters in it and an
     * initial capacity of 16 characters.
     */
    public StringBuffer() {
        super(16);//创建一个默认大小为16的char型数组
    }
 
    /**
     * Constructs a string buffer with no characters in it and
     * the specified initial capacity.
     *
     * @param      capacity  the initial capacity.
     * @exception  NegativeArraySizeException  if the {@code capacity}
     *               argument is less than {@code 0}.
     */
    public StringBuffer(int capacity) {
        super(capacity);//自定义创建大小为capacity的char型数组
    }
//省略其他代码……

StringBuilder类的构造函数与StringBuffer类的构造函数实现方式相同,此处就不贴代码了。下面来看看StringBuilder类的append方法和insert方法的代码,因StringBuilder和StringBuffer的方法实现基本上一致,不同的是StringBuffer类的方法前多了个synchronized关键字,即StringBuffer是线程安全的。所以接下来我们就只分析StringBuilder类的代码了。StringBuilder类的append方法,insert方法都是Override 父类AbstractStringBuilder的方法,所以我们直接来分析AbstractStringBuilder类的相关方法。

public AbstractStringBuilder append(String str) {
        if (str == null)
            return appendNull();
        int len = str.length();
       //调用下面的ensureCapacityInternal方法
        ensureCapacityInternal(count + len);
        str.getChars(0, len, value, count);
        count += len;
        return this;
    }
 
private void ensureCapacityInternal(int minimumCapacity) {
        // overflow-conscious code
        if (minimumCapacity - value.length > 0)
           //调用下面的expandCapacity方法实现“扩容”特性
            expandCapacity(minimumCapacity);
    }
 
   /**
     * This implements the expansion semantics of ensureCapacity with no
     * size check or synchronization.
     */
    void expandCapacity(int minimumCapacity) {
       //“扩展”的数组长度是按“扩展”前数组长度的2倍再加上2 byte的规则来扩展
        int newCapacity = value.length * 2 + 2;
        if (newCapacity - minimumCapacity < 0)
            newCapacity = minimumCapacity;
        if (newCapacity < 0) {
            if (minimumCapacity < 0) // overflow
                throw new OutOfMemoryError();
            newCapacity = Integer.MAX_VALUE;
        }
        //将value变量指向Arrays返回的新的char[]对象,从而达到“扩容”的特性
        value = Arrays.copyOf(value, newCapacity);
    }

从上述代码分析得出,StringBuilder和StringBuffer的append方法“扩容”特性本质上是通过调用Arrays类的copyOf方法来实现的。接下来我们顺藤摸瓜,再分析下Arrays.copyOf(value, newCapacity)这个方法吧。代码如下:

public static char[] copyOf(char[] original, int newLength) {
        //创建长度为newLength的char数组,也就是“扩容”后的char 数组,并作为返回值
        char[] copy = new char[newLength];
        System.arraycopy(original, 0, copy, 0,
                         Math.min(original.length, newLength));
        return copy;//返回“扩容”后的数组变量
    }

其中,insert方法也是调用了expandCapacity方法来实现“扩容”特性的,此处就不在赘述了。

接下来,分析下delete(int start, int end)方法,代码如下:

public AbstractStringBuilder delete(int start, int end) {
        if (start < 0)
            throw new StringIndexOutOfBoundsException(start);
        if (end > count)
            end = count;
        if (start > end)
            throw new StringIndexOutOfBoundsException();
        int len = end - start;
        if (len > 0) {
            //调用native方法arraycopy对value数组进行复制操作,然后重新赋值count变量达到“删除”特性
            System.arraycopy(value, start+len, value, start, count-end);
            count -= len;
        }
        return this;
    }

从源码可以看出delete方法的“删除”特性是调用native方法arraycopy对value数组进行复制操作,然后重新赋值count变量实现的

最后,来看下substring方法,源码如下 :

public String substring(int start, int end) {
        if (start < 0)
            throw new StringIndexOutOfBoundsException(start);
        if (end > count)
            throw new StringIndexOutOfBoundsException(end);
        if (start > end)
            throw new StringIndexOutOfBoundsException(end - start);
        //根据start,end参数创建String对象并返回
        return new String(value, start, end - start);
    }

三,运行速度:

总体上:String小于StringBuffer小于StringBuilder

原因:String是不可变的,为字符串常量,每次对 String 类型进行改变的时候其实都等同于生成了一个新的 String 对象,然后将指针指向新的 String 对象。这就会对程序运行产生很大的影响,因为当内存中的无引用对象多了以后,JVM的GC进程就会进行垃圾回收,这个过程会耗费很长一段时间,因此经常改变内容的字符串最好不要用 String类的对象。而如果是使用 StringBuffer 类则结果就不一样了,每次结果都会对 StringBuffer 对象本身进行操作,而不是生成新的对象,再改变对象引用。所以在一般情况下我们推荐使用 StringBuffer ,特别是字符串对象经常改变的情况下。
注意:但是在某些特殊情况下,String对象的字符串拼接其实是被JVM解释成了StringBuffer对象的拼接,所以这时候String对象的速度并不比StringBuffer对象慢。如:

String  m = "I"+"am"+"boy";
StringBuffer n = new StringBuffer("I").append("am").append("boy");

但是如果要拼接的字符串来自于不同的String对象的话,那结果就不一样了(常用),StringBuffer要比String快的多,如:

String p = "China is";
            String q = "very good";
            String t = p + q;

在运行速度方面StringBuffer<StringBuilder,这是因为StringBuffer由于线程安全的特性,常常应用于多线程的程序中,为了保证多线程同步一些线程就会遇到阻塞的情况,这就使得StringBuffer的运行时间增加,从而使得运行速度减慢;而StringBuilder通常不会出现多线程的情况,所以运行时就不会被阻塞,运行速度也自然就比StringBuffer快了。

StringBuffer和StringBuilder性能测试程序:

public static void main(String[] args) {
        StringBuilder sb = new StringBuilder();
        long builderThreadStartTime = (new Date()).getTime();//builder 多线程开始时间
        for(int i=0;i<10000000;i++){
            sb.append(i);
        }
        long builderThreadEndTime = (new Date()).getTime();//builder 多线程结束时间
        System.out.println("builder 多线程消耗时间==="+(builderThreadEndTime-builderThreadStartTime));
        StringBuffer sbf = new StringBuffer();
        long bufferThreadStartTime = (new Date()).getTime();//buffer 单线程开始时间
        for(int i=0;i<10000000;i++){
            sbf.append(i);
        }
        long bufferThreadEndTime = (new Date()).getTime();//buffer 单线程结束时间
        System.out.println("buffer 单线程消耗时间==="+(bufferThreadEndTime-bufferThreadStartTime));
    }

输出结果:

builder 多线程消耗时间===391
buffer  单线程消耗时间===611

四,线程安全与不安全

  StringBuffer是线程安全的,StringBuilder是非线程安全的

部分源码:StringBuffer:

public synchronized String substring(int start) {
        return substring(start, count);
    }

    /**
     * @throws IndexOutOfBoundsException {@inheritDoc}
     * @since      1.4
     */
    @Override
    public synchronized CharSequence subSequence(int start, int end) {
        return super.substring(start, end);
    }

    /**
     * @throws StringIndexOutOfBoundsException {@inheritDoc}
     * @since      1.2
     */
    @Override
    public synchronized String substring(int start, int end) {
        return super.substring(start, end);
    }

    /**
     * @throws StringIndexOutOfBoundsException {@inheritDoc}
     * @since      1.2
     */
    @Override
    public synchronized StringBuffer insert(int index, char[] str, int offset,
                                            int len)
    {
        toStringCache = null;
        super.insert(index, str, offset, len);
        return this;
    }

    /**
     * @throws StringIndexOutOfBoundsException {@inheritDoc}
     */
    @Override
    public synchronized StringBuffer insert(int offset, Object obj) {
        toStringCache = null;
        super.insert(offset, String.valueOf(obj));
        return this;
    }

  StringBuilder:

    @Override
    public StringBuilder append(long lng) {
        super.append(lng);
        return this;
    }

    @Override
    public StringBuilder append(float f) {
        super.append(f);
        return this;
    }

    @Override
    public StringBuilder append(double d) {
        super.append(d);
        return this;
    }

    /**
     * @since 1.5
     */
    @Override
    public StringBuilder appendCodePoint(int codePoint) {
        super.appendCodePoint(codePoint);
        return this;
    }

我们可以发现StringBuffer类中的大部分成员方法都被synchronized关键字修饰,而StringBuilder类没有出现synchronized关键字;至于StringBuffer类中那些没有用synchronized修饰的成员方法,如insert()、indexOf()等,通过源码上的注释可以知道,它们是调用StringBuffer类的其他方法来实现同步的。注意:toString()方法也是被synchronized关键字修饰的

五,synchronized关键字解析:

一>、修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象;

  • 当两个并发线程访问同一个对象中的synchronized(this){}同步代码块时,同一时间内只能有一个线程得到执行。另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块;
  • 当一个线程访问对象中的一个synchronized(this){}同步代码块时,另一个线程仍然可以访问该对象中的非synchronized(this){}同步代码块;
  • 当一个线程访问对象中的一个synchronized(this){}同步代码块时,其他线程对对象中所有其它synchronized(this){}同步代码块的访问将被阻塞。

二>、修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象;

三>、修饰一个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象;

  • 因为静态方法(或变量)是属于其所属类的,而不是属于该类的对象的,所以synchronized关键字修饰的静态方法锁定的是这个类的所有对象,即所有对象都是同一把锁。

六, String, StringBuffer, StringBuilder都能够用final关键字修饰,此处不再过多解释。

但需要注意的是:

  • final修饰的类不能被继承;
  • final修饰的方法不能被继承类重写;
  • final修饰的变量为常量,不能被改变。

七,总结:

1、String类型的字符串对象是不可变的,一旦String对象创建后,包含在这个对象中的字符系列是不可以改变的,直到这个对象被销毁。
2、StringBuilder和StringBuffer类型的字符串是可变的,不同的是StringBuffer类型的是线程安全的,而StringBuilder不是线程安全的
3、如果是多线程环境下涉及到共享变量的插入和删除操作,StringBuffer则是首选。如果是非多线程操作并且有大量的字符串拼接,插入,删除操作则StringBuilder是首选。毕竟String类是通过创建临时变量来实现字符串拼接的,耗内存还效率不高,怎么说StringBuilder是通过JNI方式实现终极操作的。
4、StringBuilder和StringBuffer的“可变”特性总结如下:
(1)append,insert,delete方法最根本上都是调用System.arraycopy()这个方法来达到目的
(2)substring(int, int)方法是通过重新new String(value, start, end - start)的方式来达到目的。因此,在执行substring操作时,StringBuilder和String基本上没什么区别。

5、为什么String是Final类型,确可以进行+等操作呢?

答:因为String的+操作实际是通过StringBuffer的append方法进行操作,然后又通过StringBuffer的toString()操作重新赋值的。

本文参考博客:https://www.cnblogs.com/Wilange/p/7570633.html

https://blog.csdn.net/hj7jay/article/details/52770174

点赞
收藏
评论区
推荐文章
blmius blmius
2年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Jacquelyn38 Jacquelyn38
2年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Stella981 Stella981
2年前
KVM调整cpu和内存
一.修改kvm虚拟机的配置1、virsheditcentos7找到“memory”和“vcpu”标签,将<namecentos7</name<uuid2220a6d1a36a4fbb8523e078b3dfe795</uuid
Wesley13 Wesley13
2年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Stella981 Stella981
2年前
HIVE 时间操作函数
日期函数UNIX时间戳转日期函数: from\_unixtime语法:   from\_unixtime(bigint unixtime\, string format\)返回值: string说明: 转化UNIX时间戳(从19700101 00:00:00 UTC到指定时间的秒数)到当前时区的时间格式举例:hive   selec
Wesley13 Wesley13
2年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Stella981 Stella981
2年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Wesley13 Wesley13
2年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
3个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这
Easter79
Easter79
Lv1
今生可爱与温柔,每一样都不能少。
文章
2.8k
粉丝
5
获赞
1.2k