String类定义的字符串特点
1.字符串内容永不可变.
2.字符串可以共享使用.
3.字符串的效果相当于char[]字符数组,但是底层原理都是byte[]字节数组.
创建字符串对象的方式有几种,有什么区别
1.public String():创建一个空白字符串.new String().
2.public String(char[] arr)
(1):根据字符数组的内容,来创建对应的字符串.
char c[]={'a','b','c'};
String str=new String(c);
(2):根据字节数据的内容,来创建对应的字符串.
byte b[]={1,2,3,4};
String str=new String(b);
3.直接创建:
String str="abcd";
4.字符串常量池:程序当中直接写上双引号的字符串,就在字符串常量池中.
(1)对于基本类型来说,==是进行数值的比较.
(2)对于引用类型来说,==是进行[地址值]得比较.
(3)从jdk1.7开始,在堆内存中有一个部分叫做字符串常量池,字符串常量池中保存的对象指向byte[]字节数组的地址值.
String,StringBuffer,StringBuilder的区别
1.String,StringBuffer,StringBuilder的区别
(1)String:String底层是一个final类型的字符数组,所以String的值是不可变得,每次对String操作都会生成新的对象.
(2)而StringBuffer和StringBuilder都继承了AbstractStringBuilder抽象类,从AbstractStringBuilder抽象类中我们可以看到他们的底层都是可变的字符数组,所以在进行频繁的字符串操作时,建议使用StringBuffer和StringBuilder来进行操作.
2.StringBuffer,StringBuilder的区别
(1)StringBuffer是线程安全的,而StringBuilder是线程不安全的(不能同步访问),但StringBuilder相比于StringBuffer有速度优势,多数情况下建议使用StringBuilder,但在应用程序要求线程安全的情况下,则必须使用StringBuffer.
(2)StringBuffer在jdk1.0就有了,而StringBuilder在jdk5.0时才出现.
(3)StringBuffer比StringBuilder多了一个toStringCache字段,用来在toString方法中进行缓存,每次append操作之前都先把toStringCache设置为null,若多次调用toString方法,可避免每次Arrays.copyOfRange(value,0,count)操作,节省性能.
(4)由于StringBuilder没有考虑同步,在单线程的情况下,StringBuilder的性能要优于StringBuffer.
3.单线程StringBuffer和StringBuilder的区别:
这个才是我们重点讨论的,单线程下StringBuffer加了synchronized,虽然是单线程, 但是synchronized获取锁和释放锁也还是需要时间的, 而StringBuilder没有,这个就是重点区别。因此重点要讨论synchronized锁的状态,从获取锁到释放锁的过程,因此需要讨论一下锁的升级和优化。
锁的4中状态:无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态(级别从低到高)
(1)偏向锁:
偏向锁是指一段同步代码一直被一个线程所访问,那么该线程会自动获取锁。降低获取锁的代价。
为什么要引入偏向锁?
因为经过HotSpot的作者大量的研究发现,大多数时候是不存在锁竞争的,常常是一个线程多次获得同一个锁,因此如果每次都要竞争锁会增大很多没有必要付出的代价,为了降低获取锁的代价,才引入的偏向锁。
偏向锁的升级
当线程1访问代码块并获取锁对象时,会在java对象头和栈帧中记录偏向的锁的threadID,因为偏向锁不会主动释放锁,因此以后线程1再次获取锁的时候,需要比较当前线程的threadID和Java对象头中的threadID是否一致,如果一致(还是线程1获取锁对象),则无需使用CAS来加锁、解锁;如果不一致(其他线程,如线程2要竞争锁对象,而偏向锁不会主动释放因此还是存储的线程1的threadID),那么需要查看Java对象头中记录的线程1是否存活,如果没有存活,那么锁对象被重置为无锁状态,其它线程(线程2)可以竞争将其设置为偏向锁;如果存活,那么立刻查找该线程(线程1)的栈帧信息,如果还是需要继续持有这个锁对象,那么暂停当前线程1,撤销偏向锁,升级为轻量级锁,如果线程1 不再使用该锁对象,那么将锁对象状态设为无锁状态,重新偏向新的线程。
偏向锁的取消:
偏向锁是默认开启的,而且开始时间一般是比应用程序启动慢几秒,如果不想有这个延迟,那么可以使用-XX:BiasedLockingStartUpDelay=0;
如果不想要偏向锁,那么可以通过-XX:-UseBiasedLocking = false来设置;
(2)轻量级锁
轻量级锁是指当锁是偏向锁的时候,被另一个线程所访问,偏向锁就会升级为轻量级锁,其他线程会通过自旋的形式尝试获取锁,不会阻塞,提高性能
为什么要引入轻量级锁?
轻量级锁考虑的是竞争锁对象的线程不多,而且线程持有锁的时间也不长的情景。因为阻塞线程需要CPU从用户态转到内核态,代价较大,如果刚刚阻塞不久这个锁就被释放了,那这个代价就有点得不偿失了,因此这个时候就干脆不阻塞这个线程,让它自旋这等待锁释放。
(3)重量级锁
重量级锁是指当锁为轻量级锁的时候,另一个线程虽然是自旋,但自旋不会一直持续下去,当自旋一定次数的时候,还没有获取到锁,就会进入阻塞,该锁膨胀为重量级锁。重量级锁会让其他申请的线程进入阻塞,性能降低。
轻量级锁什么时候升级为重量级锁?
线程1获取轻量级锁时会先把锁对象的对象头MarkWord复制一份到线程1的栈帧中创建的用于存储锁记录的空间(称为DisplacedMarkWord),然后使用CAS把对象头中的内容替换为线程1存储的锁记录(DisplacedMarkWord)的地址;
如果在线程1复制对象头的同时(在线程1CAS之前),线程2也准备获取锁,复制了对象头到线程2的锁记录空间中,但是在线程2CAS的时候,发现线程1已经把对象头换了,线程2的CAS失败,那么线程2就尝试使用自旋锁来等待线程1释放锁。
但是如果自旋的时间太长也不行,因为自旋是要消耗CPU的,因此自旋的次数是有限制的,比如10次或者100次,如果自旋次数到了线程1还没有释放锁,或者线程1还在执行,线程2还在自旋等待,这时又有一个线程3过来竞争这个锁对象,那么这个时候轻量级锁就会膨胀为重量级锁。重量级锁把除了拥有锁的线程都阻塞,防止CPU空转。
*注意:为了避免无用的自旋,轻量级锁一旦膨胀为重量级锁就不会再降级为轻量级锁了;偏向锁升级为轻量级锁也不能再降级为偏向锁。一句话就是锁可以升级不可以降级,但是偏向锁状态可以被重置为无锁状态。
综上可知,StringBuffer虽然是单线程,但它是有偏向锁升级过程判断的,会耗费时间,效率固然低于StringBuilder.
4.StringBuffer和StringBuilder的应用场景:
1.StringBuffer多线程安全,但加了synchronized,其效率低,故适用于多线程下,并发量不是很高的场景.
2.StringBuilder没有加任何的锁,其效率高,适用于单线程场景,但同时也适用于高并发场景中,提高高并发场景下程序的响应性能,至于线程安全问题可以通过其他手段解决,如ThreadLocal,CAS操作等.
3.所以对于高并发场景下,若用到二者,建议优先适用StringBuilder.
String类常用的方法有哪些
1.equals()方法:
引用数据类型使用"=="比较时是对象的地址值得比较,而在String类中的equals()被重写了比较的是对象的值.
注意事项:
(1)任何对象都可以被Object接收.
(2)equals方法具有对称性,a.equals(b)和b.equals(a)效果相同.
(3)如果比较一个常量一个变量,推荐把常量字符串写在前面.
2.length()方法:
获取字符串当中含有的字符个数,返回字符串长度.
3.concat()方法:
将当前字符串和参数字符串进行拼接并返回新的字符串.
4.cahrAt()方法:
获取指定位置的单个字符.
5.indexOf()方法:'
查找参数字符串在本字符串当中首次出现的索引位置,如没有,则返回-1.
6.substring()方法:
返回一个子字符串从第一参数位置开始到第二个参数位置结束,左闭右开.
7.toCharArray()方法:
将次字符串转换为新的字符数组.
8.getBytes()方法:
使用平台的默认字符集将该String编码转换为新的字节数组.
9.replace()方法:
将字符串中匹配第一个参数改变为第二个参数.
10.split()方法:
将次字符串按照给定的参数(规则)拆分为字符串数组.
== equals的区别
(1)"==":
1."=="是运算符.
2.如果比较的对象是基本数据类型,则比较的是其存储的值是否相等.
3.如果比较的引用数据类型,则比较的是所指向对象的地址是否相等(是否是同一个对象)
(2)equals()方法:
1.equals()方法是Object的方法,用来比较两个对象的内容是否相等.
2.equals()方法不能用于比较基本数据类型,如果没有对equals()方法进行重写,则相当于"==",比较的是引用类型的变量所指向对象的地址值.
3.一般情况下,类会重写equals()方法用来比较两个对象的内容是否相等.比如String类中的equals()方法是被重写了,比较的是对象的值.
(3)"=="与"equals()方法"最大的区别:
1."==":是运算符,如果是基本数据类型,则比较存储的值,如果是引用数据类型,则比较所指向对象的地址值.
2.equals是Object的方法,equals()不可以比较基本数据类型,比较的是所指向对象的地址值,一般情况下,重写之后比较的是对象的值.