String
不可变性
public final class String
# 由于是final,所以string是不可变的
长度限制
由于stirng其实就是一个char数组
char数组的下标是整型,integer
https://segmentfault.com/a/1190000020381075
substring
https://www.hollischuang.com/archives/1232
jdk6和jdk7之后的差别
jdk6
jdk6的时候,当截取字符串的时候,会在堆中new 一个新的string对象,但是这个string对象使用的char数组还是之前的数组,如果你只是在很长的字符串中引用了很小的一块数据,但是由于这个char数组是有引用的,所以无法进行垃圾回收,但是由于你所使用的字符串只是很小的一部分,但是你却用了这么大的char数组,会导致好像那么一大空间不存在似的,这就产生了内存泄露的问题。
内存泄露:在计算机科学中,内存泄漏指由于疏忽或错误造成程序未能释放已经不再使用的内存。 内存泄漏并非指内存在物理上的消失,而是应用程序分配某段内存后,由于设计错误,导致在释放该段内存之前就失去了对该段内存的控制,从而造成了内存的浪费。
jdk7
既然已经知道了上述问题所在,那么只需要new一个新的string的时候,让这个string指向自己的包含的char数组即可
public String(char value[], int offset, int count) {
if (offset < 0) {
throw new StringIndexOutOfBoundsException(offset);
}
if (count <= 0) {
if (count < 0) {
throw new StringIndexOutOfBoundsException(count);
}
if (offset <= value.length) {
this.value = "".value;
return;
}
}
// Note: offset or count might be near -1>>>1.
if (offset > value.length - count) {
throw new StringIndexOutOfBoundsException(offset + count);
}
//就是在这里将char数组拷贝过来,截取的offset和截取字符串的值是一样的
this.value = Arrays.copyOfRange(value, offset, offset+count);
}
replaceFirst、replaceAll、replace
public String replaceFirst(String regex, String replacement) {
return Pattern.compile(regex).matcher(this).replaceFirst(replacement);
}
public String replaceAll(String regex, String replacement) {
return Pattern.compile(regex).matcher(this).replaceAll(replacement);
}
public String replace(CharSequence target, CharSequence replacement) {
return Pattern.compile(target.toString(), Pattern.LITERAL).matcher(
this).replaceAll(Matcher.quoteReplacement(replacement.toString()));
}
replaceFirst的作用是让regex去replacement替换原有string的第一个字符
replaceAll的作用是replacement替换regex
replace的作用是将原有字符串的所有target替换为repalcement
String、StringBuilder、StingBuffer
StringBuilder
public StringBuilder() {
super(16);
}
public StringBuilder(int capacity) {
super(capacity);
}
public StringBuilder(String str) {
super(str.length() + 16);
append(str);
}
@Override
public StringBuilder append(Object obj) {
return append(String.valueOf(obj));
}
@Override
public StringBuilder append(String str) {
super.append(str);
return this;
}
从源码可以看出,默认的stringbuilder是16个字节,如果指定了大小就用指定的大小,如果直接给了个参数就是字符串的长度加上16个字节
方法中没有synchronized,所以这是线程不安全的
StingBuffer
@Override
public synchronized int length() {
return count;
}
@Override
public synchronized int capacity() {
return value.length;
}
@Override
public synchronized void ensureCapacity(int minimumCapacity) {
super.ensureCapacity(minimumCapacity);
}
方法都是synchronized修饰的,所以是线程安全的
为什么要重写equals方法
原因
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence
虽然从代码来看string是这样继承的,没有显示声明继承object类,但是实际上所有的基本上都是默认继承object类的,有版本差别,jdk1.7之前的是编译器处理的,jdk1.7之后包括1.7都是由虚拟机来处理的,当然IDE可以提示object类中的方法是因为IDE也对这个object做了相关处理
Object类中有一个equals()方法,它的默认实现是比较两个对象的引用是否相等。但是在实际应用中,我们通常需要比较对象的内容是否相等,而不是比较引用是否相等。因此,String类重写了equals()方法,使得它比较的是两个字符串对象的内容是否相等。
具体来说,String类中的equals()方法会比较两个字符串对象的字符序列是否相同。如果两个字符串的字符序列相同(包括字符的顺序、数量、大小写等方面),则equals()方法返回true,否则返回false。
方法
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
String对“+”的重载
https://juejin.im/post/6844903960608784392
package com.test;
public class demo {
public static void main(String[] args) {
String a = "1";
String b = "2";
System.out.println(a+b);
}
}
从反编译的过程可以看出,是调用了stringbuilder的append方法,
String.valueOf和Integer.toString的区别
stirng
//stirng不能为null,为null会报NullPointerException
public String toString() {
return this;
}
//这个方法的功能就是如果形参是null,那么返回null字符串,而不是直接报NullPointerException异常
public static String valueOf(Object obj) {
return (obj == null) ? "null" : obj.toString();
}
integer
public static String toString(int i, int radix) {
if (radix < Character.MIN_RADIX || radix > Character.MAX_RADIX)
radix = 10;
/* Use the faster version */
if (radix == 10) {
return toString(i);
}
char buf[] = new char[33];
boolean negative = (i < 0);
int charPos = 32;
if (!negative) {
i = -i;
}
while (i <= -radix) {
buf[charPos--] = digits[-(i % radix)];
i = i / radix;
}
buf[charPos] = digits[-i];
if (negative) {
buf[--charPos] = '-';
}
return new String(buf, charPos, (33 - charPos));
}
switch对String的支持
package com.test;
public class demo {
public static void main(String[] args) {
String str = "world";
switch (str) {
case "hello":
System.out.println("hello");
break;
case "world":
System.out.println("world");
break;
default:
break;
}
}
}
反编译
由反编译的代码可以看出来,switch中的case是通过hashCode来进行匹配的,使用equals方法来进行值的比较
字符串池
intern
https://tech.meituan.com/2014/03/06/in-depth-understanding-string-intern.html
String比较特别的地方
- 直接中双引号引起来的,是放在常量池中,如代码所示String s2="1";
- 如果不是用双引号声明的String对象,可以使用String提供的intern方法。intern 方法会从字符串常量池中查询当前字符串是否存在,若不存在就会将当前字符串放入常量池中
实际例子验证:
package com.test;
public class demo02 {
public static void main(String[] args) {
String s="1";
String s1=new String("1");
String s2=s.intern();
System.out.println(s==s1);
System.out.println(s1==s2);
System.out.println(s2==s);
}
}
运行结果:false false true
jdk7之后,intern方法对 intern 操作和常量池都做了一定的修改。主要包括2点:
- 将String常量池 从 Perm 区移动到了 Java Heap区
- String#intern 方法时,如果存在堆中的对象,会直接保存对象的引用,而不会重新创建对象。
由上图代码及运行结果可以看出,双引号的的String是直接在常量池中的,而new出来的对象是在堆中的,当s2调用intern的方法之后,会去常量池中查找是否有这变量,如果有的话会直接引用常量池中的对象
两个String相加
package com.test;
public class demo01 {
public static void main(String[] args) {
String s=new String("1");
String s1=new String("1");
String s2=s+s1;
System.out.println(s==s1);
}
}
反编译
Compiled from "demo01.java"
public class com.test.demo01 {
public com.test.demo01();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: new #2 // class java/lang/String
3: dup
4: ldc #3 // String 1
6: invokespecial #4 // Method java/lang/String."<init>":(Ljava/lang/String;)V
9: astore_1
10: new #2 // class java/lang/String
13: dup
14: ldc #3 // String 1
16: invokespecial #4 // Method java/lang/String."<init>":(Ljava/lang/String;)V
19: astore_2
20: new #5 // class java/lang/StringBuilder
23: dup
24: invokespecial #6 // Method java/lang/StringBuilder."<init>":()V
27: aload_1
28: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
31: aload_2
32: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
35: invokevirtual #8 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
38: astore_3
39: getstatic #9 // Field java/lang/System.out:Ljava/io/PrintStream;
42: aload_1
43: aload_2
44: if_acmpne 51
47: iconst_1
48: goto 52
51: iconst_0
52: invokevirtual #10 // Method java/io/PrintStream.println:(Z)V
55: return
}
由上图可以看出是采用StringBuilder的append的方法来进行字符串拼接的
参考
- 感谢你赐予我前进的力量