目录
六、StringBuffer 和 StringBuilder
一、创建字符串
1、字符串是不能被继承的;String是一个引用类型
注意:java中的字符串和C语言不一样,java中的字符串没有所谓的‘\0’结尾
2.创建方式:
// 方式一
String str = "Hello Bit";
// 方式二 调用构造方法进行构造对象
String str2 = new String("Hello Bit");
// 方式三
char[] array = {'a', 'b', 'c'};
String str3 = new String(array);
其实不止以上三种创建方式,其他的创建方式我们在用到的时候再去查就好了
3.不是说传引用就能改变实参的值,我们要看这个引用干了什么事情,我们来看一下这个代码:
import java.util.Arrays;
public class TestDemo {
public static void func(String s, char[] array){
s = "love";//修改了s的指向
array[0] = 'l';
}
public static void main(String[] args) {
String str = "abcdef";
char[] chars = {'d','o','g'};
func(str,chars);
System.out.println(str);
System.out.println(Arrays.toString(chars));
}
}
编译并运行该程序,输出如下:
abcdef
['l','o','g']
str并没有按我们的预期被改为"love",我们一起来看一下这个代码的内存布局图:
二、字符串比较相等
1.
String str1 = "hello";//产生一个String对象
String str2 = new String("hello");//产生一个String对象,自己又new了一个对象,一共2个对象
System.out.println(str1 == str2);//这里比较的是两个字符串的地址,而不是内容
编译并运行该程序,输出如下:
false
分析如下:
1.一些概念:
Class文件常量池:如:int a = 10;此时10就放在这里
运行时常量池:当程序把编译好的字节码文件,加载到JVM当中后,会生成一个运行时常量池(方法区),实际上是Class文件常量池
字符串常量池 :主要存放字符串常量,本质上是一个哈希表(StringTable),JDK1.8开始,放在堆里面

3.我们现在来分析一下它的内存布局:

注意:如果是简单类型的比较,则比较的是它们的值
int x = 10 ;
int y = 10 ;
System.out.println(x == y);
编译并运行该程序,输出如下:
true
2.
String str1 = "hello";
String str2 = new String("hello");
System.out.println(str1 == str2);
编译并运行该程序,输出如下:
true
字符串常量池不会再存"hello",因为第一次已经存过了,我们现在来分析一下它的内存布局:
3.
String str1 = "hello";
String str2 = "he" + "llo";
System.out.println(str1 == str2);
编译并运行该程序,输出如下:
true
此时"he" + "llo"是两个常量,编译的时候,就已经确定好了"hello"
4.
String str1 = "hello";
String str2 = "he";
String str3 = str2 + "llo";
System.out.println(str1 == str3);
编译并运行该程序,输出如下:
false
String str3 = str2 + "llo";此时的str2是变量,编译的时候不知道它是什么
5.
String str1 = "11";
String str2 = new String("1") + new String("1");
System.out.println(str1 == str2);
编译并运行该程序,输出如下:
false
我们现在来分析一下它的内存布局:
其实不画图也可以得到答案,因为new String("1") + new String("1")是一个新的对象
6.
String str1 = new String("1") + new String("1");
str1.intern();//手动入池
String str2 = "11";
System.out.println(str1 == str2);
编译并运行该程序,输出如下:
true
我们现在来分析一下它的内存布局:
7.
String str1 = "11";
String str2 = new String("1") + new String("1");
str2.intern();//手动入池
System.out.println(str1 == str2);
编译并运行该程序,输出如下:
false
str1的11已经入池了,str2的11不会再入池,内存分布图和5的一样
8.
String str1 = "hello";
String str2 = new String("hello");
System.out.println(str1.equals.(str2));
编译并运行该程序,输出如下:
true
此时比较的是内容,所以为true
注意:
String str = new String("Hello");
// 方式一
System.out.println(str.equals("haha"));
// 方式二
System.out.println("haha".equals(str));
我们推荐方式二写代码,一旦 str 是 null, 方式一的代码会抛出异常, 而方式二不会,会显示false
三、理解字符串的不可变
String str = "hello" ;
str = str + " world" ;
str += "!!!" ;
System.out.println(str);
这里一共创建了5个对象,"hello","world","helloworld","!!!","helloworld!!!",,它并没有修改字符串,所以说字符串的拼接是一个非常废效率的问题
如果我们非要改变字符串,可以用到反射,"反射" 这样的操作可以破坏封装, 访问一个类内部的 private 成员
String str = "Hello";
// 获取 String 类中的 value 字段. 这个 value 和 String 源码中的 value 是匹配的.
Field valueField = String.class.getDeclaredField("value");
// 将这个字段的访问属性设为 true
valueField.setAccessible(true);
// 把 str 中的 value 属性获取到.
char[] value = (char[]) valueField.get(str);
// 修改 value 的值
value[0] = 'h';
System.out.println(str);
现在"Hello"就改变为"hello"啦
法二:
String str = "Hello";
str = "h" + str.substring(1);
System.out.println(str);
现在"Hello"也改变为"hello"啦
那么为什么String要不可变呢?
四、字符, 字节与字符串
1.字符与字符串
No | 方法名称 | 类型 | 描述 |
1 | public String(char value[]) | 构造 | 将字符数组中的所有内容变为字符串 |
2 | public String(char value[],int offset,int count) | 构造 | 将部分字符数组中的内容变为字符串 |
3 | public char charAt(int index) | 普通 | 取得指定索引位置的字符,索引从0开始 |
4 | public char[] tocharArray() | 普通 | 将字符串变为字符数组返回 |
分别举个例子:
public class TestDemo {
public static void main(String[] args) {
char[] val = {'a','b','c'};
String str = new String(val);
System.out.println(str);
String str2 = new String(val,1,2);//注意不要越界
System.out.println(str2);
}
}
编译并运行该程序,输出如下:
abc
bc
public class TestDemo {
public static void main(String[] args) {
String str = "hello";
char ch = str.charAt(1);//获取1下标的字符
System.out.println(ch);
}
}
编译并运行该程序,输出如下:
e
import java.util.Arrays;
public class TestDemo {
public static void main(String[] args) {
String str = "hello";
char[] chars = str.toCharArray();
System.out.println(Arrays.toString(chars));
}
}
编译并运行该程序,输出如下:
[h, e, l, l, o]
我们来看一条题:给定字符串一个字符串, 判断其是否全部由数字所组成
public class TestDemo {
public static boolean isNumberChar(String s){
for(int i = 0; i < s.length(); i++){
char c = s.charAt(i);
/*if(c < '1' || c > '9'){
return false;
}*/
//或者
boolean flg = Character.isDigit(c);//判断某个字符是不是数字
if(flg == false){
return false;
}
}
return true;
}
public static void main(String[] args) {
String str = "12a456";
System.out.println(isNumberChar(str));
}
}
编译并运行该程序,输出如下:
false
2.字节与字符串
字节常用于数据传输以及编码转换的处理之中,String 也能方便的和 byte[] 相互转换
No | 方法名称 | 类型 | 描述 |
1 | public String(byte byres[]) | 构造 | 将字节数组变为字符串 |
2 | public String(byte byres[],int offset,int length) | 构造 | 将部分字节数组的内容变为字符串 |
3 | public byte[] getBytes() | 普通 | 将字符串以字节数组的形式返回 |
4 | public byte[] getBytes(String charSetName)throws UnspportedEncodingException | 普通 | 编码变换处理 |
分别举个例子:
(1)(2)
public class TestDemo {
public static void main(String[] args) {
byte[] bytes = {97,98,99,100};
String str = new String(bytes);
System.out.println(str);
String str2 = new String(bytes,2,2);
System.out.println(str2);
}
}
编译并运行该代码,输出如下:
abcd
cd
注意:
(3)
import java.util.Arrays;
public class TestDemo {
public static void main(String[] args) {
String str = "hello";
byte[] bytes = str.getBytes();
System.out.println(Arrays.toString(bytes));
}
}
编译并运行该代码,输出如下:
[104, 101, 108, 108, 111]
(4)
编译并运行该代码,输出如下:
[-24, -117, -71, -26, -98, -100]
//java的编码是JDK编码
小结:么何时使用 byte[], 何时使用 char[] 呢?
五、字符串常见操作
1.字符串的比较
No | 方法名称 | 类型 | 描述 |
1 | public boolean equals(Object anObject) | 普通 | 区分大小写的比较 |
2 | public boolean equalsIgnoreCase(String anotherString) | 普通 | 不区分大小写的比较 |
3 | public int compareTo(String anotherString) | 普通 | 比较两个对字符串的大小关系 |
分别举个例子:
(1)(2)
public class TestDemo {
public static void main(String[] args) {
String str1 = "hello" ;
String str2 = "Hello" ;
System.out.println(str1.equals(str2));
System.out.println(str1.equalsIgnoreCase(str2));
}
}
编译并运行该代码,输出如下:
false
true
我们来看一下equals的底层源码:
(3)String类中compareTo()方法是一个非常重要的方法,该方法返回一个整型
相等:返回0. 小于:返回内容小于0. 大于:返回内容大于0
public class TestDemo {
public static void main(String[] args) {
String str1 = "hello" ;
String str2 = "Hello" ;
int ret = str1.compareTo(str2);
System.out.println(ret);
}
}
编译并运行该代码,输出如下:
32
我们来看一下它的底层源码:
2.字符串的查找
No | 方法名称 | 类型 | 描述 |
1 | public boolean contains(CharSequence s) | 普通 | 判断一个子字符串是否存在 |
2 | public int indexOf(String str) | 普通 | 从头开始查找指定字符串的位置,查找了返回位置的开始索引,如果查找不到返回-1 |
3 | public int indexOf(String str,int fromIndex) | 普通 | 从指定位置开始查找子字符串 |
4 | public int lastIndexOf(String str) | 普通 | 由后向前查找字符串位置 |
5 | public int lastIndexOf(String str,int fromIndex) | 普通 | 从指定位置由后向前查找 |
6 | public boolean startsWith(String prefix) | 普通 | 判断是否以指定字符串开头 |
7 | public boolean startsWith(String prefix,int offset) | 普通 | 从指定位置开始判断是否以指定字符串开头 |
8 | public boolean endsWith(String suffix) | 普通 | 判断是否以指定字符串结尾 |
分别举个例子:
public class TestDemo {
public static void main(String[] args) {
String str = "ababcabcd";
String tmp = "abc";
boolean flg = str.contains(tmp);
System.out.println(flg);
int index = str.indexOf(tmp);
System.out.println(index);
int index2 = str.indexOf(tmp,3);//从str的3位置开始从前往后找tmp第一次出现的位置
System.out.println(index2);
int index3 = str.lastIndexOf(tmp);
System.out.println(index3);
int index4 = str.lastIndexOf(tmp,6);//从str的6位置开始从后往前找tmp第一次出现的位置
System.out.println(index4);
System.out.println(str.startsWith("ab"));//判断str是不是以ab开头
System.out.println(str.startsWith("c", 4));//在str偏移量为4的地方判断是否以c开头
System.out.println(str.endsWith("cd"));//判断str是不是以cd结尾
}
}
编译并运行该代码,输出如下:
为什么tmp是String类型,但是可以作为contains的参数,这是因为String实现了CharSequence的接口
3.字符串的替换
No | 方法名称 | 类型 | 描述 |
1 | public String replace(char oldChar, char newChar) | 普通 | 用 newChar 字符替换字符串中出现的所有 oldChar 字符 |
2 | public String replace(CharSequence target, CharSequence replacement) | 普通 | 用 newChar 字符串替换字符串中出现的所有 oldChar 字符串 |
3 | public String replaceAll(String regex, String replacement) | 普通 | 替换所有的指定内容 |
4 | public String replaceFirst(String regex, String replacement) | 普通 | 替换首个内容 |
分别举个例子:
public class TestDemo {
public static void main(String[] args) {
String str = "ababdfaafvabadab";
String ret = str.replace('a','z');
System.out.println(ret);
String ret2 = str.replace("ab","oo");
System.out.println(ret2);
String ret3 = str.replaceAll("ab","oo");
System.out.println(ret3);
String ret4 = str.replaceFirst("ab","oo");
System.out.println(ret4);
}
}
编译并运行,输出如下:
注意: 由于字符串是不可变对象, 替换不修改当前字符串, 而是产生一个新的字符串】
4.字符串的拆分
No | 方法名称 | 类型 | 描述 |
1 | public String[] split(String regex) | 普通 | 将字符串全部拆分 |
2 | public String[] split(String regex,int limit) | 普通 | 将字符串部分拆分,该数组长度就是limit极限 |
分别举个例子:
(1)
public class TestDemo {
public static void main(String[] args) {
String str = "name=zhangsa&age=19";
String[] strings = str.split("&");
for(String s:strings){
//System.out.println(s);//name=zhangsa
//age=19
//进一步拆分
String[] ss = s.split("=");
for(String tmp:ss){
System.out.println(tmp);
}
}
}
}
编译并运行该代码,输出如下:
注意:
- 字符"|","*","+"都得加上转义字符,前面加上"\".
- 而如果是"\",那么就得写成"\\\\".
- 如果一个字符串中有多个分隔符,可以用"|"作为连字符.
如:
public class TestDemo {
public static void main(String[] args) {
String str = "192.168.1.1" ;
String[] result = str.split("\\.") ;//转义.要加上\,但是\本身又是一个特殊符号,需要被再次转义
for(String s: result) {
System.out.println(s);
}
}
}
public class TestDemo {
public static void main(String[] args) {
String str = "Java66 12&99#hello";
String[] strings = str.split(" |&|#");
for(String s: strings) {
System.out.println(s);
}
}
}
编译并运行该程序,输出如下:
(2)
public class TestDemo {
public static void main(String[] args) {
String str = "192.168.1.1" ;
String[] result = str.split("\\.",2) ;
for(String s: result) {
System.out.println(s);
}
System.out.println("---------------------------");
String[] result2 = str.split("\\.",6) ;//做多6组,不一定要6组
for(String s: result2) {
System.out.println(s);
}
}
}
编译并运行该代码,输出如下:
5.字符串的截取
No | 方法名称 | 类型 | 描述 |
1 | public String substring(int beginIndex) | 普通 | 从指定索引截取到结尾 |
2 | public String substring(int beginIndex,int endIndex) | 普通 | 截取部分内容 |
分别举个例子;
public class TestDemo {
public static void main(String[] args) {
String str = "helloworld" ;
System.out.println(str.substring(5));
System.out.println(str.substring(2, 5));//区间为左闭右开
}
}
编译并运行该程序,输出如下:
world
llo
注意 :
6.其他操作方法
No | 方法名称 | 类型 | 描述 |
1 | public String trim() | 普通 | 去掉字符串中的左右空格 |
2 | public String toUpperCase() | 普通 | 字符串转大写 |
3 | public String toLowerCase() | 普通 | 字符串转小写 |
4 | public native String intern() | 普通 | 字符串入池操作 |
5 | public String concat(String str) | 普通 | 字符串拼接,等同于“+”,拼接后的字符串不入池 |
6 | public int length() | 普通 | 取得字符串长度 |
7 | public boolean isEmpty() | 普通 | 判断是否为空字符串,但不是null,而是长度为0 |
分别举一些例子:
(1)
public class TestDemo {
public static void main(String[] args) {
String str =" abc abc ";
System.out.print(str.trim());
System.out.println("===");
}
}
编译并运行该程序,输出如下:
abc abc===
(2)(3)
public class TestDemo {
public static void main(String[] args) {
String str = "abcABC";
String ret1 = str.toUpperCase();
String ret2 = str.toLowerCase();
System.out.println(ret1);
System.out.println(ret2);
}
}
编译并运行该代码,输出如下;
ABCABC
abcabc
(5)
public class TestDemo {
public static void main(String[] args) {
String str = "hello";
String ret = str.concat("world");
System.out.println(ret);
}
}
编译并运行该代码,输出如下;
helloworld
六、StringBuffer 和 StringBuilder
public class TestDemo {
public static void main(String[] args) {
/*//法一:
StringBuilder sb = new StringBuilder("abcdef");
System.out.println(sb);//默认调用sb的toString方法,StringBuilder里面有toString方法*/
//法二:
StringBuilder sb = new StringBuilder();
sb.append("abcdef");
System.out.println(sb);
//继续append
sb.append("123");
System.out.println(sb);//注意:此时没有创建新对象
//append可以连用
System.out.println(sb.append("abcdef").append("123"));
}
}
编译并运行该代码,输出如下;
abcdef
abcdef123
abcdef123abcdef123
其实由此可见,StringBuilder是一个可变的对象,因为它没有像String那种不可变对象一样不断创建新对象
public class TestDemo {
public static void main(String[] args) {
/*String str = "abcdef";
for(int i = 0; i < 10; i++){
str += i;
}
System.out.println(str);*/
//底层是这样做的
/*String str = "abcdef";
for(int i = 0; i < 10; i++){
StringBuilder sb = new StringBuilder();
sb.append(str).append(i);
str = sb.toString();
}
System.out.println(str);*/
//或者这样写:
String str = "abcdef";
StringBuilder sb = new StringBuilder();
sb.append(str);
for(int i = 0; i < 10; i++){
sb.append(i);
str = sb.toString();
}
System.out.println(str);
}
}
小结论:如果是在循环里面,进行字符串的拼接,尽量不要使用String,优先使用StringBuffer 和 StringBuilder

比如:
public class TestDemo {
public static void main(String[] args) {
StringBuilder sb = new StringBuilder("abcdef");
sb.reverse();//字符串的逆置
System.out.println(sb);
StringBuilder sb2 = new StringBuilder("helloworld");
System.out.println(sb2.delete(5, 10));//删除数据
StringBuilder sb3 = new StringBuilder("helloworld");
System.out.println(sb3.delete(5, 10).insert(0, "你好"));//插入数据
}
}
编译并运行该代码,输出如下;fedcbahello你好hello

4.String和StringBuilder或StringBuffer类不能直接转换。如果要想互相转换,可以采用如下原则:
- String变为StringBuilder或StringBuffer:利用StringBuilder或StringBuffer的构造方法或append()方法
- StringBuilder或StringBuffer变为String:调用toString()方法
//String变StringBuilder(法一) 使用构造方法
public static StringBuilder func1() {
String str = "abcdef";
return new StringBuilder(str);
}
//String变StringBuilder(法二) 使用构造方法
public static StringBuilder func(){
String str = "abcdef";
StringBuilder sb = new StringBuilder();
sb.append(str);
return sb;
}
//StringBuilder变String
public static StringBuilder func1() {
StringBuilder sb = new StringBuilder();
return sb.toString();
}