结构图
Serializable接口:实现java.io.Serializable 接口的类是可序列化的。
Comparable接口:重写compareTo方法,进行比较。
CharSequence接口:JDK1.4开始,为了统一字符串操作的标准提供的接口。
构造器
String提供了很多构造器:
比如随机挑选一个:
public String(char value[], int offset, int count) { this(value, offset, count, rangeCheck(value, offset, count)); }
能把一个char数组转换成String,offset为起始位置,count为长度。
char[] a = {'a', 'a', 'a', 'a', 'a'};
String s = new String(a, 1, 4);
System.out.println(s);//aaaa
常用方法
String重写了equals方法,比较的是内容,默认的Object的equals比较的是地址
演示:
String s = "0123456789a";
String t = "0123456789A";
// equalslgnoreCase忽略大小写的判断内容是否相等
boolean b = s.equalsIgnoreCase(t);
System.out.println("equalslgnoreCase:" + b);
// length 获取字符的个数,字符串的长度
System.out.println("length;" + s.length());
// charAt 根据下标返回字符
System.out.println("charAt:" + s.charAt(0));
// contains是否包含
boolean contains = s.contains("a");
System.out.println("contains:" + contains);
// indexOf 获取字符在字符串对象中第一次出现的索引,索引从0开始,如果找不到,返回-1
String k = "ababa";
System.out.println("indexOf:" + k.indexOf("a"));
// lastIndexOf 获取字符子啊字符串中最后一次出现的索引,索引从O开始,如果找不到,返回-1
System.out.println("lastIndexOf:" + k.lastIndexOf("a"));
// substring截取指定范围的子串,经典左闭右开
System.out.println("substring:" + s.substring(0, 6));
// toUpperCase toLowerCase concat 转大写、转小写、字符串拼接
String upperCase = s.toUpperCase();
System.out.println("toUpperCase:" + upperCase);
String lowerCase = upperCase.toLowerCase();
System.out.println("toLowerCase:" + lowerCase);
String concat = s.concat(t);
System.out.println("concat: " + concat);
// replace替换字符串中的字符
String replace = s.replace("a", "sm");
System.out.println("replace:" + replace);
// split分割字符串 \转义符
String str = "a\\b\\c\\d";
String[] split = str.split("\\\\");//第一个\是转义字符,意思是第二个\是\。所以这里表示以\\分割
System.out.println("split:" + split[0] + " " + split[1] + "...");
// compareTo 比较两个字符串的大小
int i = s.compareTo(t);//按顺序每个字符进行比较,返回s[i]-t[i]的值,这里返回'a'-'A'
System.out.println("compareTo:" + i);
// toCharArray 转换成字符数组,做题挺有用的,转成字符数组可以用增强for循环。
char[] charArray = s.toCharArray();
System.out.println("toCharArray:" + charArray[0] + " " + charArray[1] + "...");
// format 格式字符串,%s字符串%c字符%d整型%.2f 浮点型案例
// 梦回c语言printf
String name = "sm";
char gender = '男';
double score = 4.23;
String sss = String.format("你好,%s,性别%c,绩点%.2f", name, gender, score);
System.out.println("format:" + sss);
控制台输出:
equalslgnoreCase:true
length;11
charAt:0
contains:true
indexOf:0
lastIndexOf:4
substring:012345
toUpperCase:0123456789A
toLowerCase:0123456789a
concat: 0123456789a0123456789A
replace:0123456789sm
split:a b...
compareTo:32
toCharArray:0 1...
format:你好,sm,性别男,绩点4.23
String是不可变的&重写equals方法
直接看看String的源码。
1、final修饰的类不能被继承,所以String不会被子类破坏String的不可变性(immutable)。
2、value数组被final修饰(指向的地址不可修改)并且设为私有,而且没有对外界提供修改value的方法
在java9之后,value数组使用byte类型来实现,之前是char类型。byte是1个字节,char是2个字节,可以节省一半空间。
StringBuilder与StringBuffer与StringJoiner
StringBuilder、StringBuffer继承AbstractStringBuilder类,不过这个类中的value没有被final和private修饰
StringBuider最重要的方法就是append,支持链式编程,用来做字符串拼接,StringBuffer操作与其一致。
StringBuffer sb = new StringBuffer(); sb.append("[").append("a").append("]");
关于线程安全:
String 中对象不变,常量,线程当然安全。
StringBuilder 不安全,没有加Synchronized关键字。
StringBuffer 安全,把各种修改数据的方法都加上了Synchronized关键字实现。
StringJoiner是JDK8提供的,会让代码更加简洁。
可以指定三个参数,分别是delimiter,prefix,suffix,分别代表分隔符,开始元素,结束元素
// 可以指定左右和间隔符
StringJoiner sj = new StringJoiner(", ", "[", "]");
使用add拼接,而不是append
for(int i = 0; i < a.length; i++){
sj.add(a[i] + "");
}
选择+还是append
String类重载了”+“”+=“运算符,本质上是调用StringBuilder的append()方法,然后又调用toString()方法得到一个String对象。
每次循环都要创建StringBuilder对象,所以循环里少用String的+,因此直接使用StringBuilder性能会比String高,因为只需要创建一个对象。
String s = "";for (int i = 0; i < 4; i++) { s += "a";}
但是JDK9的字符串的“+”改成了动态方法makeConcatWithConstants(),所以JDK9以后可以放心使用。
字符串常量池是什么
JVM为了提升性能和减少内存消耗,针对字符串专门开辟的区域,避免字符串重复创建,这里存放的是字符串对象的引用。
一个经典问题:
String s = new String("a");
这句话创建了几个对象?
答案是一个或者两个。
两个的情况:
首先先在堆中创建一个String对象,然后判断字符串常量池是否保存了对应字符串的引用,没有保存,在堆中创建对应的字符串对象(第2个对象)并且将字符串对象的引用保存到字符串常量池中。
一个的情况:
首先先在堆中创建一个String对象,然后判断字符串常量池是否保存了对应字符串的引用,保存了就直接返回字符串对象的引用。
再说一个方法,String.intern(),这是个native方法
文档的意思大概是,如果常量池已经有了这个字符串对象的引用,那么直接返回这个字符串(string from the pool),要是没有,那么久在常量池添加一个,然后返回这个字符串对象的引用。
那么可以看看以下代码的执行:
String s1 = "a"; String s2 = s1.intern(); String s3 = new String("a"); String s4 = s3.intern(); System.out.println(s1==s2); System.out.println(s3==s4); System.out.println(s1==s4);
s1拿到的是 常量池中字符串对象"a"的引用。
s2返回的是 常量池中字符串对象"a"的引用。
所以s1 == s2(地址相等)
s3 返回的是 堆中String对象的引用。
s4返回的是 常量池中字符串对象"a"的引用。
所以s3和s4指向的不是同一个地址,刚才说的new String("a")会创建两个对象,就是这种情况。
再理解一下String不可变性。
String s = "a";
s = "bb";
一开始s是 常量池中字符串对象"a"的引用。
然后s是 常量池中字符串对象"bb"的引用。
"a"和"bb"这两个字符串对象都存在,确实没有变,变的只是s对象指向的地址。
常量折叠Constant Folding
javac编译器会把常量表达式求出来作为常量,嵌套在最终代码中。
对于 String s = "str" + "ing";
编译器会优化成 String s = "string";
。