详谈 Java String 类

1.字符串的构造

常见的构造String的方式:
//方式一
String str = "字符串";
//方式二
String str = new String("字符串");
//方式三
char[] str = {
    
    'a','b','c','d'};
String s1 = new String(str);
String s2 = new String(str,1,3); // 从1下标开始,选取3个字符
注意:
  1. 像"hello" 这样的字符串字面值常量,类型为String,只要是双引号引起来的都会被存放到字符串常量池中。
  2. 一旦一个字符串变量,被存储到常量池当中,只存在一份,如果后续还有相同的字符串要存放,首先要看当前常量池当中是否存在该字符串变量。
  3. java中数组,自定义的类都是引用类型,String也是引用类型,String类不能被继承。

例如:

String str = "hello";

在内存中的布局为:

在这里插入图片描述
又如:

String str1 = "hello";
String str2 = str1;

在内存中的布局为:
这里 str1 和 str2 都指向的是"hello"
在这里插入图片描述
那么,如果修改str1的内容,str2会不会变?答案是不会,只是更改了str1的指向。在堆中为修改的内容重新开辟内存,然后将str1指向新内容,str2的指向依旧不变。

String str1 = "hello";
String str2 = str1;
str1 = "world";
System.out.println(str1);
System.out.println(str2);
//运行结果
world
hello

内存中的布局为:
在这里插入图片描述

2.字符串的比较

一般来说,在java中对于简单类型的变量判段其相等可以直接使用 == 完成。

使用==比较String类型 :比较的并不是字符串内容,而是比较引用是否相同

代码示例1:

String str1 = "hello";
String str2 = "hello"; //等同 String str2 = str1;
System.out.println(str1==str2);
//运行结果:
true

代码示例2:

String str1 = new String("hello");
String str2 = new String("hello");
System.out.println(str1==str2);
//运行结果:
false
两种创建String方式的差异:

代码1: 内存布局前面已有实现,可以发现str1 和str2都是指向同一个对象的,此时"hello"这样的字符常量是在字符串常量池中。
代码2: 内存布局如下图所示:
在这里插入图片描述
可以看到,如果是通过 new String(“字符串”)这样的方式,相当于在堆上另外开辟了空间来存储"hello"的内容。

在java中想要比较字符串的内容,必须采用String类提供的equals方法。

equals方法的使用:
比较 str 和"Hello"两个字符串是否相等

String str = new String("Hello");
// 方式一
System.out.println(str.equals("Hello"));
// 方式二
System.out.println("Hello".equals(str));
//运行结果:
true
true

在上面代码中,我们一般推荐方式二,因为一旦 str 为 null ,方式一的代码会抛出 NullPointerException 异常。

扫描二维码关注公众号,回复: 12426992 查看本文章

3.字符串常量池

在上面的例子中,String 类的两种实例化操作,直接赋值和 new 一个新的String。

a)直接赋值

String str1 = "hello" ;
String str2 = "hello" ; 
String str3 = "hello" ; 
System.out.println(str1 == str2); // true
System.out.println(str2 == str3); // true
System.out.println(str1 == str3); // true

内存布局如下:
在这里插入图片描述
可以看到,此时是并没有开辟新的堆内存空间的。这是因为String的设计使用了共享设计模式。

在JVM底层实际上会自动维护一个对象池(字符串常量池)
  1. 如果现在采用了直接赋值的模式进行String类的对象实例化操作,那么该实例化对象(字符串内容)将自动保存到这个对象池之中。
  2. 如果下次继续使用直接赋值的模式声明String类对象,此时对象池之中如若有指定内容,将直接进行引用。
  3. 如若没有,则开辟新的字符串对象而后将其保存在对象池之中以供下次使用。
    b)采用构造方法:
String str = new String("hello") ;

内存布局如图:
在这里插入图片描述
可以看到:

  1. 如果使用String构造方法就会开辟两块堆内存空间,并且其中一块堆内存将成为垃圾空间(字符串常量 “hello” 也是一个匿名对象, 用了一次之后就不再使用了, 就成为垃圾空间, 会被 JVM 自动回收掉).
  2. 字符串共享问题. 同一个字符串可能会被存储多次,比较浪费空间。
    因此我们可以使用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

内存布局如下:
在这里插入图片描述
这样就会避免字符串多次存放,对于同一字符串,手动如池以供下次继续使用,因此我们一般会采用直接赋值的方式创建String对象。

4.字符串不可变

字符串是一种不可变对象,它的内容不可改变。String 类的内部实现也是基于 char[] 来实现的, 但是 String 类并没有提供 set 方法之类的来修改内部的字符数组。

代码示例:
String str = "hello" ; 
str = str + " world" ; 
str += "!!!" ; 
System.out.println(str); 
// 执行结果
hello world!!!

形如+=这样的操作,表面上好像是修改了字符串,其实不是,还是在修改的引用的指向,内存布局如下:

在这里插入图片描述
可以看到虽然打印结果变了,但是没有对字符串进行修改,只是将str的指向修改了,也就是将 str 引用中存放的地址更改了。

修改字符串
a)常见方法:借助原字符串,创建新字符串。
String str = "Hello";
str = "h" + str.substring(1); //substring()字符串切割
System.out.println(str);
// 执行结果
hello
b)特殊办法(了解):

使用 “反射” 这样的操作可以破坏封装, 访问一个类内部的 private 成员。
代码示例:

String str = "hello";
System.out.println(str);
//获取Class对象
Class cla = String.class;
//获取String类中的value字段
Field field= cla.getDeclaredField("value");
//将这个字段的访问属性设为true
field.setAccessible(true);
char[] value = (char[])field.get(str);
//修改value中的值
value[0] = 'H';
System.out.println(str);

//运行结果:
hello
Hello

不可变对象的好处:

  1. 方便实现字符串对象池. 如果 String 可变, 那么对象池就需要考虑何时深拷贝字符串的问题了。
  2. 不可变对象是线程安全的。
  3. 不可变对象更方便缓存 hash code,作为key时可以更高效的保存到 HashMap 中。

5.String类常用方法

5.1字符与字符串
(1) public String(char value[ ]),将字符数组中的所有内容变为字符串。

代码示例:

char[] value = {
    
    'a','b','c','d'};
String str = new String(value);
System.out.println(str);
//运行结果:
abcd
(2)public String(char value[ ],int offset,int count),将部分字符数组的内容变为字符串。

代码示例:

char[] value = {
    
    'a','b','c','d'};
String str = new String(value,0,3);//0下标是偏移位置,3是要偏移的数量
System.out.println(str);
//运行结果:
abc
(3)public char charAt(int index),取得指定索引位置的字符,索引从0开始。

代码示例:

String str = "hello";
System.out.println(str.charAt(1)); //这里注意下标不能越界,否则会抛出异常
//运行结果:
e
(4) public char[ ] toCharArray( ),将字符串变为字符数组返回。

代码示例:

String str = "hello";
char[] value = str.toCharArray();
for (int i = 0; i < value.length; i++) {
    
    
		System.out.print(value[i]+" ");
}
//运行结果:
h e l l o

char[ ] 是吧 String 按照一个字符一个字符的方式处理, 更适合针对文本数据来操作, 尤其是包含中文的时候。

5.2字节与字符串

字节常用于数据传输以及编码转换的处理之中,String 也能方便的和 byte[ ] 相互转换。

(1) public String(byte bytes[ ]),将字节数组变为字符串。

代码示例:

byte[] bt = {
    
    65,97,90,100,102,48};
String str = new String(bt);
System.out.println(str);
//运行结果:
AaZdf0
(2) public String(byte bytes[ ] , int offset , int length),将部分字节数组的内容变为字符串。

代码示例:

byte[] bt = {
    
    65,97,90,100,102,48};
String str = new String(bt,13);
System.out.println(str);
//运行结果:
aZd
(3) public byte[ ] getBytes( ),将字符串以字节数组的形式返回。

代码示例:

String str = "hello";
byte[] bt = str.getBytes();
for (int i = 0; i < bt.length; i++) {
    
    
		System.out.print(bt[i]+" ");
}
//运行结果:
104 101 108 108 111 
(4)public byte[ ] getBytes(String charsetName) throws UnsupportedEncodingException,编码转换处理

代码示例:

String str = "你好";
byte[] bt1 = str.getBytes("GBK"); //参数为字符集编码方式
byte[] bt2 = str.getBytes("utf-8");
for (int i = 0; i < bt1.length; i++) {
    
    
	System.out.print(bt1[i]+" ");
}
System.out.println();
for(int i = 0;i<bt2.length;i++) {
    
    
	System.out.print(bt2[i]+" ");
}
//运行结果:
-60 -29 -70 -61 
-28 -67 -96 -27 -91 -67 

byte[ ] 是把 String 按照一个字节一个字节的方式处理, 这种适合在网络传输, 数据存储这样的场景下使用。 更适合针对二进制数据来操作。

5.3字符串比较

上面使用过String类提供的equals()方法,该方法本身是可以进行区分大小写的相等判断。除了这个方法之外,String类还提供有如下的比较操作:

(1)public boolean equals IgnoreCase ( String anotherString ),不区分大小写进行比较。

代码示例:

String str1 = "hello";
String str2 = "HEllo";
System.out.println(str1.equalsIgnoreCase(str2));
//运行结果:
true
(2)public int compareTo(String anotherString),比较两个字符串大小关系。

代码示例:

//返回的整型
相等:返回0
小于:返回内容小于0 
大于:返回内容大于0
String str1 = "hello";
String str2 = "HEllo";
System.out.println(str1.compareTo(str2));
//运行结果:
32 //返回的是第一次不相等的两个字符之间的unicode码的差值

字符串的比较大小规则:总结成三个字 “字典序” 相当于判定两个字符串在一本词典的前面还是后面. 先比较第一个字符的大小(根据 unicode 的值来判定), 如果不分胜负,就依次比较后面的内容。

5.4字符串查找
(1) public boolean contains(CharSequence s),判断一个子字符串是否存在。

代码示例:

String str = "abcdef";
System.out.println(str.contains("ab"));
//运行结果:
true
(2) public int indexOf(String str),从头开始查找指定字符串的位置,查到了返回位置的开始索引,如果查不到返回-1。

代码示例:

String str = "abcdef";
System.out.println(str.indexOf("bc"));
//运行结果:
1
(3) public int indexOf(String str , int fromIndex),从指定位置查找子字符串的位置,查到了返回位置的索引,如果查不到返回-1。

代码示例:

String str = "abcdef";
System.out.println(str.indexOf("cd",1));
//运行结果:
2
(4) public int lastIndexOf(String str),由后向前查找子字符串的位置,查到了返回位置的索引,如果查不到返回-1。

代码示例:

String str = "abcdef";
System.out.println(str.indexOf("cd"));
//运行结果:
2
(5) public int lastIndexOf(String str , int fromIndex),从指定位置由后向前查找子字符串的位置,查到了返回位置的索引,如果查不到返回-1。

代码示例:

String str = "abcdef";
//从字符c位置开始向前查找
System.out.println(str.indexOf("cd",2));
//运行结果:
-1
(6) public boolean startWith(String prefix),判断是否以指定字符串开头。

代码示例:

String str = "abcdef";
System.out.println(str.startsWith("a"));
System.out.println(str.startsWith("abc"));
//运行结果:
true
true
(7) public boolean startWith(String prefix , int toffset),从指定位置开始判断是否以指定字符串开头。

代码示例:

String str = "abcdef";
System.out.println(str.startsWith("cd",3)); //从 d 位置开始
System.out.println(str.startsWith("cd",2)); //从 c 位置开始
false
true
(8) public boolean endWith(String prefix),判断是否以指定字符串结尾。

代码示例:

String str = "abcdef";
System.out.println(str.endsWith("f"));
System.out.println(str.endsWith("ef"));
//运行结果:
true
true
注意:使用indexOf()需要注意的是,如果内容重复,只能返回查找的第一个位置。
5.5字符串替换

使用一个指定的新的字符串替换掉已有的字符串数据,可用的方法如下:

(1) public String replaceFirst (String regex , String replacement) ,只替换首个内容

代码示例:

String str = "abcdefab";
//替换字符串中第一个"ab"
String ret = str.replaceFirst("ab","cd");
System.out.println(ret);
//运行结果:
cdcdefab
(2) public String replaceAll (String regex , String replacement) ,替换所有的指定内容。

代码示例:

String str = "abcdefab";
//替换字符串将所有的"ab"替换为"cd"
String ret = str.replaceAll("ab","cd");
System.out.println(ret);
//运行结果:
cdcdefcd
注意:由于字符串是不可变对象, 替换不修改当前字符串, 而是产生一个新的字符串。
5.6 字符串拆分

可以将一个完整的字符串按照指定的分隔符划分为若干个子字符串。
可用方法如下:

(1) public String [ ] split(String regex),将字符串全部拆分。

代码示例:

String str1 = "192-168-135-1";
String str2 = "hello world !";
String str3 = "192.168.135.1";
String str4 = "java 30-bit#stu-40";
//将字符串按照 - 拆分
String[] string1 = str1.split("-");
System.out.println(Arrays.toString(string1));
//特殊字符 需要转义  ( | , * + . \) 都要加转义
String[] string2 = str3.split("\\.");
System.out.println(Arrays.toString(string2));
//将字符串按照空格拆分
String[] string3 = str2.split(" ");
System.out.println(Arrays.toString(string3));
//多个字符分割  注意前面空格不能少
String[] string4 = str4.split(" |-|#");
System.out.println(Arrays.toString(string4));
//多次拆分
String[] string5 = str4.split("#");
for (String string : string5) {
    
    
	String[] string6 = string.split("-");
	for (String string7 : string6) {
    
    
		System.out.println(string7);
	}
}
//运行结果:
[192, 168, 135, 1]
[192, 168, 135, 1]
[hello, world, !]
[java, 30, bit, stu, 40]
java 30
bit
stu
40
(2) public String [ ] split(String regex , int limit),将字符串部分拆分,该数组长度就是limit极限。

代码示例:

String str = "hello world !";
String[] string1 = str.split(" ",2);
String[] string2 = str.split(" ",3);
System.out.println(Arrays.toString(string1));
System.out.println(Arrays.toString(string2));
//运行结果:
[hello, world !]
[hello, world, !]
注意:
  1. 字符"|","*","+“都得加上转义字符,前面加上”"。
  2. 而如果是"",那么就得写成"\"。
  3. 如果一个字符串中有多个分隔符,可以用"|"作为连字符。
5.7 字符串截取

从一个完整的字符串之中截取出部分内容。可用方法如下:

(1) public String subString(int beginIndex),从指定索引位置截取到结尾。

代码示例:

String str1 = "hello world";
System.out.println(str1.substring(2)); //截取该下标之后的所有字符串
//运行结果:
llo world
(2) public String subString(int beginIndex , int endIndex),截取部分内容。

代码示例:

String str1 = "hello world";
System.out.println(str1.substring(2, 6));  //左开右闭
//运行结果:
llo
注意:
  1. 索引从0开始。
  2. 注意前闭后开区间的写法, substring(2, 6) 表示包含 2 号下标的字符, 不包含 6号下标。
5.8 其他操作方法
(1) public String trim(),去掉字符串中的左右空格,保留中间空格。

代码示例:

String str = " hello world  ";
//去掉字符串的左右空格
System.out.println(str.trim());
//运行结果:
hello world
(2) public String toUpperCase(),字符串转大写。

代码示例:

String str1 = "Hello World";
//将字符串转换为大写
System.out.println(str1.toUpperCase());
//运行结果:
HELLO WORLD
(3) public String toLowerCase(),字符串转小写。

代码示例:

String str = "HELLo";
System.out.println(str.toLowerCase());
//运行结果:
hello
(4) public native String intern(),字符串入池操作。

代码示例:

String str = new String("hello").intern();
(5) public String concat(String str),字符串连接,相当于"+",不入池。

代码示例:

String str1 = "Hello World";
String str2 = "HELLo";
//字符串拼接,等同于 +
System.out.println(str1.concat(str2));
Hello WorldHELLo
(6) public int length(),取得字符串长度。

代码示例:

String str = "Hello World";
System.out.println(str.length());
//运行结果:
11
(7) public boolean isEmpty(),判断是否为空字符串,但不是null,而是长度为0。

代码示例:

//判断字符串是否为空
String str1 = ""; //空字符串
System.out.println(str1.isEmpty());
//运行结果:
true
String str2 = null; //空引用
System.out.println(str2.isEmpty()); //会抛出异常
//Exception in thread "main" java.lang.NullPointerException
	at practice.Demo02.main(Demo02.java:25)
注意:
  1. trim 会去掉字符串开头和结尾的空白字符(空格,换行,制表符等)。
  2. toUpperCase()方法和 toLowerCase()方法 只转换字母。
  3. 数组长度使用数组名称.length属性,而String中使用的是length()方法。
延申:首字母大写(String类未提供,需自己实现)

代码示例:

public static String firstUpper(String str) {
    
    
	if("".equals(str)||str==null) {
    
    
		return str;
	}
	if(str.length()>1) {
    
    
	//将首字母截取下来转成大写在和截取的剩余字符串拼接
		str = str.substring(0, 1).toUpperCase()+str.substring(1);
	}
	return str;		
}
public static void main(String[] args)  {
    
    
	String str = "hello";
	System.out.println(firstUpper(str));
}
//运行结果:
Hello

6.小结

需要注意的点:

  1. 字符串的创建
  2. 字符串的比较, ==, equals, compareTo 之间的区别。
  3. 了解字符串常量池, 体会 “池” 的思想。
  4. 理解字符串不可变。
  5. byte[ ] 和 char [ ] 的应用场景。
  6. indexOf 和 lastIndexOf 的应用场景。
  7. split 的应用场景,分隔符的转义操作,多次拆分。

猜你喜欢

转载自blog.csdn.net/weixin_46078890/article/details/110250071