前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Java的String类

Java的String类

作者头像
鲜于言悠
发布2024-06-04 08:04:44
770
发布2024-06-04 08:04:44
举报
文章被收录于专栏:c/c++的学习笔记

前言

推荐一个网站给想要了解或者学习人工智能知识的读者,这个网站里内容讲解通俗易懂且风趣幽默,对我帮助很大。我想与大家分享这个宝藏网站,请点击下方链接查看。 https://www.captainbed.cn/f1

Java的String类用于处理文本数据,提供了丰富的字符串操作方法和功能,包括字符串连接、比较、查找、截取、替换等,是不可变类,每次修改都会生成新的String对象。

一、创建字符串

常见的构造 String 的方式

代码语言:javascript
复制
// 方式一
String str = "Hello Bit";
// 方式二
String str2 = new String("Hello Bit");
// 方式三
char[] array = {'a', 'b', 'c'};
String str3 = new String(array);

官方文档上 (https://docs.oracle.com/javase/8/docs/api/index.html) 我们可以看到 String 还支持很多其他的构造方式, 我们用到的时候去查就可以了.

注意事项

  • hello” 这样的字符串字面值常量, 类型也是 String.
  • String 也是引用类型. String str = "Hello"; 这样的代码内存布局如下

引用

引用类似于 C 语言中的指针, 只是在栈上开辟了一小块内存空间保存一个地址. 但是引用和指针又不太相同, 指针能进行各种数字运算(指针+1)之类的, 但是引用不能, 这是一种 “没那么灵活” 的指针.

另外, 也可以把引用想象成一个标签, “贴” 到一个对象上. 一个对象可以贴一个标签, 也可以贴多个. 如果一个对象上面一个标签都没有, 那么这个对象就会被 JVM 当做垃圾对象回收掉.

Java 中数组, String, 以及自定义的类都是引用类型.

由于 String 是引用类型, 因此对于以下代码

代码语言:javascript
复制
String str1 = "Hello";
String str2 = str1;

内存布局如图

那么有读者可能会说, 是不是修改 str1 , str2 也会随之变化呢?

代码语言:javascript
复制
str1 = "world";
System.out.println(str2);
// 执行结果
Hello

我们发现, “修改” str1 之后, str2 也没发生变化, 还是 hello?

事实上, str1 = "world" 这样的代码并不算 “修改” 字符串, 而是让 str1 这个引用指向了一个新的 String 对象.

二、字符串比较相等

如果现在有两个int型变量,判断其相等可以使用 == 完成。

代码语言:javascript
复制
int x = 10 ;
int y = 10 ;
System.out.println(x == y); 
// 执行结果
true

如果说现在在String类对象上使用 ==

代码示例

示例一
代码语言:javascript
复制
String str1 = "Hello";
String str2 = "Hello"; 
System.out.println(str1 == str2); 
// 执行结果
true 
示例二

看起来貌似没啥问题, 再换个代码试试, 发现情况不太妙.

代码语言:javascript
复制
String str1 = new String("Hello");
String str2 = new String("Hello");
System.out.println(str1 == str2);
// 执行结果
false

我们来分析两种创建 String 方式的差异.

示例一的内存布局

我们发现, str1str2 是指向同一个对象的. 此时如 “Hello” 这样的字符串常量是在 字符串常量池 中

关于字符串常量池

如 “Hello” 这样的字符串字面值常量, 也是需要一定的内存空间来存储的. 这样的常量具有一个特点, 就是不需要修改(常量嘛). 所以如果代码中有多个地方引用都需要使用 “Hello” 的话, 就直接引用到常量池的这个位置就行了, 而没必要把 “Hello” 在内存中存储两次

示例二的内存布局

通过 String str1 = new String("Hello"); 这样的方式创建的 String 对象相当于再堆上另外开辟了空间来存储"Hello" 的内容, 也就是内存中存在两份 “Hello”.

总结

String 使用 == 比较并不是在比较字符串内容, 而是比较两个引用是否是指向同一个对象

关于对象的比较

面向对象编程语言中, 涉及到对象的比较, 有三种不同的方式, 比较身份, 比较值, 比较类型.

在大部分编程语言中 == 是用来比较比较值的.

但是 Java 中的 == 是用来比较身份的.

如何理解比较值和比较身份呢

可以想象一个场景, 现在取快递, 都有包裹储物柜. 上面有很多的格子. 每个格子里面都放着东西.

例如, “第二行, 左数第五列” 这个柜子和 “第二行, 右数第二列” 这个柜子是同一个柜子, 就是 身份相同. 如果身份相同, 那么里面放的东西一定也相同 (值一定也相同).

例如, “第一行, 左数第一列” 这个柜子和 “第一行, 左数第二列” 这两个柜子不是同一个柜子, 但是柜子打开后发现里面放着的是完全一模一样的两双鞋子. 这个时候就是 值相同.

equals

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

代码语言:javascript
复制
String str1 = new String("Hello");
String str2 = new String("Hello");
System.out.println(str1.equals(str2));
// System.out.println(str2.equals(str1)); // 或者这样写也行
// 执行结果
true
equals 使用注意事项

现在需要比较 str 和 “Hello” 两个字符串是否相等, 我们该如何来写呢?

代码语言:javascript
复制
String str = new String("Hello");
// 方式一
System.out.println(str.equals("Hello"));
// 方式二
System.out.println("Hello".equals(str));

在上面的代码中, 哪种方式更好呢?

我们更推荐使用 “方式二”. 一旦 strnull, 方式一的代码会抛出异常, 而方式二不会.

代码语言:javascript
复制
String str = null;
// 方式一
System.out.println(str.equals("Hello"));  // 执行结果 抛出 java.lang.NullPointerException 异
常
// 方式二
System.out.println("Hello".equals(str));  // 执行结果 false

注意事项: “Hello” 这样的字面值常量, 本质上也是一个 String 对象, 完全可以使用 equals 等 String 对象的方法.

三、字符串常量池

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

直接赋值

代码语言:javascript
复制
String str1 = "hello" ;
String str2 = "hello" ; 
String str3 = "hello" ; 
System.out.println(str1 == str2); // true
System.out.println(str1 == str3); // true
System.out.println(str2 == str3); // true
为什么现在并没有开辟新的堆内存空间呢

String类的设计使用了共享设计模式

在JVM底层实际上会自动维护一个对象池(字符串常量池)

  • 如果现在采用了直接赋值的模式进行String类的对象实例化操作,那么该实例化对象(字符串内容)将自动保存到这个对象池之中.
  • 如果下次继续使用直接赋值的模式声明String类对象,此时对象池之中如若有指定内容,将直接进行引用
  • 如若没有,则开辟新的字符串对象而后将其保存在对象池之中以供下次使用
理解 “池”

“池” 是编程中的一种常见的, 重要的提升效率的方式, 我们会在未来的学习中遇到各种 “内存池”, “线程池”, “数据库连接池” …

采用构造方法

类对象使用构造方法实例化是标准做法。分析如下程序:

代码语言:javascript
复制
String str = new String("hello") ;

这样的做法有两个缺点:

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

我们可以使用 String 的 intern 方法来手动把 String 对象加入到字符串常量池中

代码语言:javascript
复制
// 该字符串常量并没有保存在对象池之中
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

总结

  • 直接赋值:只会开辟一块堆内存空间,并且该字符串对象可以自动保存在对象池中以供下次使用。
  • 构造方法:会开辟两块堆内存空间,不会自动保存在对象池中,可以使用intern()方法手工入池。

四、字符串不可变

字符串是一种不可变对象. 它的内容不可改变.

String 类的内部实现也是基于 char[] 来实现的, 但是 String 类并没有提供 set 方法之类的来修改内部的字符数组.

感受下形如这样的代码:

代码语言:javascript
复制
String str = "hello" ; 
str = str + " world" ; 
str += "!!!" ; 
System.out.println(str); 
// 执行结果
hello world!!!

形如 += 这样的操作, 表面上好像是修改了字符串, 其实不是. 内存变化如下:

+= 之后 str 打印的结果却是变了, 但是不是 String 对象本身发生改变, 而是 str 引用到了其他的对象.

那么如果实在需要修改字符串, 例如, 现有字符串 str = "Hello" , 想改成 str = "hello" , 该怎么办?

修改字符串

常见办法

借助原字符串, 创建新的字符串

代码语言:javascript
复制
String str = "Hello";
str = "h" + str.substring(1);
System.out.println(str);
// 执行结果
hello
特殊办法

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

IDEA 中 ctrl + 左键 跳转到 String 类的定义, 可以看到内部包含了一个 char[] , 保存了字符串的内容.

代码语言:javascript
复制
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

关于反射

反射是面向对象编程的一种重要特性, 有些编程语言也称为 “自省”.

指的是程序运行过程中, 获取/修改某个对象的详细信息(类型信息, 属性信息等), 相当于让一个对象更好的 “认清自己” .

Java 中使用反射比较麻烦一些. 我们后面的课程中会详细介绍反射的具体用法.

为什么 String 要不可变

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

注意事项: 如下代码不应该在你的开发中出, 会产生大量的临时对象, 效率比较低.

代码语言:javascript
复制
String str = "hello" ; 
for(int x = 0; x < 1000; x++) {
    str += x ; 
}
System.out.println(str);

五、字符、字节与字符串

字符与字符串

字符串内部包含一个字符数组,String 可以和 char[] 相互转换.

代码示例
获取指定位置的字符
代码语言:javascript
复制
String str = "hello" ; 
System.out.println(str.charAt(0)); // 下标从 0 开始 
 
// 执行结果 
h 
 
System.out.println(str.charAt(10)); 
 
// 执行结果 
产生 StringIndexOutOfBoundsException 异常 
字符串与字符数组的转换
代码语言:javascript
复制
String str = "helloworld" ; 
 
// 将字符串变为字符数组 
char[] data = str.toCharArray() ; 
for (int i = 0; i < data.length; i++) { 
 System.out.print(data[i]+" "); 
} 
 
// 字符数组转为字符串 
System.out.println(new String(data)); // 全部转换 
System.out.println(new String(data,5,5)); // 部分转换 
给定字符串一个字符串, 判断其是否全部由数字所组成.

思路: 将字符串变为字符数组而后判断每一位字符是否是" 0 “~”‘9’"之间的内容,如果是则为数字.

代码语言:javascript
复制
public static void main(String[] args) { 
 String str = "1a23456" ; 
 System.out.println(isNumber(str)? "字符串由数字所组成!" : "字符串中有非数字成员!"); 
 } 
 
 public static boolean isNumber(String str) { 
 char[] data = str.toCharArray() ; 
 for (int i = 0; i < data.length; i++) { 
 if (data[i]<'0' || data[i]>'9') { 
 return false ; 
 } 
 } 
 return true ; 
 } 

字节与字符串

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

代码示例
实现字符串与字节数组的转换处理
代码语言:javascript
复制
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)); 

总结

那么何时使用 byte[], 何时使用 char[] 呢?

  • byte[] 是把 String 按照一个字节一个字节的方式处理, 这种适合在网络传输, 数据存储这样的场景下使用. 更适合针对二进制数据来操作.
  • char[] 是吧 String 按照一个字符一个字符的方式处理, 更适合针对文本数据来操作, 尤其是包含中文的时候.

六、字符串常见操作

字符串比较

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

代码示例
不区分大小写比较
代码语言:javascript
复制
String str1 = "hello" ; 
String str2 = "Hello" ; 
System.out.println(str1.equals(str2)); // false 
System.out.println(str1.equalsIgnoreCase(str2)); // true 

在String类中compareTo()方法是一个非常重要的方法,该方法返回一个整型,该数据会根据大小关系返回三类内容:

  • 相等:返回0.
  • 小于:返回内容小于0.
  • 大于:返回内容大于0。
观察compareTo()比较
代码语言:javascript
复制
System.out.println("A".compareTo("a")); // -32 
System.out.println("a".compareTo("A")); // 32 
System.out.println("A".compareTo("A")); // 0 
System.out.println("AB".compareTo("AC")); // -1 
System.out.println("刘".compareTo("杨")); 

compareTo()是一个可以区分大小关系的方法,是String方法里是一个非常重要的方法。

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

字符串查找

从一个完整的字符串之中可以判断指定内容是否存在,对于查找方法有如下定义:

代码示例
字符串查找,最好用最方便的就是contains()
代码语言:javascript
复制
String str = "helloworld" ; 
System.out.println(str.contains("world")); // true 

该判断形式是从JDK1.5之后开始追加的,在JDK1.5以前要想实现与之类似的功能,就必须借助、indexOf()方法完成。

使用indexOf()方法进行位置查找
代码语言:javascript
复制
String str = "helloworld" ; 
System.out.println(str.indexOf("world")); // 5,w开始的索引 
System.out.println(str.indexOf("bit")); // -1,没有查到 
if (str.indexOf("hello") != -1) { 
 System.out.println("可以查到指定字符串!"); 
} 

现在基本都是用contains()方法完成。

使用indexOf()需要注意的是,如果内容重复,它只能返回查找的第一个位置

使用indexOf()的注意点
代码语言:javascript
复制
String str = "helloworld" ; 
System.out.println(str.indexOf("l")); // 2 
System.out.println(str.indexOf("l",5)); // 8 
System.out.println(str.lastIndexOf("l")); // 8 

在进行查找的时候往往会判断开头或结尾。

判断开头或结尾
代码语言:javascript
复制
String str = "**@@helloworld!!" ; 
System.out.println(str.startsWith("**")); // true 
System.out.println(str.startsWith("@@",2)); // ture 
System.out.println(str.endsWith("!!")); // true 

字符串替换

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

代码示例
字符串的替换处理
代码语言:javascript
复制
String str = "helloworld" ; 
System.out.println(str.replaceAll("l", "_")); 
System.out.println(str.replaceFirst("l", "_"));

注意事项: 由于字符串是不可变对象, 替换不修改当前字符串, 而是产生一个新的字符串.

字符串拆分

可以将一个完整的字符串按照指定的分隔符划分为若干个子字符串。

代码示例
实现字符串的拆分处理
代码语言:javascript
复制
String str = "hello world hello bit" ; 
String[] result = str.split(" ") ; // 按照空格拆分 
for(String s: result) { 
 System.out.println(s); 
} 
字符串的部分拆分
代码语言:javascript
复制
String str = "hello world hello bit" ; 
String[] result = str.split(" ",2) ; 
for(String s: result) { 
 System.out.println(s); 
}

拆分是特别常用的操作. 一定要重点掌握. 另外有些特殊字符作为分割符可能无法正确切分, 需要加上转义.

拆分IP地址
代码语言:javascript
复制
String str = "192.168.1.1" ; 
String[] result = str.split("\\.") ; 
for(String s: result) { 
 System.out.println(s); 
} 

注意事项:

  1. 字符"|,*“,”+“都得加上转义字符,前面加上”\".
  2. 而如果是 ‘’ '' ‘’,那么就得写成"\\".
  3. 如果一个字符串中有多个分隔符,可以用"|"作为连字符.
多次拆分
代码语言:javascript
复制
String str = "name=zhangsan&age=18" ; 
String[] result = str.split("&") ; 
for (int i = 0; i < result.length; i++) { 
 String[] temp = result[i].split("=") ; 
 System.out.println(temp[0]+" = "+temp[1]); 
} 

字符串截取

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

代码示例
观察字符串截取
代码语言:javascript
复制
String str = "helloworld" ; 
System.out.println(str.substring(5)); 
System.out.println(str.substring(0, 5)); 

注意事项:

  1. 索引从0开始
  2. 注意前闭后开区间的写法, substring(0, 5) 表示包含 0 号下标的字符, 不包含 5 号下标

其他

代码示例
观察trim()方法的使用
代码语言:javascript
复制
String str = " hello world " ; 
System.out.println("["+str+"]"); 
System.out.println("["+str.trim()+"]");

trim 会去掉字符串开头和结尾的空白字符(空格, 换行, 制表符等).

大小写转换
代码语言:javascript
复制
String str = " hello%$$%@#$%world 哈哈哈 " ; 
System.out.println(str.toUpperCase()); 
System.out.println(str.toLowerCase()); 

这两个函数只转换字母。

字符串length()
代码语言:javascript
复制
String str = " hello%$$%@#$%world 哈哈哈 " ; 
System.out.println(str.length()); 

注意:数组长度使用数组名称.length属性,而String中使用的是length()方法

观察isEmpty()方法
代码语言:javascript
复制
System.out.println("hello".isEmpty()); 
System.out.println("".isEmpty()); 
System.out.println(new String().isEmpty()); 

String类并没有提供首字母大写操作,需要自己实现.

首字母大写
代码语言:javascript
复制
public static void main(String[] args) { 
 System.out.println(fistUpper("yuisama")); 
 System.out.println(fistUpper("")); 
 System.out.println(fistUpper("a")); 
 } 
 public static String fistUpper(String str) { 
 if ("".equals(str)||str==null) { 
 return str ; 
 } 
 if (str.length()>1) { 
 return str.substring(0, 1).toUpperCase()+str.substring(1) ; 
 } 
 return str.toUpperCase() ; 
 }

七、StringBuffer 和 StringBuilder

任何的字符串常量都是String对象,而且String的常量一旦声明不可改变,如果改变对象内容,改变的是其引用的指向而已。

通常来讲String的操作比较简单,但是由于String的不可更改特性,为了方便字符串的修改,提供StringBufferStringBuilder类。

StringBufferStringBuilder 大部分功能是相同的,主要介绍 StringBuffer 在String中使用"+"来进行字符串连接,但是这个操作在StringBuffer类中需要更改为append()方法:

代码语言:javascript
复制
public synchronized StringBuffer append(各种数据类型 b) 

示例

观察StringBuffer使用
代码语言:javascript
复制
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"); 
 } 
} 

StringStringBuffer最大的区别在于:String的内容无法修改,而StringBuffer的内容可以修改。频繁修改字符串的情况考虑使用StingBuffer

为了更好理解StringStringBuffer,我们来看这两个类的继承结构:

String类

StringBuffer类

public final class String implements java.io.Serializable,Comparable,CharSequence

public final class StringBuffer extendsAbstractStringBuilder implements java.io.Serializable,CharSequence

可以发现两个类都是"CharSequence"接口的子类。这个接口描述的是一系列的字符集。所以字符串是字符集的子类,如果以后看见CharSequence,最简单的联想就是字符串。

注意:String和StringBuffer类不能直接转换。如果要想互相转换,可以采用如下原则:

  • String变为StringBuffer:利用StringBuffer的构造方法或append()方法
  • StringBuffer变为String:调用toString()方法。

除了append()方法外,StringBuffer也有一些String类没有的方法:

  • 字符串反转:
代码语言:javascript
复制
public synchronized StringBuffer reverse() 

示例

字符串反转
代码语言:javascript
复制
StringBuffer sb = new StringBuffer("helloworld"); 
System.out.println(sb.reverse()); 
删除指定范围的数据
代码语言:javascript
复制
public synchronized StringBuffer delete(int start, int end) 
观察删除操作
代码语言:javascript
复制
StringBuffer sb = new StringBuffer("helloworld"); 
System.out.println(sb.delete(5, 10)); 
插入数据
代码语言:javascript
复制
public synchronized StringBuffer insert(int offset, 各种数据类型 b) 
观察插入操作
代码语言:javascript
复制
StringBuffer sb = new StringBuffer("helloworld"); 
System.out.println(sb.delete(5, 10).insert(0, "你好")); 
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2024-06-02,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 一、创建字符串
    • 常见的构造 String 的方式
      • 注意事项
        • 引用
        • 二、字符串比较相等
          • 代码示例
            • 示例一
            • 示例二
            • 示例一的内存布局
            • 示例二的内存布局
            • 总结
          • 关于对象的比较
            • 如何理解比较值和比较身份呢
          • equals
            • equals 使用注意事项
        • 三、字符串常量池
          • 直接赋值
            • 为什么现在并没有开辟新的堆内存空间呢
            • 理解 “池”
          • 采用构造方法
            • intern
          • 总结
          • 四、字符串不可变
            • 修改字符串
              • 常见办法
              • 特殊办法
            • 关于反射
              • 为什么 String 要不可变
              • 五、字符、字节与字符串
                • 字符与字符串
                  • 代码示例
                • 字节与字符串
                  • 代码示例
                • 总结
                • 六、字符串常见操作
                  • 字符串比较
                    • 代码示例
                  • 字符串查找
                    • 代码示例
                  • 字符串替换
                    • 代码示例
                  • 字符串拆分
                    • 代码示例
                  • 字符串截取
                    • 代码示例
                  • 其他
                    • 代码示例
                • 七、StringBuffer 和 StringBuilder
                  • 示例
                    • 观察StringBuffer使用
                  • 示例
                    • 字符串反转
                    • 删除指定范围的数据
                    • 观察删除操作
                    • 插入数据
                    • 观察插入操作
                相关产品与服务
                数据保险箱
                数据保险箱(Cloud Data Coffer Service,CDCS)为您提供更高安全系数的企业核心数据存储服务。您可以通过自定义过期天数的方法删除数据,避免误删带来的损害,还可以将数据跨地域存储,防止一些不可抗因素导致的数据丢失。数据保险箱支持通过控制台、API 等多样化方式快速简单接入,实现海量数据的存储管理。您可以使用数据保险箱对文件数据进行上传、下载,最终实现数据的安全存储和提取。
                领券
                问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档