Java小白一文深入介绍String、StringBuffer、StringBuilder

String类

  • String对象用于保存字符串,也就是一组字符序列

  • 字符串常量对象是用双引号括起的字符序列,例如 “Kerwin”

  • 字符串的字符使用Unicode字符编码,一个字符(不区分字母还是汉字)占两个字节

  • String类较常用构造方法

    String s1 = new String();

    String s2 = new String(String original);

    String s3 = new String(char[] a);

    String s4 = new String(char[] ,int startIndex,int count);

String 实现了Serializable和Comparable 接口,说明String可以串行化,String对象可以比较大小

​ 可串行化:可以在网络传输

  • String是final 类,不能被其他的类继承
  • String有属性private final char value[]; 用于存放字符串内容
  • 一定要注意: value 是一个final类型,赋值之后不可修改;
    • 这里的不可修改是指地址不可修改,不是指字符串内容不可修改,即value不能指向新的地址,但是单个字符内容是可以变化
        final char[] value = {'a','b','c'};
        value[0]= 'H';//这样修改value数组中的值是可以的,不会报错
        char[] value2 = {'t','o','m'};//再创建一个char数组
//        value = value2;//此时就会报错,不能再value指向新的数组了
        //但是,如果把上面的final去掉,则上面的语句不会报错

在这里插入图片描述

创建String 对象的两种方式

  • 直接赋值String s = “essence”;

    • 先从常量池查看是否有“essence”数据空间,如果有,直接指向;如果没有则重新创建,

      然后指向,s 最终指向的是常量池中的空间地址

  • 调用构造器String s = new String(“essence”);

    • 先在堆中创建空间,里面维护了value属性,指向了常量池中的“essence”空间,如果常量池中没有“essence”,

      重新创建,如果有,直接通过value指向,最终指向的是堆中的空间地址
      在这里插入图片描述

练习

在这里插入图片描述

//输出如下
true  //String已经重写了equals方法,所以二者内容相同,返回true
true  //a和b二者都是先到常量池去看有没有“abc”,发现有,则二者指向的地址是一样的,所以return  true

在这里插入图片描述

//输出如下
true  //重写equals后,比较的是内容,二者内容相同,所以true
false	//二者指向的地址不同
true
false
    String a = "hsp";// a 指向常量池中的“hsp”;
	String b = new String("hsp");//b 指向堆中的对象,堆中对象的value再指向常量池中的“hsp”;
	System.out.println(a.equals(b));//此时重写之后,比较的是字符串内容,返回true
	System.out.println(a==b);//a指向的是常量池中的“hsp”,b是指向堆的,堆中有个对象,对象中有个value。
			//value指向常量池中的“hsp”,而a、b指向的对象不同,所以return  false
	System.out.println(a==b.intern());
		//a的内容是“hsp”,b.intern()会去看对象b的内容是什么,是hsp,它就到常量池中看,有没有一个字符串也是hsp
		// 之前已经放进去了hsp到常量池中,此时是有的,如果有的话,它就直接把这个hsp返回,此时再比较,
		//二者都是hsp,自然就相等,返回true了
	System.out.println(b==b.intern());
		//此中的b指向的是堆中的对象,而b.intern()则指向的是常量池中的“hsp”,
		//二者不一样,所以return false
//拓展普及
	当调用intern方法时,如果池中已经包含了一个等于此String对象的字符串(equals(Object)方法确定),则返回池中的字符串,
    否则,将该String对象添加到池中,并返回此String对象的引用
        //b.intern()方法最终返回的是常量池的地址(对象)

在这里插入图片描述

//输出如下
false
    //二者指向的对象不同,前者指向常量池中的“Java”,而后者指向堆,堆中的value指向常量池中的“Java”
true
    //二者的内容相同,所以指向常量池中的同一位置,so相等
true
    //equals比较的是内容,二者内容相同,所以true
false
    //二者内容都不同,就要各自在常量池中创建,二者指向常量池中的指向不同

在这里插入图片描述
在这里插入图片描述

//输出如下
true
    //equals比较内容,所以相等
true
    //二者的name指向的都是常量池中的“hspedu”,指向相同,so true
true
    //前者指向常量池中的“hspedu”,后者本就是在常量池中,so true
false
    //二者是指向两个不同的对象,指向不同,无非二者的name属性值相同罢了

在这里插入图片描述

字符串的特性

说明:

  • String是一个final类,代表不可变的字符序列
  • 字符串是不可变的,一个字符串对象一旦被分配,其内容是不可变的

在这里插入图片描述

//千万不要认为第二个语句是把常量池中的“hello”给直接覆盖替换了,
//第二个语句,它会先看常量池中有没有“haha”,如果有,那就直接指向,反之,
//它就会在常量池中创建“haha”,然后让s1重新指向“haha”,原先指向“hello”的那条线就莫得了
    //综上,在常量池中创建了两个对象

在这里插入图片描述

//String a = "hello" + "abc",这个语句编译器会将其优化为String a = "helloabc"
	//所以,综上,创建了一个对象 

//分析
	1.编译器不傻,做一个优化,判断创建的常量池对象,是否有引用指向
        	//如果创建了3个,那么单个的hello、abc,没人用,你说创建它干什么
    2.String a = "hello"+"abc"; ---> String a = "helloabc"; 

在这里插入图片描述

//注意,此中的String c = a+b 和 String "hello"+"abc"是不一样的哈
	String c = a + b;
	//剖析底层执行流程
	1.创建一个StringBuilder sb = StringBuilder();
	2.执行  sb.append("hello");
	3.sb.append("abc");
	4.String c = sb.toString();//	toString()方法底层仍然是new
	5.最后,其实是c指向堆中的String类型对象,其中的value数组再指向池中的"helloabc"
//所以,综上创建了3个对象,a、b指向常量池中对象,c指向堆
        如果此时再来句String  d = "helloabc";试问下面这句的返回值
            System.out.println(d==c);

//答案是false,因为c指向的是堆中的对象,而d指向的是常量池中的"helloabc";

//重要规则:
	String c = "ab" + "cd"; 常量相加,看的是池;String  c1 =  a + b ;变量相加,是在堆中;
再来多问一句:
    	String d = "helloabc";
    	System.out.println(d==c);//此时返回的就是true哟,因为二者都是指向池的;

在这里插入图片描述
在这里插入图片描述

//输出入下
true
true
解析:
    //s6是指向常量池中的hspedujava

在这里插入图片描述
在这里插入图片描述

//
hsp and hava
    /**
    new String("hsp") 这一行首先在常量池中查找是否有 "hsp" 字符串。如果没有,
    则在常量池中创建 "hsp"。
    然后通过 new String("hsp") 在堆中创建一个新的 String 对象,并将该对象的值设为 "hsp",
    并且 str 引用指向这个堆中的 String 对象。inal char[] ch = {'j', 'a', 'v', 'a'} 
    创建了一个字符数组 ch,该数组存储在堆中。数组的内容为 {'j', 'a', 'v', 'a'},
    ch 引用指向这个堆中的数组。调用 ex.change(ex.str, ex.ch); 时,
    ex.str 和 ex.ch 被传递到 change 方法中。
    在 Java 中,参数传递是按值传递的。对于引用类型,传递的是引用的副本。
    在 change 方法中,str 变量指向了一个新的字符串 "java",
    这个字符串查找常量池中是否有 "java",如果没有,则在常量池中创建 "java" 字符串,
    并让 str 变量指向这个常量池中的 "java"。
    需要注意的是,这里 change 方法中的 str 是 ex.str 的副本,
    对它的修改不会影响 ex.str 原本的引用。
    因此,ex.str 仍然指向堆中那个值为 "hsp" 的 String 对象。
    ch[0] = 'h'; 这行代码修改了数组 ch 的第一个元素,将其从 'j' 改为 'h'。
	由于 ch 是一个引用,传递的是引用的副本,但它仍然指向堆中的同一个数组,
	因此这个修改会影响到 ex.ch 数组。
	最终,ex.ch 的内容变为 {'h', 'a', 'v', 'a'}。
	因为在 change 方法中对 str 的修改没有影响到原始的 ex.str,
	所以此时输出语句中ex.str 仍然是 "hsp"。
	而ex.ch 数组被修改过,因此其内容为 {'h', 'a', 'v', 'a'},打印时会输出 "hava"。
	因此,最后的输出结果是    hsp and java									*/

String类的常见方法

说明:String类是保存字符串常量的,每次更新都需要重新开辟空间,效率较低,因此Java设计者还提供了StringBuilder和StringBuffer来增强String的功能,并提高效率。

  • equals//区分大小写,判断内容是否相等
  • equalsIgnoreCase//忽略大小写的判断内容是否相等
  • length//获取字符串的个数,字符串的长度
  • indexOf//获取字符在字符串中第一次出现的索引,索引从0开始,如果找不到,返回-1;
  • lastIndexOf//获取字符在字符串中最后一次出现的索引,索引从0开始,如果找不到,返回-1;
  • substring//截取指定范围的子串
  • trim//去前后空格
  • charAt//获取某索引处的字符,注意不能使用Str[index]这种方式
    在这里插入图片描述
//以上几行输出语句,依次输出
Success3
3		//补充:也可以s1.indexOf("we");来查看"we"在字符串中第一次出现的索引位置
11
张三
hello  //注意是开区间,不会取5索引的值
  • toUpperCase
  • toLowCase
  • concat
  • replace//替换字符串中的字符
  • split 分割字符串,对于某些分割字符,我们需要 转义 比如 | \等
  • compareTo//比较两个字符串的大小,如果前者大,则返回正数,后者大,则返回负数,如果相等,返回0
    • 例如String a = “jchn”; String b = “jack”; sout.(a.compareTo(b));
      • 其返回值是’c’ - ‘a’ = 2 所以a、b 二者之中,前者大
    • 再例如String a = “jac”; String b = “jack”; sout(a.compareTo(b));
      • 其返回值此时是len1 - len2 ,即 3 - 4 = -1; 所以二者之中,后者大
    • 再例如String a = “jackabc”; String b = “jack”; sout(a.compareTo(b));
      • 其返回值此时是len1 - len2 ,即 7 - 4 = 3; 所以二者之中,前者大
//源码展示
public int compareTo(String anotherString) {
        int len1 = value.length;
        int len2 = anotherString.value.length;
        int lim = Math.min(len1, len2);
        char v1[] = value;
        char v2[] = anotherString.value;

        int k = 0;
        while (k < lim) {
            char c1 = v1[k];
            char c2 = v2[k];
            if (c1 != c2) {
                return c1 - c2;
            }
            k++;
        }
        return len1 - len2;
}
  • toCharArray//转换成字符数组
  • format//格式字符串,%s 字符串 %c字符 %d 整型 %.2f浮点型
    在这里插入图片描述
//输出结果如下
HELLO
hello
宝玉林黛玉薛宝钗together
//将所有的 林黛玉 替换成 薛宝钗,因为没找到“林黛玉”,所以输出不变为s1
	//s1.replace方法执行后,返回的结果才是替换过的,注意,对s1没有任何影响,变化的是返回的结果罢了
//以 , 为标准对poem进行分割,返回一个数组,遍历数组,就会得到四句诗
 
//在对字符串进行分割时,如果有特殊字符,需要加入转义符  \
E:
aaa
bbb
h
a
a
p
p
y
//format方法的应用实例
        String name = "kerwin";
        int age = 10;
        double score = 92.5/2;
        char gender = '男';
        //将所有信息都拼接到一起

        String info = "我的姓名是"+name+"年龄是"+age+"成绩是"+score+"性别是"+gender;
        System.out.println(info);
        String format = "我的姓名是%s  年龄是%d  成绩是%.2f   性别是%c";
//        String info2 = String.format("我的姓名是%s  年龄是%d  成绩是%.2f   性别是%c",name,age,score,gender);
        //此时就可以实现便捷地复用,当然,如果需要有改动,直接改动format就好了,就可以实现输出的相应调整
        String info2 = String.format(format,name,age,score,gender);
        System.out.println(info2);


//输出结果如下
我的姓名是kerwin年龄是10成绩是46.25性别是男
我的姓名是kerwin  年龄是10  成绩是46.25   性别是男
//拓展补充
    //其中%s %d %.2f  %c是占位符
    //%s 表示后面由字符串来替换
    //%d 表示后面由整数来替换
    //%.2f  表示后面使用小数来替换,替换后,只会保留小数点后两位,并且进行四舍五入处理
    //%c  表示使用char类型来替换

StringBuffer类

介绍:

  • java.lang.StringBuffer代表可变的字符序列,可以对字符串内容进行增删
  • 很多方法与String相同,但StringBuffer是可变长度的
  • StringBuffer是一个容器
        //1.StringBuffer的直接父类是AbstractStringBuilder
        //2.StringBuffer实现了Serializable,即StringBuffer的对象可以串行化
        //3.在父类中,AbstractStringBuilder有属性 char[] value,其不是final的哈
        //  该value数组存放字符串内容,不在常量池中,因此其是存放在堆中的
        //4.StringBuffer 是一个final类,不能被继承
	   //5.因为StringBuffer字符内容是存放在  char[] value, 所以在变化(增加/删除)后,
	   //  不用每次都更换地址,即不是每次都创建对象,所以效率高于String
        /**
         * 展示对应部分源码
         * abstract class AbstractStringBuilder implements Appendable, CharSequence {
         *      char[] value;
         */
        StringBuffer stringBuffer = new StringBuffer();

String VS StringBuffer

  • String保存的是字符串常量,里面的值不能更改,每次String类的更新实际上就是更改地址,效率较低;

    • 内层源码 private final char value[];
  • StringBuffer保存的是字符串变量,里面的值可以更改,每次StringBuffer的更新实际上可以更新内容,

    不用每次更新地址,效率较高,内层源码 char[] value ---->其存放在堆中

StringBuffer构造器的使用

        //构造器的使用
        //1.创建一个大小为16的char[],用于存放字符内容
        StringBuffer stringBuffer = new StringBuffer();
        //2.通过构造器指定 char[] 大小
        StringBuffer stringBuffer1 = new StringBuffer(100);
        //3.通过给一个String 创建StringBuffer,此时的char[]大小就是str.length()+16
        //  即essence的字符长度+16 = 23
        StringBuffer stringBuffer2 = new StringBuffer("essence");

String和StringBuffer之间的相互转换

        //String --> StringBuffer
        String str = "enjoy your life";
        //方式1 使用构造器
        //注意: 返回的是才是StringBuffer对象,对str本身是没有影响的;
        StringBuffer stringBuffer = new StringBuffer(str);
        //方式2 使用append方法
        StringBuffer stringBuffer1 = new StringBuffer();
        stringBuffer1 = stringBuffer1.append(str);


        //StringBuffer --> String
        StringBuffer stringBuffer3 = new StringBuffer("enjoy the life");
        //方式1  使用StringBuffer提供的toString方法
        java.lang.String string = stringBuffer3.toString();
        //方式2  使用构造器来搞定
        String s = new String(stringBuffer3);

StringBuffer类常见方法

  • 增 append
  • 删 delete(start, end)
  • 改 replace(start, end , string) //将start — end 间的内容替换掉,不含end
  • 查 indexOf //查找子串在字符串第一次出现的索引,如果找不到,返回-1;
  • 插 insert
  • 获取长度 length
        StringBuffer s = new StringBuffer("hello");
        //增
        s.append("kerwin");
        s.append("多").append(1110).append(true).append(6.49);
        System.out.println(s);//hellokerwin多1110true6.49
        /**
         * StringBuilder的toString源码
         *      @Override
         *     public synchronized String toString() {
         *         if (toStringCache == null) {//先判断是否为空,如果是的话,采用数组拷贝
         *             toStringCache = Arrays.copyOfRange(value, 0, count);
         *         }
         *         return new String(toStringCache, true);//反之则创建一个String对象
         *     }
         */

        //删
        //删除索引为>= start && < end 处的字符
        s.delete(0,5);//也就是删除[0,5)范围内的
        System.out.println(s);//kerwin多1110true6.49



        //改
        s.replace(0,6,"jasos");//替换[0,6)范围内的字符
        System.out.println(s);//jasos多1110true6.49

        //查找指定的子串在字符串中第一次出现的索引,如果找不到就返回-1
        int i = s.indexOf("多");
        System.out.println(i);//5

        //插
        System.out.println(s);//jasos多1110true6.49
        s.insert(6,"巴胺");//插入“巴胺”到6索引处
        System.out.println(s);//jasos多巴胺1110true6.49

        //长度
        System.out.println(s.length());//20

练习

在这里插入图片描述

//输出结果如下
4
    //深挖append底层源码,可得
    private AbstractStringBuilder appendNull() {
        int c = count;
        ensureCapacityInternal(c + 4);
        final char[] value = this.value;
        value[c++] = 'n';
        value[c++] = 'u';
        value[c++] = 'l';
        value[c++] = 'l';
        count = c;
        return this;
    }
    //其调用的是父类AbstractStringBuilder的appendNull方法,将null转为数组
	//所以,求其长度为4
null
空指针异常
    //传进去的是一个空对象,挖下源码,找出对应的构造器。可知
   	public StringBuffer(String str) {
        super(str.length() + 16);
        append(str);
     }
	//此中的str此时是null,那么显然,此时会报空指针异常

在这里插入图片描述

        //思路分析
        /**
         * 1.定义一个Scanne对象,接收用户输入的价格
         * 2.希望使用到StringBuffer的insert,需要将String转成StringBuffer
         * 3.然后使用相关方法进行字符串的处理
         */
     /*  String price = "123564.59";
        StringBuffer sb = new StringBuffer(price);
        int i = sb.lastIndexOf(".");
        sb.insert(i-3,',');//
        System.out.println(sb);//123,564.59*/


        //上面这种处理过于死板,如果输入的价格很长,就无法实现预期效果了,所以
        //我们要考虑使用循环来实现

        String price = "123456.59";
        StringBuffer sb = new StringBuffer(price);
        //底下这个循环,切记,要先在判断条件中-3,然后在i处insert,否则,
        // 判断条件中不-3,直接在方法体中insert(i-3,',')会出现,123,564.49的情况
        for(int i = sb.lastIndexOf(".")-3;i>0;i-=3){
            sb.insert(i,',');
        }
        System.out.println("商品价格为:");
        System.out.println(sb);

StringBuilder类

介绍:

  • 一个可变的字符序列,此类提供一个与StringBuffer兼容的API。但不保证同步(StringBuilder不是线程安全的),该类被设计用作StringBuffer的一个简易替换,用在字符缓冲区被单个线程使用的时候。如果可能,建议优先采用该类,因为在大多数实现中,它比StringBuffer要快
  • 在StringBuilder上的主要操作是append和insert方法,可重载这些方法,以接受任意类型的数据

StringBuilder常用方法

  • StringBuilder和StringBuffer均代表可变的字符序列,方法是一样的,所以其使用和StringBuffer一样;
//源码剖析
public final class StringBuilder
    extends AbstractStringBuilder
    implements java.io.Serializable, CharSequence{
/    
    abstract class AbstractStringBuilder implements Appendable, CharSequence {
    /**
     * The value is used for character storage.
     */
    char[] value;

    

注意事项及要点

  • StringBuilder继承了AbstractStringBuilder类
  • 实现了Serializable,说明StringBuilder对象是可串行化的(即对象可以网络传输,可以保存到文件)
  • StringBuilder 是final类,不能被继承
  • StringBuilder 对象字符序列仍然是存放在其父类AbstractStringBuilder的char[ ] value,因此,字符序列是存在堆中
  • StringBuilder的方法,没有做互斥处理,即没有用synchronized关键字修饰,因此在单线程的情况下使用StringBuilder

String、StingBuilder、StringBuffer三者的比较

  • StringBuilder和StringBuffer非常类似,均代表可变的字符序列,而且方法也一样
  • String:不可变字符序列,效率低,但是复用率高
    • 这里的复用率高是指,新建一个字符串“essence”,只要有了,所有的对象都可以指向常量池中的“essence”;
  • StringBuffer:可变字符序列,效率较高(增删),线程安全
  • StringBuilder: 可变字符序列,效率最高,线程不安全
  • String使用注意说明:
    • String s = “a”;//创建了一个字符串
    • s += “b”;// 实际上原来的"a"字符串对象已经丢弃了,现在又产生了一个字符串s + “b”(也就是"ab")。如果多次执行这些改变串内容的操作,会导致大量的副本字符串对象存留在内存中,降低效率,如果这样的操作放到循环中,会极大地影响程序的性能
    • 所以:===>如果我们对String 做大量修改,不要使用String
  • 效率比较
    • 通过同样次数的字符累加,然后输出各自类型的用时,可以得出效率大小,如下:
    • StringBuilder > StringBuffer > String

String、StringBuilder、StringBuffer如何选择?

使用原则,结论如下【!!!】:

  • 如果字符串存在大量的修改操作,一般使用StringBuilder或StringBuffer
  • 如果字符串存在大量的修改操作,并在单线程的情况下,使用StringBuilder
  • 如果字符串存在大量的修改操作,并在多线程的情况下,使用StringBuffer
  • 如果我们字符串很少修改,被多个对象引用,使用String,比如配置信息等

StringBuilder的方法使用和StringBuffer一样,不再说

感谢看到这儿的小伙伴,欢迎各位留言指出文章的不足之处,下期再见,拜~~

  • 22
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值