1. 创建字符串
常见的构造 String 的方式
//方式一:
String str = "hello world";
//方式二:
String str2 = new String("Hello world");
//方式三:
char[] array = {'a','b','c'};
String str3 = new String(array);
注意事项:
- " hello " 这样的字符串字面值常量,类型也是 String.
- String 也是引用类型, String str = " hello " ; 这样的代码内存布局如下
我们曾经在学习数组的时候就提到了引用的概念。
引用类似于C语言中的指针,只是在栈上开辟了一小块内存空间保存一个地址,但是引用和指针又不太相同,指针能进行各种数字运算,但是引用不能,这是一种" 没那么灵活的指针 “。
另外,也可以把引用想象成一个标签,可以” 贴 "到对象上,一个对象可以贴一个标签,也可以贴多个。如果一个对象上面一个标签也没有,那么这个对象就会被JVM当成垃圾回收掉。
由于String 是引用类型,因此对于以下代码
String str1 = "hello";
String str2 = str1;
内存布局如图:
那么有同学就会说,是不是修改 str1 , str2 也会随之变化呢?
String str1 = "hello";
String str2 = str1;
str1 = "world";
System.out.println(str2);
执行结果:
我们发现," 修改 " str1 之后,str2 并没有发生变化,输出的结果仍然是 hello .这是为什么呢?
事实上,str1 = " world " 这样的代码并不算" 修改 "字符串,而是让 str1 这个引用指向了一个新的String 对象。
2. 字符串比较相等
2.1 使用 == 比较
对于内置类型,== 比较的是变量中的值。
代码一:
int x = 10;
int y = 10;
System.out.println(x == y);
执行结果:
对于引用类型,== 比较的是引用中的地址。
代码二:
String str1 = "hello";
String str2 = "hello";
System.out.println( str1 == str2);
执行结果:
看起来貌似没什么问题,再换个代码试一下,就会发现意外的小惊喜!
String str1 = new String("hello");
String str2 = new String("hello");
System.out.println( str1 == str2);
执行结果:
我们来分析一下两种创建String 方式的差异。
代码一内存布局:
我们发现,str1 和 str2 是指向同一个对象的。此时如 " hello " 这样的字符串常量是在字符串常量池中的。
关于字符串常量池:
如" hello " 这样的字符串字面值常量,也是需要一定的内存空间来存储的,这样的常量有一个特点,就是不需要修改。所以如果代码中有多个地方引用都需要使用 " hello " 的话,就之间引用到常量池的这个我位置就行了,而没必要把 " hello " 在内存中存储多次。
代码二内存布局:
我们可以得出结论:
通过 String str1 = new String ( " hello" ) ; 这样的方式创建的String 对象相当于在堆上另外开辟了空间来存储 " hello " 的内容,也就是此时内存中存在两份 " hello " .
String 使用 == 比较的并不是字符串中的内容,而是比较两个引用是否指向了同一个对象。
2.2 使用equals比较
Java中要想比较字符串中的内容,必须采用 String 类提供的 equals 方法。
String str1 = new String("hello");
String str2 = new String("hello");
System.out.println( str1.equals(str2));
执行结果:
equals 使用注意事项
现在需要比较 str 和 " hello " 两个字符串是否相等,我们应该如何来写呢?
String str = new String("hello");
//方式一:
System.out.println(str.equals("hello"));
//方式二;
System.out.println("hello".equals(str));
那么在上面的代码中,哪种方式更好呢?
我们更推荐使用" 方式二 " ,一旦 str 是 null ,方式一 的代码会抛出异常,而方式二不会。
注意事项:
" hello " 这样的字面值常量,本质上也是一个 String 对象,完全可以使用 equals 等String 对象的方法。
2.3 使用compareTo 方法比较
与equals不同的是,equals返回的是boolean类型,而 compareTo 返回的是 int 类型。具体比较方式:
1.先按照字典次序进行比较,如果出现不等的字符,直接返回两个字符的大小差值。
2.如果前k个字符相等(k为两个字符串长度中的较小值),返回值为两个字符串的长度差值。
public static void main(String[] args) {
String s1 = new String("abc");
String s2 = new String("ac");
String s3 = new String("abc");
String s4 = new String("abcdef");
System.out.println(s1.compareTo(s2));
System.out.println(s1.compareTo(s3));
System.out.println(s1.compareTo(s4));
}
解析:
- 遇到了不同的字符,输出字符差值-1.
- 两个字符串相同,输出0.
- 前k个字符完全相同,输出长度差值-3.
2.4使用 compareToIgnoreCase方法比较
注意:此方法与compareTo方法的不同之处在于 忽略大小写比较
public static void main(String[] args) {
String s1 = new String("abc");
String s2 = new String("ac");
String s3 = new String("ABc");
String s4 = new String("abcdef");
System.out.println(s1.compareToIgnoreCase(s2));
System.out.println(s1.compareToIgnoreCase(s3));
System.out.println(s1.compareToIgnoreCase(s4));
}
所以,上述代码的运行结果,和之前的结果一样。
2.5 字符串查找
1.char charAt ( int index )
功能:返回index位置上的字符,如果index为负数或者越界,抛出IndexOutOfBoundsException异常。
public static void main(String[] args) {
String str = "hello";
char ch = str.charAt(2);
System.out.println(ch);
}
上述代码为获取字符串 “hello” 下标为2的字符,第一个字符的下标是0,那么依次往后,下标为2的字符就是 ’ l '.
2. int indexOf ( int ch )
功能:返回ch 第一次出现的位置,若字符串中没有ch, 那么返回-1.
public static void main(String[] args) {
String str = "hello";
int index = str.indexOf("l");
System.out.println(index);
}
上述代码是获取字符串 “hello” 中 " l ",的下标,这里有一个点需要注意,字符串中有两个
" l ",但是这个方法它获取的是第一次出现的位置下标,所以输出的结果是2.
3. int indexOf ( int ch, int fromIndex )
功能:从fromIndex 位置开始找ch 第一次出现的位置,没有就返回-1.
public static void main(String[] args) {
String str = "hello";
int index = str.indexOf("l",3);
System.out.println(index);
}
上述代码是从第三个位置开始找字符 " l ",我们可以看到第三个位置正好就是字符 " l ",所以结果输出3。另外我们也注意到,这里是从第3个位置开始找,同时也是包括3这个位置的。
4. int indexOf ( String str )
功能:返回 str 第一次出现的位置,没有返回-1.
public static void main(String[] args) {
String str = "abcdefcd";
int index = str.indexOf("cd");
System.out.println(index);
}
上述代码是求字符串 " cd " 在字符串 " abcdefcd " 中第一次出现的位置,输出的结果是2(输出的是子字符串中首个字符的下标)。
5. int indexOf ( String str, int fromIndex )
功能:从fromIndex位置开始找str第一次出现的位置,没有就返回-1。
public static void main(String[] args) {
String str = "abcdefcd";
int index = str.indexOf("cd",5);
System.out.println(index);
}
上述代码是求字符串 " cd " 在字符串 " abcdefcd " 中从5下标开始第一次出现的位置,输出的结果是6(输出的是子字符串中首个字符的下标).
6. int lastIndexOf ( int ch )
功能:从后往前找,返回字符ch第一次出现的位置,没有则返回-1.
public static void main(String[] args) {
String str = "abcdefcd";
int index = str.lastIndexOf("c");
System.out.println(index);
}
上述代码是求字符 " c " 在字符串 " abcdefcd " 中最后的位置开始第一次出现的位置,输出的结果是6(输出的是子字符串中首个字符的下标).
7. int lastIndexOf ( int ch, int fromIndex)
功能:从fromIndex位置开始找,返回字符ch 第一次出现的位置,没有就返回-1.
public static void main(String[] args) {
String str = "abcdabed";
int index = str.lastIndexOf("c",1);
System.out.println(index);
}
上述代码是求字符串 " c " 在字符串 " abcdabed " 中下标为1的位置开始从后往前查找,由于这样一来刚好就错过了c出现的位置,所以输出的结果是-1.
8. int lastIndexOf ( String str )
功能:从后往前找,返回字符串str第一次出现的位置,没有则返回-1.
public static void main(String[] args) {
String str = "abcdefcd";
int index = str.lastIndexOf("cd");
System.out.println(index);
}
上述代码是求字符串 " cd " 在字符串 " abcdefcd " 中最后的位置开始第一次出现的位置,输出的结果是6(输出的是子字符串中首个字符的下标).
9. int lastIndexOf ( String str, int fromIndex )
功能:从fromIndex位置开始找,从后往前找字符串 str 第一次出现的位置,没有则返回-1.
public static void main(String[] args) {
String str = "abcdabed";
int index = str.lastIndexOf("cd",1);
System.out.println(index);
}
上述代码是求字符串 " cd " 在字符串 " abcdabed " 中下标为1的位置开始从后往前查找,由于这样一来刚好就错过了cd出现的位置,所以输出的结果是-1.
2.6 转化
1.数值和字符串转化
1)数字转字符串
class Student{
public String name;
public int age;
public Student(String name,int age){
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
public static void main(String[] args) {
String s1 = String.valueOf(1234);
String s2 = String.valueOf(12.34);
String s3 = String.valueOf(true);
String s4 = String.valueOf(new Student("翠花",2));
System.out.println(s1);
System.out.println(s2);
System.out.println(s3);
System.out.println(s4);
}
运行结果:
s1是 int 向 String 的转化;
s2是double 向 String 的转化;
s3是boolean 向 String 的转化;
s4是 引用类型向 String 的转化,由于在Student类中重写了toString 方法,所以这里可以正常输出。
2. 字符串转数字
public static void main(String[] args) {
int num1 = Integer.parseInt("1234");
double num2 = Double.parseDouble("12.34");
System.out.println(num1);
System.out.println(num2);
}
上述代码分别是将字符串转为整型和double型,需要注意的一点是,在接收返回值的时候类型一定要匹配。
3. 大小写转换
小写转大写:
public static void main(String[] args) {
String str1 = "hello";
System.out.println(str1.toUpperCase());
}
大写转小写:
public static void main(String[] args) {
String str1 = "HELLO";
System.out.println(str1.toLowerCase());
}
4. 字符串转数组
public static void main(String[] args) {
String str = "hello";
char[] array = str.toCharArray();
System.out.println(array[2]);//输出l
}
数组转字符串:
public static void main(String[] args) {
char[] array = {'a','b','c'};
String str = new String(array);
System.out.println(str);//输出abc
}
2.7 字符串转换
使用一个特定的新的字符串换掉已有的字符串数据,可用的方法如下:
1)替换所有的指定内容:String replaceAll ( String regex, String replacement );
public static void main(String[] args) {
String str = "cdabstab";
System.out.println(str.replaceAll("ab", "oo"));
}
执行结果:
上述代码是将字符串中所有的 " ab “,都替换为了” oo".
2) 替换首个内容: String replaceFirst ( String regex, String replacement );
public static void main(String[] args) {
String str = "cdabstab";
System.out.println(str.replaceFirst("ab", "oo"));
}
上述代码是将字符串中的第一个 " ab “,替换为了” oo".
注意这里是直接输出,所以能看到替换的效果,但如果最后输出的是str,那输出的还是原来的对象,因为字符串是不可变对象,替换不会修改当前的字符串,而是会产生一个新的字符串。
2.8 字符串拆分
可以将一个完整的字符串按照指定的分隔符划分为若干个子字符串。
可用方法如下:
1)按照字符串中的字符将字符串全部拆分:String[ ] split ( String regex )
public static void main(String[] args) {
String str = "hello world hello bit";
String[] array = str.split(" ");
System.out.println(Arrays.toString(array));
}
执行结果:
上述代码是将字符串str以空格拆分成了一个字符串数组。
2) 字符串的部分拆分:
将字符串以指定的格式,拆分为 limit 组:String[ ] split ( String regex, int limit )
public static void main(String[] args) {
String str = "hello world hello bit";
String[] array = str.split(" ",2);
System.out.println(Arrays.toString(array));
}
执行结果:
拆分是特别常用的操作,需要我们去重点掌握。另外有些特殊字符作为分割符可能无法正确切分,需要加上转义。
例如,拆分IP地址:
public static void main(String[] args) {
String str = "192.168.1.1";
String[] array = str.split("\\.");
System.out.println(Arrays.toString(array));
}
执行结果:
注意事项:
- 字符 " | ", " * " " + " " . "都得加上转义字符,前面加上 " \ "
- 如果是 " \ ",那么就得写成 " \\ "
- 如果一个字符串中有多个分割符,可以用 " | "作为连字符
示例代码:多次拆分
public static void main(String[] args) {
String str = "name=zhangsan&age=18";
String[] array = str.split("=|&");
System.out.println(Arrays.toString(array));
}
执行结果:
2.9 字符串截取
从一个完整的字符串中截取出部分内容,可用方法如下:
1)String substring ( int beginIndex )
功能:从指定索引截取到结尾
public static void main(String[] args) {
String str1 = "abcdef";
String str2 = str1.substring(2);
System.out.println(str2);
}
上述代码是从字符串 str1 下标为2的地方开始截取(也包括2),一直截取到字符串结尾,输出的结果是 " cdef ".
2) String substring ( int beginIndex , int endIndex )
功能:截取字符串的部分内容
public static void main(String[] args) {
String str1 = "abcdef";
String str2 = str1.substring(2,5);
System.out.println(str2);
}
上述代码是从字符串 str1 2下标的位置开始截取(包括2),一直到下标为5的地方结束(不包括5),所以输出的结果是 " cde ".
需要注意的一点是这里的区间是左闭右开区间 [ ),假设x处于这个区间,那x的取值范围就是左边 ≤ x < 右边,也就是包含左边,但不包含右边。
其他操作
1)String trim ()
功能:去掉字符串中的左右空格,保留中间空格
public static void main(String[] args) {
String str1 = " hello world ";
String str2 = str1.trim();
System.out.println(str1);
System.out.println(str2);
}
我们可以分别输出 str1 和 str2 来观察,trim 会去掉字符串开头和结尾的空白字符(空格、换行、制表符等)。
3. 字符串的不可变性
String 是一种不可变对象,字符串中的内容是不可改变的,字符串不可修改,是因为:
- String类在设计时就是不可改变的,String类在描述中就已经说明了。
String类中的字符实际保存在内部维护的value字符数组中,该图还可以看出:
1.String类被final 修饰,表明该类不能被继承
2.value被final修饰,表明value自身的值不能改变,即不能引用其他字符数组,但是其引用空间中的内容可以修改。 - 所有涉及到可能修改字符串内容的操作都是创建一个新对象,改变的是新对象
上面提到的大小写转换也是一样:
final 修饰类表明该类不想被继承,final 修饰引用类型表明该引用变量不能引用其他对象,但是其引用对象中的内容是可以修改的。
public static void main(String[] args) {
final int[] array = {1,2,3,4,5};
array[0] = 100;
array = new int[]{4,5,6};
}
执行结果:
为什么 String 要设计成不可变的?(不可变对象的好处是什么?)
1.方便实现字符串对象池。如果 String 可变,那么对象池就需要考虑写时拷贝的问题了。
2.不可变对象是线程安全的。
3.不可变对象更方便缓存 hash code,作为 key 时可以更高效的保存到 HashMap 中。
那如果想要修改字符串中的内容,该如何操作呢?
注意:尽量避免直接对 String 类对象进行修改,因为 String 类是不能修改的,所有的修改都会创建新对象,效率非常低下。
可以看待在对String类进行修改时,效率是非常慢的,因此:尽量避免对String的直接需要,如果要修改建议尽量使用StringBuffer 或者 StringBuilder.
4.StringBuffer 和 StringBuilder
4.1 StringBuilder 的介绍
由于String的不可修改特性,为了方便字符串的修改,Java 中又提供了StringBuffer 和 StringBuilder类。这两个类大部分功能是相同的,这里介绍StringBuilder常用的一些方法。
方法 | 说明 |
---|---|
StringBuffer append ( String str ) | 在尾部追加,相当于String的+=,可以追加:boolean、char、char[]、double、float、int、long、Object、String、StringBuffer的变量 |
char charAt ( int index ) | 获取index位置的字符 |
int length( ) | 获取字符串的长度 |
int capacity( ) | 获取底层保存字符串空间总的大小 |
void ensureCapacity ( int minimumCapacity ) | 扩容 |
void setCharAt ( int index, char ch ) | 将index位置的字符设置成ch |
int indexOf (String str ) | 返回str第一次出现的位置 |
int indexOf (String str, int fromIndex ) | 从fromIndex位置开始查找str第一次出现的位置 |
int lastIndexOf ( String str ) | 返回最后一次出现str的位置 |
int lastIndexOf ( String str,int fromIndex ) | 从fromIndex位置开始查找str最后一次出现的位置 |
StringBuffer insert ( int offset, String str ) | 在offset位置插入:八种基本类型&String类型&Object类型 |
StringBuffer deleteCharAt (int index ) | 删除index位置的字符 |
StringBuffer delete ( int start, int end ) | 删除[start, end]区间内的字符 |
StringBuffer replace ( int start, int end, String str ) | 将[start, end]位置的字符替换为str |
String substring ( int start ) | 从start开始一直到末尾的字符以String的方式返回 |
String substring ( int start, int end ) | 将[start, end]范围内的字符以String的方式返回 |
StringBuffer reverse ( ) | 反转字符串 |
String toString ( ) | 将所有字符按照String的方式返回 |
public static void main(String[] args) {
String str = "hello";
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append(str);
stringBuilder.append(" world");
str = stringBuilder.toString();
System.out.println(str);
}
运行结果:
面试题
- String、StringBuffer、StringBuilder的区别
- String的内容不可修改,StringBuilder和StringBuffer的内容可以修改
- StringBuilder和StringBuffer的大部分功能是相似的
- StringBuffer采用同步处理,属于线程安全操作;而StringBuilder未采用同步处理,属于线程不安全操作