博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
String、StringBuffer、StringBuffer 分析
阅读量:4150 次
发布时间:2019-05-25

本文共 4587 字,大约阅读时间需要 15 分钟。

关于String 类容易错误的几点内容,首先我们来看String 类的几种方法

public class Test {    public static void main(String[] args) {        String a = "hello";        String b = new String("hello");        System.out.println(a == b);  //false         System.out.println(a.equals(b)); //true        System.out.println("--------");        String c = "hel";        String d = "lo";        String e = c + d;        String f = "hel"+"lo";        System.out.println(a == e); //false        System.out.println(a == f); //true    }}

以上是关于String 类的 == 和 equals 方法在不同情况下的返回结果。我们来逐步分析,为什么会产生不同的结果。

String 当中 equals 方法

我们来看下String 类当中equals 方法的源码

public boolean equals(Object anObject) {    if (this == anObject) {        return true;   //①    }    if (anObject instanceof String) { //②        String anotherString = (String)anObject;        int n = value.length;        if (n == anotherString.value.length) { // ③            char v1[] = value;            char v2[] = anotherString.value;            int i = 0;            while (n-- != 0) {                if (v1[i] != v2[i])                    return false;                i++;            }            return true;        }    }    return false;}

第一步:判断要比较的对象和当前对象地址是否相同,如果相同直接返回true;

第二步:如果需要判断的对象是String 类型,进行强制类型转换

第三步:如果两个对象的char[] 长度相同,那么对两个字符串中char[] 数组逐个对比,如果有不相同的,直接返回false

结论:String 类中equals 方法对比的就是两个字符串的字面值,只要字符串的内容相同,那么equals方法,就会返回true, 与生成该对象的方式无关。

接下来我们来主要看 String 类当中不同情况下 == 方法为什么返回的是不同的。

1、String s = "hello";

2、String s = new String("hello");

3、String a = "hel",b = "lo", c = a+b;

对于以上三种生成String 对象的方式我们从java编译出来的源码来看下。

public class Test {    public static void main(String[] args) {        String a = "hello";        String b = "hello";        String c = "hel"+"lo";    }}

使用javap -v 对Test.class文件进行编译,我们可以看到常量池:

对于String a = "hello"; 这段代码由两部分组成,0:ldc 2 :astore_1 , 可以理解为在常量池当中为字符串hello开辟了一个内存空间,接下来当我们在执行 String b = "hello" 时,也是同样的操作,以及字符串内容拼接时,可以看到后面都是 #2 两者返回的都是常量池当中的同一个地址。

接下来对于String s = new String("hello"); 这段代码编译出来的内容

public class Test {    public static void main(String[] args) {        String a = new String("hello");        String b = "hello";    }}

 

 

0-3 行,java 堆上为String 类申请内存。

4 判断常量池中是否存在字符串“hello”,如果没有在常量池中创建一个“hello”对象。并返回

10 是String b = "hello" 这个字符串已经在常量池中存在了,所以不创建新的对象,直接返回常量池中的对象。

总结:使用new String 的方式创建对象时,首先会在java内存的堆上创建一个string 对象,然后去判断常量池中是否存在这个字符串对象,如果存在直接,不创建字符串池中的对象,如果不存在,需要创建一个常量池中的对象,但是此时返回的是堆中对象的地址。

String a = "hel",b = "lo", c = a+b;

public class Test {    public static void main(String[] args) {        String a = "hello ";        String b = "world";        String c = a + b;        String d = "hello world";    }}

可以看到字符串变量 a+b 这段代码在底层的操作

6-9 在java堆上为StringBuilder申请内存

10 调用StringBuilder 的无参构造方法

14、18 调用append方法拼接字符串

21 调用StringBuilder toString 方法返回

所以我们可以看到字符串变量的拼接,其底层其实是StringBuilder 的append 方法,然后使用toString返回的。

@Overridepublic String toString() {    // Create a copy, don't share the array    return new String(value, 0, count);}

而StringBuilder中的toString 方法实际上市创建了一个新的String对象,因此 == 判断时,地址是不相同的。

关于StringBuilder 和StringBuffer 

StringBuilder和StringBuffer 底层都是维护了一个char[] 数组,不同的是 StringBuffer 在部分方法上会加上synchronized

关键字来保证线程安全。

所以在高并发场景下,StringBuffer 的性能是不如StringBuilder 的,但是并不是说使用StringBuilder 就能达到性能最优

关于字符串的操作有以下的建议

1、在for 循环当中,不建议使用 String + 的方式

2、在使用StringBuilder 时,最好可以初始化一个合适的长度

关于第一点:

在上面String 中已经介绍了,如果使用 变量 + 的方式,每次都要创建一个StringBuilder对象,造成内存的极大浪费,同时也会降低执行时间。

第二点:

StringBuilder 在创建时,会出事化一个数组长度为16 char[] 数组,如果我们append 追加的长度超过16 ,我们来看下StringBuilder 会怎么做

/** * This implements the expansion semantics of ensureCapacity with no * size check or synchronization. */void expandCapacity(int minimumCapacity) {    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.copyOf(value, newCapacity);}

1、在其父类当中会对数据进行扩容操作,首先扩容为原长度的2倍+2

2、如果扩容后,还是小于新增量,按照给定的容量

3、将原来的value 数组拷贝到新的数组当中

所以,如果我们在初始化的时候,没有指定StringBuilder 的长度,加入一个长度为129的字符串,通过StringBuilder append方法追加进来,需要经过16,34,70,142,四次复制和丢弃,一共浪费掉了几百个字符的数组。

所以初始化一个相对来说大一点的内存,会比成倍的扩容要好~

但素, 还是会有内存上的浪费,因为在最后toString 时,会new String 的方式拷贝一份数组到String 对象当中。

我们可以使用unsafe 类来避免这次拷贝,这里推荐一个比较好的方法,参照BigDecimal中 提供的StringBuilderHelper 的方法

// Accessors.StringBuilder getStringBuilder() {    sb.setLength(0);    return sb;}

设置StringBuilder的length 为0,这样的话,并没有清空数组中的内容,只是重置了count指针,这样在toString 时,count 也是传递的参数,就不担心超过新内容的旧数据被拷贝过去。

关于并发时,使用StringBuilder 和StringBuffer 如果需要并发访问,最好是将StringBuiler 放在ThreadLocal 当中访问

而不是使用StringBuffer ,毕竟加锁会比较降低访问效率。

参考:  

 

你可能感兴趣的文章
大牛手把手带你!2021新一波程序员跳槽季,全套教学资料
查看>>
Android高级工程师进阶学习,分享PDF高清版
查看>>
Context都没弄明白凭什么拿高薪?年薪50W
查看>>
看完吊打面试官!大厂经典高频面试题体系化集合,最强技术实现
查看>>
看完直接怼项目经理!应聘高级Android工程师历程感言,薪资翻倍
查看>>
看完老板就给加薪了!30岁以后搞Android已经没有前途?工作感悟
查看>>
绝了!这么香的技术还不快点学起来,大牛最佳总结
查看>>
网络优化软件apk,金九银十怎么从中小企业挤进一线大厂?我先收藏为敬
查看>>
美团安卓面试,这些年我所经历的所有面试,完整版开放下载
查看>>
美团安卓面试,阿里巴巴Android面试都问些什么?含小米、腾讯、阿里
查看>>
腾讯T2亲自讲解!阿里面试100%会问到的JVM,源码+原理+手写框架
查看>>
腾讯T3亲自讲解!字节大牛耗时八个月又一力作,大牛最佳总结
查看>>
你所不知道的Android原生开发的现状,手慢无
查看>>
做了3年Android还没看过OkHttp源码?小白也能看明白
查看>>
免费Android高级工程师学习资源,挥泪整理面经
查看>>
全世界都在问Android开发凉了吗?赶紧收藏!
查看>>
全世界都在问Android开发凉了吗?送大厂面经一份!
查看>>
全网最具深度的三次握手、四次挥手讲解,最全Android知识总结
查看>>
全网最具深度的三次握手、四次挥手讲解,看这一篇就够了!
查看>>
全网最具深度的三次握手、四次挥手讲解,知乎上转疯了!
查看>>