String类浅谈
String源码学习
String类被final修饰,实现了Serializable、CharSequence(实现类有:String、StringBuilder、StringBuffer。有length()和charAt等方法方法)、Comparable<String>接口。
成员变量
private final char value[];
private int hash; // Default to 0
1、常用方法
hashCode();
获得字段的hash值
equals();
重写了Object的equals()方法。
除了做地址比较,还做了字符的逐一比较,但是要求传入对象是一个String类型的值,否则会返回false。
contentEquals(CharSequence cs);
用于charSequence接口实现类值是否相等的比较。也就是通用与StringBuffer、StringBuider和String的equals比较。
compareTo();
实现了Comparable<String> 接口,重写了compareTo()接口 并且还多加了一个compareToIgnoreCase()方法。
字符串不同,返回第一个不相同的charASCII码值的差值,否则返回字符串长度差值
trim();
去除字符串前后空字符,开启两个while循环,char[i] <= ' '被去掉,' '的ASCII码值是32,是字符ASCII的开始,32之前的是控制符、通信专用字符的ASCII码值。
如控制符:LF(换行)、CR(回车)、FF(换页)、DEL(删除)、BS(退格)、BEL(响铃)等;
通信专用字符:SOH(文头)、EOT(文尾)、ACK(确认)等;
关于忽略大小写的比较和equals方法,其中使用到了Character类的toUpperCase和toLowerCase方法。其底层通过二进制的按位与运算获得字母大小写转化。
indexOf()
laseIndexOf()
substring()
split()
字符串截取
concat()
创建一个数组,先把当前字符串value复制进去,再通过getChars把传入字符串的value复制到char数组里,底层都是调用了System.arragcopy方法(native修饰的本地方法)。
public native String intern();
获得字符串对应的常量池中的字符串值。
一般用作通过new String()获得字符串时,获取常量池字符串位置。
new的方式创建对象,会去堆上创建个对象,然后再将对象的引用指向常量池对应的常量。
public static String valueOf(double d)
public static String valueOf(float f)
public static String valueOf(long l)
public static String valueOf(int i)
indexOf():查询字符串首次出现的下标位置
lastIndexOf():查询字符串最后出现的下标位置
contains():查询字符串中是否包含另一个字符串
toLowerCase():把字符串全部转换成小写
toUpperCase():把字符串全部转换成大写
length():查询字符串的长度
trim():去掉字符串首尾空格
replace():替换字符串中的某些字符
split():把字符串分割并返回字符串数组
join():把字符串数组转为字符串
2、相关知识
StringBuilder
StringBuffer
StringBuilder和StringBuffer是应对String被final修饰后做字符串拼接的封装类
两者都继承了AbstractStringBuilder类,实现了CharSequence方法,基本的字符串功能都是在AbstractStringBuilder里实现的,不通点在于,java对所有的StringBuffer操作,都加了synchronized修饰。
3、常见问题
为什么 String 类型要用 final 修饰?
Java 语言之父 James Gosling 的回答是,他会更倾向于使用 final,因为它能够缓存结果,当你在传参时不需要考虑谁会修改它的值;如果是可变类的话,则有可能需要重新拷贝出来一个新值进行传参,这样在性能上就会有一定的损失。
James Gosling 还说迫使 String 类设计成不可变的另一个原因是安全,当你在调用其他方法时,比如调用一些系统级操作指令之前,可能会有一系列校验,如果是可变类的话,可能在你校验过后,它的内部的值又被改变了,这样有可能会引起严重的系统崩溃问题,这是迫使 String 类设计成不可变类的一个重要原因。
总结来说,使用 final 修饰的第一个好处是安全;第二个好处是高效,以 JVM 中的字符串常量池来举例,如下两个变量:
String s1 = "java";
String s2 = "java";
只有字符串是不可变时,我们才能实现字符串常量池,字符串常量池可以为我们缓存字符串,提高程序的运行效率,如下图所示:
== 和 equals 的区别是什么?
String 和 StringBuilder、StringBuffer 有什么区别?
String 的 intern() 方法有什么含义?
String 类型在 JVM(Java 虚拟机)中是如何存储的?编译器对 String 做了哪些优化?
String 常见的创建方式有两种,new String() 的方式和直接赋值的方式,直接赋值的方式会先去字符串常量池中查找是否已经有此值,如果有则把引用地址直接指向此值,否则会先在常量池中创建,然后再把引用指向此值;而 new String() 的方式一定会先在堆上创建一个字符串对象,然后再去常量池中查询此字符串的值是否已经存在,如果不存在会先在常量池中创建此字符串,然后把引用的值指向此字符串,如下代码所示:
String s1 = new String("Java");
String s2 = s1.intern();
String s3 = "Java";
System.out.println(s1 == s2); // false
System.out.println(s2 == s3); // true
它们在 JVM 存储的位置,如下图所示:
JDK 1.7 之后把永生代换成的元空间,把字符串常量池从方法区移到了 Java 堆上,除此之外编译器还会对 String 字符串做一些优化,将常用字符拼接不再创建多个常量池常量。
String s1 = "Ja" + "va";
String s2 = "Java";
System.out.println(s1 == s2);