目录
②String str = new String("hello java");
③char[] array = {'a','b','c'};String str3 = new String(array);
注意:这样写代码会出现大量的临时对象,效率比较低,不建议这样写。
5.1 StringBuffer和StringBuilder的定义及与String的区别
5.2 StringBuffer和StringBuilder与String之间的转换
5.3 StringBuffer与StringBulider的区别
一、创建字符串
1.1 字符串常量池
“池”是编程中一种常见的、重要的提高效率的方式。字符串常量池就是用来存储字符串的。在编译的过程中,如果编译器拿到一个字符串,先去字符串常量池中查找该字符串是否存在,如果不存在,就入池。如果存在,就不会入池。
在JVM中,并没有划分区域,说这块内存就是字符串常量池。在设计时,是用一个哈希表来存储字符串常量。
1.2常见的三种创建字符串的方式以及每种方式下的内存布局
①String str = "hello java";
在这种创建方式下,str是个引用变量,存储的是字符串在字符串常量池中的地址。
②String str = new String("hello java");
在这种方式下,str2中存的是对象的地址。
备注:String类中的value属性,可以查看源码。
③char[] array = {'a','b','c'};String str3 = new String(array);
1.3 理解字符串不可变
字符串是一种不可变对象,他的内容不可改变。
String类的内部实现是基于char[]来实现的,但是String类并没有提供set之类的方法来修改内部的字符数组。
public static void main(String[] args) {
String str = "hello";
str = str + "java";
str += "!!!";
System.out.println(str);
}
表面上是修改了字符串,其实并不是,内存变化如下:
运行结果:hellojava!!!。不是String对象本身发生变化,而是str引用了其他的对象。
疑问:如果想要修改字符串,该怎么办?
方法1:借助原字符串
public static void main(String[] args) {
String str = "hello";
str = "H" + str.substring(1);
System.out.println(str);
}
这种改法,还是产生了一个新的对象,并没有在原来hello上将h改为H。
方法2:借助反射。
是Java类的一种自省的方式。通常情况下,类的内部细节,有时在类外是看不到的,但是通过反射就可以看到。
注意:这样写代码会出现大量的临时对象,效率比较低,不建议这样写。
public static void main(String[] args) {
String str = "hello";
for (int i = 0; i < 1000; i++) {
str += i;
}
System.out.println(str);
}
建议的写法
// 建议写法
public static void main(String[] args) {
String str1 = "hello";
StringBuffer sb = new StringBuffer();
sb.append(str1);
for(int i = 0;i<1000;i++){
sb.append(i);
}
str1 = sb.toString();
System.out.println(str1);
}
1.4 总结
①了解创建字符串的三种对象的方式以及每种方式下的内存分布。
②理解字符串的不可变性。
二、字符串比较相等
2.1 常见题型
实例一:
public static void main(String[] args) {
String str1 = "hello java";
String str2 = new String("hello java");
System.out.println(str1 == str2);
}
结果为false 。str1存的是“hello java”在字符串常量池中的地址,str2中存的是new的对象的地址,所以str1和str2不相等。
实例二:
public static void main(String[] args) {
String str1 = "hellojava";
String str2 = "hello"+"java";
System.out.println(str1 == str2);
}
结果为true。
首先,常量在编译的时候就已经被运算了,所以str2=“hellojava”。
补充一个知识点:采用直接赋值的方式进行String类的实例化,如果字符串的内容在字符串常量池中不存在,会被自动保存到字符串常量池中;如果存在,就直接引用已经存在的字符串。
因此,str1和str2指向的是同一个对象,str1=str2.
实例三:
public static void main(String[] args) {
String str1 = "hellojava";
String str2 = "hello";
String str3 = str2 +"java";
System.out.println(str1 == str3);
}
运行结果是false,hellojava、hello、java三个字符串入池,str3是个变量,在编译时不知道里面的值,得等到程序运行时才知道。执行第三行代码,hello和java会在堆里面重新生成一个新对象,str3指向这个对象。因此,str1不等于str3.
实例四:
public static void main(String[] args) {
String str1 = "hellojava";
String str2 = "hello" + new String("java");
System.out.println(str1 == str2);
}
运行结果:false。new string("java")后,在堆里面new了一个对象,对象实例化需要用到“java”,对象的成员方法value里面存的“java”的地址。执行第二行代码,刚刚new的String对象和“hello”重新组成了一个新对象,里面存的值时hellojava,str2指向该对象。
实例五:
public static void main(String[] args) {
String str1 = "hellojava";
String str2 = new String("hello") + new String("java");
System.out.println(str1 == str2);
}
运行结果:false。new的两个对象指向java和hello。这两个对象组成一个新对象,str2指向这个新对象。
实例六:
public static void main(String[] args) {
String str1 = "hello java";
String str2 = new String("hello java");
str2.intern();
System.out.println(str1 == str2);
}
运行结果:false。str2.intern()方法就是将str2放入字符串常量池,如果str2所指的字符串在池中不存在,就入池,如果存在,就不入池。所以str1不等于str2.
实例七:
public static void main(String[] args) {
String str1 = new String("aa") + new String("bb");
str1.intern();
String str2 = "aabb";
System.out.println(str1 == str2);
}
运行结果为true。"aa","bb"先入池,new的两个对象分别指向 "aa"和"bb",两个对象组成一个新对象,里面的值时"aabb",此时"aabb"在堆里,执行intern()后,"aabb"入池,执行str2="aabb"时,先在池里查找,发现"aabb"存在,str2就直接指向池里面的"aabb".str1和str2指向的是同一个对象,所以str1=str2.
实例八:
public static void main(String[] args) {
String str1 = new String("aa") + new String("bb");
String str2 = "aabb";
str1.intern();
System.out.println(str1 == str2);
}
运行结果:false。在执行完 String str2 = "aabb"后,字符串常量池中已经有了"aabb",执行str1.intern(),str1指向的对象无法入池,所以str1指向不变。
实例九:
public static void main(String[] args) {
String str1 = "hello";
String str2 = str1; // str2指向str1指向的对象
str1 = "java";
System.out.println(str1 == str2);
}
运行结果:false。
实例十:
public static void main(String[] args) {
String str = "hello";
func(str);
System.out.println(str);
}
public static void func(String str) {
str = "java"; // 修改的是形参的引用
}
运行结果:false。
2.2 总结
这块比较的情况比较复杂,建议画图来分析。
三、字符、字节和字符串
3.1 字符和字符串
字符串内部包含一个字符数组,String可以和char[]相互转换。
方法名称 | 类型 | 描述 |
public String(char value[]) | 构造 | 将字符数组中的所有内容变成字符串 |
public String(Char value[],int offset,int count) | 构造 | 将字符数组中的部分内容变成字符串 |
public char charAt(int index) | 普通 | 获得指定索引位置的字符,索引从0开始 |
public char[] toCharArray() | 普通 | 将字符串变成字符数组返回 |
public static void main(String[] args) {
char[] ch = {'a','b','c','1','2','d'};
String str1 = new String(ch);
System.out.println(ch); // 运行结果:abc12d
String str2 = new String(ch,1,4);
System.out.println(str2); // 运行结果:bc12
for (int i = 0; i < str1.length(); i++) {
System.out.println(str1.charAt(i));
}
char[] ch2 = str2.toCharArray();
System.out.println(ch2[0]);
}
// 判断一个字符串是否全部由数字组成
public static void main(String[] args) {
String str = "1a23456";
System.out.println(isNumber(str) ? "全部由数组组成":"不是全部由数字组成");;
}
private static boolean isNumber(String str) {
for (int i = 0; i < str.length(); i++) {
if(str.charAt(i) < '0' || str.charAt(i) > '9'){
return false;
}
}
return true;
}
3.2 字节和字符串
字节常用于数据传输以及编码转换的处理之中,String也能方便的和byte[]相互转换。
方法名称 | 类型 | 描述 |
public String(byte bytes[]) | 构造 | 将字节数组中的所有内容变成字符串 |
public String(byte bytes[],int offset,int length) | 构造 | 将字节数组中的部分内容变成字符串 |
public byte[] getBytes() | 普通 | 将字符串以字节数组的形式返回 |
public byte[] getBytes(String charsetName) throws | 普通 | 编码转换处理 |
public static void main(String[] args) {
byte[] bytes = {97,98,99,100};
String str1 = new String(bytes);
System.out.println(str1); // 运行结果:abcd
String str2 = new String(bytes,1,2);
System.out.println(str2); // 运行结果:bc
byte[] bytes2 = str2.getBytes();
System.out.println(Arrays.toString(bytes2)); //运行结果:[98, 99]
String str3 = "world";
byte[] bytes3 = str3.getBytes();
System.out.println(Arrays.toString(bytes3)); //[119, 111, 114, 108, 100]
}
3.3 总结
byte[]是把String按照一个字节一个字节的方式处理,这种适合在网络传输、数据存储这样的场景下使用,更适合针对二进制数据来操作。
char[]是把String按照一个字符一个字符的方式处理,更适合针对文本数据来操纵,尤其是包含中文的时候。
四、字符串常见操作
4.1 字符串比较
方法名称 | 类型 | 描述 |
public boolean equals(Object obj) | 普通 | 区分大小写的比较 |
public boolean equalsIgnoreCase(String str) | 普通 | 不区分大小写的比较 |
public int compareTo(String str) | 普通 | 比较两个字符串的大小关系 |
public static void main(String[] args) {
String str1 = "abcd";
String str2 = "ABCD";
System.out.println(str1.equals(str2)); //false
System.out.println(str1.equalsIgnoreCase(str2)); // true
System.out.println(str1.compareTo(str2)); //32
// 如果前n个字符相同,就从第n+1个开始比较
String str3 = "abcde";
String str4 = "abcdf";
System.out.println(str3.compareTo(str4)); // -1
// 如果两个字符串长度不一样,返回的是长度差
String str5 = "abcde";
String str6 = "abcdefd";
System.out.println(str5.compareTo(str6)); // -2
}
equals方法是比较是否相等,compareTo比较的是大小。
4.2 字符串查找
方法名称 | 类型 | 描述 |
public boolean contains(CharSequence s) | 普通 | 判断一个子字符串是否存在 |
public int indexOf(String str) | 普通 | 从头开始查找指定字符串的位置,查到了返回位置的开头索引,如果查不到就返回-1 |
public int indexOf(String str,int fromIndex) | 普通 | 从指定位置开始查找子字符串位置 |
public int lastIndexOf(String str) | 普通 | 从后向前查找子字符串位置 |
public int lastIndexOf(String str,int fromIndex) | 普通 | 从指定位置后向前查找子字符串位置 |
public boolean startsWith(String prefix) | 普通 | 判断是否以指定字符串开始 |
public boolean startsWith(String prefix,int tooffset) | 普通 | 从指定位置判断是否以指定字符串开始 |
public boolean endsWith(String prefix) | 普通 | 判断是否以指定字符串结尾 |
public static void main(String[] args) {
String str1 = "abcdabcdef";
System.out.println(str1.contains("ab")); // true
// 如果内容重复,他只能返回查找的第一个位置
System.out.println(str1.indexOf("ab")); // 0
System.out.println(str1.indexOf("ab", 2)); // 4
// 如果找不到,返回-1
System.out.println(str1.indexOf("abf")); //-1
System.out.println(str1.lastIndexOf("cd")); //6
System.out.println(str1.lastIndexOf("cd", 4)); //2
System.out.println(str1.startsWith("ab")); // true
System.out.println(str1.startsWith("ab", 1)); // false
System.out.println(str1.endsWith("ef")); // true
}
4.3 字符串替换
方法名称 | 类型 | 描述 |
public String replaceAll(String str1,String str2) | 普通 | 替换指定的位置 |
public String replaceFirst(String str1,String str2) | 普通 | 替换首个位置 |
public static void main(String[] args) {
String str1 = "abcdabcdab";
String str2 = str1.replace("b","q"); // 不会修改原本的String对象,会创建一个新的
System.out.println(str2); // aqcdaqcdaq
String str3 = str1.replaceFirst("a","g");
System.out.println(str3); // agcdabcdab
}
注意:由于String是不可变对象,替换不会修改当前字符串,而是产生一个新的字符串。
4.4 字符串拆分
方法名称 | 类型 | 描述 |
public String[] split(String str) | 普通 | 将字符串全部拆分 |
public String split(String str,int limit) | 普通 | 将字符串部分拆分,数组长度就是limit极限 |
public static void main(String[] args) {
String str1 = "ab cd ef";
String[] result = str1.split(" ");
System.out.println(Arrays.toString(result)); // [ab, cd, ef]
String str2 = "ab cd ef";
String[] result2 = str1.split(" ",2);
System.out.println(Arrays.toString(result2)); // [ab, cd ef]
// 有些特殊字符作为分隔符可能无法正常的进行切分,需要加上转义
String str3 = "127.0.0.1";
String[] str4 = str3.split("\\."); //[127, 0, 0, 1]
System.out.println(Arrays.toString(str4));
}
注意:
字符:"|" "*" "+"都得加上转义字符,前面加上"\\".
而不是"",就得写成"\\"
如果一个字符串中有多个分隔符,可以用"|"作为连字符。
4.5 字符串截取
方法名称 | 类型 | 描述 |
public String subString(int beginIndex) | 普通 | 从指定索引到结尾 |
public String subString(int beginIndex,int endIndex) | 普通 | 截取部分内容 |
public static void main(String[] args) {
String str = "helloWorld";
System.out.println(str.substring(3)); // loWorld
System.out.println(str.substring(4, 5)); // o
}
注意:
索引从0开始。
区间前闭后开。
4.6 其他方法
方法名称 | 类型 | 描述 |
public String trim() | 普通 | 去掉字符串中的左右空格,保留中间空格 |
public String toUpperCase() | 普通 | 字符串转大写 |
public String toLowerCase() | 普通 | 字符串转小写 |
public native String intern() | 普通 | 字符串入池操作 |
public String concat(String str) | 普通 | 字符串入池 |
public int length() | 普通 | 取得字符串长度 |
public boolean isEmpty() | 普通 | 判断是否为空字符串,但不是null,而是指长度为0 |
五、StringBuffer和StringBuilder
5.1 StringBuffer和StringBuilder的定义及与String的区别
从第一小节的介绍我们可以知道,任何的字符串常量都是String对象,String对象不可变。为了方便对String的修改,提供了StringBuffer和StringBuilder类。
同时,String是通过 + 完成字符串的拼接,StringBuffer和StringBuilder是通过append方法完成字符串的拼接。
public static void main(String[] args) {
// StringBuffer和StringBuilder功能类似,以StringBuffer为例
// StringBuffer 连接字符串使用 append
StringBuffer sb = new StringBuffer();
sb.append("hello");
sb.append("java").append(1);
System.out.println(sb);
// String连接字符串使用 +
String str1 = "java";
str1 += "hello";
System.out.println(str1);
}
综上所述,区别如下:
String:不可修改,通过 + 完成字符串的拼接
StringBuffer和StringBuilder:可以修改,通过append方法完成字符串的拼接。同时StringBuffer也有一些String没有的方法。
5.2 StringBuffer和StringBuilder与String之间的转换
StringBuffer和StringBuilder转为String:调用toString方法
String转为StringBuffer和StringBuilder:调用构造方法
public static void main(String[] args) {
// String 转StringBuffer
StringBuffer sb2 = new StringBuffer("hello");
System.out.println(sb2);
// StringBuffer 转String
String str = sb2.toString();
System.out.println(str);
}
5.3 StringBuffer与StringBulider的区别
StringBuffer和StringBuilder的大部分功能是类似的。
StringBuffer采用同步处理,属于线程安全操作;StringBuilder未采用同步处理,属于线程不安全操作。
5.4 总结
①String的内容不可改,StringBuilder和StringBuffer可改。
②String转StringBuilder或StringBuffer,调用StringBuilder或StringBuffer的构造方法。
StringBuilder或StringBuffer转String,调用toString方法。
③ StringBuilder和StringBuffer有一些String没有的操作字符串的方法。
④连接字符串,String采用+,StringBuilder和StringBuffer采用append方法。
⑤StringBuilder线程不安全,StringBuffer线程安全。