Java五十四: 字符串类(String类、StringBuffer类、StringBuilder类)

字符串类

导读:
  1. 本文分为三部分:String、StringBuffer、StringBuilder,其中StringBuilder类最常用,有可变、自动扩容,效率高的特点。
  2. 重点:
    • String的不可变性:体现为存储在常量池中的,以及底层字符数组是final型的
    • String与char数组、byte数组之间的互转:toCharArray() , getBytes , new String( )
    • StringBuffer/StringBulider的扩容源码分析
一、String的特殊性

String:字符串,使用一对 “ ” 引起来表示

  1. String声明为final不可被继承

  2. String实现的接口

    实现Serializable接口:表示字符串是支持序列化的

    在I/O流中会涉及到对象流,就会涉及到序列化,简单说:对于面向对象框架来所,其各种数据都是封装在对象当中的,对象实现了 Serializable接口后就可以以字节的形式通过网络传输的

    实现Comparable接口:表示字符串可以比较大小

    // String类源码
    public final class String
        implements java.io.Serializable, Comparable<String>, CharSequence {}
    
  3. String内部定义了final char[ ] value 用于存储字符串数据,在底层以字符数组的形式存储,但该数组被声明为final,不可改变。

  4. String:代表不可变的字符序列。简称:不可变性(重点)

    ① 体现1:当对字符串重新赋值时,需要重新指定内存区域赋值,不能使用原有的value进行赋值
    ② 体现2:当对现有的字符串进行连接操作时,也需要重新指定内存区域赋值,不能使用原有的value进行赋值
    ③ 体现3:当调用String的replace()方法修改指定的字符或字符串时,也需要重新指定内存区域赋值

    总结:只要字符串不一样,就会重新制定内存区域,这就是不可变

  5. 通过字面量的方式(区别于new)给一个字符串赋值,此时的字符串值声明在字符串常量池中

  6. 字符串常量池中,不同地址的内存区域内不会存储相同内容的字符串的

二、String的实例化方式
  • 方式一:通过字面量定义的方式

    String s1 = "abc"
    
  • 方式二:通过new + 构造器的方式(共造了两个对象

    String s2 = new String("abc");
    
    • 面试题:

      String s = new String("abc");方式创建对象,在内存中创建了几个对象?
      
      两个:一个是在堆空间中的new结构;一个是char[ ] 对应的常量池中的数据:“abc”
      
    • 内存图
      在这里插入图片描述

三、String拼接后的存储情况概述
  1. String类型的数据的拼接情况
    public class StringTest {
        @Test
        public void test1(){
            String s1 = "hello";
            String s2 = "java";
            String s3 = "hellojava";
            String s4 = "hello" + "java";
            String s5 = s1 + "java";
            String s6 = s1 + s2;
            String s7 = (s1 + s2).intern();
            final String s8 = "hello";
            String s9 = s8 + "java";
            // 1.s4是两个常量的运算结果,值还是存储在常量池中  true
            System.out.println(s3 == s4);
            // 2.s5是一个常量和一个变量的运算结果,值存储在堆中  false
            System.out.println(s3 == s5);
            // 3.s6是两个变量运算的结果,值也存在堆中  false
            System.out.println(s3 == s6);
            // 4.s7是调用intern方法的结果,值存在常量池中  true
            System.out.println(s3 == s7);
            // 5.s9是常量与常量的运算结果,因为s8被final修饰,是一个常量  true 
            System.out.println(s3 == s9);	
        }
    }
    
  2. 结论

    ① 常量与常量的拼接结果在常量池。且常量池中不会存在相同内容的常量
    只要其中一个是变量,结果就在堆中。
    ③ 如果拼接的结果调用了intern()方法,返回值就在常量池中。

四、字符串类的面试题
  1. String的不可变性,虽然change()方法进行的是引用传递,传递进了str的地址值,但是字符串str的值也是不会改变的
  2. 代码
    public class Person1 {
        public static String str = "good";
        public static char[] ch = {'t','e','s','t'};
        public void change(String str , char ch[]){
            str = "study";
            ch[0]= 'b';
        }
    
        public static void main(String[] args) {
            Person1 p = new Person1();
            p.change(str,ch);
            // 字符串变量底层是final字符数组,其值不会发生改变  输出good
            System.out.println(str);
            // ch只是普通字符数组,值可以改变  输出best
            System.out.println(ch);		
        }
    }
    
五、字符串的常用方法
  • 方法表

    方法名作用
    int length( )返回字符串的长度:return value.length
    char charAt( int index )返回某索引处的字符:return value[index]
    boolean isEmpty( )判断是否是空字符串:return value.length == 0
    String toLowerCase( )使用默认语言环境,将String中的所有字符转换为小写
    String toUpperCase( )使用默认语言环境,将String中的所用字符转换为大写
    String trim( )返回字符串的副本,忽略前导空白和尾部空白
    boolean equals( Object obj )比较字符串内容是否相等,区分大小写
    boolean equalsIgnoreCase( String anotherString )与equals方法类似,忽略大小写
    String concat( String str )将指定字符串连接到此字符串的结尾,等价于 +
    int compareTo( String anotherString )比较两个字符串的大小
    String substring( int beginIndex )返回一个新的字符串,它是此字符串的从beginIndex开始
    截取后的子字符串
    String substring( int beginIndex , int endIndex )返回一个新的字符串,它是此字符串从beginIndex开始,
    到endIndex结束,前开后闭,的子字符串
    类似正则表达式
    boolean endsWith( String suffix )测试此字符串是否以指定的后缀结束
    boolean startsWith( String prefix )测试此字符串是否以指定的前缀开始
    boolean startsWith( String prefix, int toffset )测试此字符串从指定索引开始的子字符串是否以指定的前缀开始
    boolean contains( CharSequence s )当且仅当此字符串包含指定的char值序列时,返回true
    返回索引值
    int indexOf( String str )返回指定子字符串在此字符串中第一次出现处的索引
    int indexOf( String str, int fromIndex )返回指定子字符串在此字符串中,从指定的索引处开始,第一次出现出的索引
    int lastIndexOf( String str )返回指定子字符串在此字符串中最右边出现处的索引
    int lastIndexOf( String str, int fromIndex)返回指定子字符串再次字符串中最后一次出现处的索引,从指定的索引开始反向搜索(但索引值还是从前往后数)
    indexOf 和 lastindexOf 方法如果未找到都是返回 -1
    替换
    String replace( char oldChar,char new char)返回一个新的字符串,它是通过用 newChar 替换此字符串中出现的所有 oldChar 而得到的
    String replace( CharSequence target, CharSequence replacement)使用指定的字面值序列替换此字符串所有匹配字面值目标序列的子字符串
    String replaceAll(String regex, String replacement)使用给定的replacement 替换此字符串所有匹配给定的正则表达式的子字符串
    String replaceFirst( String regex, String replacement)使用给定的replacement 替换此字符串匹配给定的正则表达式的第一个子字符串
    匹配
    boolean matches(String regex)告知此字符串是否匹配给定的正则表达式
    切片
    String[ ] split( String regex)根据给定正则表达式的匹配拆分此字符串
    String[ ] split( String regex, int limit)根据匹配给定的正则表达式来拆分此字符串,最多不超过limit个,如果超过了,剩下的全部都放到最后一个元素中
  • 代码示例

    @Test
        public void test2(){
            String s1 = "hello world";
            System.out.println(s1.length());        // 11
            System.out.println(s1.charAt(4));       // o
            System.out.println(s1.isEmpty());       // false
    
            String s2 = "HelloWorld";
            System.out.println(s2.toLowerCase());   // helloworld
            System.out.println(s2.toUpperCase());   // HELLOWORLD
            System.out.println(s2);                 // HelloWorld
    
            String s3 = "  hello java  ";
            System.out.println(s3.trim());          // hello java
    
            String s4 = "helloworld";
            System.out.println(s2.equals(s4));      //false
            System.out.println(s2.equalsIgnoreCase(s4));    //true
    
            System.out.println(s2.concat(s3));      //HelloWorld  hello java
    
            String s5 = "helloworlf";
            System.out.println(s2.compareTo(s5));   // -32
    
            System.out.println(s2.substring(4));    // oWorld
            System.out.println(s2.subSequence(2,6));// lloW
    
            System.out.println(s2.endsWith("d"));   // true
            System.out.println(s2.startsWith("h")); // false 区分大小写
            System.out.println(s2.startsWith("lo",3));  // true
    
            String s6 = "lo";
            System.out.println(s2.contains("lo"));  // true
            System.out.println(s2.contains(s6));    // true
    
            String s7 = "hellowlorld";
            System.out.println(s7.indexOf("lo"));   // 3
            System.out.println(s7.indexOf('o'));    // 4
            System.out.println(s7.indexOf("lo",5)); //6
    
            System.out.println(s7.lastIndexOf("lo"));   // 6
            System.out.println(s7.lastIndexOf("lo",5)); // 3
        }
    
六、字符串与常用数据类型之间、字符数组、字节数组之间的转换
  1. 字符串与常用数据类型之间的转换详见day51 包装类

  2. 字符串(Sting)与字符数组(char[ ])之间的转换

    • String - - > char[ ] toCharArray( )

      String s1 = "abc123";
      char[] charArray = s1.toCharArray();
      for (int i = 0; i < s1.length(); i++) {
           System.out.println(charArray[i]);
       }
      
    • char[ ] - - > String new String( 字符数组名 )

      char[] arr = new char[]{'h','e','l','l','o'};
      String s2 = new String(arr);
      System.out.println(s2);
      
  3. 字符串(String)与字节数组(byte[ ])之间的转换

    • 编码:String - - > byte[ ] getBytes( ) 看得懂 - - > 看不懂的二进制数据

      String s1 = "abc123中国";
      // 使用默认的字符集,进行编码
      byte[] bytes = s1.getBytes();  
      System.out.println(Arrays.toString(bytes));
      
    • 解码:byte[ ] - - > String new String( ) 看不懂的二进制数据 - - > 看得懂

      // 指定使用gbk字符集进行编码
      byte[] gbks = s1.getBytes("gbk");  
      // 会出现乱码,与系统字符集不匹配
      System.out.println(Arrays.toString(gbks));  
      // 转String时指定了对应的字符集,不会出现乱码,
      String s2 = new String(gbks, "gbk");  
      
七、StringBuffer类、StringBuilder类
  1. String与StringBuffer、StringBuilder的异同

    String:不可变的字符序列;底层使用char[ ]存储

    StringBuffer:可变的字符序列;底层使用char[ ] 存储;线程安全的,效率低

    StringBuilder:可变的字符序列;底层使用char[ ] 存储;JDK5.0后新增,线程不安全,效率高

    /*
    1.扩容问题
      如果要添加的数据底层数组装不下,就要扩容底层的数组。
      默认情况下,扩容为原来容量的2倍 + 2,同时将原有数组中的元素复制到新的数组中
    2.建议
      开发中使用StringBuffer(int capacity),可以指定容量,避免因需不断进行扩容而降低代码效率
    */
    
  2. 代码对比
    @Test
        public void test6(){
            
            // 1.String 
            // char[] value = new char[0]
            String str1 = new String(); 
            // char[] value = new char[]{'a','b','c'};
            String str2 = "abc";    
            
    	    // 2.StringBuffer
            // char[] value = new char[16];底层创建一个长度16的字符数组
            StringBuffer s2 = new StringBuffer(); 
            // 在没达到扩容条件前,默认长度仍是 0
            System.out.println(s2.length());	
            s2.append('a');     //value[0] = 'a';
            s2.append('b');     //value[1] = 'b';
    
            StringBuffer s3 = new StringBuffer("abc");
            // 在没达到扩容条件前,长度是实际长度 3
            System.out.println(s3.length());	
            // 将指定索引处的字符做了更改,体现了StringBuilder的可变性,输出mbc
            s3.setCharAt(0,'m');
            System.out.println(s3);		
        }
    
  3. 扩容问题源码分析
    // StringBuffer类的继承实现
    public final class StringBuffer
        extends AbstractStringBuilder
        implements java.io.Serializable, Comparable<StringBuffer>, CharSequence
    {
    
    // 1.构造器
        public StringBuffer(String str) {
        	// 默认可以装下最少16个字符
            super(str.length() + 16);
            append(str);
        }
    
    // 2.StringBuffer中的 append()方法
    @Override
        public synchronized StringBuffer append(String str) {
            toStringCache = null;
            super.append(str);
            return this;
        }
    
    // 3.父类中的append()方法
        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;
        }
    
    // 4.父类中判断字符串长度的方法
        private void ensureCapacityInternal(int minimumCapacity) {
            // overflow-conscious code  如果不够,就要进行增容
            if (minimumCapacity - value.length > 0) {	
                // newCapacity()为增容方法
                value = Arrays.copyOf(value, newCapacity(minimumCapacity));	
            }
        }
    
    // 5.增容方法
         private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
    
         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)
                ? hugeCapacity(minCapacity)
                : newCapacity;
        }
    
    // 6.超出最大范围,抛出异常
        @Native public static final int   MIN_VALUE = 0x80000000;   
        @Native public static final int   MAX_VALUE = 0x7fffffff;
    
        
        private int hugeCapacity(int minCapacity) {
            if (Integer.MAX_VALUE - minCapacity < 0) { // overflow
                throw new OutOfMemoryError();
            }
            return (minCapacity > MAX_ARRAY_SIZE)
                ? minCapacity : MAX_ARRAY_SIZE;
        }
    
  4. StringBuffer的常用方法
    方法名作用
    append()提供了很多的append()方法,用于字符串拼接
    delete(int start, int end)删除指定位置的内容
    replace(int start , int end, String str)把[ start , end ) 位置的字符替换为str
    insert( int offset , xxx)在指定位置插入 xxx
    reverse()把当前字符序列进行逆转
    上述这些方法支持方法链操作
    setCharAte( int index , ch char)将指定索引处的字符更换为新的字符
    // StringBuilder类中的append方法源码
    @Override
    public StringBuilder append(String str){
        super.append(str);
        //返回的是引用对象本身,故可以一直进行引用,形成方法链
        return this;	
    }
    
    // 方法链示例:
    StringBuffer s = new StringBuffer();
    s.append('1').append('2').append('3').append('...')    
    
  5. 三个类的效率对比

    StringBuilder > StringBuffer > String

     @Test
        public void test7(){
            long startTime = 0L;
            long endTime = 0L;
            String str = "";
            StringBuffer buffer1 = new StringBuffer();
            StringBuilder builder1 = new StringBuilder();
            
            // StringBuilder的append方法添加字符模式  3ms
            startTime = System.currentTimeMillis();
            for (int i = 0; i < 20000; i++) {
                builder1.append(String.valueOf(i));
            }
            endTime = System.currentTimeMillis();
            System.out.println("StringBuilder花费时间为:" + (endTime - startTime) + "ms");
            
    		// StringBuffer的append方法添加字符模式  5ms
            startTime = System.currentTimeMillis();
            for (int i = 0; i < 20000; i++) {
                buffer1.append(String.valueOf(i));
            }
            endTime = System.currentTimeMillis();
            System.out.println("StringBuffer花费时间为:" + (endTime - startTime) + "ms");
    		
    		// String的连接运算模式  1101ms
            startTime = System.currentTimeMillis();
            for (int i = 0; i < 20000; i++) {
                str = str + String.valueOf(i);
            }
            endTime = System.currentTimeMillis();
            System.out.println("String花费时间为:" + (endTime - startTime) + "ms");
        }
    
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

e_nanxu

感恩每一份鼓励-相逢何必曾相识

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值