String类的创建
//方式一
String str = "hello word";
//方式二
String str = new String("hello word");
//方式三
char[] arr = {'a', 'b', 'c'};
String str = new String(arr);
我们看看以上代码的内存分布图:
引用类型有点类似c语言中的指针,我们在内存中开辟了一小块内存空间保存一个地址,但是与c语言不同的是指针能进行数字运算,但引用不行,java中String就是引用类型.
由于String是引用类型,因此我们分析以下代码的内存分布:
String str1 = "hello word';
String str2 = str1;
修改str1的值,看看str2是否会随之改变:
public class test {
public static void main(String[] args) throws NameException, PasswordException {
String str1 = "hello word";
String str2 = str1;
str1 = "zmm";
System.out.println(str2);
}
}
从内存分布分析此结果:
可以看出我们修改str1并不会影响str2的结果,也就意味着我们修改了str1其实只是修改了str1的指向,让str1引用指向了一个新的String对象
字符串比较相等
import java.util.Arrays;
public class test {
public static void main(String[] args) throws NameException, PasswordException {
String str1 = "hello";
String str2 = "hello";
System.out.println(str1 == str2);
}
}
那么根据以上结果,是否就能确定判断字符串相等可以用 '' == '', 我们再看一个例子:
import java.util.Arrays;
public class test {
public static void main(String[] args) throws NameException, PasswordException {
String str1 = new String("hello");
String str2 = new String("hello");
System.out.println(str1 == str2);
}
}
我们分析两种创建String方式的差异
第一种:
str1 和str2 是指向同一个对象,是由于像 " hello '' 这样的字符串,我们是将它保存在字符串常量池中的,如果我们需要使用 '' hello '' 这个字符串常量,就直接引用字符串常量池的这个位置就可.
第二种:
用 String str1 = new String(" hello ") 这个方式创建对象,我们在内存中开了两块空间,来保存 " hello "这个字符串,因此在用" == " 比较两个字符串是否相等的时候,其实是在比较两个字符串的地址是否一样,而不是比较字符串的内容
equals方法来比较字符串内容是否相等
import java.util.Arrays;
public class test {
public static void main(String[] args) throws NameException, PasswordException {
String str1 = new String("hello");
String str2 = new String("hello");
System.out.println(str1.equals(str2));
}
}
使用 equals 的注意事项
//方式一
System.out.println(" hello ".equals(str2));
//方式二
System.out.println(str1.equals(str2));
以上两种方式我们更推荐方式一,若一旦 " str1 = null "那么就会抛出异常
方式二
import java.util.Arrays;
public class test {
public static void main(String[] args) throws NameException, PasswordException {
String str1 = null;
String str2 = new String("hello");
System.out.println(str1.equals(str2));
}
}
方式一
import java.util.Arrays;
public class test {
public static void main(String[] args) throws NameException, PasswordException {
String str1 = null;
String str2 = new String("hello");
System.out.println("hello".equals(str1));
}
}
字符串常量池
直接赋值
import java.util.Arrays;
public class test {
public static void main(String[] args) throws NameException, PasswordException {
String str1 = "hello";
String str2 = "hello";
System.out.println(str1 == str2);
}
}
在JVM底层实际上会自动维护一个对象池(字符串常量池)
- 如果现在采用了直接赋值的模式进行String类的对象实例化操作,那么该实例化对象(字符串内容)将自动保存到这个对象池之中.
- 如果下次继续使用直接赋值的模式声明String类对象,此时对象池之中如若有指定内容,将直接行引用
- 如若没有,则开辟新的字符串对象而后将其保存在对象池之中以供下次使用
采用构造方法
String str = new String(" hello ");
这样的做法有两个缺点:
- 如果使用String构造方法就会开辟两块堆内存空间,并且其中一块堆内存将成为垃圾空间(字符串常量 "hello" 也是一个匿名对象, 用了一次之后就不再使用了, 就成为垃圾空间, 会被 JVM 自动回收掉).
- 字符串共享问题,同一个字符串可能被储存多次,比较浪费空间
intern 方法可以显示把对象入池
import java.util.Arrays;
//判断str是否入池
public class test {
public static void main(String[] args) throws NameException, PasswordException {
String str1 = new String("hello");
System.out.println(str1 == "hello");
}
}
import java.util.Arrays;
public class test {
public static void main(String[] args) {
//显示入池
String str1 = new String("hello").intern();
System.out.println(str1 == "hello");
}
}
结论
- 直接赋值:只会开辟一块堆内存空间,并且该字符串对象可以自动保存在对象池中以供下次使用。
- 构造方法:会开辟两块堆内存空间,不会自动保存在对象池中,可以使用intern()方法手工入池
- 我们一般采用直接赋值的方式创建String类对象
字符串的不可变
字符串是一种不可变对象,它的内容不可变
String类的内部实现也是基于char[ ]来实现,但是String类并没有提供set方法来修改内部字符数组
public class example {
public static void main(String[] args) {
String str = "hello ";
str = str + "word";
str += "!!!";
System.out.println(str);
}
}
内存分布图
字符串str拼接以后,str的引用指向改变了,但String的对象并没有改变
String不可变的好处:
- 方便实现字符串对象池
- 不可变对象线程安全
- 不可变对象更方便缓存 hash code, 作为 key 时可以更高效的保存到 HashMap 中
字符、字节与字符串
方法名称 | 类型 | 描述 |
---|---|---|
public String(char value[]) | 构造 | 使用字符数组的内容构造一个字符串 |
public String(char value[],int offset, int court) | 构造 | 使用字符数组中以offset下标开始的连续count个字符构造一个字符串 |
public char charAt(int index) | 普通 | 获得字符串中指定索引的字符 |
public char[] toCharArray() | 普通 | 将字符串变为字符数组返回 |
- 示例代码1:字符数组转字符串
import java.util.Scanner;
public class example {
public static void main(String[] args) {
char[] arr = {'a','b','c'};
String str = new String(arr);
System.out.println(str);
}
}
- 示例代码2:将指定字符转成字符串
import java.util.Scanner;
public class example {
public static void main(String[] args) {
char[] arr = {'a','b','c','d','f'};
String str = new String(arr,2,3);
System.out.println(str);
}
}
- 示例代码3:获得字符串中的某个字符
import java.util.Scanner;
public class example {
public static void main(String[] args) {
String str = "hello";
char ch = str.charAt(4);
System.out.println(ch);
}
}
- 示例代码4:字符串转成字符数组
import java.util.Arrays;
import java.util.Scanner;
public class example {
public static void main(String[] args) {
String str = "hello";
char[] str1 = str.toCharArray();
System.out.println(Arrays.toString(str1));
}
}
字符串常见操作
字符串常见比较
方法名称 | 类型 | 描述 |
---|---|---|
public boolean equals(Object anObject) | 普通 | 区分大小写的比较 |
public boolean equalsIgnoreCase(String anotherString) | 普通 | 不区分大小写的比较 |
public int compareTo(String anotherString) | 普通 | 两个字符串比较关系 |
- 示例代码1:
import java.util.Arrays;
import java.util.Scanner;
public class example {
public static void main(String[] args) {
String str1 = "hello";
String str2 = "Hello";
System.out.println(str1.equals(str2));
}
}
- 示例代码2:
import java.util.Arrays;
import java.util.Scanner;
public class example {
public static void main(String[] args) {
String str1 = "hello";
String str2 = "Hello";
System.out.println(str1.equalsIgnoreCase(str2));
}
}
- 示例代码3:
import java.util.Arrays;
import java.util.Scanner;
public class example {
public static void main(String[] args) {
String str1 = "hello";
String str2 = "Hello";
System.out.println(str1.compareTo(str2));
}
}
public class example {
public static void main(String[] args) {
String str1 = "Hello";
String str2 = "hello";
System.out.println(str1.compareTo(str2));
}
}
public class example {
public static void main(String[] args) {
String str1 = "Hello";
String str2 = "Hello";
System.out.println(str1.compareTo(str2));
}
}
使用compareTo()这个方法,若两个字符串相等,则返回0;反之返回将字符串中的字符一一比较返回其差值;
不区分大小写的字符串比较
public class example {
public static void main(String[] args) {
String str1 = "Hello";
String str2 = "Hello";
System.out.println(str1.equalsIgnoreCase(str2));
}
}
字符串查找
所谓字符串查找就是从一个完整的字符串之中可以判断指定内容是否存在
方法名称 | 类型 | 描述 |
---|---|---|
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 toffset) | 普通 | 从指定位置开始判断是否以指定字符串开头 |
public boolean endsWith(String suffix) | 普通 | 判断是否以指定字符串结尾 |
- 示例代码1:
public class example {
public static void main(String[] args) {
String str1 = "Hello word";
System.out.println(str1.contains("word"));
}
}
- 示例代码2:
public class example {
public static void main(String[] args) {
String str1 = "Hello word";
String str2 = "word";
System.out.println(str1.indexOf(str2));
}
}
- 示例代码3:
package xsk;
public class Test {
public static void main(String[] args) {
String str = "adcmfro";
String str1 = "m";
System.out.println(str.indexOf(str1, 3));
}
}
- 示例代码4:
package xsk;
public class Test {
public static void main(String[] args) {
String str = "adrmfro";
String str1 = "r";
System.out.println(str.lastIndexOf(str1));
}
}
- 示例代码5:
package xsk;
public class Test {
public static void main(String[] args) {
String str = "adrmfro";
String str1 = "r";
System.out.println(str.lastIndexOf(str1, 4));
}
}
- 示例代码6:
package xsk;
public class Test {
public static void main(String[] args) {
String str = "adrmfro";
System.out.println(str.startsWith("amm"));
}
}
- 示例代码7:
package xsk;
public class Test {
public static void main(String[] args) {
String str = "adrmfro";
System.out.println(str.startsWith("ad",0));
}
}
- 示例代码8:
package xsk;
public class Test {
public static void main(String[] args) {
String str = "adrmfro";
System.out.println(str.endsWith("r"));
}
}
字符串替换
使用一个指定的新的字符串替换掉已有的字符串数据,可用方法如下
方法名称 | 类型 | 描述 |
---|---|---|
public String replaceAll(String regex, String replacement) | 普通 | 替换所有的指定内容 |
public String replaceFirst(String regex, String replacement) | 普通 | 替换首个内容 |
- 示例代码:
package xsk;
public class Test {
public static void main(String[] args) {
String str = "helloword";
System.out.println(str.replaceAll("l", "m"));
System.out.println(str.replaceFirst("l", "x"));
System.out.println(str.replace("w", "o"));
}
}
注意事项: 由于字符串是不可变对象, 替换不修改当前字符串, 而是产生一个新的字符串.
字符串拆分
可以将一个完整的字符串按照指定的分隔符划分为若干个子字符串
方法名称 | 类型 | 描述 |
---|---|---|
public String[] split(String regex) | 普通 | 将字符串全部拆分 |
public String[] split(String regex, int limit) | 普通 | 将字符串部分拆分,该数组的长度极限就是limit |
- 示例代码1:
package xsk;
public class Test {
public static void main(String[] args) {
String str = "hello word";
String[] s = str.split(" ");
for(String x: s) {
System.out.println(x);
}
}
}
- 示例代码2:
package xsk;
public class Test {
public static void main(String[] args) {
String str = "hello word hello zmm";
String[] s = str.split(" ", 2);
for(String x: s) {
System.out.println(x);
}
}
}
- 示例代码3:
package xsk;
public class Test {
public static void main(String[] args) {
String str = "192.168.1.1";
String[] s = str.split("\\.");
for(String x: s) {
System.out.println(x);
}
}
}
注意事项:
- 字符"|","*","+"都得加上转义字符,前面加上" \ "即可.
- 而如果是"",那么就得写成"\".
- 如果一个字符串中有多个分隔符,则可以用" | "作为连接符
字符串截取
从一个完整字符串之中截取部分内容,可用方法如下
方法名称 | 类型 | 描述 |
---|---|---|
public String substring(int beginIndex) | 普通 | 从指定索引截取到结尾 |
public String substring(int beginIndex, int endIndex) | 普通 | 截取部分内容 |
- 示例代码1:
package xsk;
public class Test {
public static void main(String[] args) {
String str = "helloword";
System.out.println(str.substring(4));
}
}
- 示例代码2:
package xsk;
public class Test {
public static void main(String[] args) {
String str = "helloword";
System.out.println(str.substring(4, 6));
}
}
注意事项:
- 索引从0开始
- 注意前闭后开区间的写法, substring(0, 5) 表示包含 0 号下标的字符, 不包含 5 号下标
其他操作方法
方法名称 | 类型 | 描述 |
---|---|---|
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) |
- 示例代码1:
package xsk;
public class Test {
public static void main(String[] args) {
String str = " hello bit hello ";
System.out.println(str.trim());
}
}
- 示例代码2:
package xsk;
public class Test {
public static void main(String[] args) {
String str = " Hello bit hello ";
System.out.println(str.toUpperCase());
}
}
- 示例代码3:
package xsk;
public class Test {
public static void main(String[] args) {
String str = " Hello bit hello ";
System.out.println(str.toLowerCase());
}
}
- 示例代码4:
package xsk;
public class Test {
public static void main(String[] args) {
String str = " Hello bit hello ";
String str1 = "aaa";
System.out.println(str.concat(str1));
}
}
- 示例代码5:
public class Test {
public static void main(String[] args) {
String str = " Hello bit hello ";
System.out.println(str.isEmpty());
System.out.println(str.length());
}
}
String Buffer 和 String Builder
StringBuffer和StringBuilder是为了解决String是不可变对象带来的麻烦
我们来看一下之前那个频繁拼接字符串的代码:
public class Test {
public static void main(String[] args) {
String str = "hooo";
for(int i = 0; i < 10; i++) {
str += i;
}
System.out.println(str);
}
}
我们知道String是不可变对象,所以这个代码的效率很低,就是在频繁的创建新的对象。我们使用StringBuffer和StringBuilder来分别优化一下这个代码:
public class Test {
public static void main(String[] args) {
StringBuffer str = new StringBuffer("hoooo");
for(int i = 0; i < 10; i++) {
str.append(i);
}
System.out.println(str);
}
}
String、StringBuffer和StringBuilder的区别
- String是不可变的,StringBuffer和StringBuilder都是可变的;
- StringBuffer和StringBuilder大部分功能都是类似的;
- StringBuffer采用同步处理,属于线程安全;StringBuilder没有采用同步操作,线程不安全。