String
1 相等性规则
==比较运算符
1)== 比较基本数据类型,判断的是值是否相等(基本数据类型只能用" == "比较)
2)== 比较引用类型,判断的是 内存地址 是否相等
equals()方法 Object类中继承下来的
1)默认的equals方法与==一样,比较的是内存地址
2)按照自己的相等性规则进行比较,需要自定义equals方法(String类的equals方法比较的是值)
String类型
String类型是一个引用类型,即栈中存放对象名和对象的引用,堆中存放String对象;
String类型的对象,底层实际是一个final修饰的char类型的数组。
String类也可以加final关键字修饰
2.1 创建String类型的对象
// 第一种 String类构造方法创建
/*
这种创建方式,就是创建一个String类型的对象,是在内存中新申请一块内存
*/
String a = new String("abc");
// 第二种 字面值的方式创建
/*
这种创建方式,会把值存放到字符串常量池中
字符串常量池,可以理解为一个String的数据集,它是在堆内存中开辟的一块特殊内存
*/
String b = "abc";
String c = "abc";
boolean tar1 = (a == b); // tar1的值为false,因为==比较的是内存地址
boolean tar2 = (b == c); // tar2的值为true,因为第二种创建方式将值放入字符串常量池中,创建时会先去常量池中查看是否
// 已经存在,如果已经存在就会直接使用已存在的字符串值
2.2 常用方法
方法 | 返回值 | 作用 | 示例 |
---|---|---|---|
equals() | boolean | 比较字符串是否值相等 | String a = “hi”; String b = “hello”; boolean tar = a.equals(b); |
length() | int | 返回字符串的长度 | int length = a.length(); |
trim() | String | 去除字符串两端的空格 | String b = a.trim(); |
replace() | String | 替换字符串中的指定字符(串) 把能匹配上的全部替换 | a.replace(s1, s2); s1 - 被替换的字符换 s2 - 替换字符串 |
concat() | String | 拼接字符串 | a.concat(“123”); |
toLowerCase() | String | 字符串转小写 | a.toLowerCase(); |
toUpperCase() | String | 字符串转大写 | a.toUpperCase(); |
split() | String[] | 根据指定字符把字符串切分成数组,返回String[] | String a = “a,b,c”; String[] arr = a.split(“,”); |
contains() | boolean | 判断字符串中是否包含指定字符 | a.contains(“a”); |
substring(1) | String | 根据下标截取字符串,从指定下标到字符串末尾 | substring(int startIndex); String b = a.substring(1); |
substring(1, 2) | String | 截取下标范围内的字符串,从开始下标到结束下标 (包含开始,不包含结束) | substring(int s, int e); s - 开始下标 e - 结束下标 |
indexOf() lastIndexOf() | int | 获取指定字符在字符串中的下标 | int index = a.indexOf(“a”); |
charAt() | char | 获取字符串中指定下标的字符 | char c = a.charAt(1); |
isEmpty() | boolean | 判断字符串是否为空 | boolean tar = a.isEmpty(); |
toCharArray() | char[] | 将字符串转成字符数组 | char[] d = a.toCharArray(); |
String.valueOf() | String | 将变量转为String类型 | int a = 1; Sring b = null; b = String.valueOf(a); |
String类的静态方法format() 能用来创建可复用的格式化字符串
float f1 = 1.1f;
int i2 = 10;
String s3 = "hello";
String str = String.format("浮点型%f 整型%d 字符串%s", f1, i2, s3);
System.out.println(str); // 打印:浮点型1.100000 整型10 字符串hello
3 字符串常量池
3.1 字符串常量池的设计思想
字符串的分配,和其他的对象分配一样,耗费高昂的时间与空间代价,作为最基础的数据类型,大量频繁的创建字符串,极大程度的影响程序的性能
3.2 常量池工作原理
JVM为了提高性能和减少内存开销,在实例化字符串常量的时候进行了一些优化
为字符串开辟一个字符串常量池,类似于缓冲区
创建字符串常量时,首先检查字符串常量池是否存在该字符串
存在该字符串,返回引用实例,不存在,实例化该字符串并放入池中
3.3 实现的基础
实现该优化的基础是因为字符串是不可变的(final + private),可以不用担心数据冲突进行共享
运行时实例创建的全局字符串常量池中有一个表,总是为池中每个唯一的字符串对象维护一个引用,这就意味着它们一直引用着字符串常量池中的对象,所以,在常量池中的这些字符串不会被垃圾收集器回收
4 StringBuffer与StringBuilder
String和StringBuffer、StringBuilder他们都可以存储和操作字符串,即包含多个字符的字符串数据。
String类是字符串常量,是不可更改的常量。而StringBuffer/StringBuilder是字符串变量,它们的对象是可以扩充和修改的。
特点:
1)修改String对象的值,内存地址会发生改变;
2)StringBuffer 和 StringBuilder 类的对象能够被多次的修改,并且不产生新的对象,即内存地址不改变;
3)StringBuffer 是线程安全的,StringBuilder 是线程不安全的;
4)StringBuilder 的执行速度比 StringBuffer 快;
5)常用方法:
方法 | 返回值 | 作用 | 示例 |
---|---|---|---|
append() | StringBuffer | 拼接字符串到末尾 | StringBuffer a; a.append(“abc”); |
reverse() | StringBuffer | 将字符串中的字符顺序反转 | |
delete() | StringBuffer | 删除字符串中指定下标范围的字符 | a.delete(1, 3); |
insert() | 在字符串中指定下标位置插入字符 | a.insert(1, “efd”); | |
replace() | 替换指定索引区间内的字符串 | a.replace(1, 3, “hul”); | |
length() | int | 字符串长度 | a.length(); |
toString() | 转成String字符串 | a.toString(); |
5 关于字符串的补充
-
+号拼接
String s1 = "ab"; String s2 = "cd"; String s3 = s1 + s2; String s4 = s1 + "cd"; String s5 = new String("ab") + new String("cd"); String s6 = new String("ab") + "cd"; // 拼接字符串时 程序是如何运行的? // 首先创建StringBuilder对象,然后调用append // 需要注意 String s7 = "ab" + "cd"; // s7创建的字符串对象在编译时就变成了"abcd" // 所以字符串常量池中只创建"abcd"的对象 不会分别创建"ab"和"cd"
-
intern()方法
因为不是所有的字符串创建方式都会在常量池中生成对应的字符串,而intern方法用于根据所给的字符串在常量池中创建对应的字符串。jdk1.7以后,调用intern方法时,如果该字符串已经存在于常量池中,则将常量池中的引用直接返回;如果不存在,则在常量池中生成一个对原字符串的引用。String s1 = "abcd"; String s2 = s1.intern(); System.out.println(s1 == s2); // 结果为true 两者都指向常量池中的 abcd String s3 = new String("abcd"); String s4 = s3.intern(); System.out.println(s3 == s4); // 结果为false s3指向堆 s4指向常量池中的 String a1 = new String("abc"); String a2 = a1 + "d"; // a2.intern(); String a3 = "abcd"; System.out.println(a2 == a3); // false // 如果打开intern()方法的代码 就为true // 此时常量池中的"abcd"是个引用,指向a2的对象
String str1 = new StringBuilder("计算机").append("软件").toString(); String str2 = str1.intern(); System.out.println(str1 == str2); // true String str3 = new StringBuilder("ja").append("va").toString(); String str4 = str1.intern(); System.out.println(str3 == str4); // false /* 注意:java是jvm一启动就默认放入常量池中的字符串 */
在jdk1.6中
intern方法会把首次遇到的字符串复制到方法区中,返回的也是方法区这个字符串的引用。
而由StringBuilder创建的字符串实例在Java堆上,所以必然不是一个引用,所以返回false
str1指向堆,str2指向方法区,所以返回结果返回false。同理,str3和str4的返回结果也为false
jdk1.7+的intern方法不会再复制实例,只是在常量池中记录首次出现的实例引用。
因此str2指向的引用其实就是str1指向Java堆中StringBuilder创建的字符串实例。所以返回结果为true
但是java这个字符串常量在编译期就已经在方法区的常量池中了,不符合首次出现,所以str4指向的是常量池中的java字面量,所以 返回结果为false。
jdk9 String底层由char数组换成了 private final byte[] value;