4.String类

从Java 13开始,字符串可以用"""..."""表示多行字符串(Text Blocks)了

String 类代表字符串。Java 程序中的所有字符串字面值(如 "abc" )都作为此类的实例实现。

字符串的字符使用unicode字符编码,一个字符(不区分字母还是汉字)占用两个字节或多个。

String类结构

字符串的构造器比较多,常用的有以下几个:

String()初始化新创建的 String对象,使其表示空字符序列。
String(byte[] bytes)通过使用平台的默认字符集解码指定的字节数组来构造新的 String 。
String(byte[] bytes, Charset charset)构造一个新的String由指定用指定的字节的数组解码charset 。
String(byte[] bytes, int offset, int length)通过使用平台的默认字符集解码指定的字节子阵列来构造新的 String 。
String(byte[] bytes, int offset, int length, Charset charset)构造一个新的String通过使用指定的指定字节子阵列解码charset 。
String(byte[] bytes, int offset, int length, String charsetName)构造一个新的 String通过使用指定的字符集解码指定的字节子阵列。
String(byte[] bytes, String charsetName)构造一个新的String由指定用指定的字节的数组解码charset 。
String(char[] value)分配一个新的 String ,以便它表示当前包含在字符数组参数中的字符序列。
String(char[] value, int offset, int count)分配一个新的 String ,其中包含字符数组参数的子阵列中的字符。
String(int[] codePoints, int offset, int count)分配一个新的 String ,其中包含 Unicode code point数组参数的子阵列中的 字符 。
String(String original)初始化新创建的String对象,使其表示与参数相同的字符序列;
String(StringBuffer buffer)分配一个新的字符串,其中包含当前包含在字符串缓冲区参数中的字符序列。
String(StringBuilder builder)分配一个新的字符串,其中包含当前包含在字符串构建器参数中的字符序列。

字符串类是final的,所以不能被外部类继承, 字符串类中有一个final的属性为字节数组:

java 复制代码
public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence,
               Constable, ConstantDesc {
    ...
    private final char value[];
    ...
}

上面代码,说明两点:

  • value主要用来存储字符串的值

  • 字符串本质还是一个字符数组。

  • final保证value引用不变;
  • String通过封装保证内容不可变(String的设计是不可变:value私有、类final、不暴露修改方法、操作返回新对象)。

字符串类关系图

实现Serializalble接口的对象可以串行化,表示可以在网络上传输

实现Comparable接口的对象可以相互比较

CharSequence是一个描述字符串结构的接口

常用方法

Return Type Method and Description
char charAt(int index)返回 char指定索引处的值。
int compareTo(String anotherString)按字典顺序比较两个字符串。
int compareToIgnoreCase(String str)按字典顺序比较两个字符串,忽略大小写。
String concat(String str)将指定的字符串连接到该字符串的末尾
boolean equalsIgnoreCase(String anotherString)将此 String与其他 String比较,忽略大小写。
String substring(int beginIndex, int endIndex)返回一个字符串,该字符串是此字符串的子字符串。重载方法
char[] toCharArray()将此字符串转换为新的字符数组。
String replace(char oldChar, char newChar)返回从替换所有出现的导致一个字符串 oldChar在此字符串 newChar 。
boolean isEmpty()返回 true如果,且仅当 length()为 0 。
int lastIndexOf(String str)返回指定子字符串最后一次出现的字符串中的索引。重载方法
int indexOf(String str)返回指定子字符串第一次出现的字符串内的索引。重载方法
String[] split(String regex)将此字符串分割为给定的 regular expression的匹配。重载方法
String trim()返回一个字符串,其值为此字符串,并删除任何前导和尾随空格
boolean contains(String findStr) 查看字符串中是否包含某段字符串

示例:

java 复制代码
//substring
String name = "Hello 张三";
System.out.println(name.substring(0, 5));
//indexOf
String s1 = "sfsfd@ssdkd@.com";
System.out.println(s1.indexOf('@'));
System.out.println(s1.indexOf("fd"));
System.out.println(s1.lastIndexOf("@"));
//trim
String s2= "   ssd hello 你好  ";
System.out.println(s2.trim()+ "\t" + s2);
//charAt
System.out.println(s1.charAt(1));
//concat
System.out.println(name.concat("; hello").concat(" 李四;"));
//replace
String s3 = "宝玉 and 林黛玉 and 林黛玉 and 宝玉";
System.out.println(s3.replace("宝玉", "泰森") + "\t" + s3);
//split
String s4 = "床前明月光;疑似地上霜;举头望明月;低头思故乡";
String[] s5 = s4.split(";");
for (int i = 0; i < s5.length; i++) {
    System.out.println(s5[i]);
}
//某字符串是否包含字符串
System.out.println(s4.contains("明月"));
//compareTo
System.out.println("json".compareTo("Json"));
//toCharArray
String s6 = "import earth";
char[] c2 = s6.toCharArray();
for (int i = 0; i < c2.length; i++) {
    System.out.println(c2[i]);
}

String对象

常量池

常量池就像一张哈希表/缓存表,表里保存的是 String 对象的引用,注意:常量池里面不存储字符串内容,保存的是字符串的引用,字符串内容保存在字符串对象的value属性中 。

在 HotSpot 里,字符串常量池可以理解成:

text 复制代码
StringTable:一个哈希表
key   -> 字符串内容(或哈希后的code)
value -> String 对象的引用

对象创建

分为两种方式

  1. 直接赋值如:String s = "hello"

    先从常量池 查看是否有"light"数据对象的引用,如果有,直接指向;如果没有则重新创建,然后指向。String s最终指向的是常量池的引用

    JVM执行过程:

    • 去StringTable里查有没有内容为hello的记录。
    • 如果没有, 在堆 中创建一个 String 对象,假设它是对象1。
    • 把对象1的引用登记到 StringTable。
    • 把这个引用赋给 a。
    text 复制代码
    StringTable(表,存的是引用)
      "hello"/hash ──> 对象1
    堆
      对象1:内容 "hello"
              ↑
              │
    a ────────┘

    假设有语句 String s2 = "hello",JVM执行过程:

    • 去 StringTable查,查到了 "hello",对应对象1。
    • 直接取出对象1的引用, 赋给 b.

    这一步不创建新对象 ,也不在StringTable新增表记录。

    text 复制代码
    StringTable
      "hello" ──> 对象1
    堆
      对象1:value ──┐
         │          │
         │          v
         │         char[] {'h','e','l','l','o'}        
    a ───┘     
    b ───┘
  2. 调用构器String s3 = new String("hello");

    JVM执行过程:

    • new String("hello") 括号里的 "hello" 是字面量,JVM 先处理它:
      • 去 StringTable 查,已经有 "hello",对应对象1。如果没有要去创建。
      • 直接用对象1。
        这一步不创建新对象 ,也不在StringTable新增表记录。
    • 在执行new操作
      • 在堆中创建"对象2",内容也是 "hello"
      • 用参数 对象1 的内容初始化 对象2
        可以理解为:对象2的 value 引用指向对象1的 value 数组。两个可能指向同一数组。
      • 把 对象2 的引用赋给 s3
        对象2不会自动登记进 StringTable。 StringTable 里仍然只有一条 "hello" 记录,指向对象①。
    text 复制代码
    StringTable
      "hello" ──> 对象①
    
    堆
      对象1:value ──┐
         │          │
         │          v
         │         char[] {'h','e','l','l','o'}        
    a ───┘          ^
    b ───┘	        │
                    │
      对象2:value ──┘
        ↑
        │
    c ──┘

s2 = s2.intern() 返回字符串常量池中与 s3 内容相等的那个"规范实例"的引用。 intern() 做两件事之一:

  1. 池中已有内容为 hello 的字符串, 直接返回池中那个对象的引用。
  2. 池中没有内容为 hello 的字符串 , 把当前字符串登记到池中,然后返回池中对应引用。
    下面示例:
java 复制代码
String greeting = "Hello"; //initialize greeting to a string
if (greeting == "Hello") ...
// probably true
if (greeting.substring(0,3) == "Hel") ... //substring返回需要重新new String,所以重新创建对象。与"Hel"对应的引用不一样
// probably false

String变量还可以存放一个特殊的值,那就是null,这表示目前没有任何对象与该变量关联。要检查一个字符串是否为null,要使用以下条件:if (str == null)

有时要检查一个字符串既不是null也不为空串,这种情况下就需要使用以下条件:if (str != null && str.1ength()!= 0) 注意顺序, 先要判断不为null

总结:

== 比较引用:两个变量是否指向同一个对象

equals() 比较内容:两个字符串的字符序列是否一样

对象特性

  1. 描述下列语句的内存执行过程
java 复制代码
String s1 = "hello";//先从常量池查看是否有"hello"数据引用,如果有,直接指向;如果没有则重新创建,然后指向。
s1 = "hahaha";//先从常量池查看是否有"hahaha"数据引用,如果有,先断开原来的指向地址,然后直接指向新的地址;如果没有则创建,然后先断开原来的指向地址,再直接指向新的地址;
  1. 面试题

    String a = "hello"+"abc"; 创建了几个对象?

    只有1个对象,这里两个都是字符串字面量 ,属于编译期常量表达式,javac 会直接做常量折叠,编译后等价于:"helloabc"

  2. 下面代码创建了几个对象?

    java 复制代码
    String h = "hello";
    String c = h + "abc";

    对于String c = h + "abc"语句, java8采用StringBuilder对象调用append方法,具体为:

    1. 先构建一个StringBuilder对象。

    2. 调用StringBuilder类中的public StringBuilder append(String str)这个方法,str参数为"hello"

    3. 再次调用StringBuilder类中的public StringBuilder append(String str)这个方法,str参数为"abc"

    4. 调用StringBuilder类中toString方法:

      java 复制代码
      @Override
      public String toString() {
          // Create a copy, don't share the array
          return new String(value, 0, count);//这里的value值为helloabc
      }
    5. 然后将创建的对象赋给c。

    这样会产生的问题:

    • 拼接策略在编译期就写死了。
    • 可能创建 StringBuilder 中间对象。
    • 默认容量 16,可能多次扩容。
    • JVM 想优化也难,因为字节码里已经是普通方法调用。

    Java 9+ 对运行时字符串 + 拼接使用 invokedynamic,一句话总结:

    javac 不再把 + 固定翻译成 StringBuilder.append 链,而是生成一条 invokedynamic 指令,运行时由 StringConcatFactory 决定怎么拼。

    运行时拼接结果 c 通常会创建一个新的 String 对象 , 来接收拼接的字符串。所有最终 String 对象通常还是要创建的,优化的是中间过程。

  3. 下列程序运行结果是什么?尝试画出内存图

    JAVA 复制代码
    public class String03 {
        String str = new String("light");
        //使用final修饰的引用不能指向另一个引用
        final char[] ch = { 'j', 'a', 'v', 'a'};
    
        public static void main(String[] args) {
            String03 ex = new String03();
            ex.change(ex.str, ex.ch);
            System.out.print(ex.str + "and "); //lightand
            System.out.println(ex.ch);//lava
        }
        public void change(String str, char ch[]) {
            str = "java";
            ch[0] = 'l';
        }
    }

    从内存分析中得出结果:lightand lava

    ch 是一个引用变量,{ 'j', 'a', 'v', 'a' } 是一个数组对象 。ch 里存的不是四个字符,而是数组对象的引用/地址。堆里的数组对象大致包含:

    text 复制代码
    char[] 数组对象
    ┌─────────────────────┐
    │ 对象头              │  Mark Word + 类型指针
    ├─────────────────────┤
    │ 长度 length = 4     │
    ├─────────────────────┤
    │ [0] 'j'             │  2 字节
    │ [1] 'a'             │  2 字节
    │ [2] 'v'             │  2 字节
    │ [3] 'a'             │  2 字节
    └─────────────────────┘

上面代码的内存分析图:

StringBuffer

String类是保存字符串常量的。每次更新都需要重新开辟空间,效率较低,因此java设计这还提供了StringBuilder和StringBuffer来增强String的功能,并提供效率。

StringBuffer线程安全的可变字符序列。可以对字符串内容进行增删。StringBuffer也是一个容器。

StringBuffer特性

  1. StringBuffer父类继承了抽象类AbstractStringBuilder。

  2. StringBuffer实现了Serializable接口,实现了串行化。

  3. StringBuffer的父类AbstractStringBufferBuilder中有属性value[]。与String不同,此value属性没有使用private final修饰。此属性用于存储字符串的内容,并且存放在堆中的。

    java 复制代码
    abstract class AbstractStringBuilder implements Appendable, CharSequence {
        /**
         * The value is used for character storage.
         */
        char[] value;
        ...
  4. StringBuffer是一个用final修饰的类。

String Vs StringBuffer

String保存的是字符串常量,里面的值不能更改,每次String类更新实际上是更改地址,效率低。

StringBuffer保存的是字符串变量,里面的值是可以更改的。StringBuffer不用每次都更新地址,效率更高。

构造器

JAVA 复制代码
//构造器的使用
//1.创建一个为16的char[],用于存放字符内容
StringBuffer stringBuffer = new StringBuffer();
//2.通过构造器指定char[]大小
StringBuffer stringBuffer1 = new StringBuffer(100);
//3.通过给一个String创建StringBuffer,char[]大小就是str.length()+16
StringBuffer hello = new StringBuffer("hello");

String与StringBuffer互转

JAVA 复制代码
//String--->StringBuffer
String str = "hello tom";
//方式1:使用构造器; 注意:返回的才是StringBuffer对象,对str本身没有影响
StringBuffer stringBuffer2 = new StringBuffer(str);
//方式2:使用的是append方法
StringBuffer stringBuffer3 = new StringBuffer();
StringBuffer append = stringBuffer3.append(str);

//StringBuffer---->String
StringBuffer stringBuffer4 = new StringBuffer("XIAOSSS");
//方式1:使用StringBuffer提供的toString方法, 重写的toString里面其实也是return new String(StringBuffer);
String s = stringBuffer4.toString();
//方式2:使用构造器来搞定
String s1 = new String(stringBuffer4);

StringBuffer常用方法

Return Type Method and Description
StringBuffer append(String str)将指定的字符串附加到此字符序列。类中此方法重载
StringBuffer insert(int offset, String str)将字符串插入到此字符序列中。类中此方法重载
StringBuffer replace(int start, int end, String str)将start----end间的内容替换掉,不含end 。
StringBuffer delete(int start, int end)删除此序列的子字符串中的字符。不含end 。
StringBuffer deleteCharAt(int index)删除 char在这个序列中的指定位置。
int indexOf(String str)找子在字符串第1次出现的索引,如果找不到返回-1。
int indexOf(String str, int fromIndex)找子在字符串第1次出现的索引,从指定的索引开始。如果找不到返回-1。
StringBuffer reverse()导致该字符序列被序列的相反代替。
int length()返回长度(字符数)。
int capacity()返回当前容量。
char charAt(int index)返回 char在指定索引在这个序列值。
String substring(int start)返回一个新的 String ,其中包含此字符序列中当前包含的字符的子序列。
String substring(int start, int end)返回一个新的 String ,其中包含此序列中当前包含的字符的子序列。

在java中,offset 是指偏移量/位置,而index指的是索引。start、end表示索引(而且一般这种表示的都是不包括end)

示例:

JAVA 复制代码
//常用方法
StringBuffer s =new StringBuffer("hello");
//增
s.append(',');
s.append("张三丰");
s.append("赵敏").append(100).append(true).append(10.5);
System.out.println(s);// hello,张三丰赵敏100true10.5
//删 删除索引为[11, 14)处的字符
s.delete(11,14);
System.out.println(s);
//改 [9,11)位置的字符
s.replace(9,11, "周芷若");
System.out.println(s);
//查 查找指定的子串在字符串第一次出现的索引,如果找不到返回---1
int indexOf = s.indexOf("张三丰");
System.out.println(indexOf);
//插 hello,张三丰周芷若true10.5
s.insert(9,"赵敏");
System.out.println(s);
//长度
System.out.println(s.length());
System.out.println(s);

StringBuilder

基本介绍

1)一个可变的字符序列。此类提供一个与StringBuffer兼容的API,但不保证同步(StringBuilder不是线程安全)。该类被设计用作StringBuffer的一个简易

替换,用在字符串缓冲区被单个线程使用的时候。如果可能,建议优先采用该类,因为在大多数实现中,它比StringBuffer要快。

2)在StringBuiIder上的主要操作是append和insert方法,可重载这些方法,以接受任意类型的数据。

类关系图及特性

特性:具有和StringBuffer相同的4点特性,但是StringBuiIder不是线程安全的,所有方法没有synchronized关键字。

String、StringBuffer、StringBuilder比较

1)StringBuiIder和StringBuffer非常类似,均代表可变的字符序列,而且方法也一样。

2)String:不可变字符序列,效率低,但是复用率高(可以共享)。

3)StringBuffer:可变字符序列、效率较高(增删)、线程安全

4)StringBuilder:可变字符序列、效率最高、线程不安全

String使用注意说明:

string s = "a"; //创建了一个字符串

s += "b"; //实际上原来的"a"字符串对象已经丢弃了,现在又产生了一个字符串s += "b"(也就是"ab")。如果多次执行这些改变内容的操作,会导致大量副本字符串对象存留在内存中,降低效率。如果这样的操作放到循环中,会极大影响程序的性能。结论:如果我们对String做大量修改,不要使用String。

相关推荐
蚰蜒螟1 小时前
从 Java SocketChannel 到 TCP 挥手:一次 close() 的完整调用链与引用计数解析
java·python·tcp/ip
damoluomu1 小时前
国产 Java CMS 的协议和坑,讲清楚
java·开发语言
Wang's Blog1 小时前
Java 项目实战: 交友项目 - 黑名单:分页查询黑名单
java·开发语言·交友
蚰蜒螟2 小时前
从 Java write 到磁盘扇区:一次写入操作的完整内核穿越之旅
java·开发语言
en.en..2 小时前
volatile 关键字整理(嵌入式 C 标准)
java·开发语言
captain3762 小时前
Spring IoC和DI(2)
java·后端·spring
Shaoshing2 小时前
高可用与核心概念
java·mq
小羊没烦恼!2 小时前
深入浅出SQL Server中的死锁
java·开发语言·前端·数据库·后端·spring·c#