文章目录
1. 概念
Java中的String类用于字符串的创建以及有关字符串的一系列操作,程序中所有的字符串字面值都是String类的对象。字符串具有以下特点:
-
字符串是不可变的:String类定义和保存数据的char型数组都被
final关键字所修饰:public final class String implements java.io.Serializable, Comparable<String>, CharSequence { /** The value is used for character storage. */ private final char value[]; ... } -
字符串是可共享的:由于字符串是不可变的,因此即使被共享使用也无法被修改
-
字符串效果上相当于
char[]字符数组,但底层原理是byte[] 数组
2. 创建方式
字符串的创建方法有很多,String对应的构造方法如下所示:
其中构造方法所能接受的参数有byte数组、char数组、codePoints的int数组、已有的字符串、StringBuffer和StringBuilder。常用的主要是四种:直接创建和其中三种构造方法
-
直接创建:这也是使用最多的一种方法,如
String s = "Hello World"; -
构造方法:
public String():创建一个空白字符串,不包含任何内容public String(char[] array):根据字符数组的内容创建对应的字符串public String(byte[] array):根据字节数组的内容创建字符串
public class StringTest {
public static void main(String[] args) {
// 三种构造
String str1 = new String();
System.out.println(str1); // ""
char [] array = new char []{'k', 'o', 'b', 'e'};
String str2 = new String(array);
System.out.println(str2); // "kobe"
byte[] bytearray = new byte[]{97, 98, 99};
String str3 = new String(bytearray); // "kobe"
System.out.println(str3);
// 直接创建
String str4 = "kobe";
System.out.println(str4); // "kobe"
}
}
3. 字符串常量池
3.1 字符串创建
程序中直接写上双引号的字符串都在字符串常量池中,那么它们和使用构造方法创建的字符串有什么不同呢?首先通过一个例子直观的看一下:
public class StringTest1 {
public static void main(String[] args) {
String str1 = "abc";
String str2 = "abc";
char[] chararray = new char[]{'a', 'b', 'c'};
String str3 = new String(chararray);
System.out.println(str3);
System.out.println(str1 == str2); // true
System.out.println(str1 == str3); // false
System.out.println(str2 == str3); // false
}
}
如上所示,str1和str2通过双引号直接创建的,str3通过构造方法创建。然后我们使用== 来比较它们发现,str1和str2是相同的,但它们与str3都是不同的。表面上看起来它们都创建了字符串"abc",为什么会出现这种情况呢?
这是因为,通过双引号直接创建的字符串都保存在堆中的字符串常量池中,而通过构造方法创建的字符串在堆内存空间中。对于基本类型来说,==是进行数值的比较,而对于引用类型来说是进行地址值的比较。下面我们通过内存空间来看一下为什么会有这种现象。
代码如上所示,接下来我们按照代码的执行过程依次看以下内存空间是如何变化的:
str1和str2是通过""直接创建的,它们实际都指向了保存在堆内存的字符串常量池中的"abc"的地址,假设为0x233- 接着创建一个char型数组,数组内容为
['a', 'b', 'c'],它保存在堆中 - 然后通过构造方法创建一个String对象,它同样保存在堆中,同时假设它在堆中的地址为
0x666,因此栈中str3保存的就是对象的地址0x666。但底层仍然是一个byte[]
由上可知,由于str1和str2指向的是字符串常量池中同样的内容,因此地址相同,==的结果为true。而str3是new的一个新的String对象,它保存在堆的其他位置而不是在字符串常量池中,因此地址不同,==的结果为false。
由上面的分析知道,== 进行的是地址值的比较。如果想要进行字符串内容的比较,可以使用以下两个方法:
public boolean equals(Object obj): 参数可以是任何对象,只有参数是一个字符串并且内容相同时返回true,否则返回false
- 任何对象都能用Object接收
- equals方法具有对称性,即
a.equals(b)和b.equals(a)效果一样- 如果比较一个常量和一个变量,推荐使用
常量.equals(变量)的写法
-
public boolean equalsIgnoreCase(String str): 忽略大小写,进行内容比较public class StringTest2 { public static void main(String[] args) { String str1 = "abc"; String str2 = "abc"; char[] chararray = new char[]{'a', 'b', 'c'}; String str3 = new String(chararray); System.out.println(str1.equals(str2)); // true System.out.println(str1.equals(str3)); // true System.out.println(str3.equals("abc")); // true System.out.println("abc".equals(str1)); // true String strA = "Java"; String strB = "java"; System.out.println(strA.equals(strB)); // false System.out.println(strA.equalsIgnoreCase(strB)); // true } }
下面再来看一下其他创建字符串的方法,以及对应的内存空间的变化情况。例如,此时代码如下所示:
@Test
public void test(){
char[] chararray = new char[]{'a', 'b', 'c'};
String s = new String(chararray);
System.out.println(s); // "abc"
System.out.println(chararray.hashCode() == s.hashCode()); // false
}
那么它们在内存中是如何存放的呢?如下所示:
虽然s的字符数组和chararray数组的内容是相同的,但它们并不是在相同的空间中进行存放。另外,如果使用Strings = new String(chararray, 0, 1)创建字符串,那么s保存的方式同样是在堆中创建了一个新的字符数组。
最后再来看一下String s = "abc";和String s1 = new String("abc");在内存空间中的情况,如下所示:
String s1 = new String("abc");首先在堆中创建了一个String对象,但对象指向的仍然是字符串常量池中的"abc"。
3.2 字符串拼接
接着看一下字符串的拼接操作在内存空间中是如何变化的,首先我们来从代码入手,如下所示:
@Test
public void test(){
String s1 = "hello";
String s2 = "world";
String s3 = "hello" + "world";
String s4 = s1 + "world";
String s5 = s1 +s2;
String s6 = (s1 +s2).intern();
System.out.println(s3 == s4); // false
System.out.println(s3 == s5); // false
System.out.println(s4 == s5); // false
System.out.println(s3 == s6); // true
}
如何理解上述代码的输出呢?下面通过图示的方法来看一下:
s1和s2都是直接创建的字符串字面值,因此,对应的"hello"和"world"都直接保存在字符串常量池中。s3进行的是字符串的拼接操作,它本身并不涉及对象的操作,因此拼接后的字符串"helloworld"也是保存在字符串常量池中。s4和s5都涉及String对象的操作,虽然对象中保存的数据内容相同,但是分别指向的是堆中不同的String对象。s6首先进行字符串的拼接,结果字符串是"helloworld",但是调用intern()知道它已经在字符串常量池中存在,因此直接将其赋给s6,并不会创建新的字符串常量。
4. 常用方法
4.1 String中和获取相关的方法:
-
public int length(): 获取字符串中含有的字符个数 -
public String concat(String str): 将当前字符串和参数字符串拼接为新字符串返回,当然同样可以通过 + 实现字符串拼接 -
public char charAt(int index): 获取指定索引位置的单个字符 -
public int indexOf(String str): 查找参数字符串在本字符串当中首次出现的索引位置,如果没有返回-1public class StringGetTest { public static void main(String[] args) { String str = "abc"; System.out.println(str.length()); // 3 String str1 = "hello"; String str2 = "world"; String str3 = str1.concat(str2); System.out.println(str1); // hello System.out.println(str2); // world System.out.println(str3); // helloworld System.out.println(str.charAt(0)); // a System.out.println(str.indexOf("bc")); // 1 System.out.println(str.indexOf("ff")); // -1 } }
4.2 字符串的截取方法:
-
public String subString(int index):截取从参数位置一直到字符串末尾,返回新字符串 -
public String subString(int begin, int end):截取[begin, end)范围内的字符串public class SubStringTest { public static void main(String[] args) { String str = "hello world"; String str2 = str.substring(2); System.out.println(str2); // llo world System.out.println(str.substring(2, str.length() - 2)); // llo wor // strA 中保存的是地址值 // 为strA 赋予不同的字符串是改变了strA中保存的地址值,但"hello"和"world"是不可改变的 String strA = "hello"; System.out.println(strA); // hello strA = "world"; System.out.println(strA); // world } }
4.3 字符串转换相关方法
-
public char[] toCharArray(): 将当前字符串拆分为字符数组返回 -
public byte[] getBytes(): 获取当前字符串底层的字节数组 -
public String replace(charSequence oldString, CharSequence newString): 将所有出现的老字符串替换成新的字符串,返回替换之后的结果public class StringConvertTest { public static void main(String[] args) { String str = "helloworld"; char [] charArray = str.toCharArray(); System.out.println(charArray); // helloworld System.out.println(Arrays.toString(charArray)); // [h, e, l, l, o, w, o, r, l, d] System.out.println(charArray.length); // 10 byte [] byteArray = str.getBytes(); System.out.println(byteArray[3]); // 108 String str1 = "Fologen love Kobe"; String str2 = str1.replace("o", "*"); System.out.println(str1); // Fologen love Kobe System.out.println(str2); // F*l*gen l*ve K*be } }
4.4 字符串分割方法
-
public Sting[] split(String regex):按照参数的规则,将字符串切分成若干部分,其中regex为正则表达式public class StringSplitTest { public static void main(String[] args) { String str = "aaa,bbb,ccc"; String [] array = str.split(","); for (int i = 0; i < array.length; i++) { System.out.println(array[i]); // aaa bbb ccc } } }
5. 更多
6. StringBuffer
StringBuffer用于表示可变的字符序列,对于使用StringBuffer声明的字符串内容增删时,不会产生新的对象。StringBuffer的定义如下:
public final class StringBuffer extends AbstractStringBuilder implements Serializable, CharSequence {
private transient char[] toStringCache;
static final long serialVersionUID = 3388685877147921107L;
private static final ObjectStreamField[] serialPersistentFields;
public StringBuffer() {
super(16);
}
public StringBuffer(int var1) {
super(var1);
}
public StringBuffer(String var1) {
super(var1.length() + 16);
this.append(var1);
}
public StringBuffer(CharSequence var1) {
this(var1.length() + 16);
this.append(var1);
}
public synchronized int length() {
return this.count;
}
public synchronized int capacity() {
return this.value.length;
}
...
}
类中所定义的方法都被synchronized关键字所修饰,因此,它是线程安全的。但是synchronized所带来的线程同步开销,使得StringBuffer的操作效率较低。
StringBuffer提供了四个构造器,定义如下:
public StringBuffer() {
super(16);
}
public StringBuffer(int var1) {
super(var1);
}
public StringBuffer(String var1) {
super(var1.length() + 16);
this.append(var1);
}
public StringBuffer(CharSequence var1) {
this(var1.length() + 16);
this.append(var1);
}
其中无参构造器默认使用初始容量为16的字符串缓冲区;StringBuffer(int var1)用于构造指定容量的字符串缓冲区;StringBuffer(String var1)用于将内容初始化为指定字符串内容。
常用的方法有:
append(xxx)delete(int start, int end)replace(int start, int end, String str)insert(int offset, xxx)reverse()indexOf(String str)subString(int start, int end)charAt(int n)serCharAt(int n, char ch)
方法的使用见名知意,这里不多解释,详细可用可参考API文档。
值得注意的一点就是,当使用append()或是insert()时,如果原来的value数组长度不够,那么StringBuffer会进行扩容。
7. StringBuilder
StringBuilder和StringBuffer非常类似,也可以用来表示可变字符序列,不同之处在于它是线程不安全的,但操作效率较高。
public class StringBuilderMain {
public static void main(String[] args) {
StringBuilder sb = new StringBuilder();
System.out.println(sb); // ""
sb.append("Forlogen");
sb.append("kobe");
sb.append(24);
System.out.println(sb); // Forlogenkobe24
StringBuilder sb1 = new StringBuilder("Forlogen");
System.out.println(sb1); // Forlgoen
System.out.println(sb1.toString()); // Forlogen
System.out.println(sb1.length()); // 8
System.out.println(sb1.insert(3, "KOBE")); // ForKOBElogen
}
}