String 类

1、String 对象是一组字符序列,用于保存字符串

2、字符串常量对象是双引号括起的字符序列

3、字符用 Unicode 编码,一个字符(不区分字母、汉字)占用两个字节

4、String 类实现 Serializable 接口,可以串行化,在网络传输;实现 Comparable 接口,对象之间可以比较

5、String 类被 final 修饰,不能被其它类继承

6、类中存在属性 private final char value[],字符数组存放字符串内容,final 是指 value 指向字符串常量池的常量的地址不可修改,每 new 一次,value 就指向新常量

 

创建 String 对象

1、两种方式

(1)字面量

(2)new(构造器)

2、字符串对象存在于堆,字符串常量池只会存放引用

 

通过字面量赋值

String str = "King";

1、查看字符串常量池是否存在引用指向 King 字符串

2、若存在,栈中 str 直接指向堆中 King

3、若不存在,在堆创建 King,在字符串常量池创建 King 的引用,栈中 str 指向 King

 

使用 new 创建对象

String str = new String("King");

1、堆中创建 String 对象,但未初始化

2、执行 ldc

(1)判断符号引用是否已经解析成了直接引用

(2)如果没有,则会进行解析,判断字符串常量池是否存在对 King 的引用

(3)如果存在,则将符号引用解析成字符串常量池的引用

(4)如果不存在,则在堆中创建一个 King 字符串对象,然后将符号引用解析成字符串常量池的引用

3、初始化 1 中的 String 对象为 King,str 指向它

4、此时,有两个 King 字符串对象

(1)str(栈)指向的 King(堆)

(2)字符串常量池的引用指向的 King(堆)

 

String str1 = new String("jionghui");

1、假设:最初字符串常量池中不存在 jionghui 字符串

2、该代码编译后其字节码如下

 0 new #2 <java/lang/String>
 3 dup
 4 ldc #3 <jionghui>
 6 invokespecial #4 <java/lang/String.<init> : (Ljava/lang/String;)V>
 9 astore_1
10 return

(1)字节码中带 # 号的数字,是常量池里面的符号引用,这些符号引用会在类加载的解析阶段被解析为直接引用,直接引用可以理解为就是对象在内存中的地址

(2)#2 对应 java.lang.String 的 Class 类

(3)#3 对应 jionghui 字符串

(4)#4 对应 String 的初始化方法

3、new

(1)新建一个 String 对象,但是此时还未初始化,是一个空对象

(2)同时,这个字节码会将创建的对象的引用,存放到操作数栈的栈顶

(3)执行完该指令后的结构

4、dup

(1)复制一份栈顶的元素

(2)在栈顶复制一个 String 的引用,因为后续调用 String 的初始化方法会消耗掉栈里的一个引用,所以这边提前复制一份出来,最后才有引用可以赋值给局部变量表的 str1

(3)执行完该指令后的结构

5、ldc

(1)将 int、float 或 String 型常量值从常量池中推送至栈顶,此处的 ldc 指令会附带另外一个功能:触发符号引用解析为直接引用

(2)符号引用会在解析阶段被解析成直接引用,但是有一些特例,字符串对象就是一个特例,字符串对象不会在解析阶段就将符号引用解析成直接引用,而是等到某个“合适的时机”才去解析,此处 ldc 就是这个时机

(3)判断符号引用是否已经解析成了直接引用,如果没有,则会进行解析

(4)判断 jionghui 字符串是否已经在字符串常量池存在,如果存在,则将符号引用解析成字符串常量池的引用;如果不存在,则会在字符串常量池中创建一个jionghui 字符串对象,然后同样将符号引用解析成字符串常量池的引用

(5)将对应的字符串常量池推送到栈顶

(6)执行完该指令后的结构

6、invokespecial

(1)调用超类构造方法,实例初始化方法,私有方法

(2)此处调用 String 的初始化方法,因为前面通过 new 创建的是个空对象,还未进行初始化

(3)初始化使用栈顶的两个元素,一个元素指向要初始化的对象,另一个元素指向初始化使用的参数

(4)初始化完毕后,这个空字符串对象会被初始化成 jionghui 字符串对象

(5)执行完该指令后的结构

7、astore_1

(1)将栈顶引用元素存到指定本地变量

(2)将栈顶的引用存放到本地变量表,指定到 str1 变量

(3)执行完该指令后的结构

8、最终创建两个对象

(1)一个是通过 new String 创建的对象,它的引用被复赋值给 str1

(2)一个是在常量池里创建的字符串对象

 

String str2 = "jionghui";

1、是 String str1 = new String("jionghui");  的简版,去掉 new String 的过程,其他基本一样

2、执行结束的内存结构

 

String 对象特性

1、String 是 final 类,final 类代表不可变字符序列,字符串对象一旦被分配,其内容不可变,但引用可变

String str1 = "A";
str1 = "B";//常量池存在“A”、“B”,str1先指向A,后指向B

2、常量相加,前端编译器进行优化

String str1 = "A" + "B";//等价于 String str = "AB";
String str2 = "AB";
System.out.println(str1 == str2);//true,常量池存在一个引用指向 AB,str1、str2 都指向堆中同一个 AB

3、String str4 = new String("jiong") + "hui"

(1)双引号修饰的字面量 jiong 和 hui 分别会在字符串常量池中创建字符串对象

(2)new String 关键字会再创建一个 jiong 字符串对象

(3)通过 StringBuilder 来进行字符串的拼接操作,先创建了一个 StringBuilder,然后 append("jiong"),然后 append("hui"),最后执行 toString 返回,toString 底层是通过 new String 方法返回,所以最终这边拼接也会创建一个新的字符串

(4)执行结束的内存结构

 

字符串拼接操作

1、常量与常量的拼接,结果在字符串常量池

(1)原理:都是常量时,前端编译器会进行代码优化

(2)常量:使用 final 修饰、使用 "" 直接参与拼接

(3)在实际开发中,尽量使用 final

2、只要其中有一个是变量,结果就在堆中新建 String 对象,不论字符串常量池是否有相同 String

(1)变量拼接原理:使用 new 创建 StringBuilder,调用 append(),进行拼接

(2)变量:不使用 final 修饰、使用变量名、new 字符串

(3)每初始化一个 String,StringBuilder 就调用 append();直到最后一个 String 拼接完成

(4)最后 StringBuilder 调用 toString(),底层为 return new String(value, 0, count);,所以结果在堆中的 String 对象

public String(char[] value,
              int offset,
              int count)

3、相同字符串

(1)堆中可能存在多个相同字符串

(2)栈中可能存在对同一个字符串的多个引用

(3)字符串常量池中不会存在指向相同字符串的引用

4、字符串拼接性能

(1)时间:StringBuilder.append() < StringBuffer.append() < String 使用 + 拼接

(2)在实际开发中,对于需要多次或大量拼接的操作,在不考虑线程安全问题时,尽可能使用 StringBuilder 进行 append 操作

(3)StringBuilder 空参构造器的初始化大小为 16,如果提前知道需要拼接 String 个数,应该直接使用带参构造器指定 capacity,以减少扩容的次数

 

intern() 例1

String str5 = new String("1") + new String("1");
str5.intern();
String str6 = "11";
System.out.println(str5 == str6);

1、JDK 7

(1)双引号修饰的字面量 1 会在字符串常量池中创建字符串对象,有两个字面量 1,但是只会创建1次,另一个直接复用

(2)两个 new String 创建两个字符串对象 1

(3)字符串拼接通过 StringBuilder 创建出一个新的字符串对象 11,并将引用赋值给 str5

(4)str5 调用 intern 方法,检查到字符串常量池还没有字符串 11,则将字符串对象放入常量池,此时字符串常量池中的 11 就是 str5 指向的字符串对象

(5)双引号修饰的字面量 11 检查到字符串常量池中已经存在字符串 11,则直接使用字符串常量池中的对象,所以 str6 被赋值为字符串常量池中的对象引用

(6)输出结果为 true

(7)执行结束的内存结构

2、验证字符串的符号引用在运行阶段才被解析成直接引用

(1)假设字符串的符号引用在类加载的解析阶段,就解析成直接引用,那么这个例子的流程如下(JDK7及之后版本)

(2)解析阶段,双引号修饰的字面量 1 和 11 会在字符串常量池中创建字符串对象

(3)两个 new String 创建两个字符串对象 1

(4)字符串拼接通过 StringBuilder 创建出一个新的字符串对象 11,并将引用赋值给 str5

(5)str5 调用 intern 方法,检查到字符串常量池存在字符串11,则不做任何操作

(6)str6 被赋值为字符串常量池中的对象引用,此时 str6 和 str5 指向的是不同的字符串对象

(7)输出结果为 false

(8)实际上在 JDK 7 及之后版本的输出结果为 true

3、JDK 6

(1)JDK 6 存在永久代,字符串常量池指向的字符串对象在 JDK 6 中是在永久代创建的,JDK 7 才被移动到堆中

(2)所以当执行 str5.intern 时,发现永久代中没有字符串11,则会在永久代创建字符串对象 11,后续的 str6 也是指向永久代的字符串对象,所以,此时 str5 和 str6 指向的不同对象

 

intern() 例2

String str7 = new String("1") + new String("1");
String str8 = "11";
String str9 = str7.intern();
System.out.println(str7 == str8);
System.out.println(str8 == str9);

1、双引号修饰的字面量 1 会在字符串常量池中创建字符串对象,这边有两个字面量 1,但是只会创建一次,另一个直接复用

2、两个 new String 创建两个字符串对象 1

3、字符串拼接通过 StringBuilder 创建出一个新的字符串对象 11,并将引用赋值给 str7

4、双引号修饰的字面量 11 会在字符串常量池中创建字符串对象,并将引用赋值给 str8

5、str7 调用 intern(),检查到字符串常量池存在字符串 11,则不做任何操作,同时返回字符串常量池的引用,并赋值给 str9

6、输出结果为 false 和 true

7、执行结束的内存结构

 

String 类常用方法

1、使用 equals 方法,判断常量池是否存在调用 intern 方法的 String 对象的引用,若存在,则返回堆中字符串的地址;若不存在,将此 String 对象的引用将添加到常量池中,并返回此 String 对象在堆中的地址

public String intern()

2、区分大小写,判读内容是否相等

public boolean equals(Object anObject)

3、忽略大小写,判断内容是否相等

public boolean equalsIgnoreCase(String anotherString)

4、获取字符串长度,即字符个数

public int length()

5、返回指定字符第一次出现的字符串内的索引,不存在,则返回 -1

public int indexOf(int ch)

6、返回指定字符第一次出现的字符串内的索引,以指定的索引开始搜索,不存在,则返回 -1

public int indexOf(int ch, int fromIndex)

7、返回指定子字符串第一次出现的字符串内的索引(字符串第一个字符的索引),不存在,则返回 -1

public int indexOf(String str)

8、返回指定子字符串第一次出现的字符串中的索引(字符串第一个字符的索引),从指定的索引开始,不存在,则返回 -1

public int indexOf(String str, int fromIndex)

9、返回指定字符的最后一次出现的字符串中的索引,如果此字符串中没有此字符,则返回 -1 

public int lastIndexOf(int ch)

10、返回指定字符的最后一次出现的字符串中的索引,从指定的索引开始向后搜索,不存在,则返回 -1

public int lastIndexOf(int ch, int fromIndex)

11、返回指定子字符串最后一次出现的字符串中的索引(字符串第一个字符的索引),空字符串“”的最后一次出现被认为发生在索引值this.length() ,不存在,则返回 -1

public int lastIndexOf(String str)

12、返回指定子字符串的最后一次出现的字符串中的索引(字符串第一个字符的索引),从指定索引开始向后搜索,空字符串“”的最后一次出现被认为发生在索引值this.length() ,不存在,则返回 -1

public int lastIndexOf(String str, int fromIndex)

13、返回一个字符串,该字符串是此字符串的子字符串,子字符串以指定索引处的字符开头,并扩展到该字符串的末尾

public String substring(int beginIndex)

14、返回一个字符串,该字符串是此字符串的子字符串, 子串开始于指定 beginIndex 并延伸到字符索引 endIndex - 1,子串的长度为 endIndex - beginIndex

public String substring(int beginIndex, int endIndex)

15、返回一个字符串,其值为此字符串,并删除任何前导和尾随空格

public String trim()

16、返回 char 指定索引处的值,索引范围为 0 至 length() - 1

public char charAt(int index)

17、将此 String 中的所有字符转换为大写

public String toUpperCase()

18、将此 String 中的所有字符转换为小写

public String toLowerCase()

19、将指定的字符串连接到该字符串的末尾

public String concat(String str)

20、返回一个 oldChar 替换为 newChar 的字符串,不影响调用的字符串

public String replace(char oldChar, char newChar)

21、返回一个 target 替换为 replacement 的字符串,不影响调用的字符串

public String replace(CharSequence target, CharSequence replacement)

22、返回一个正则表达式匹配的子字符串替换为 replacement 的字符串,不影响调用的字符串

public String replaceAll(String regex, String replacement)

23、返回一个正则表达式匹配的第一个子字符串替换为 replacement 的字符串,不影响调用的字符串

public String replaceFirst(String regex, String replacement)

24、将此字符串拆分为给定的正则表达式的匹配

public String[] split(String regex)

25、将此字符串转换为新的字符数组

public char[] toCharArray()

26、比较基于字符串中每个字符的 Unicode 值

(1)长度相同、内容相同,返回 0

(2)长度不同、较短字符串完全匹配较长字符串的前面部分,返回调用方法字符串长度 - 参数字符串长度

(3)长度不同、内容不同,返回调用字符串第一个不同字符的 Unicode 值 - 参数字符串第一个不同字符的 Unicode 值

public int compareTo(String anotherString)

27、忽略大小写比较字符串

public int compareToIgnoreCase(String str)

28、args 参数依次填入 format 中的占位符

public static String format(String format, Object... args)

搭配转换符的标志

posted @   半条咸鱼  阅读(53)  评论(0编辑  收藏  举报
(评论功能已被禁用)
相关博文:
阅读排行:
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
点击右上角即可分享
微信分享提示