首页 > 其他分享 >24-7-31String类,StringBuffer类,StringBuilder类的详解与比较

24-7-31String类,StringBuffer类,StringBuilder类的详解与比较

时间:2024-08-01 22:27:38浏览次数:10  
标签:24 31String String StringBuffer System println public out

24-7-31 String类,StringBuffer类,StringBuilder类的详解与比较

文章目录

String

String的结构

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

String字符串的字符使用Unicode的字符编码,一个字符无论是中文还是汉字都占两个字节。

String有许多重载的构造器,因此可以接收多种数据类型并创建对象,比如说下面的:

String s1 = new String();

String s2 = new String(String original);

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

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

String s5 = new String(byte[] b)…

String我们大多不陌生,观察它的继承结构也可以发现它主要是实现了三个接口。image-20240731172446084

其中,实现Serializable接口表示串行化,可在网络传输,Compareable表示可以比较大小。

String是final类,因此不能被其他类继承。

String有属性private final char value[],用于放置字符串内容,前面加了final表示不可以修改,不过不可以修改指的是地址不可以修改,不是数组里面的值不能修改,比如下面的image-20240731173430257

在这里,我们可以发现value数组中的值是可以修改的,但倘若我们想要让final类型的数组指向其他地址,编译器就会报错。

String的方法

String对象的两种创建方法

String对象有两种创建方法。

//方式一
String s = "hx";
//方式二
String s2 = new String("hx");

方式一首先看看常量池里面有没有”hx“,如果有,直接指向,如果没有,创建一个再指向,总之最后指向的是常量池的空间地址。

方式二现在堆中创建空间,里面维护value空间,指向常量池的hx空间,如果常量池没有hx,创建一个,如果有,直接指向,总之最后指向的是堆中的对象。

下面是两种方式的内存分布图:

image-20240731174330934

String的其他方法
public class StringMethod02 {
    public static void main(String[] args) {
        String s1 = "heLLo java";
        System.out.println(s1.toUpperCase());//全部转大写
        System.out.println(s1.toLowerCase());//全部转小写
        
        String s2 ="hx";
        s2 = s2.concat("yyds").concat("jiayou");//拼接字符串
        System.out.println(s2);
        
        String s3 ="很喜欢喜欢喜欢";
        s3 = s3.replace("喜欢","讨厌");
        System.out.println(s3);//替换字符串
        
        String poem ="锄禾日当午,汗滴禾下土,谁知盘中餐,粒粒皆辛苦";
        String[] poems = poem.split(",");
        for(String s : poems) {
            System.out.println(s);
        }//将字符串分割并装入字符串数组
        
        String address ="E:\\aaa\\bbb";
        String[] addresss =address.split("\\\\");
        for(String s:addresss) {
            System.out.println(s);
        }//分割标记是/的话要多加一个/
        
        char[] ch =s1.toCharArray();
        for(char c:ch) {
            System.out.println(c);
        }//将字符串放入字符数组
        
        String s4 ="hello";
        String s5 ="hel";
        System.out.println(s4.compareTo(s5));//字符串比较

        String name ="黄暄";
        int age =19;
        char gender = '男';
        String school = "江苏师范大学";
        String info = "我的名字是" + name +",年龄是" + age +
                ",性别是"+gender+",就读于"+ school;
        System.out.println(info);

        String formatStr ="我的名字是%s,年龄是%d,性别是%c," +
                "就读于%s";
        String s6 = String.format(formatStr,name,age,gender,school);
        System.out.println(s6);//字符串拼接的两种方法

    }
}

输出为

HELLO JAVA
hello java
hxyydsjiayou
很讨厌讨厌讨厌
锄禾日当午
汗滴禾下土
谁知盘中餐
粒粒皆辛苦
E:
aaa
bbb
h
e
L
L
o

j
a
v
a
2
我的名字是黄暄,年龄是19,性别是男,就读于江苏师范大学
我的名字是黄暄,年龄是19,性别是男,就读于江苏师范大学

String练习

StringExercise01
public class StringExercise01 {
    public static void main(String[] args) {
        String a = "abc";
        String b = "abc";
        System.out.println(a.equals(b));
        System.out.println(a==b);
    }
}

我在上一篇博客曾经说过,equals比较的是值相等不相等,==比较的是地址相等不相等,在这里,第一个输出的肯定是true,因为都是abc,然后由于在a创建的时候常量池里面创建了一个abc,所以当创建b的时候会直接指向那个abc,也就是它们指向的地址相等,所以输出的也是true。

StringExercise02
public class StringExercise03 {
    public static void main(String[] args) {
        String a = "hx";
        String b = new String("hx");
        System.out.println(a.equals(b));
        System.out.println(a==b);
        System.out.println(a==b.intern());
        System.out.println(b==b.intern());
    }
}

第一个,两个String对象都是hx,所以equals输出true;

第二个,a指向的是常量池中的hx,b指向的是堆中的value,value指向常量池,所以输出false;

第三个,在做这题之前,我们应该先搞明白intern方法,我们来看定义

image-20240731175145925

简单来说,就是如果在调用这个方法的时候,常量池中有和调用这个方法的String类对象相同的字符串,就返回常量池中这个字符串的地址,如果没有都话,就加一个这样的字符串。

所以当b调用intern方法时候,因为常量池中有hx,所以会返回hx的在常量池的地址,这个地址当然与a相等,所以返回的是true;

第四句,因为intern返回的是hx在常量池的地址,而b是指向堆的,所以不是相同的地址,输出flase;

StringExercise03
public class StringExercise04 {
    public static void main(String[] args) {
        String s1 = "hx";
        String s2 = "java";
        String s4 = "java";
        String s3 = new String("java");
        System.out.println(s2==s3);//F
        System.out.println(s2==s4);//T
        System.out.println(s2.equals(s3));//T
        System.out.println(s1==s2);//F
    }

还是之前讲的那一套,不多说。

StringExercise04
public class StringExercise05 {
    public static void main(String[] args) {
        Person p1 = new Person();
        p1.name = "hx";
        Person p2 = new Person();
        p2.name = "hx";
        System.out.println(p1.name.equals(p2.name));//T
        System.out.println(p1.name == p2.name);//T
        System.out.println(p1.name == "hx");//T
        String s1 = new String("hx");
        String s2 = new String("hx");
        System.out.println(s1 == s2);//F
    }
}

这里需要注意,虽然开辟了两个对象,但是这两个对象都先后指向常量池的hx,所以如果只比较字符串的话,会输出true,但是要是比较字符串的话,因为指向堆的不同地址,所以还是输出false;

StringExercise05
public class StringExercise06 {
    public static void main(String[] args) {
        String s1 = "hello";
        s1 = "haha";
        //上面两行创建了两个对象
    }
}

上面两句话的意思其实是在创建字符串s1的时候先看看字符串有没有hello,如果有,直接指向,如果没有,直接创建,再指向,然后再在常量池里面创建一个haha的常量,再让s1指向haha,所以根本上是创建了两个对象。

image-20240731223110115

StringExercise06
public class StringExercise07 {
    public static void main(String[] args) {
        String a = "hello" + "abc";//底层优化为helloabc
        System.out.println(a);
        //只创建了一个string对象
    }
}

为什么这里只创建了一个对象,因为编译器很聪明,它在底层会自动把这两个字符串拼接在以前,也就是“helloabc”,所以只需要创建一个对象。

StringExercise07
public class StringExercise008 {
    public static void main(String[] args) {
        String a = "hx";
        String b ="yyds";
        String c = a + b;
        //创建了三个string对象
        String d = "hxyyds";
        System.out.println(c == d);
        String e ="hx" +"yyds";
        System.out.println(e == d);
    }
}

输出

false
true

与上一题那种写法不一样,通过调试,我们可以知道,直接将字符串相加在底层的运行其实是,先创建一个StringBuilder的对象,然后执行它的append方法分别将hx与yyds拼接最后通过toString方法返回一个String对象给String c,所以c其实是指向堆的,然后堆中的value指向池中的hxyyds。

StringExercise08
public class StringExercise09 {
    public static void main(String[] args) {
        String s1 ="hx";
        String s2 = "java";
        String s5 ="hxjava";
        String s6 = (s1+s2).intern();
        System.out.println(s5 == s6);//T
        System.out.println(s5.equals(s6));//T
    }
}

前面讲过这个道理,因为在intern方法调用之前,池里已经有hxjava了,所以会直接指向现成的地址,因此返回true,当然内容也肯定相等。

StringExercise09
public class StringExercise10 {
    String str = new String("hx");
    final char[] ch = {'j','a','v','a'};
    public void change(String str,char[] ch) {
    str = "java";
    ch[0] = 'h';

    }

    public static void main(String[] args) {
        StringExercise10 ex = new StringExercise10();
        ex.change(ex.str,ex.ch);
        System.out.println(ex.str + "and");
        System.out.println(ex.ch);
    }
}

hxand
hava

接下来要讲的特别想C语言里面的值传递与地址传递,我的理解如下:在主栈中ex指向堆中的对象,然后调用change方法的时候因为要在栈中新开一个栈,原先这个栈中的str是指向堆中的value的,但是现在通过赋值语句,它指向了常量池中的java,ch它原先指向的是java,现在通过赋值语句把堆中的这个数组修改成了hava,在调用语句执行完毕以后,这个新栈就销毁了,此时ex指向的str没有改变,但是ex指向的char数组是发生改变了的。

image-20240731225659547

StringBuffer

StringBuffer的结构

与String不同的是,StringBuffer是可变的字符序列,也就是说,StringBuffer不用像String那样如果要修改一个字符串需要新建一个对象,而是可以直接修改自身的字符串,我们来看StringBuffer的结构。

image-20240801193858240

我们可以看到,stringBuffer的直接父类是AbstractStringBuilder,同时由于StringBuffer实现了Serializable接口,所以StringBuffer也可以串行化,在父类中有属性char[] value,它是存放在堆中的,不是final,这也是我刚刚说StringBuffer是可变的字符序列的原因,StringBuffer还是一个final类,它不能被继承。

String与StrngBuffer的对比

1.String保存的是字符串常量,里面的值不能够被修改,每次String类更新实际上更新的就是地址,效率比较慢。

2.StringBuffer保存的是字符串变量,里面的值可以更改,每次StringBuffer的更新实际上更新的是确确实实的内容,不用更新地址,效率比较高。

StringBuffer的方法

StringBuffer有多种多样的构造器,例如没有参数的,int参数的,String类型参数的,如果不传参数进去,那么StringBuffer默认创建一个容量为16的对象,不够的时候再自己增加,如果我们想要指定一个参数,那也可以,直接传一个int就可以指定大小啦,如果传的是String,那么就可以把这个String类型转为StringBuffer类型。当然,StringBuffer还有许多的方法,比如说下面的

image-20240801195633672

常用方法如下

public class StringBufferMethod {
    public static void main(String[] args) {
        StringBuffer stringBuffer = new StringBuffer("hello");
        stringBuffer.append(",world").append("!");
        System.out.println(stringBuffer);//拼接
        
        stringBuffer.delete(0,5);
        System.out.println(stringBuffer);//删除0-5
        
        stringBuffer.replace(0,2,"hxrrr");
        System.out.println(stringBuffer);//将0-2替换为对应字符串
        
        int index = stringBuffer.indexOf("o");
        System.out.println(index);//找寻对应下标
        
        stringBuffer.insert(5,"哈哈哈");
        System.out.println(stringBuffer);//在对应下标插入

    }
}

输出结果如下:

hello,world!
,world!
hxrrrorld!
5
hxrrr哈哈哈orld!

StringBuffer与String的相互转换

String与StringBuffer的相互转换各有两种方式,如下

public class StringAndStringBuffer {
    public static void main(String[] args) {
        //String->StringBuffer
        String s = "hx";
        //1.使用构造器
        StringBuffer stringBuffer = new StringBuffer(s);
        //2.使用append
        StringBuffer stringBuffer1 = new StringBuffer();
        StringBuffer stringBuffer2 = stringBuffer1.append(s);

        //StringBuffer->String
        //使用toString
        StringBuffer stringBuffer2 = new StringBuffer("hx");
        String s1 = stringBuffer2.toString();
        //使用构造器
        String s2 = new String(stringBuffer2);
    }
}

总结下来就是要么构造器直接提供,要么使用方法。

StringBuffer的练习

StringBufferExercise01
public class StringBufferExercise01 {
    public static void main(String[] args) {
        String str = null;
        StringBuffer stringBuffer = new StringBuffer();
        stringBuffer.append(str);
        System.out.println(stringBuffer.length());

        System.out.println(stringBuffer);
        StringBuffer stringBuffer1 = new StringBuffer(str);
        System.out.println(stringBuffer1);
    }
}

image-20240801201258193

有人会疑惑,为什么会输出null呢,而且长度是4,要想知道奥秘,我们需要看append的源码,我们首先找到append的String参数方法

image-20240801201721964

我们发现它是调用父类的append方法,我们再看看它父类的append方法

image-20240801201834841

通过父类的append方法,我们指定如果str为空,也就是我们现在这种情况,会返回appendNull方法返回的值,我们再看看appendNull方法返回的值。

image-20240801201959919

哈哈,我们找到幕后真凶了,原来null是这里来的。

那空指针异常又是哪来的呢,我们可以看看StringBuffer的构造函数

image-20240801202318017

我们发现,如果我们传进去的是空,那么就是导致null.length(),那必然会导致空指针异常的呀。

StringBufferExercise02
public class StringBufferExercise02 {
    public static void main(String[] args) {
        System.out.println("请输入金额");
        Scanner scanner = new Scanner(System.in);
        String s = scanner.next();
        StringBuffer stringBuffer = new StringBuffer(s);
        int index = stringBuffer.indexOf(".");
        for(int i = index - 3; i > 0;i-=3)
        {
            stringBuffer.insert(i,",");
        }

        System.out.println(stringBuffer);
    }
}

这个例子通过循环来实现将价格的整数位数三位三位的分隔开。

image-20240801202531465

StringBuilder

StringBuilder的结构

StringBuilder也是一个可变的字符序列,被设计用做StringBuffer的一个简易替换,用在字符串缓冲区被单个线程使用,如果可能,建议优先采用该类,因为再大多数实现中,它比StringBuffer要快。

image-20240801203215594

StringBuffer在结构上与StringBuilder也相同。

StringBuilder也是final类,不能被继承。

StringBuffer的方法,没有做互斥处理,即没有synchroized关键字,因此在单线程的情况下使用

StringBuilder的方法

与StringBuffer方法类似,只是没有synchroized关键字,只能单线程,不过多赘述。

StringBuilder与StringBuffer与String的性能

public class StringVSStringBufferVSStringBuilder {
    public static void main(String[] args) {
        long startTime = 0L;
        long endTime = 0L;
        StringBuilder stringBuilder = new StringBuilder("");
        startTime = System.currentTimeMillis();
        for (int i = 0; i < 20000; i++) {
            stringBuilder.append(i);
        }
        endTime = System.currentTimeMillis();
        System.out.println("StringBuilder的运行时间为"+(endTime-startTime));

        StringBuffer stringBuffer = new StringBuffer("");
        startTime = System.currentTimeMillis();
        for (int i = 0; i < 20000; i++) {
            stringBuffer.append(i);
        }
        endTime = System.currentTimeMillis();
        System.out.println("StringBuffer的运行时间为"+(endTime-startTime));

        String s = new String("");
        startTime = System.currentTimeMillis();
        for (int i = 0; i < 20000; i++) {
            s = s+i;
        }
        endTime = System.currentTimeMillis();
        System.out.println("String的运行时间为"+(endTime-startTime));
    }
}

image-20240801203815479

我们可以看到,同样是拼接20000次,它们三者速度简直是天壤之别,前面说过,String每次都要新开一个对象,效率很慢,而StringBuider的效率是最快的。

总结

image-20240801204019530

image-20240801204112496

标签:24,31String,String,StringBuffer,System,println,public,out
From: https://blog.csdn.net/2301_79291071/article/details/140834437

相关文章

  • 2024牛客暑期多校训练营6
    Abstract好难qwqA-CakeIdea全是博弈!首先来解释题目意思。phase1:给出一颗树,根节点为1,树上每一条边的权值为0或者1。初始时刻,根节点处有一只小马,小G和小O依次控制小马移动,每次只能向子节点移动,若当前节点是叶节点,phase1结束。在移动的过程中,需要记录经过的边的......
  • 2024牛客暑期多校训练营6 A Cake
    题目大意详细题目传送门\(A\)和\(B\)要从轮流走,从根到一个叶子节点位置,\(A\)先。树有边权\(0,1\),按照顺序经过的边权按字符串拼接得到一个串\(S\)。现在\(B\)可以把\(1\)拆分成任意个分数(但不能超过\(S\)的长度,且分数可以为空,)两人按照\(S\)串的顺序选取,如果\(S_......
  • 喜报 | 极限科技入选北京市 2024 年第一批科技中小企业名单
    2024年7月24日,北京市科学技术委员会、中关村科技园区管理委员会发布《关于北京市2024年第一批拟入库科技型中小企业名单的公示》。根据《科技型中小企业评价办法》(国科发政〔2017〕115号)和《科技型中小企业评价服务工作指引》(国科火字〔2022〕67号)有关规定,极限数据(北......
  • 2024.8 - 做题记录与方法总结
    2024.8-RecordofQuestionsandSummaryofMethodology先分享一个歌单:永无止境的八月!2024/08/01先来点重量级的P4768[NOI2018]归程题面:[NOI2018]归程题目描述本题的故事发生在魔力之都,在这里我们将为你介绍一些必要的设定。魔力之都可以抽象成一个\(n\)个节......
  • 2024暑假集训测试17
    前言比赛链接。T1没加记忆化莫名原因T飞了,T2没做过IO交互不知道咋测样例干脆没交,T3到现在还不知道为啥爆零了,赛时不知道咋合并背包根本不敢打,离线下来寻思快点结果全死了,T4不可做题。还是老毛病,遇到之前见的不多题型(尤其是T1、T2放)就寄,这次T1倒是没卡住(但是挂分......
  • 2024.8.1 test
    A\(n\)个点的完全图,\(i\toj(i<j)\)的边权是\(u_j-u_i\),问最小生成树。\(n\le3e5\)。考虑boruvka算法。boruvka算法是重复以下过程,直到只有一个连通块。找到所有连通块的连向外面的最小边,并把这些边加入最小生成树。不难发现这是最多做\(\logn\)次的。我们现在考虑......
  • 2024年1月刷题记录
    2024年1月1日【leetcode】1599.经营摩天轮的最大利润题意:你正在经营一座摩天轮,该摩天轮共有4个座舱,每个座舱最多可以容纳4位游客。你可以逆时针轮转座舱,但每次轮转都需要支付一定的运行成本runningCost。摩天轮每次轮转都恰好转动1/4周。给你一个长度为n的......
  • 2024年4月刷题记录
    2024年4月1日【leetcode】2810.故障键盘题意:你的笔记本键盘存在故障,每当你在上面输入字符'i'时,它会反转你所写的字符串。而输入其他字符则可以正常工作。给你一个下标从0开始的字符串s,请你用故障键盘依次输入每个字符。返回最终笔记本屏幕上输出的字符串。2024......
  • 2024年3月刷题记录
    2024年3月1日【leetcode】2369.检查数组是否存在有效划分题意:给你一个下标从0开始的整数数组nums,你必须将数组划分为一个或多个连续子数组。如果获得的这些子数组中每个都能满足下述条件之一,则可以称其为数组的一种有效划分:子数组恰由2个相等元素组成,例如,子......
  • 2024年2月刷题记录
    2024年2月2日【leetcode】1686.石子游戏VI题意:Alice和Bob轮流玩一个游戏,Alice先手。一堆石子里总共有n个石子,轮到某个玩家时,他可以移出一个石子并得到这个石子的价值。Alice和Bob对石子价值有不一样的的评判标准。双方都知道对方的评判标准。给你两个长度为......