最近在准备跳槽,目标岗位是后台java工程师,但是对java中的数据结构总是今天记住明天就忘了,因此开此专栏,边记录边刷题,每天进步一点点,争取早日上岸!
String在java里是最简单的数据结构了,它是一个final类,至于final的具体原理,先埋个坑,之后把final文章写好就粘过来。反正知道final修饰的话,String类是不能被继承的,并且成员方法method都会隐式被指定为final方法,成员属性可以根据自己的需要设置。
图书馆里正在码字的我看到身边一群年轻人刷刷地走进了会议室,突然好羡慕人家丰富多彩的周末呀,看来以后要多参加活动了=.=
String使用private final char value[] 来实现字符串的存储,所以String对象创建后,就不能再修改此对象存储的字符串内容,所以说String类型是不可变的。
所有可以修改字符串的功能都是通过创建一个新的字符串对象来实现的,而不是对原来的字符串对象进行修改。
JVM为了提高性能和减少内存开销,在实例化字符串时进行了优化:
每当创建字符串常量时,JVM会首先检查字符串常量池,如果该字符串已经存在常量池中,那么就直接返回常量池中的实例引用。如果字符串不在常量池中,就会实例化该字符串,并将其放在常量池中
由于字符串的不可变性可以肯定,常量池中一定不存在两个相同的字符串
常量池分为:静态常量池和运行时常量池。
静态常量池:*.class文件中的常量池,class文件中的常量池不仅仅包含字符串、数字等字面量,还包含类、方法等信息
运行时常量池:JVM虚拟机完成类装载操作后,将class文件中的常量池载入到内存中,并保存在方法区中
常说的常量池,就是指方法区中的运行时常量池
此处留个坑,对于方法区常量池,学习到JVM后补。
接下来进行一波薛定谔的字符串比较:
String a = "hello";
String b = "world";
String origin = "helloworld";
String res = a + b;
String res2 = "hello" + "world";
System.out.println(origin==res); // false
System.out.println(origin==res2); // true
System.out.println(res==res2); //false
当一个字符串由多个字符串常量连接而成时,它自己肯定也是字符串常量,所以origin和res2比较结果为true;
**JVM对String a = "hello"
对象放在常量池中是在编译时做的,而String res = a + b;
是在运行时刻才能知道的,new对象也是在运行时才做的。所以origin和res结果为false;
分析步骤:
1)栈中开辟一块空间存放引用a,a指向String常量"hello"。
2)栈中开辟一块空间存放引用b,b指向String常量"world"。
3)栈中开辟一块空间存放引用origin,origin指向字符串常量"helloworld"。
4)栈中开辟一块空间存放引用res。
5)a+b通过StringBuilder的最后一步toString()方法还原一个新的String对象"helloworld",因此堆中开辟一块空间存放此对象
6)引用res指向堆中的新String对象。
7)res指向的对象在堆中,常量"helloworld"在常量池中,所以输出为false。
编译器和运行期等概念留坑
总结:字面量“+”拼接是在编译器进行,拼接后的字符串存放在字符串常量池中;而字符串引用的“+”拼接运算是在运行时进行的,新创建的字符串放在堆中。由此可见,使用字面量+拼接效率更高
以下存疑,仅作参考
new创建字符串时首先会查看常量池中是否有相同值的字符串,如果有,拷贝一份到堆中,然后返回堆中的地址;如果常量池中没有,则在堆中创建一份,然后返回堆中的地址。
说明:调用intern方法时,如果池中已经包含一个等于此String对象的字符串(是否等于由equals方法判定),则返回池中的字符串。否则,将此String对象添加到池中,并返回此String对象的引用。
String s0 = "hello";
char[] chars = {'h','e','l', 'l','o'};
String s1 = new String(chars);
System.out.println(s0==s1); // false
System.out.println(s0==s1.intern()); // true
s0指向常量池中的对象,s1指向堆中的对象,但是s1.intern方法调用后将s1指向的堆中的对象放入了常量池中,所以第一个结果是false,第二个结果是true。
说明:此方法实现原理是判断字符数组的长度是否等于0,但是平时总会忘记有这么个方法,特别记忆下。
说明:返回指定下标处的字符值
说明:返回指定下标处的unicode代码值
String s0 = "大俊加油";
System.out.println(s0.charAt(0)); // 大
System.out.println(s0.codePointAt(0)); // 22823
说明:将string的值复制进数组,改变数组的值(不常用)
String s0 = "hello";
char[] chars = {'w', 'o', 'r', 'l', 'd'};
s0.getChars(0, 3, chars, 2);
System.out.println(s0); // hello
System.out.println(Arrays.toString(chars)); // [w, o, h, e, l]
String.equals(object) | String.compareTo(string) | String.contentEquals(charSequence) |
---|---|---|
object是一个完全相同的字符串时返回true | 两个string对象的每个字符进行比较,相同返回0,不同返回比较字符的差值 | charSequence对象是stringbuilder/stringbuffer,且字符序列相同返回true |
忽略大小写:String.equalsIgnoreCase(string) | String.compareToIgnoreCase(string) |
说明:字符串比较方法众多,和不同对象比较也有对应的方法,记不清时可以都把他们转成string,使用compareTo和compareToIgnoreCase方法
说明:从int为下标的字符开始,String以s0开始则返回true
说明:string以s1结束则返回true
说明: 根据一定的算法返回一串哈希码
String.indexOf(int) | String.indexOf(string) | String.lastIndexOf(int) | String.lastIndexOf(string) |
---|---|---|---|
根据unicode字符返回对应下标 | 根据字符返回对应下标,调用charAt()方法 | 根据unicode字符返回最后一次匹配下标 | 根据字符返回最后一次匹配下标 |
String s0 = "ohello";
System.out.println(s0.indexOf("o")); // 1
System.out.println(s0.lastIndexOf("o")); // 5
说明:返回子字符串
说明:连接字符串
注意和+实现方式区别:
说明:newChar替换所有oldChar,返回一个新的字符串
String.matches(String regex)
note: 判断是否匹配regex
String.contains(charSequence)
note:判断是否包含charSequence
String.split(string)
note:用给定的string拆分字符串,返回字符串数组
String.join(CharSequence delimiter, CharSequence… elements)
note:使用delimiter拼接字符串
String s0 = String.join("-", "hello", "da", "jun");
System.out.println(s0); // hello-da-jun
参考文章:
深入理解Java中的String
Java hashCode() 和 equals()的若干问题解答