字符串(java不死)
第十一天
因为拉了几天,两天全补回来了
字符串
1.String、String StringBuffer 和 StringBuilder 的区别是什么?
String是只读字符串,它并不是基本数据类型,而是一个对象。1.8之前从底层源码来看是一个final类型的 字符数组,1.8后使用final类型的
byte
数组加上编码标志字段,所引用的字符串不能被改变,一经定义,无法再增删改。每次对String的操作都会生成 新的String对象。StringBuffer和StringBuilder他们的底层都是可变的字符数组,所以在进行频繁的字符串操作时,建议使用StringBuffer和 StringBuilder来进行操作。
另外StringBuffer 对方法加了同步锁或者对调用的方法加了同步锁,所 以是线程安全的。StringBuilder 并没有对方法进行加同步锁,所以是非线程安全的。
2.字符串常量池是如何实现的?
字符串常量池(String Constant Pool)是Java中一块特殊的内存区域,用于存储字符串常量。
当程序中出现字符串常量时,Java编译器会将其放入字符串常量池中。字符串常量是不可变的,因此可以共享。如果字符串常量池中已存在相同内容的字符串,编译器会直接引用已存在的字符串常量,而不会创建新的对象。
在HotSpot虚拟机中:在JDK 1.6及之前的版本,字符串常量池通常被实现为方法区的一部分,即永久代(Permanent Generation),用于存储类信息、常量池、静态变量、即时编译器编译后的代码等数据。
从JDK 1.7开始,字符串常量池的实现方式发生了重大改变。字符串常量池不再位于永久代,而是直接存放在堆(Heap)中,与其他对象共享堆内存。之所以要挪到堆内存中,主要原因是因为永久代的 GC 回收效率太低,只有在FullGC的时候才会被执行回收。但是Java中往往会有很多字符串也是朝生夕死的,将字符串常量池放到堆中,能够更高效及时地回收字符串内存
3.字符串常量池,不可变和共享的理解?
/*** 字符串的拼接以及常量池* 1. 字符串常量池:* jvm为了提升性能和减少内存开销,专门为字符串的一些操作,在内存中专门提供了* 一块区域,用于存储字符串对象。该内存区域就是字符串常量池(字符串缓冲区,缓冲池)** 什么时候会用到字符串常量池?* 当使用字面量给变量赋值时,会先去常量池中寻找是否该字面量的对象,如果有,就将其地址直接* 给变量。如果没有,就将该字面量对象创建在常量池中,然后将其地址赋值给变量。*/ public class StringDemo01 {public static void main(String[] args) {String s1 = "abc"; //String s2 = "abc"; ////判断s1和s2的对象地址是不是同一个System.out.println(s1 == s2); //true/** 两个字面量做拼接操作时,编译器在编译期间就做了优化操作* 直接计算出结果,即s3="abcd"*/String s3 = "abc"+"d";String s4 = "abcd";System.out.println(s3 == s4); //true String s5 = "d";// 拼接操作只要有变量参与,那么javac不会进行优化。只能在运行期间进行计算,// 计算后的结果,存储在堆中,并不是常量池里String s6 = "abc"+s5;System.out.println(s6 == s4); //false String s7 = "ab"+"c"+s5; // 优化成了"abc"+s5. 但是依然在运行期间与s5做的计算. 不会去堆里找,是新对象System.out.println(s7 == s6); // false String s8 = new String("d");String s9 = "abc"+s8;System.out.println(s7 == s9);//false String s10 = new String("abc");String s11 = new String("abc");System.out.println(s10 == s11);//falseString s15 = new String("abc");String s16 = "abc";System.out.println(s15 == s16);//false1. String s11 = "abc" 的创建过程JVM会首先检查字符串常量池中是否存在"abc"这个字符串对象如果不存在,则在字符串常量池中创建一个新的"abc"字符串对象如果已存在,则直接返回常量池中该对象的引用s11直接指向字符串常量池中的这个对象2. String s10 = new String("abc") 的创建过程同样会先检查字符串常量池中是否存在"abc"无论常量池是否存在,都会在堆内存中创建一个新的String对象这个新对象的内容与常量池中的"abc"相同,但是是独立的对象s10指向堆内存中的这个新对象字符串常量池: "abc" <--- s11指向这里↑ | (复制关系,不是同一对象)堆内存: String对象 <--- s10指向这里 (包含从常量池复制的value数组)//假如只有下面三行代码:问 内存中总共有多少个对象 5个对象String s12 = "h";String s14 = "abc"+s12+"o";String s13 = new String(s14);} }
序号 | 构造器 | 解析 |
---|---|---|
1 | String() | 初始化一个新创建的空字符序列的字符串对象 |
2 | String(String str) | 初始化一个新创建的字符串对象,使其字符序列与参数相同。换句话说,新创建的对象是该参数的副本 |
3 | String(byte[] bytes) | 使用默认编码集解码byte数组,构建一个字符串对象 |
4 | String(byte[] bytes,String charsetName) | 使用指定编码集charsetName 解码byte数组,构建一个字符串对象 |
5 | String(byte[] bytes, int offset, int length) | 使用默认编码集解码byte数组的从offset开始,length个元素,构建一个字符串对象 |
6 | String(char[] value) | 初始化一个字符串对象,使其字符序列包含参数的元素 |
String concat(String str) | 将this字符串与字符串str进行拼接,返回一个新的字符串对象。 相当于 两个字符串使用+号做拼接操作 |
---|---|
static String join(String delimiter,String... elements) | 将指定的所有字符串元素使用delimiter字符连接起来,返回一个新的字符串 |
int length() | 返回字符串中的字符个数。 注意与数组的length区分开。 |
char charAt(int index) | 返回指定索引处的char字符 |
int indexOf(String subStr) | 返回指定字符串在此字符串中第一次出现的索引,找不到返回-1 |
int indexOf(String str,int fromIndex) | 从指定位置fromIndex开始向后检索,返回指定字符串在此字符串中第一次出现的索引,找不到返回-1 |
int lastIndexOf(String str) | 用于返回指定字符串在此字符串中最后一次出现的索引,找不到返回-1 |
int lastIndexOf(String str,int endIndex) | 从开始检索到指定位置endIndex,用于返回指定字符串在此字符串中最后一次出现的索引,找不到返回-1 |
String toUpperCase() | 将字符串中的所有字母转换成大写 |
String toLowerCase() | 将字符串中的所有字母转换成小写 |
char[] toCharArray() | 将字符串转变成字符数组 |
boolean startsWith(String str) | 检查一个字符是否以指定的字符串为前缀 |
boolean endsWith(String str) | 检查一个字符是否以指定的字符串为后缀 |
String substring(int beginIndex) | 从指定位置beginIndex开始截取,截取到最后一个字符,并返回 |
String substring(int beginIndex,int endIndex) | 从指定位置beginIndex开始截取,截取到指定位置endIndex,并返回,包前不包后。 |
String trim() | 去掉一个字符串的前与后的空字符,不会去掉中间的空字符 |
String replace(String target,String replacement) | 使用replacement字符串替换掉target字符串 |
byte[] getBytes() | |
byte[] getBytes(String charset) | |
boolean isEmpty() | |
boolean contains(String subStr) | |
int compareTo(String str) | |
int compareToIgnoreCase(String str) | |
static String valueOf(int value) | 将其他类型转换成字符串类型 |
static String valueOf(double d) | |
static String valueOf(char[] ch) | |
static String valueOf(Object obj) | |
boolean matches(String regex) | |
String[] split(String regex) | |
replaceAll(String regex, String replacement) | |
replaceFirst(String regex, String replacement) |
4.StringBuilder和StringBuffer
StringBuilder()
:构造一个不带任何字符的字符串生成器,其初始容量为16个字符StringBuilder(String str)
:构造一个字符串生成器,其初始化为指定的字符串内容
常用方法 | 作用 |
---|---|
StringBuilder append(String str) | 将指定字符串追加到this字符串后面,返回this |
StringBuilder insert(int index,String str) | 将指定字符串插入到this字符串的指定索引处,返回this |
StringBuilder delete(int start, int end) | 删除字符串中的一部分 |
StringBuilder reverse() | 将字符序列进行反转(左右颠倒) |
String toString() | 将StringBuilder对象转变成String对象 |
5.以下代码创建了几个 String 对象?
String s1 = new String("hello"); String s2 = "hello";
如果常量池中没有字符串hello,String s1 = new String("hello");他会在常量池和堆内存创建对象,如果有字符串hello,就会在堆中创建对象并且将字符串中的值复制过来。
如果常量池中有字符串hello,String s2 = "hello";就不会在字符串创建对象,如果常量池中没有字符串hello就会在常量池创建对象。
6.equals() 和 equalsIgnoreCase() 方法有什么区别
方法 | 区分大小写 | 比较规则 | 适用场景 |
---|---|---|---|
equals() | 是 | 完全匹配(包括大小写) | 需要精确匹配时 |
equalsIgnoreCase() | 否 | 忽略大小写比较 | 不关心大小写时 |
7.如何比较两个字符串的大小?有哪些方法?
使用
compareTo()
方法最常用的字符串比较方法,按字典顺序比较:使用
compareToIgnoreCase()
方法忽略大小写的比较:使用
Comparator
接口使用
Arrays.sort()
对字符串数组排序
8.如何将字符串转换为基本数据类型(int, double等)?
方法1:使用
Integer.parseInt()
String str = "123"; int num = Integer.parseInt(str);
方法2:使用
Integer.valueOf()
int num = Integer.valueOf(str); // 返回Integer对象,自动拆箱为int
9.解释 String 的 intern() 方法的作用和使用场景。
intern()的作用
intern()`方法的作用是将字符串对象添加到字符串常量池(String Pool)中,并返回池中的引用
适合使用intern()的情况
大量重复字符串处理:如处理CSV、XML等文本数据时
作为Map的键:可以节省内存并提高比较速度
长期存在的字符串:如配置项、常量等
需要频繁比较的字符串:用
==
代替equals()
提高性能
10.什么是不可变对象?为什么 String 要设计成不可变的?
不可变对象是指一旦创建后其状态(属性值)就不能被修改的对象。任何修改操作都会创建一个新的对象,而不是改变原有对象。
不可变对象的实现需要所有字段都是final的、类本身是final的(防止子类修改行为)、没有修改内部状态的方法(setter)、对可变组件的防御性拷贝(如数组、集合等)
String 设计成不可变的,可以使线程安全,因为无需同步,字符串复用,减少对象的复用。