String、StringBuffer、StringBuilder

一 、String类的特性

  • String类:代表字符串,Java 程序中的所有字符串字面值(如 “abc” )都作

    为此类的实例实现

  • String是一个final类,代表不可被继承

  • 字符串是常量,在创建之后不能更改(不可变性)

  • String底层使用char value[]数组来保存多个字符

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    
    /** char[]数组存储字符 */
    private final char value[];

    /** hash值*/
    private int hash; // Default to 0

    ...
}

二、String在内存的结构

2.1 字面量的定义方式

String str1 = “abc” ;

String str2 = “hello” ;

方法区中的常量池创建常量"abc",“helloe”,不可变,str1,str2分别指向该内存地址

String、StringBuffer、StringBuilder_第1张图片

方法区中的常量只能定义唯一,相同的字面量不会定义两个,例如:

String str1 = “abc” ;

String str2 = “abc” ;

会指向常量池中的同一个常量,此时,str1 ==s tr2

String、StringBuffer、StringBuilder_第2张图片

这也表现了String的不可变性,str1与str2都指向常量池同一个字符数组,若str1=“hello”,不会影响到str2

2.2 String不可变性表现

String底层使用char value[]数组来保存多个字符

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    
    /** char[]数组存储字符 */
    private final char value[];
    

注意:被final修饰的char数组不可变,是value指向char[]地址不可变,而非char[]的值不可变

	public static void demo3(){
        final char[] value ={'a','b','c'};
        value[0] = 'd' ;
        value[1] = 'e' ;
        value[2] = 'f' ;
        System.out.println(value);

    }	

输出结果为:def

String不可变表现:

  • String只有在创建时才给char[]数组赋值,且长度确定
  • 一旦初始化后,内部没有提供get或者任何方法可以修改char[]数组的内容或者重新赋值
  • String类还是final无法通过继承来修改char[]数组

内部所有方法都是返回一个新的String对象(有些特殊情况例外)

例如:

	public String replace(char oldChar, char newChar) {
        
        //若oldChar==newChar,条件不成立,直接返回this
        if (oldChar != newChar) {
            int len = value.length;
            int i = -1;
            char[] val = value; 

            //遍历char数组,寻找oldChar的位置
            while (++i < len) {
                if (val[i] == oldChar) {
                    break;
                }
            }
            //若i==len则表示没有oldChar该字符,返回this
            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;
    }	

读源码得知replace方法返回当前this对象的情况有两种

  • oldChar与newChar相同,返回this

  • 若oldChar不存在char[]数组中,返回this

    	public static void demo3(){
            String s1 = "abc" ;
            String s2 = s1.replace('b', 'b');
            String s3 = s1.replace('e', 'g');
    
            System.out.println(s1==s2);
            System.out.println(s1==s3);
    
        }	
    

    执行结果都为true

2.3 String对象的创建

字面量

String str = “hello” ;

指向常量池中的"hello"字符数组地址

new 创建

new 关键字创建的对象都存在堆中

  • String str = new String();

    本质上this.value=new char[0]

  • String str = new String(char[] a);

    本质上this.value = Arrays.copyOf(a,a.length)

  • String str = new String(String original);

    本质上this.value=original.value

  • String str = new String(char[] c,int startIndext,int count);

    本质上this.value = Arrays.copyOfRange(value, startIndext, startIndext+count);

2.4 String赋值

由于String的不可变性,任何重新赋值操作都是重新创建对象

String str = “hello” ;

str = “helloworld” ;

重新赋值不是在字符数组"hello"上修改,而是在常量池总创建新的字符数组"helloworld",然后str指向"helloworld"的地址

String、StringBuffer、StringBuilder_第3张图片

2.5 String 在内存中的状态(重点)

String str ;

未初始化

未指向内存任何地址

String、StringBuffer、StringBuilder_第4张图片

String str = null ;

初始化为null ;

JVM会让这个str 变量指向一个不确定类型的空对象内存(即null内存, 在静态区域永久固定的null内存 ) ,假如输出System.out.println(str),则会输出null。

null是一个固定的不确定类型的内存,即可以看做是什么类型也不是,也没有继承Object,也没有toString()方法,所以这句代码不会默认调用str的toString()方法

String、StringBuffer、StringBuilder_第5张图片

在Java中,未初始化变量是不能使用的,只是声明一个变量,告诉JVM可能要用到这个变量,在内存中没有指向任何地址

String str ;	
str.replace('a', 'b');

编译不能通过

初始化为null,变量在内存中指向null内存,所以可以使用

String str = null ;
str.replace('a', 'b');

编译可以,但 运行时又会报错空指针异常 ,因为null不属于任何一个对象

String str = “” ;

初始化为空字符串常量

相当于this.value = new char[0] ;

此时str已经指向方法区中的常量池的字符数组""内存地址,长度为0

String、StringBuffer、StringBuilder_第6张图片

String str = “hello” ;

指向常量池中"hello"字符数组常量的内存地址

String、StringBuffer、StringBuilder_第7张图片

String str1 = new String("")

String str2 = new String();

由源码可知,两者都是空字符串

public String() {
        this.value = "".value;
}

public String(String original) {
        this.value = original.value;
        this.hash = original.hash;
}

new 关键字都在堆内存创建一个对象,str1和str2都是指向堆中,不是同一个内存地址,所以 str1==str2 结果为false

但str1和str2在堆中的对象都是指向常量池中的同一个字符数组常量地址

String、StringBuffer、StringBuilder_第8张图片

String str = new String(“hello”);

首先判断方法区中的常量池是否为"hello"字符数组,若没有,会先在常量池中创建"hello"字符数组

然后在堆内存创建一个对象,然后str指向堆中的对象的地址,而堆内存中的对象会指向常量池中的"hello"地址

String、StringBuffer、StringBuilder_第9张图片

比较:

String str1 = "hello" ;
String str2 = new String("hello") ;
str1 == str2 ;

所以比较结果为false

String、StringBuffer、StringBuilder_第10张图片

String str = “hello” + “world” ;

会在常量池创建三个字符数组,分别是"hello",“world”,“helloworld”,然后str指向"helloworld"字符数组的地址

String、StringBuffer、StringBuilder_第11张图片

准确来说:可能会在内存中创建最多3个对象,可能为0,1,2,3个

比较:

String str1 = "helloworld" ;
String str2 = "hello" + "world" ;
str1==str2 ;

由于str1与str2都是指向常量池中的"helloworld"字符数组的地址,所以比较结果为true

若说两者有所不同,那就是前者直接在常量池中生成"helloworld",后者则会先生成"hello",“world”,然后再生成"helloworld"(浪费资源)

所以使用String尽可能少使用拼接"+",或者使用StringBuffer、StringBuilder

String在对象中的存储

public class Pet{
    private String name ;
    private Integer age ;
    
    ...
}

	public static void demo3(){
        Pet p1 = new Pet("json", 12);
        Pet p2 = new Pet("json", 12);
        boolean b = p2.getName() == p1.getName();//true
    }	

比较结果为true,两者都是字面量的定义,都是指向常量池中的"json"的地址

String、StringBuffer、StringBuilder_第12张图片

Pet p1 = new Pet(new String(“json”) , 12 ) ; 两者比较自然为false

String、StringBuffer、StringBuilder_第13张图片

所以:String尽量使用字面量定义,使用new关键字会额外在堆中创建对象

2.6 String比较

以下讨论的情况均为内存地址,而非内容,若调用equals()方法比较内容,结果全为true

	public static void demo02(){

        String s1 = "hello" ;
        String s2 = "world" ;
        final String s3 = "hello" ; 
        
        String s4 = "helloworld" ;
        String s5 = "hello"+"world" ;
        String s6 = s1 + "world" ;
        String s7 = "hello" + s2 ;
        String s8 = s1 + s2 ;
        
        String s9 = s3 + "world" ; //使用final修饰也为常量,所以在常量池中
        String s10 = (s1 + s2).intern() ;

        System.out.println(s4 == s5);  //true
        System.out.println(s4 == s6);  //false
        System.out.println(s4 == s7);  //false
        System.out.println(s4 == s8);  //false
        System.out.println(s4 == s9);  //true
        System.out.println(s3 == s10); //true
    }		

结论:

  • 常量与常量的拼接结果在常量池,且常量池不会存在相同内容的常量
  • 只要有变量的拼接,结果存放在堆中
  • 拼接结果调用intern()方法,返回值就在常量池中(会将常量池中的常量赋给引用)

intern()方法会将String变量从 指向堆空间中 转为 指向常量池中的字符数组常量

String、StringBuffer、StringBuilder_第14张图片

相同变量拼接

	public static void demo3(){
        String str1 = "hello" ;
        String str2 = "world" ;
        
        String str3 = str1 + str2 ;
        String str4 = str1 + str2 ;
        
        String str5 = str1 + "world" ;
        String str6 = str1 + "world" ;

        System.out.println(str3 == str4);//false
        System.out.println(str5 == str6);//false
    }	

即使str3和str4是相同的变量拼接,或者str5与str6是相同的变量与常量拼接,(可以看做new)都会在堆中创建不同的对象,然后该对象指向常量池中的地址

所以两组比较都为false

String、StringBuffer、StringBuilder_第15张图片

String str1 = “hello” ;

str1 = str1 + “world” ;

该情况生成的对象自然也存在堆中

说明:

  • 实际上原来的"hello"字符串对象已经丢弃了,在堆空间中产生了一个字符串str1 +“world”(也就是"helloworld")
  • 多次执行这些改变串内容的操作,会导致大量副本 字符串对象存留在内存中,降低效率。
  • 如果这样的操作放到循环中,会极大影响 程序的性能。

String、StringBuffer、StringBuilder_第16张图片

2.7 String 使用陷阱

三、String的常用方法

3.1 增加操作

  • String concat(String str)

    将指定字符串连接到此字符串的结尾。 等价于用"+"

String str = "hello" ;
str = str.concat("world");

源码:

	//参数不能为null
	public String concat(@NotNull() String str) {
        //获取拼接的字符串长度
        int otherLen = str.length();
        //若长度为0,直接返回当前对象
        if (otherLen == 0) {
            return this;
        }
        
        //获取原字符串长度
        int len = value.length;
        
		//复制新的字符数组
        char buf[] = Arrays.copyOf(value, len + otherLen);
        str.getChars(buf, len);
        
        //返回创建新的String
        return new String(buf, true);
    }

若拼接的字符串为null,则抛异常,若拼接的字符串为长度为0,直接返回当前对象,其他返回一个在堆中的新的String

3.2 替换操作

  • String toLowerCase()

    将 String 中的所有字符转换为小写

  • String toUpperCase()

    将 String 中的所有字符转换为大写

  • String replace(char oldChar , char newChar)

    用 newChar 替换此单个字符中出现的所有 oldChar

  • String replace(CharSequence target , CharSequence replacement)

    使用replacement字符序列替换String中匹配的target字符序列

3.3 查询操作

根据下标返回字符(字符串)

  • char charAt(int indext)

    返回某索引处的字符return value[index]

  • String substring(int beginIndext)

    返回一个新的字符串,从下标beginIndex开始截取到最后的一个子字符串

  • String substring(int beginIndex , int endIndex)

    返回一个新的字符串,从下标beginIndex开始截取到endIndex(不包含)的一个子字符串 [ beginIndex , endIndex )

根据字符(字符串)返回下标

  • int indexOf(String str)

    返回指定子字符串在此字符串中**第一次出现的下标

  • int indexOf(String str , int fromIndex)

  • 返回指定子字符串在此字符串中第一次出现处的索引,从指定的fromIndex位置开始

  • int lastIndexOf(String str)

    返回指定子字符串在此字符串中最后出现的下标

  • int lastIndexOf(String str , int fromIndex)

    返回指定子字符串在此字符串最后出现处的索引,从指定的fromIndex索引开始反向搜索

3.4 判断操作

  • boolean isEmpty()

    判断是否是空字符串:return value.length == 0

  • boolean equals(Object obj)

    比较字符串的内容是否相同

  • boolean equalsIgnoreCase(String anotherString)

    与equals方法类似,忽略大小写

  • boolean endsWith(String suffix)

    判断此字符串是否为指定的后缀结束

  • boolean startsWith(String prefix)

    判断此字符串是否为指定的前缀开始

  • boolean startsWith(String prefix , int toffset)

    判断此字符串从toffset位置开始是否为指定的前缀开始

  • boolean contains(CharSequence s)

    判断此字符串是否包含指定的字符序列

3.5 其他操作

  • int length()

    返回字符串的长度

  • String trim()

    返回新的字符串,忽略前面空格和尾部空格 (中间不忽略)

  • int compareTo(String str)

    比较两个字符串的大小

  • String[] split(String regex)

    根据指定的字符串拆分该字符串,并存在String[]数组中

  • String[] split(String regex , int limit)

    根据指定的字符串拆分该字符串,并存在String[]数组中,最多不能超过limit个,若超过了,剩下的全部放到最后一个元素中

四、 String与其他类型转换

4.1 String与基本数据类型转换

String转换为基本数据类型

Integer包装类中的parseInt方法,可以将由**"数字"字符组成的字符串**转换为整型,若不是"数字"的字符串或者为null,则会抛数字格式化异常NumberFormatException

  • public static int parseInt(String s)

类似地,使用java.lang包中的Byte、Short、Long、Float、Double等包装类调相应

的类方法可以将由“数字”字符组成的字符串,转化为相应的基本数据类型

Boolean源码

	public static boolean parseBoolean(String s) {
        return ((s != null) && s.equalsIgnoreCase("true"));
    }	

可以看出:只有String不为null和忽略大小写的"true"两者都成立返回true,其他都返回false

基本数据类型转换为String

调用String类相应的方法(鸡肋,了解即可)

  • String valueOf(byte b)
  • String valueOf(short s)
  • String valueOf(int i)
  • String valueOf(long l)
  • String valueOf(float f)
  • String valueOf(double d)
  • String valueOf(char c)
  • String valueOf(boolean b)

可由参数的相应基本数据类型转换为String

转换实质:

  • number类型(int、long、double、float…)调用包装类的toString方法转换

  • char类型使用构造器new String(char[] value, boolean share)

  • boolean类型直接返回return b ? “true” : "false"

基本类型与String类型相加则自动转换为String

String s1 = "" + 1 ;
String s2 = "" + 2.0 ;
String s3 = "" + 'a' ;
String s4 = "" + true ;

4.2 String与字符数组转换

字符数组转换为String

使用String构造器

  • new String(char[] value)

    将char[]中全部字符转换为String

  • new String(char value , int offset , int length)

    将char[]中从offset位置开始,一共length长度的字符转换为String

String转化为字符数组

  • char[] toCharArray()

    将字符串中的全部字符存放在一个字符数组

  • void getChars(int srcBegin , int srcEnd , char[] dst , int dstBegin)

    截取String中**[srcBegin , srcEnd )**区间的字符放进字符数组dst中,从dstBegin下标开始

	public static void demo3(){
		String s = "hello" ;
        char[] c=new char[5];
        s.getChars(1,3,c,1);
        System.out.println(c);

    }

输出结果为:空格el空格空格

五、StringBuffer

5.1 StringBuffer的特性

StringBuffer为String的增强类,与String最大的不同为StringBuffer中的字符可变

StringBuffer底层使用父类AbstractStringBuilder中的char[]数组存储字符

abstract class AbstractStringBuilder implements Appendable, CharSequence {
    /**
     * 使用char数组存储字符
     */
    char[] value;
    
    //记录字符的长度
    int count;
    ...
            
}

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

由源码可知存储字符数组char[] value没有final修饰,可以改变内存地址

且StringBuffer内部许多提供了许多方法可以改变char[]数组的内容,例如:appen方法

	public synchronized StringBuffer append(String str) {
        toStringCache = null;
        super.append(str);
        return this;
    }	

	public AbstractStringBuilder append(String str) {
        
        //判断拼接的字符串str是否为null
        if (str == null)
            return appendNull();
        int len = str.length();
        
        //判断是否扩容(类似集合)
        ensureCapacityInternal(count + len);
        
        //将拼接的str的所有字符加入到字符数组value里,在count位置开始添加
        str.getChars(0, len, value, count);
        count += len;
        return this;
    }

str.getChars(0, len, value, count);将拼接的str的所有字符加入到字符数组value里,在count位置开始添加,即拼接字符串str直接在value上修改,而非创建一个新的StringBuffer类

而且StringBuffer类中多数方法为链式方法,进行逻辑后,返回当前对象

String、StringBuffer、StringBuilder_第17张图片

	public static void demo3(){
        String s = "hello" ;
        StringBuffer strb1 = new StringBuffer(s);
        StringBuffer strb2 = strb1.append(1).
                                 append(true).
                                 append(new char[]{'a', 'b'}).
                                 append("string");
    	
        System.out.println(strb1==strb2);//true

    }

5.2 StringBuffer的创建

StringBuffer构造器初始化底层字符数组char[]的情况

  • new StringBuffer()

    空参构造器,初始化字符数组char[]长度为16

  • new StringBuffer(int capacity)

    给定容量的构造器,初始化字符数组char[]长度为capacity

  • new StringBuffer(String str)

    给定字符串的构造器,初始化字符数组char[]长度为该字符串str长度+16

  • new StringBuffer(CharSequence seq)

    给定字符序列的构造器,初始化字符数组char[]长度为该字序列串seq长度+16

源码分析:

abstract class AbstractStringBuilder implements Appendable, CharSequence {

    char[] value;

    AbstractStringBuilder(int capacity) {
        value = new char[capacity];
    }	
    ...
}

//StringBuffer类
public final class StringBuffer
    extends AbstractStringBuilder
    implements java.io.Serializable, CharSequence
{

    //空参构造器,初始化字符数组char[]长度为16
	public StringBuffer() {
        super(16);
    }

    /**
     *给定容量的构造器,初始化字符数组char[]长度为capacity
     */
    public StringBuffer(int capacity) {
        super(capacity);
    }

    /**
     *给定字符串的构造器,初始化字符数组char[]长度为该字符串str长度+16
     */
    public StringBuffer(String str) {
        super(str.length() + 16);
        append(str);
    }

    /**
     * 给定字符序列的构造器,初始化字符数组char[]长度为该字序列串seq长度+16
     */
    public StringBuffer(CharSequence seq) {
        this(seq.length() + 16);
        append(seq);
    }
    
 	...   
}

若开发中频繁添加字符串,建议使用StringBuffer,且使用有参有参构造器给定容量大小,避免不断扩容,影响性能

5.3 StringBuffer与String的转换

StringBuffer转换为String

调用toString方法即可

String 转换为StringBuffer

使用构造器new StringBuffer(String str)即可

5.4 扩容机制

StringBuffer底层使用char[]数组存储字符,数组的长度是确定的,若拼接的字符超过数组的长度,则需要扩容

判断是否需要扩容

	//minimumCapacity为新的字符的长度
	private void ensureCapacityInternal(int minimumCapacity) {
        // 若字符的长度>char[]数组的长度
        if (minimumCapacity - value.length > 0) {
            /*
            	1、调用newCapacity()方法计算扩容后char[]字符数组的长度
            	2、Arrays.copyOf方法进行数组复制
            */
            value = Arrays.copyOf(value,
                    newCapacity(minimumCapacity));
        }
    }	

计算扩容后的长度

	private int newCapacity(int minCapacity) {

        // 扩容后的容量首先为 原来的字符数组长度 *2 + 2
        int newCapacity = (value.length << 1) + 2;

        //若新的容量还比字符串长度小,直接让字符串长度等于扩容后的容量
        if (newCapacity - minCapacity < 0) {
            newCapacity = minCapacity;
        }

        /**
         *  MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8 ,char[]数组规定的最大容量
         *  条件说明:
         *      newCapacity <= 0 (左移溢出)
         *      MAX_ARRAY_SIZE - newCapacity < 0(扩容后的容量大于char[]数组规定的最大容量)
         *  若扩容后的容量没有溢出 和 没有大于char[]数组规定的最大容量
         *  返回当前newCapacity
         *  若扩容后的容量溢出或超过最大容量则调用hugeCapacity方法进行新的判断
         *  返回MAX_ARRAY_SIZE或者抛异常
         */
        return (newCapacity <= 0 || MAX_ARRAY_SIZE - newCapacity < 0)
                ? hugeCapacity(minCapacity)
                : newCapacity;
    }	


	private int hugeCapacity(int minCapacity) {
        //若字符串长度minCapacity大于Integer的最大值,则抛溢出异常
        if (Integer.MAX_VALUE - minCapacity < 0) { 
            throw new OutOfMemoryError();
        }
        //字符串长度大于数组规定最大的容量,取字符串长度为扩容后的容量,
        //否则取数组规定最大的容量为扩容后的容量
        return (minCapacity > MAX_ARRAY_SIZE)
                ? minCapacity : MAX_ARRAY_SIZE;
    }

步骤:

  1. 首先计算

    扩容后的数组的长度 =原来的字符数组长度的2倍 + 2

  2. 若新的容量 < 字符串长度

    • 直接让扩容后的容量 = 字符串长度
  3. 判断新的容量是否溢出大于数组规定的最大长度

    • 没溢出 和 没大于数组规定的最大长度,则为最终的扩容后的容量
    • 溢出 或者 大于数组规定的最大长度
      • 判断字符串长度是否大于Integer的最大值(因为计算后得到的容量已经溢出了,所以不能使用计算后的容量,应当使用实际字符串长度再判断)
        • 大于Integer的最大值,抛溢出异常
        • 没大于Integer的最大值
          • 若数组规定的最大容量< 字符串的长度 <= Integer的最大值,字符串的长度 则为最终的扩容后的容量
          • 字符串的长度 < 数组规定的最大容量,数组规定的最大容量则为最终的扩容容量

扩容后的数组的长度有几种情况:

  1. 原来的字符数组长度的2倍 + 2
  2. 字符串长度
  3. char[]数组规定的最大长度MAX_ARRAY_SIZE=Integer.MAX_VALUE-8

总结:扩容的最大容量为Integer的最大值,超过该值抛异常

5.5 StringBuffer的常用方法

5.5.1 增加操作

  • StringBuffer append(xxx)

提供了很多append重载(String、int、long、short、byte、double、float、char、boolean、CharSequence)的方法,用于进行字符串的拼接

String s = "hello" ;
StringBuffer strb1 = new StringBuffer(s);
StringBuffer strb2 = strb1.append(1).
                           append(1.0D).
                           append((short)4).
                           append(true).
                           append(new char[]{'a', 'b'}).
                           append(s);

5.5.2 删除操作

  • StringBuffer delete( int start , int end )

    删除指定位置的字符串(左闭又开)

  • StringBuffer deleteCharAt(int index)

    删除指定索引的字符

StringBuffer strb1 = new StringBuffer("helloworld");
strb1 = strb1.delete(2, 4);
System.out.println(strb1);

结果为heoworld

5.5.3 替换操作

  • StringBuffer replace(int start , int end , String str)

    替换指定位置的字符串(左闭右开)

  • void setCharAt(int index ,char ch)

    替换(设置)指定位置的字符

StringBuffer strb1 = new StringBuffer("helloworld");
strb1 = strb1.replace(5,6,"test");
System.out.println(strb1);

结果为hellotestorld

5.5.4 插入操作

  • String insert(int start , xxx)

在指定位置插入xxx

重载方法,xxx(String、int、long、short、byte、double、float、char、boolean、CharSequence)

	public static void demo3(){
        StringBuffer strb1 = new StringBuffer("helloworld");
        strb1 = strb1.insert(5," ").insert(6, 0.5D).insert(9,' ');
        System.out.println(strb1);
    }

结果为hello 0.5 world

5.5.5 反转操作

  • StringBuffer reverse()

    把当前字符序列逆转

StringBuffer strb1 = new StringBuffer("helloworld");
strb1 = strb1.reverse();
System.out.println(strb1);

结果为dlrowolleh

  • append和insert时,如果原来value数组长度不够,扩容。

  • 如上这些方法支持方法链操作

5.5.6 查询操作

根据索引返回字符或字符串

  • char charAt(int index)

    截取指定索引位置的字符

  • String substring(int beginIndext)

    返回一个新的字符串,从下标beginIndex开始截取到最后的一个子字符串

  • String substring(int beginIndex , int endIndex)

    返回一个新的字符串,从下标beginIndex开始截取到endIndex(不包含)的一个子字符串 [ beginIndex , endIndex )

根据字符串返回索引

  • int indexOf(String str)

    返回指定子字符串在此字符串中第一次出现的下标

  • int indexOf(String str , int fromIndex)

    返回指定子字符串在此字符串中第一次出现处的索引,从指定的fromIndex位置开始

  • int lastIndexOf(String str)

    返回指定子字符串在此字符串中最后出现的下标

  • int lastIndexOf(String str , int fromIndex)

    返回指定子字符串在此字符串最后出现处的索引,从指定的fromIndex索引开始反向搜索

5.5.7 其他操作

  • int length()

    获取字符串的长度(不是char[]数组的长度)

  • int capacrity()

    获取char[]数组的长度

StringBuffer strb1 = new StringBuffer("helloworld");
int capacity = strb1.capacity();
int length = strb1.length();
System.out.println(capacity);//26
System.out.println(length);//10

六、StringBuilder

6.1 StringBuilder与StringBuffer的区别

相同点:

  • 均代表可变的字符序列,而且提供相关功能的方法也一样
  • 都继承了AbstractStringBuilder抽象类
  • StringBuilder与StringBuffer大多数的方法实现都是调用父类中的方法

不同点:

  • StringBuffer为可变字符序列、效率低、线程安全
  • StringBuilder为可变字符序列、效率高、线程不安全

例如:

源码

StringBuffer的append方法使用了synchronize,然后调用super.append具体实现

public synchronized StringBuffer append(String str) {
        toStringCache = null;
        super.append(str);
        return this;
    }	

StringBuilder的append方法没有使用了synchronize,然后调用super.append具体实现

	public StringBuilder append(String str) {
        super.append(str);
        return this;
    }

6.2 String、StringBuffer、StringBuilder对比

  • String(JDK 1.0):不可变字符序列

  • StringBuffer(JDK 1.0):可变字符序列、效率低、线程安全

  • StringBuilder(JDK 5.0):可变字符序列、效率高、线程不安全

注意:作为参数传递的话,方法内部String不会改变其值,StringBuffer和StringBuilder会改变其值

面试题

public static void demo3(){
        String str = "hello" ;
        char[] chs = {'w','o','r','l','d'};
        int i = 10 ;
        boolean flag = true ;
        test(str,chs,i,true);
        System.out.println(str);
        System.out.println(chs);
        System.out.println(i);
        System.out.println(flag);
    }
    public static void test(String str,char[] chs,int i,boolean flag){
        str = "test" ;
        chs[0] = 'a' ;
        i = 20 ;
        flag = false ;
    }

    public static void main(String[] args) {
        demo3();
    }

执行结果:

String、StringBuffer、StringBuilder_第18张图片

分析结果:

String str

  • 在调用test方法,局部变量str把常量池中的"hello"的内存地址给了参数变量str,这时两人变量str都指向了常量池中的"hello"
  • 然后在test方法内部参数变量str=“test”,相当于参数变量str指向了新的指向"test",此时局部变量str并无任何改变。
  • 执行完test方法,局部变量str被回收,局部变量str依旧无改变

String、StringBuffer、StringBuilder_第19张图片

char[] chs

char[] 为引用类型,调用test方法时,在内部chs[0]='a’相当于修改其属性,所以可以成功

String、StringBuffer、StringBuilder_第20张图片

这体现了String的不可变性,其他基本类型也类似

结论:

1、String与8个基本类型作为方法参数,在方法内部修改,不会改变外部值

2、除了String与8个基本类型之外其他类型,在方法内部修改属性,会影响到外部

6.3 比较String、StringBuffer、StringBuilder速度

拼接200000次的速度比较

	public static void testStr(){
        long start = System.currentTimeMillis();
        String str = "" ;
        for (int i = 0; i < 200000 ; i++) {
            str = str + i ;
        }
        long end = System.currentTimeMillis();
        System.out.println("String拼接200000次的时间为:"+(end-start));
    }

    public static void testBuffer(){
        long start = System.currentTimeMillis();
        StringBuffer buffer = new StringBuffer("") ;
        for (int i = 0; i < 200000 ; i++) {
            buffer.append(i);
        }
        long end = System.currentTimeMillis();
        System.out.println("StringBuffer拼接200000次的时间为:"+(end-start));
    }

    public static void testBuilder(){
        long start = System.currentTimeMillis();
        StringBuilder builder = new StringBuilder("") ;
        for (int i = 0; i < 200000 ; i++) {
            builder.append(i);
        }
        long end = System.currentTimeMillis();
        System.out.println("StringBuilder拼接200000次的时间为:"+(end-start));
    }

    public static void main(String[] args) {
        testStr();
        testBuffer();
        testBuilder();
    }

比较结果:

String、StringBuffer、StringBuilder_第21张图片

七、 常见面试题

String str = null ;
StringBuffer buffer = new StringBuffer(null);
buffer.append("a");
System.out.println(buffer);//调用toString方法

String、StringBuffer、StringBuilder_第22张图片

若参数为null,则StringBuffer与StringBuilder只要该对象都会报空指针异常,包括toString,==比较

你可能感兴趣的:(String、StringBuffer、StringBuilder)