一、String
1、基础知识:
String不是8种基本类型之一,String是一个对象,因为对象默认值为null,则String默认值也为空,同时也是一个特殊的对象,具备一些特性;(String s=new String()创建的是空串而不是null)。
String 类代表字符串。Java 程序中的所有字符串字面值(如 “abc” )都作为此类的实例实现。字符串是常量;它们的值在创建之后不能更改。字符串缓冲区支持可变的字符串。因为 String 对象是不可变的(被finall修饰),所以可以共享。
2、注意事项
1)字符串常量池
在字符串中,如果采用直接赋值的方式(String str=“Lance”)进行对象的实例化,则会将匿名对象“Lance”放入对象池,每当下一次对不同的对象进行直接赋值的时候会直接利用池中原有的匿名对象,这样,所有直接赋值的String对象,如果利用相同的“Lance”,则String对象==返回true;
String str =new String("Lance").intern();//对匿名对象"hello"进行手工入池操作
String str1="Lance";
System.out.println(str==str1);//true
2)两种实例化方式的区别
直接赋值(String str = “hello”):只开辟一块堆内存空间,并且会自动入池,不会产生垃圾。
构造方法(String str= new String(“hello”);):会开辟两块堆内存空间,其中一块堆内存会变成垃圾被系统回收,而且不能够自动入池,需要通过public String intern();方法进行手工入池。
在开发的过程中不会采用构造方法进行字符串的实例化。
3)String类对象一旦声明则不可以改变;而改变的只是地址,原来的字符串还是存在的,并且产生垃圾!
3、常用API
String的判断功能
boolean equals(Object obj):比较字符串的内容是否相同
boolean equalsIgnoreCase(String str): 比较字符串的内容是否相同,忽略大小写
boolean startsWith(String str): 判断字符串对象是否以指定的str开头
boolean endsWith(String str): 判断字符串对象是否以指定的str结尾
boolean isEmpty(String str):判断是否为空
boolean contain(String str1):判断原字符串是否有它
String类的获取功能
int length():获取字符串的长度,其实也就是字符个数
char charAt(int index):获取指定索引处的字符
int indexOf(String str):获取str在字符串对象中第一次出现的索引,没有出现返回-1
String substring(int start):从start开始截取字符串
String substring(int start,int end):从start开始,到end结束截取字符串。包括start,不包括end
String的转换功能
char[] toCharArray():把字符串转换为字符数组
String toLowerCase():把字符串转换为小写字符串
String toUpperCase():把字符串转换为大写字符串
其他:
去除字符串两端空格:String trim()
按照指定符号分割字符串:String[] split(String str)
测试:
String s4 = "aa,bb,cc";
String[] strArray = s4.split(",");
for (int x = 0; x < strArray.length; x++) {
System.out.println(strArray[x]);
}
结果:aa
bb
cc
4、不可变的好处(重点)
-
便于实现String常量池
只有当字符串是不可变的,字符串池才有可能实现。字符串池的实现可以在运行时节约很多heap空间,因为不同的字符串变量都指向池中的同一个字符串。但如果字符串是可变的,那么String interning将不能实现(译者注:String interning是指对不同的字符串仅仅只保存一个,即不会保存多个相同的字符串。),因为这样的话,如果变量改变了它的值,那么其它指向这个值的变量的值也会一起改变。 -
避免网络安全问题
如果字符串是可变的,那么会引起很严重的安全问题。譬如,数据库的用户名、密码都是以字符串的形式传入来获得数据库的连接,或者在socket编程中,主机名和端口都是以字符串的形式传入。因为字符串是不可变的,所以它的值是不可改变的,否则黑客们可以钻到空子,改变字符串指向的对象的值,造成安全漏洞。 -
使多线程安全
因为字符串是不可变的,所以是多线程安全的,同一个字符串实例可以被多个线程共享。这样便不用因为线程安全问题而使用同步。字符串自己便是线程安全的。 -
避免本地安全性问题
类加载器要用到字符串,不可变性提供了安全性,以便正确的类被加载。譬如你想加载java.sql.Connection类,而这个值被改成了myhacked.Connection,那么会对你的数据库造成不可知的破坏。 -
加快字符串处理速度
因为字符串是不可变的,所以在它创建的时候hashcode就被缓存了,不需要重新计算。这就使得字符串很适合作为Map中的键,字符串的处理速度要快过其它的键对象。这就是HashMap中的键往往都使用字符串。
二、StringBuilder
1.由来
有些时候,需要由较短的字符串构建字符串,例如按键或者文件中的单词。如果用字符串拼接的方式来达到这个目的,效率会很低。每次拼接字符串的时候,都会构建一个新的String对象,既耗时又浪费空间。所以引入StringBuilder来解决这个问题。
基本步骤:
构建:StringBuilder builder=new StringBuilder();
添加:builder.append(str);
转换:String s1=builder.toString();
2、常用API:
注释:
StringBuilder在Java5引入,这个类的前身是StringBuffer,它的效率低。但允许采用多线程的方式添加或删除字符。如果所有的字符串,都是单线程,使用StringBuilder.这两个类的api是一样的。
注意:
StringBuffer和StringBuffer是可变类,任何对它所指代的字符串的改变都不会产生新的对象。
三、 String、StringBuffer和StringBuilder的区别(重点又来了):
(1)速度比较:
String < StringBuffer < StringBuilder
String的处理速度比StringBuffer、StringBuilder要慢的多。
原因:
String是不可变的对象:String本身就是一个对象,因为String不可变对象,所以,每次遍历对字符串做拼接操作,都会重新创建一个对象,循环100万次就是创建100万个对象,非常的消耗内存空间,而且创建对象本身就是一个耗时操作,创建100万次对象就相当的耗时了。
StringBuffer和StringBuilder是可变对象:StringBuffer和StringBuilder只需要创建一个StringBuffer或StringBuilder对象,然后用append拼接字符串,就算拼接一亿次,仍然只有一个对象。但是StringBuffer有synchorized锁,保证了线程安全,但牺牲了一部分的效率;所以速度要低于StringBuilder。
(2)线程安全:
StringBuffer是线程安全的
StringBuilder是非线程安全的, 这也是速度比StringBuffer快的原因
(3)使用场景
(一)如果要操作少量的数据用 String
原因:
(1)String遍历代码:一开始定义一个String常量(创建一个String对象), 再开始遍历;
(2)StringBuffer代码:一开始定义一个String常量(创建一个String对象)和一个创建StringBuffer对象,再开始遍历;
(3)StringBuiler代码:一开始定义一个String常量(创建一个String对象)和一个创建StringBuiler对象,再开始遍历;
(2)和(3)比(1)多了一个创建对象流程,所以,如果数据量比较小的情况建议使用String。
(二)单线程操作字符串缓冲区 下操作大量数据 StringBuilder
(三)多线程操作字符串缓冲区 下操作大量数据 StringBuffer
四、补充:
StringBuffer,StringBuilder 和 String 类似,底层是用一个数组来存储字符串的值
详细谈谈StringBuffer:
StringBuffer 数组的默认长度为 16,即一个空的 StringBuffer 对象,数组长度为 16。实例化一个 StringBuffer 对象即创建了一个大小为 16 个字符的字符串缓冲区。
但是当我们调用有参构造函数创建一个 StringBuffer 对象时,数组长度就不再是 16 了,而是根据当前对象的值来决定数组的长度,数组的长度为“当前对象的值的长度+16”。
所以一个 StringBuffer 创建完成之后,有 16 个字符的空间可以对其值进行修改。如果修改的值范围超出了 16 个字符,会先检查StringBuffer 对象的原 char 数组的容量能不能装下新的字符串,如果装不下则会对 char 数组进行扩容。
那StringBuffer是怎样进行扩容的呢?
扩容的逻辑就是创建一个新的 char 数组,将现有容量扩大一倍再加上2,如果还是不够大则直接等于需要的容量大小。扩容完成之后,将原数组的内容复制到新数组,最后将指针指向新的 char 数组。