一、String类,StringBuffer类,StringBuilder类
String类,StringBuffer类,StringBuilder类都是Java中表示字符串的类,底层数据是char型的字符数组。三个类都被final修饰,表示不可再被继承。
String类是不可变的,创建一个字符串对象之后,对象的字符序列就不能被改变,因为String的成员char型数组是被定义为private final的。
String类被设计为不可变的原因:
1)安全性:并发场景下,由于String不能被改变,所以不会存在写竞争的问题,任何线程读到的都是同样的字符串。另外一个安全是比如在类加载器加载类时,不可变类名保证了类被正确加载。
2)String的hashCode是根据字符串内容来的,不可变保证了hashCode的不变性,对于hashMap等容器,不可变的String类使其比其他对象更适合当容器的键值。
3)性能:String类型不可变时,字符串常量池才有意义,字符串常量池的出现,可以减少创建相同字面量的字符串,让不同的引用指向池中同一个字符串,为运行时节约很多的堆内存。
StringBuilder和StringBuffer相似,创建一个字符串对象之后是可以改变,他们的父类AbstractStringBuilder中成员char型数组没有被定义为final,而且还提供一系列append方法和insert方法,将append或insert的东西加入到对象的char型数组。两者的区别在于StringBuffer是线程安全的,因为此类中的绝大多数方法都是用synchronized修饰的。
三、详细了解String类
String类的常用方法
1.构造方法
String s1 = new String(); // 定义一个空字符串""
String s2 = new String("hello"); // 声明并初始化为"hello"字符串
char[] ch =new char[]{'h','e','l','l','o'}; String s3 =new String(ch);// 字符数组
byte[] b =new byte[]{1,3,4}; String s4 =new String(b);// byte数组
int[] i =new int[]{2,4,5,6}; String s5 =new String(i,0,3);// int数组
2.其他方法
int length() // 字符串长度
boolean isEmpty() // 是否为空字符串,即字符串长度是否为0
char charAt(int index) // 某位置上的字符,从0开始。会抛StringIndexOutOfBoundsException
byte[] getBytes()、byte[] getBytes(Charset charset)、byte[] getBytes(String charsetName) // 转换成字节数组,可传指定字符集或字符集名字,字符集名字非法会抛UnsupportedEncodingException
boolean equals(Object anObject) // 比较此字符串是否和另一个对象相等,先用==比较是否是同一个对象,再看anObject是否是String的实例,再看两字符串长度是否相等,再比较每一个字符是否相同
boolean equalsIgnoreCase(String anotherString) // 忽略大小写比较两个字符串是否相同
int compareTo(String anotherString) // 比较此字符串和anotherString的大小(每个字符的Unicode编码),当前字符串大于anotherString返回正数,等于返回0,小于返回负数
int compareToIgnoreCase(String str) // 同上,只是不区分字符的大小写
boolean startsWith(String prefix)、boolean startsWith(String prefix,int toffset)、boolean endsWith(String suffix) // 以某字符串开头或以某字符串结尾
int hashCode() // 字符串的hash码,h=s[0] * 31^(n-1) + s[1] * 31^(n-2) + ... + s[n-1] (n为字符串长度),选择31的原因是:1)31是一个素数,与素数相乘的方式更容易产生唯一性;2)如果是更大的素数,乘出来的结果可能会溢出,因为结果是int类型;3)31 * i 在计算时可为 (i<<5)- i
int indexOf(int ch)、int indexOf(int ch,int fromIndex)、int lastIndexOf(int ch)、int lastIndexOf(int ch,int fromIndex) // 字符第一次出现的位置或字符最后一次出现的位置,参数int类型的ch是字符的Unicode code point
int indexOf(String str)、int indexOf(String str,int fromIndex)、int lastIndexOf(String str)、int lastIndexOf(String str,int fromIndex) // 字符串第一次出现的位置或最后一次出现的位置
String substring(int beginIndex) // 从beginIndex开始的子字符串,包括beginIndex的位置,比如"hello".subString(2)返回从第二个位置开始的字串"llo"。
String substring(int beginIndex,int endIndex) // 从beginIndex开始到endIndex的子字符串,包括beginIndex的位置,不包括endIndex的位置
String concat(String str) // 连接2个字符串 str1.concat(str2) 产生一个新的字符串,str1、str2不变
boolean contains(CharSequence s) // 判断一个字符串里是否包含某字符序列
String replace(char oldChar,char newChar) // 用新字符取代字符串中原来的字符,如 "hello".replace('e', 'i')返回"hillo",返回的"hillo"是一个新字符串,原来的"hello"不变
String replaceFirst(String regex, String replacement) // 将字符串中第一个匹配到regex字符串替换为replacement,产生一个新字符串,原字符串不变,"hellohe".replace("he","uo") 返回uollohe
String replaceAll(String regex, String replacement) // 将字符串中全部匹配到regex字符串替换为replacement,产生一个新字符串,原字符串不变,"hellohe".replace("he","uo") 返回uollouo
String replace(CharSequence target, CharSequence replacement) // 将字符串中target字符序列全部替换为
replacement字符序列,产生一个新字符串,原字符串不变,"hellohe".replace("he","uo") 返回uollouo
replace与replaceAll的区别是:replace支持字符和字符串的替换,而repalceAll支持正则表达式替换。
String[] split(String regex,int limit) // 用给定字符串作为分隔,打断字符串,返回字符串数组,例子:
原字符串:str = "boo:and:foo"
str.split(":", 2); // "boo", "and:foo"
str.split(":", 5); // "boo", "and", "foo"
str.split(":", -2); // "boo", "and", "foo"
str.split("o", 5); // "b", "", ":and:f", "", ""
str.split("o", -2); // "b", "", ":and:f", "", ""
str.split("o", 0); // "b", "", ":and:f"
String[] split(String regex) // 同上,只是limit固定为0
static String join(CharSequence delimiter, CharSequence... elements) //字符串拼接,String.join("-", "Java", "is", "cool") 为"Java-is-cool"
static String join(CharSequence delimiter,Iterable elements) // 字符串拼接,List
String toLowerCase() // 转为小写,生成新字符串
String toUpperCase() // 转为大写,生成新字符串
String trim() // 去掉字符串2端的空格
static String format(String format, Object... args) // 格式化,类似printf
3.本地方法
String类中唯一的一个本地方法:native String intern();
由此方法引出常量池的概念,在Java中为了使程序运行的更快,更节省内存,为8种基本类型及字符串常量引入了常量池,常量池类似Java系统级别提供的缓存。
对于字符串常量池,在JDK6及之前,存储在永久代(PermGen Space)即方法区中,在JDK7及之后,字符串常量池被移到了堆中,因为Perm区空间有限,如果程序中大量调用intern方法的话会产生java.lang.OutOfMemoryError:PermGen sapce错误。
对于直接使用双引号声明出的字符串,会直接放入字符串常量池中,如String s = "abc";
对于其他的String对象,调用intern方法也会被放入字符串常量池中,因为intern方法会先检查常量池中是否有此字符串(有此字符串的含义是equals方法比较相等的字符串),如果没有,就将其放入到字符串常量池并返回其引用。
一个例子:
String s1 = new StringBuilder("计算机").append("软件").toString();
s1.intern() == s1;
JDK6为false;JDK7为true
解释:JDK6中,new一个String对象时,在堆中创建了一个对象,s1指向堆内存中的地址,调s1.intern()方法时会先检查常量池中是否有"计算机软件"此字符串,发现没有,于是在常量池中创建一个对象,并返回此对象的引用,堆中的地址跟永久代中的地址肯定不一样,所以结果为false;JDK7中,也是在堆中创建一个对象,s1指向堆内存中的地址,当调s1.intern()方法时,先检查常量池中是否有"计算机软件"此字符串,因为此时字符串常量池就在堆中,所以认为常量池中已有此字符串,于是返回了堆中的引用,所以结果为true。
此例子为深入理解java虚拟机上的例子,更常见的例子见各博客。