一.创建字符串
1.构造字符串的方式
String str1="hello world!"; System.out.println("str1 = "+str1); String str2=new String("hello world!"); System.out.println("str2 = "+str2); char[] ch={'a','b','c'}; String str3=new String(ch); System.out.println("str3 = "+str3); //运行结果 str1 = hello world! str2 = hello world! str3 = abc
2.内存布局
在new String()时,会先检查字符串常量池中是否有对应字符串,如果有,就直接将该字符串在常量池中的地址赋给value[],如果没有,则在常量池中存入相应字符串,同时将地址赋给value[]。
二.字符串比较相等
1. ==(String 使用 == 比较并不是在比较字符串内容, 而是比较两个引用是否是指向同一个对象)
代码1:
String str1 = "Hello";
String str2 = "Hello";
System.out.println(str1 == str2);
// 执行结果
true
代码2:String str1 = new String("Hello");
String str2 = new String("Hello");
System.out.println(str1 == str2);
// 执行结果
false
在代码1中:str1 和 str2 是指向同一个对象的. 此时如 "Hello" 这样的字符串常量是在 字符串常量池 中
(关于字符串常量池:如 "Hello" 这样的字符串字面值常量, 也是需要一定的内存空间来存储的. 这样的常量具有一个特点, 就是不需要修改(常量). 所以如果代码中有多个地方引用都需要使用 "Hello" 的话, 就直接引用到常量池的这个位置就行了, 而没必要把 "Hello" 在内存中存储两次.)
在代码2中:通过 String str1 = new String("Hello"); 这样的方式创建的 String 对象相当于再堆上另外开辟了空间来存储"Hello" 的内容, 也就是内存中存在两份 "Hello"
2.equals(比较字符串的内容, 必须采用String类提供的equals方法)
String str1 = new String("Hello");
String str2 = new String("Hello");
System.out.println(str1.equals(str2));
// 执行结果
true
equals 使用注意事项:
以比较 str 和 "Hello" 两个字符串是否相等为例:
String str = new String("Hello");
// 方式一
System.out.println(str.equals("Hello"));
// 方式二
System.out.println("Hello".equals(str));
我们更推荐使用 "方式二". 一旦 str 是 null, 方式一的代码会抛出异常, 而方式二不会
三.字符串常量池
1.直接赋值
在JVM底层实际上会自动维护一个对象池(字符串常量池)
如果现在采用了直接赋值的模式进行String类的对象实例化操作,那么该实例化对象(字符串内容)将自动保存
到这个对象池之中.
如果下次继续使用直接赋值的模式声明String类对象,此时对象池之中如若有指定内容,将直接进行引用
如若没有,则开辟新的字符串对象而后将其保存在对象池之中以供下次使用
2.采用构造方法
如果使用String构造方法就会开辟两块堆内存空间,并且其中一块堆内存将成为垃圾空间(字符串常量 "hello" 也
是一个匿名对象, 用了一次之后就不再使用了, 就成为垃圾空间, 会被 JVM 自动回收掉).
字符串共享问题. 同一个字符串可能会被存储多次, 比较浪费空间
3.使用 String 的 intern 方法来手动把 String 对象加入到字符串常量池中
// 该字符串常量并没有保存在对象池之中
String str1 = new String("hello") ;
String str2 = "hello" ;
System.out.println(str1 == str2);
// 执行结果
false
String str1 = new String("hello").intern() ;
String str2 = "hello" ;
System.out.println(str1 == str2);
// 执行结果
true
4.String类中两种对象实例化的区别
直接赋值:只会开辟一块堆内存空间,并且该字符串对象可以自动保存在对象池中以供下次使用。
构造方法:会开辟两块堆内存空间,不会自动保存在对象池中,可以使用intern()方法手工入池
四.字符串不可变
1.
String str = "hello" ;
str = str + " world" ;
str += "!!!" ;
System.out.println(str);
// 执行结果
hello world!!!
表面上好像是修改了字符串, 其实不是.+= 之后 str 打印的结果却是变了, 但是不是 String 对象本身发生改变, 而是 str 引用到了其他的对象.引用相当于一个指针, 里面存的内容是一个地址. 我们要区分清楚当前修改到底是修改了地址对应内存的内容发生改变了, 还是引用中存的地址改变了
2.修改字符串方法
(1)借助原字符串, 创建新的字符串
String str = "Hello";
str = "h" + str.substring(1);
System.out.println(str);
// 执行结果
hello
String str = "Hello";
(2)使用 "反射" 这样的操作可以破坏封装, 访问一个类内部的 private 成员
3.为什么 String 要不可变?(不可变对象的好处是什么?)
方便实现字符串对象池. 如果 String 可变, 那么对象池就需要考虑何时深拷贝字符串的问题了.
不可变对象是线程安全的.
不可变对象更方便缓存 hash code, 作为 key 时可以更高效的保存到 HashMap 中
五.字符,字节,字符串
1.字符串内部包含一个字符数组,String 可以和 char[] 相互转换
(1)将字符数组中所有内容转换成一个字符串:public String(char value[])构造方法;
char[] array={'a','b','c'}; String str=new String(array); System.out.println(str); //abc
(2) 将字符数组中的部分内容转换成字符串:public String(char value[],int offset,int count)构造方法;
char[] array={'a','b','c','d','e'}; String str=new String(array,2,2); System.out.println(str); //cd
(3) 取指定位置的字符,索引从0下标开始:public char charAt(int index);
String str4="hello world!"; char ch=str4.charAt(4); System.out.println(ch); //o
(4) 将字符串转换成字符数组:public char[] toCharArray()
String str4="hello world!"; char[] ch1=str4.toCharArray(); System.out.println(ch1); //hello world!
2.字节常用于数据传输以及编码转换的处理之中,String 也能方便的和 byte[] 相互转换
(1)将字节数组转换为字符串:public String(byte bytes[ ])构造方法;
(2)将部分字节数组中的内容转为字符串:public String(byte bytes[ ],int offset,int length);
(3)将字符串转换成字节数组:public byte[ ] getBytes();
String str = "helloworld" ; // String 转 byte[] byte[] data = str.getBytes() ; for (int i = 0; i < data.length; i++) { System.out.print(data[i]+" "); } // byte[] 转 String System.out.println(new String(data)); //运行结果 104 101 108 108 111 119 111 114 108 100 helloworld
byte[] 是把 String 按照一个字节一个字节的方式处理, 这种适合在网络传输, 数据存储这样的场景下使用. 更适合针对二进制数据来操作.
char[] 是吧 String 按照一个字符一个字符的方式处理, 更适合针对文本数据来操作, 尤其是包含中文的时候
六.字符串常见操作
1.字符串比较
(1)区分大小写的比较:public boolean equals(Object anthorObject);
(2) 不区分大小写的比较:public boolean equalsIgnoreCase(String anthorString);
(3)比较字符串大小:public int compareTo(String anthorString);(相等:返回0. 小于:返回内容小于0. 大于:返回内容大于0)
String str7="abc"; String str8="aBc"; boolean flg1 = str7.equals(str8); boolean flg2 = str7.equalsIgnoreCase(str8); int flg3 = str7.compareTo(str8); System.out.println(flg1); System.out.println(flg2); System.out.println(flg3); //运行结果 false true 32
2.字符串查找
(1)判断一个子字符串是否存在:public boolean contains(CharSequence s);
(2) 从头开始查找指定字符串的位置,查到了返回下标,查不到返回-1:public int indexOf(String str);
(3) 从指定位置开始查找子字符串位置:public int indexOf(String str,int fromIndex);
(4) 从后向前查找子字符串位置:public int lastIndexOf(String str);
(5) 从指定位置开始从后向前查找:public int lastIndexOf(String str,int fronIndex);
(6) 判断是否以指定字符串开头:public boolean startsWith(String prefix);
(7) 从指定位置开始判断是否以指定字符串开头:public boolean startsWith(String prefix,int toffset);
(8) 判断是否以指定字符串结尾:public boolean eadsWith(String suffix)
String str9="abadcabcdabcde"; System.out.println(str9.contains("bcd"));//true System.out.println(str9.indexOf("cd"));//7 System.out.println(str9.indexOf("cd", 3));//7 System.out.println(str9.lastIndexOf("bc"));//10 System.out.println(str9.lastIndexOf("bc", 5));//-1 System.out.println(str9.startsWith("ab"));//true System.out.println(str9.startsWith("ab", 4));//false System.out.println(str9.endsWith("cd"));//false
3.字符串替换
(1)替换所有指定内容:public String replaceAll(String regex,String replacement);
(2)替换首个内容:Public String replaceFirst(String regex,String replacement);
String str0="ababcabcd"; System.out.println(str0.replaceAll("ab", "oo"));//oooocoocd System.out.println(str0.replaceFirst("ab", "oo"));//ooabcabcd
注意事项: 由于字符串是不可变对象, 替换不修改当前字符串, 而是产生一个新的字符串
4.字符串拆分
(1)将字符串全部拆分:public String[ ] split(String regex);
(2) 将字符串部分拆分,该数组长度limit就是极限:public String[ ] split(String regex,int limit);
String s0="as&as&asd&Ass"; String[] s1=s0.split("&"); for (int i = 0; i < s1.length; i++) { System.out.println(s1[i]); //as as asd Ass } String[] s2=s0.split("&",3); for (int i = 0; i < s2.length; i++) { System.out.println(s2[i]); //as as asd&Ass }
另外有些特殊字符作为分割符可能无法正确切分, 需要加上转义
字符"|","*","+"都得加上转义字符,前面加上"\".
而如果是"",那么就得写成"\\".
如果一个字符串中有多个分隔符,可以用"|"作为连字符
5.字符串截取
(1)从指定索引截取到结尾:public String substring(int beginIndex);
(2)截取部分内容:public String substring(int beginIndex,int endIndex);
String s3="hello world!"; System.out.println(s3.substring(3));//lo world! System.out.println(s3.substring(3, 7));//lo w
索引从0开始
注意前闭后开区间的写法, substring(0, 5) 表示包含 0 号下标的字符, 不包含 5 号下标
6.其他常见操作方法
(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();
(8)判断是否为空字符串“”,但不是null,而是长度为0:public boolean isEmpty();
七.StringBuffer 和StringBuilder
1.String类的特点:任何的字符串常量都是String对象,而且String的常量一旦声明不可改变,如果改变对象内容,改变的是其引用的指向而已。通常来讲String的操作比较简单,但是由于String的不可更改特性,为了方便字符串的修改,提供StringBuffer和StringBuilder类
StringBuffer 和 StringBuilder 大部分功能是相同的
在String中使用"+"来进行字符串连接,但是这个操作在StringBuffer类中需要更改为append()方法
public synchronized StringBuffer append(各种数据类型 b)
2.StringBuffer使用
public class Test{
public static void main(String[] args) {
StringBuffer sb = new StringBuffer();
sb.append("Hello").append("World");
fun(sb);
System.out.println(sb);
}
public static void fun(StringBuffer temp) {
temp.append("\n").append("www.bit.com.cn");
}
}
String和StringBuffer最大的区别在于:String的内容无法修改,而StringBuffer的内容可以修改。频繁修改字符串的情况考虑使用StingBuffer。
String和StringBuffer类不能直接转换。如果要想互相转换,可以采用如下原则:
String变为StringBuffer:利用StringBuffer的构造方法或append()方法
StringBuffer变为String:调用toString()方法
除了append()方法外,StringBuffer也有一些String类没有的方法
3.解释String、StringBuffer、StringBuilder的区别:
String的内容不可修改,StringBuffer与StringBuilder的内容可以修改.
StringBuffer与StringBuilder大部分功能是相似的
StringBuffer采用同步处理synchronized,属于线程安全操作;而StringBuilder未采用同步处理,属于线程不安全操作