认识String

1. 创建字符串

常见的构造 String 的方式

// 方式一
String str = "abcd";
// 方式二
String str2 = new String("abcd");
// 方式三
char[] array = {'a', 'b', 'c'};
String str3 = new String(array);

注意事项:

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

在这里插入图片描述
由于 String 是引用类型, 因此对于以下代码
内存布局如图

String str1 = "Hello";
String str2 = str1;

在这里插入图片描述
但是虽然str1和str2指向的都是"hello",但是“修改”str2并不会影响str1

str2 = "world";
System.out.println(str2);
// 执行结果
Hello

引用类似于c语言的指针,只是在栈上开辟了一小块内存空间保存一个地址。
事实上, str1 = “world” 这样的代码并不算 “修改” 字符串, 而是让 str1 这个引用指向了一个新的 String 对象。
在这里插入图片描述

2. 字符串比较相等

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

int x = 10 ;
int y = 10 ;
System.out.println(x == y);
// 执行结果
true

但是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

我们来分析两种创建 String 方式的差异。
代码1内存布局
在这里插入图片描述
我们发现, str1 和 str2 是指向同一个对象的。 此时如 “Hello” 这样的字符串常量是在 字符串常量池 中。

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

代码2内存布局
在这里插入图片描述
通过 String str1 = new String("Hello"); 这样的方式创建的 String 对象相当于在堆上另外开辟了空间value来存储"hello",所以str1和str2的内容是不一样的。
Java 中要想比较字符串的内容, 必须采用String类提供的equals方法。

equals 使用注意事项

String str = new String("Hello");
// 方式一
System.out.println(str.equals("Hello"));
// 方式二
System.out.println("Hello".equals(str));

两种方法都是可以的,但是我们更推荐方法二,

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

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

3. 字符串常量池

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

a) 直接赋值

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类对象,此时对象池之中如若有指定内容,将直接进行引用。
如若没有,则开辟新的字符串对象而后将其保存在对象池之中以供下次使用。

b) 采用构造方法

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

String str = new String("hello");

在这里插入图片描述
这样的做法有两个缺点:

  1. 如果使用String构造方法就会开辟两块堆内存空间。
  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类中两种对象实例化的区别

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

4. 理解字符串不可变

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

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

形如 += 这样的操作, 表面上好像是修改了字符串, 其实不是。 内存变化如下:
在这里插入图片描述
先把"hello"存进去,把存进去的引用给str,再把“world”存进去,拼接成“hello world”,把“hello world”的引用给str,再把“!!!”存进去,拼接之后的引用给str。
所以,+= 之后 str 打印的结果却是变了, 但是不是 String 对象本身发生改变, 而是 str 引用到了其他的对象。

回顾引用
引用相当于一个指针, 里面存的内容是一个地址。我们要区分清楚当前修改到底是修改了地址对应内存的内容发生改变了, 还是引用中存的地址改变了。

如果实在需要修改字符串
a) 常见办法: 借助原字符串, 创建新的字符串

String str = "Hello";
str = "h" + str.substring(1);
System.out.println(str);
// 执行结果
hello

b) 特殊办法(选学): 使用 “反射” 这样的操作可以破坏封装, 访问一个类内部的 private 成员。
IDEA 中 ctrl + 左键 跳转到 String 类的定义, 可以看到内部包含了一个 char[] , 保存了字符串的内容。
在这里插入图片描述

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

不可变对象的好处:

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

5. 字符, 字节与字符串

5.1 字符与字符串

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

No方法名称类型描述
1public String(char value[])构造将数组中的所有内容变为字符串
2public String(char value[],int offset,int count)构造将部分字符数组中的内容变为字符串
3public char charAt(int index)普通取得指定索引位置的字符,索引从0开始
4public char[] toCharArray()普通将字符串变为字符数组返回

5.2 字节与字符串

No方法名称类型描述
1public String(byte bytes[])构造将字节数组变为字符串
2public String(byte bytes[],int offset,int length)构造将部分字节数组中的内容变为字符串
3public byte[] getBytes()普通将字符串以字节数组的形式返回
4public byte[] getBytes(String charsetName)throw UnsupportedEncodingException普通编码转换处理

6. 字符串常见操作

6.1 字符串比较

No方法名称类型描述
1public boolean equals(Object anObject)普通区分大小写比较
2public boolean equalsIgnoreCase(String anotherString)普通不区分大小写的比较
3public int compareTo(String anotherString)普通比较两个字符串大小

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

  1. 相等:返回0.
  2. 小于:返回内容小于0.
  3. 大于:返回内容大于0。

6.2 字符串查找

No方法名称类型描述
1public boolean contains(CharSequence s)普通判断一个子字符串是否存在
2public int indexOf(String str)普通从头开始查找指定字符串的位置,查到了返回位置的开始索引,如果查不到返回-1
3public int indexOf(String stx,int fromIndex)普通从指定位置开始查找子字符串位置
4public int lastIndexOf(String str)普通由后向前查找子字符串位置
5public int lastIndexOf(String str,int fromIndex)普通从指定位置由后向前查找
6public boolean startsWith(String: prefix)普通判断是否以指定字符串开头
7public boolean startsWith(String prefix, int toffset)普通从指定位置开始判断是否以指定字符串开头
8public boolean endsWith(String suffix)普通判断是否以指定字符串结尾

6.3 字符串替换

No方法名称类型描述
1.public String replaceAll(String regex, String replacement)普通替换所有的指定内容
2.public String replaceFirst(String regex,String replacement)普通替换首个内容

6.4 字符串拆分

No方法名称类型描述
1public String[] split(String regex)普通将字符串全部拆分
2public String[] split(String regex, int limit)普通将字符串部分拆分,该数组长度就是 limit 极限

6.5 字符串截取

No方法名称类型描述
1public String substring(int beginIndex)普通从指定索引截取到结尾
2public String substring(int beginIndex, int endIndex)普通截取部分内容

6.6 其他操作方法

No方法名称类型描述
1public Stringtrim()普通去掉字符串中的左右空格,保留中间空格
2public String toUpperCase()普通字符串转大写
3public String toLowerCase()普通字符串转小写
4public native String intern()普通字符串入池操作
5public String concat(String str)普通字符串连接,等同于“+=”,不入池
6public int length()普通取得字符串长度
7public boolean isEmpty()普通判断是否为空字符串,但不是null,而是长度为0

7. StringBuffer 和 StringBuilder

首先来回顾下String类的特点:

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

通常来讲String的操作比较简单,但是由于String的不可更改特性,为了方便字符串的修改,提供StringBufferStringBuilder类。
StringBufferStringBuilder 大部分功能是相同的,主要介绍 StringBuffer

在String中使用"+"来进行字符串连接,但是这个操作在StringBuffer类中需要更改为append()方法:

public synchronized StringBuffer append(各种数据类型 b)

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

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

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

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

  • 字符串反转:
public synchronized StringBuffer reverse()
  • 删除指定范围的数据:
public synchronized StringBuffer delete(int start, int end)
  • 插入数据
public synchronized StringBuffer insert(int offset, 各种数据类型 b)

面试题:请解释String、StringBuffer、StringBuilder的区别:

  • String的内容不可修改,StringBuffer与StringBuilder的内容可以修改.
  • StringBuffer与StringBuilder大部分功能是相似的
  • StringBuffer采用同步处理,属于线程安全操作;而StringBuilder未采用同步处理,属于线程不安全操作
  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值