【JavaSE】《基础篇003》String类

本文详细介绍了Java中的String类,包括其不可变特性、内存模型及常用方法。讨论了String与StringBuffer、StringBuilder的区别,解释了intern()方法的工作原理,并分析了字符串拼接的效率问题。同时,还提到了String作为参数传递时的行为以及在面试中可能遇到的相关问题。
摘要由CSDN通过智能技术生成

String

1. 定义:不可变字符序列
在这里插入图片描述

  • String类被final关键字修饰,意味着String类不能被继承,并且它的成员方法都默认为final方法;
  • 字符串一旦创建就不能再修改
  • JDK 8 ,String实例的值是通过字符数组char[] value实现字符串存储的。
  • JDK 9 ,String 内部改用 byte[] value 来存储数据
  • 当给字符串重新赋值、连接concat、替换replace时,都需要重新制定内存区域,不能使用原有的value空间赋值。
  • 当使用字面量给一个字符串赋值(区别于new),此时的字符串值声明在字符串常量池中

2. 常用方法

判断方法

方法说明
boolean equals(Object obj)比较字符串内容是否相同,区分大小写
boolean equalsIgnoreCase(String str)比较字符串的内容是否相同,忽略大小写
boolean contains(String str)判断大字符串是否包含小字符串
boolean startsWith(String str)判断字符串是都以某个指定的字符串开头
boolean endsWith(String str)判断字符串是都以某个指定的字符串结尾
int compareTo(Object o)把这个字符串和另一个对象比较。
int compareTo(String anotherString)按字典顺序比较两个字符串。
int compareToIgnoreCase(String str)按字典顺序比较两个字符串,不考虑大小写。
boolean contentEquals(StringBuffer sb)当且仅当字符串与指定的StringBuffer有相同顺序的字符时候返回真。
boolean matches(String regex)告知此字符串是否匹配给定的正则表达式。
boolean regionMatches(boolean ignoreCase, int toffset, String other, int ooffset, int len)测试两个字符串区域是否相等。

获取

public int indexOf(int ch)获取指定字符在此字符串中第一次出现处的索引。
public int indexOf(int ch, int fromIndex)获取指定字符在此字符串中第一次出现处的索引。
public int indexOf(String str)返回指定字符串在此字符串中第一次出现处的索引
public int indexOf(String str, int fromIndex)返回指定字符串在此字符串中从指定位置后第一次出现处的索引
int lastIndexOf(int ch)返回指定字符在此字符串中最后一次出现处的索引。
int lastIndexOf(int ch, int fromIndex)返回指定字符在此字符串中最后一次出现处的索引,从指定的索引处开始进行反向搜索。
int lastIndexOf(String str)返回指定子字符串在此字符串中最右边出现处的索引。
int lastIndexOf(String str, int fromIndex)返回指定子字符串在此字符串中最后一次出现处的索引,从指定的索引开始反向搜索。
char charAt(int index)返回指定索引处的 char 值。
public String substring(int start)返回一个新的字符串,获取子串
public String substring(int start, int end)返回一个新的字符串,获取子串
CharSequence subSequence(int beginIndex, int endIndex)返回一个新的字符序列,它是此序列的一个子序列。
String concat(String str)将指定字符串连接到此字符串的结尾。
byte[] getBytes()使用平台的默认字符集将此 String 编码为 byte 序列,并将结果存储到一个新的 byte 数组中。
byte[] getBytes(String charsetName)使用指定的字符集将此 String 编码为 byte 序列,并将结果存储到一个新的 byte 数组中。
void getChars(int srcBegin, int srcEnd, char[] dst, int dstBegin)将字符从此字符串复制到目标字符数组。
int hashCode()返回此字符串的哈希码。
String intern()返回字符串对象的规范化表示形式。
int length()返回此字符串的长度。
String replace(char oldChar, char newChar)返回一个新的字符串,它是通过用 newChar 替换此字符串中出现的所有 oldChar 得到的。
String replaceAll(String regex, String replacement )使用给定的 replacement 替换此字符串所有匹配给定的正则表达式的子字符串。
String replaceFirst(String regex, String replacement)使用给定的 replacement 替换此字符串匹配给定的正则表达式的第一个子字符串。
String[] split(String regex)根据给定正则表达式的匹配拆分此字符串。
String[] split(String regex, int limit)根据匹配给定的正则表达式来拆分此字符串。
static String valueOf(primitive data type x)返回给定data type类型x参数的字符串表示形式。
char[] toCharArray()将此字符串转换为一个新的字符数组。
static String copyValueOf(char[] data)返回指定数组中表示该字符序列的 String。
static String copyValueOf(char[] data, int offset, int count)返回指定数组中表示该字符序列的 String。
String toLowerCase()使用默认语言环境的规则将此 String 中的所有字符都转换为小写。
String toLowerCase(Locale locale)使用给定 Locale 的规则将此 String 中的所有字符都转换为小写。
String toString()返回此对象本身(它已经是一个字符串!)。
String toUpperCase()使用默认语言环境的规则将此 String 中的所有字符都转换为大写。
String toUpperCase(Locale locale)使用给定 Locale 的规则将此 String 中的所有字符都转换为大写。
String trim()返回字符串的副本,忽略前导空白和尾部空白。

String类型转换

  1. String和int的相互转换

	//String 转 int
	int i = new Integer(s).intValue()
	int i = Integer.parseInt(String s);
	
	//int 转 String
	String s = i + "";
	String s = new Integer(i).toString;
	String s = String.valueOf(i);


  //以下两者是等价的
  s = i + ""
  s = String.valueOf(i);
  
  //以下两者也是等价的
  s = "abc" + i;
  s = new StringBuilder("abc").append(i).toString();

	
  1. array数组和string转换
 //字符串转数组
        char[] array = str.toCharArray();
//打印数组(数组转字符串)
        String s = Arrays.toString(array);
  1. string转date
  SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
  
        //字符串转日期
        Date date = format.parse("2020-12-10 12:00:00");

        //日期转字符串
        String str_date = format.format(date);
  1. 可以通过构造方法将其他基础类型转成字符串类型

在这里插入图片描述

String内存模型

1. String s = “abc” 和new String()的区别

String s1 = new String(“hello”) ,实际会创建两个对象。首先,在堆空间创建一个空间存储字符串,其次,会在常量池创建一个空间存储字符串常量值。最后 s1 引用堆空间的地址。

String s2 = “abc”,查找常量池中是否已存在该常量值,若不存在,直接在常量池开辟空间,s2 引用常量池中的地址。

2. intern() 方法

理解 intern 方法的作用:如果一个字符串调用了 intern方法,那么JVM 就会去字符串常量池中寻找该字符串,

若存在则直接返回常量池中相应Strnig的引用;

若不存在,则会在常量池中创建一个等值的String,然后返回这个String在常量池中的引用。因此,只要是等值的String对象,使用intern()方法返回的都是常量池中同一个String引用,


JDK 7 将常量池从永久代区移到了堆区,执行intern操作时,如果常量池已经存在该字符串,则直接返回字符串引用,否则复制该字符串对象的堆空间引用到常量池中并返回。由此堆中、常量池中使用的是同一份引用。

看下intern的使用

String s1 = "hello";
String s2 = new String("hello");

System.out.println(s1 == s2);//JDK 8 :false
String s1 = "hello";
String s2 = new String("hello").intern();

System.out.println(s1 == s2);//JDK 8 :true

3. new字符串过程会创建几个对象

* 思考:
* new String("ab")会创建几个对象? 		答:看字节码,就知道是2个。
*   一个对象是:new关键字在堆空间创建的
*   另一个对象是:字符串常量池中的对象"ab"。 字节码指令:ldc


* 思考:
* new String("a") + new String("b")过程会创建几个对象呢?		答:6* 对象1new StringBuilder()
* 对象2new String("a")
* 对象3: 常量池中的"a"
* 对象4new String("b")
* 对象5: 常量池中的"b"
* 
* 深入剖析: StringBuildertoString(), 并没有创建一个String对象
*   对象6new String("ab")
*   强调一下,通过字节码观察,StringBuildertoString()的调用,在字符串常量池中,没有
生成"ab"



* 如何保证变量s指向的是字符串常量池中的数据呢?
* 有两种方式:
* 方式一: String s = "shkstart";//字面量定义的方式
* 方式二: 调用intern()
*     String s = new String("shkstart").intern();
*     String s = new StringBuilder("shkstart").toString().intern();


常见的一个判断误区

		String s3 = new String("1") + new String("1");//指向堆空间
        String s4 = "11";//指向常量池
        String s5 = "11";//指向常量池
        System.out.println(s3 == s4);//false

        System.out.println(s3.equals(s4));//true,已重写
        System.out.println(s5 == s4);//true

4. String之间的加法规则
变量相加先开空间再拼接,常量先拼接再找,没有就创建

  1. 常量与常量的拼接结果在常量池,原理是编译器优化
  2. 常量池不会存在具有相同内容的常量(原理是HashTable)
  3. 如果拼接时有一个变量,编译器就无法优化,运行时才能知道该变量的值。所以拼接结果在堆内存,原理是使用StringBuilder
  4. 如果拼接结果最后调用intern(),则主动检索常量池,只有常量池中没有该结果时,才将该结果优化到常量池。并且优化后常量池中的引用就等于堆内存的引用。

调用形式的图解:
在这里插入图片描述

注:由此可见,+ 号连接符在大量使用时由于创建多个StringBuilder实例会降低效率。

public class StringTest5 {
  @Test
  public void test1(){
    String s1 = "a" + "b" + "c";//编译期优化:等同于"abc"
    String s2 = "abc"; //"abc"一定是放在字符串常量池中,将此地址赋给s2
    /*
    * 最终.java编译成.class,再执行.class
    * String s1 = "abc";
    * String s2 = "abc"
    */
    System.out.println(s1 == s2); //true
    System.out.println(s1.equals(s2)); //true
 }
@Test
  public void test2(){
    String s1 = "javaEE";
    String s2 = "hadoop";
    String s3 = "javaEEhadoop";
    String s4 = "javaEE" + "hadoop";//编译期优化
    //如果拼接符号的前后出现了变量,则相当于在堆空间中new String(),具体的内容为拼接的结
果:javaEEhadoop
    String s5 = s1 + "hadoop";
    String s6 = "javaEE" + s2;
    String s7 = s1 + s2;
    System.out.println(s3 == s4);//true
    System.out.println(s3 == s5);//false
    System.out.println(s3 == s6);//false
    System.out.println(s3 == s7);//false
    System.out.println(s5 == s6);//false
    System.out.println(s5 == s7);//false
    System.out.println(s6 == s7);//false
    //intern():判断字符串常量池中是否存在javaEEhadoop值,如果存在,则返回常量池中
javaEEhadoop的地址;
    //如果字符串常量池中不存在javaEEhadoop,则在常量池中加载一份javaEEhadoop,并返回
次对象的地址。
    String s8 = s6.intern();
    System.out.println(s3 == s8);//true
 }
/*
    如下的 s4 = s1 + s2; 的执行细节:(变量s是我临时定义的)
    ① StringBuilder s = new StringBuilder();
    ② s.append("a")
    ③ s.append("b")
    ④ s.toString() --> 约等于 new String("ab")
    补充:在jdk5.0之后使用的是StringBuilder,在jdk5.0之前使用的是StringBuffer
    */
  @Test
  public void test3(){
    String s1 = "a";
    String s2 = "b";
    String s3 = "ab";
    String s4 = s1 + s2;//左右两边是变量
    System.out.println(s3 == s4);//false
 }

还记得final、static也会被编译器优化吗?

/*
  1. 字符串拼接操作不一定使用的是StringBuilder!
   如果拼接符号左右两边都是字符串常量或常量引用,则仍然使用编译期优化,即非StringBuilder的方式。
  2. final、static都会被编译器优化,针对于final修饰类、方法、基本数据类型、引用数据类型的量的结构时,能使用上final的时候建议使用上。
 编译期优化的意思是,在链接的prepare阶段变量就已经完成了默认初始化赋值操作,已经有等号右边的值了
  */
  @Test
  public void test4(){
    final String s1 = "a";
    final String s2 = "b";
    String s3 = "ab";
    String s4 = s1 + s2;//S1、S2 是使用final修饰的非变量
    System.out.println(s3 == s4);//true
 }
 

StringBuffer

String类是字符串常量,是不可更改的常量。而StringBuffer是字符串变量,它的对象是可以扩充和修改的。

StringBuffer是使用缓冲区的,本身也是操作字符串的,但与String类不同,String类中的内容一旦声明之后不可改变,改变的只是其内存地址的指向,而StringBuffer中的内容是可以改变的 。

获取长度

  • 获取理想容量:capacity():默认初始容量是16
  • 获取实际长度:length()

反转
注意:String类是没有这个功能的

  • public StringBuffer reverse()

StringBuilder

StringBuilder是一个可变的字符序列。此类提供一个与 StringBuffer 兼容的 API,但不保证同步。该类被设计用作 StringBuffer 的一个简易替换,用在字符串缓冲区被单个线程使用的时候(这种情况很普遍)。

面试题

StringBuffer和数组的区别

  • StringBuffer 的数据最终是一个字符串数据
  • 数组可以存放多种数据类型,但必须是同一种数据类型的。
  • StringBuffer是针对字符类型的

String、StringBuffer作为参数传递

抓住拼接的本质即可,String不可变,会另外开辟空间,StringBuffer操作本身!

/**
 * 常见对象 String、StringBuffer分别作为参数传递
 */
public class Test01 {

    public static void main(String[] args) {
        String s1 = "hello";
        String s2 = "world";
        change(s1,s2);
        System.out.println(s1 + "-----" + s2);//hello-----world

        StringBuffer sb1 = new StringBuffer("hello");
        StringBuffer sb2 = new StringBuffer("world");
        change(sb1,sb2);
        System.out.println(sb1 + "-----" + sb2);//hello-----worldworld

    }

    public static void change(String s1,String s2){
        //形参s1、s2由值传递,由于String的不可变性,s1 s2 会在当前方法中开辟新的拷贝
        s1 = s2;//局部变量的引用改变,不影响原来的实参
        s2 = s1 + s2;
    }
    public static void change(StringBuffer sb1,StringBuffer sb2){
      sb1 = sb2;//局部变量sb1指向了另一个变量引用,不会影响实参
      sb2.append(sb1);//sb2改变自己的变量空间的值,会影响实参
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

_popo_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值