Java String


一、String 底层实现

  • JDK-1.8 来说,String 内部实际存储结构为 char 数组。
  • String 源码:
/**
 * Serializable 序列化接口 
 * Comparable > compareTo 比较接口
 * CharSequence 字符接口
 */
public final class String implements java.io.Serializable, Comparable<String>, CharSequence {
    // 用于存储字符串的值
    private final char value[];
    // 缓存字符串的 hash code
    private int hash; // 默认为0
    // ......其他内容
}

二、String 重要方法


1. 构造方法


1.1 String 为参数
public String(String original) {
	this.value = original.value;
   	this.hash = original.hash;
}

1.2 char[] 为参数
public String(char value[]) {
	this.value = Arrays.copyOf(value, value.length);
}
System.out.println(new String(new char[]{'A', 'B', 'C'}, 1, 2));// BC

public String(char value[], int offset, int count) {
    if (offset < 0) {
        throw new StringIndexOutOfBoundsException(offset);
    }
    if (count <= 0) {
        if (count < 0) {
            throw new StringIndexOutOfBoundsException(count);
        }
        if (offset <= value.length) {
            this.value = "".value;
            return;
        }
    }
    // 注意:偏移量或计数可能接近-1>>>1。
    if (offset > value.length - count) {
        throw new StringIndexOutOfBoundsException(offset + count);
    }
    this.value = Arrays.copyOfRange(value, offset, offset+count);
}

1.3 StringBuffer 为参数
public String(StringBuffer buffer) {
    synchronized(buffer) {
        this.value = Arrays.copyOf(buffer.getValue(), buffer.length());
    }
}

1.4 StringBuilder 为参数
public String(StringBuilder builder) {
    this.value = Arrays.copyOf(builder.getValue(), builder.length());
}

2. equals() 等于

  • 比较两个字符串是否相等。
  1. String 类型重写了 Object 中的 equals() 方法,equals() 方法需要传递一个 Object 类型的参数值。
  2. 在比较时会先通过 instanceof 判断是否为 String 类型,如果不是则会直接返回 false
  3. 当判断参数为 String 类型之后,会循环对比两个字符串中的每一个字符,当所有字符都相等时返回 true,否则则返回 false
/**
 * 将此字符串与指定对象进行比较。
 * 当且仅当参数不是{@code null}并且是一个{@code String}对象,表示与此对象相同的字符序列时,结果为{@code true}。	
 */
public boolean equals(Object anObject) {
	// 对象引用相同直接返回 true
    if (this == anObject) {
        return true;
    }
    // 判断需要对比的值是否为 String 类型,如果不是则直接返回 false
    if (anObject instanceof String) {
        String anotherString = (String)anObject;
        int n = value.length;
        if (n == anotherString.value.length) {
        	// 把两个字符串都转换为 char 数组对比
            char v1[] = value;
            char v2[] = anotherString.value;
            int i = 0;
            // 循环比对两个字符串的每一个字符
            while (n-- != 0) {
            	// 如果其中有一个字符不相等就 false,否则继续对比
                if (v1[i] != v2[i])
                    return false;
                i++;
            }
            return true;
        }
    }
    return false;
}

2.1 instanceof 关键字
Object oString = "123";
Object oInt = 123;
System.out.println(oString instanceof String);// 返回 true
System.out.println(oInt instanceof String);// 返回 false
System.out.println(null instanceof String);// false

3. equalsIgnoreCase() 相等忽略大小写

  • 忽略字符串的大小写之后进行字符串对比。
/**
 * 将此{@code String}与另一个{@code String}比较,而忽略大小写注意事项。
 * 如果两个字符串的长度相同,并且两个字符串中的相应字符等于忽略大小写,则将它们视为忽略大小写相等。
 */
public boolean equalsIgnoreCase(String anotherString) {
    return (this == anotherString) ? true
            : (anotherString != null)
            && (anotherString.value.length == value.length)
            && regionMatches(true, 0, anotherString, 0, value.length);
}

3.1 regionMatches() 指定范围比较
  • 测试两个字符串范围是否相等。
System.out.println("AbCd".regionMatches(true, 1, "BcDe", 0, 3));// true bCd == BcD
/**
 * 测试两个字符串范围是否相等。
 * 
 * @param ignoreCase: 是否忽略大小写
 * @param toffset: this起始位置
 * @param other: other对象
 * @param ooffset: other起始位置
 * @param len: 比较长度
 */
public boolean regionMatches(boolean ignoreCase, int toffset,
            String other, int ooffset, int len) {
    char ta[] = value;// this
    int to = toffset;// this起始位置
    char pa[] = other.value;// other
    int po = ooffset;// other起始位置
    // Note: toffset, ooffset, or len might be near -1>>>1.
    if ((ooffset < 0) || (toffset < 0)
            || (toffset > (long)value.length - len)
            || (ooffset > (long)other.value.length - len)) {
        return false;
    }
    while (len-- > 0) {
        char c1 = ta[to++];
        char c2 = pa[po++];
        // 比较this 和 other每个字符, 相等跳过
        if (c1 == c2) {
            continue;
        }
        // ignoreCase == true, 代表忽略大小写
        if (ignoreCase) {
            // If characters don't match but case may be ignored,
            // try converting both characters to uppercase.
            // If the results match, then the comparison scan should
            // continue.
            // 转换为大写, 再比较
            char u1 = Character.toUpperCase(c1);
            char u2 = Character.toUpperCase(c2);
            if (u1 == u2) {
                continue;
            }
            // Unfortunately, conversion to uppercase does not work properly
            // for the Georgian alphabet, which has strange rules about case
            // conversion.  So we need to make one last check before
            // exiting.
            // 转换为小写, 再比较(用于格鲁吉亚字母,该字母对大小写有奇怪的规定)
            if (Character.toLowerCase(u1) == Character.toLowerCase(u2)) {
                continue;
            }
        }
        return false;
    }
    return true;
}

4. compareTo() 比较

  • 比较两个字符串。
  1. 从源码中可以看出,compareTo() 方法会循环对比所有的字符,当两个字符串中有任意一个字符不相同时,则 return char1-char2
  2. 比如,两个字符串分别存储的是 12,返回的值是 -1;如果存储的是 11,则返回的值是 0 ,如果存储的是 21,则返回的值是 1
System.out.println("1".compareTo("2"));// -1
System.out.println("A".compareTo("Aa"));// -1
System.out.println("A".compareTo("a"));// -32
/**
 * 按字典顺序比较两个字符串。比较是基于字符串中每个字符的Unicode值。
 * 在字典上比较此{@code String}对象表示的字符序列与自变量字符串表示的字符序列。
 * 如果此{@code String}对象在字典上在参数字符串之前,则结果为负整数。
 * 如果此{@code String}对象在字典上紧随参数字符串,则结果为正整数。
 * 如果字符串相等,则结果为零;否则,结果为零。 
 * {@code compareTo}恰好在{@link #equals(Object)}方法返回{@code true}时返回{@code 0}。
 */
public int compareTo(String anotherString) {
    int len1 = value.length;
    int len2 = anotherString.value.length;
	// 获取到两个字符串长度最短的那个 int 值
    int lim = Math.min(len1, len2);
    char v1[] = value;
    char v2[] = anotherString.value;

    int k = 0;
    while (k < lim) {
        char c1 = v1[k];
        char c2 = v2[k];
        // 对比每一个字符
        if (c1 != c2) {
        	// 返回字符差
            return c1 - c2;
        }
        k++;
    }
    // 返回长度差(0为完全比配)
    return len1 - len2;
}

5. compareToIgnoreCase() 比较忽略大小写

  • 忽略大小写后比较两个字符串。
/**
 * 按字典顺序比较两个字符串,忽略大小写差异。
 * 此方法返回一个整数,该整数的符号为用规范化的字符串形式调用{@code compareTo} 
 * 通过在上调用{@code Character.toLowerCase(Character.toUpperCase(character))}消除了大小写差异每个字符。
 */
public int compareToIgnoreCase(String str) {
	// CASE_INSENSITIVE_ORDER = new CaseInsensitiveComparator();// 不区分大小写顺序
    return CASE_INSENSITIVE_ORDER.compare(this, str);
}

5.1 compare() 比较
public int compare(String s1, String s2) {
    int n1 = s1.length();
    int n2 = s2.length();
    int min = Math.min(n1, n2);
    for (int i = 0; i < min; i++) {
        char c1 = s1.charAt(i);
        char c2 = s2.charAt(i);
        if (c1 != c2) {
            c1 = Character.toUpperCase(c1);
            c2 = Character.toUpperCase(c2);
            if (c1 != c2) {
                c1 = Character.toLowerCase(c1);
                c2 = Character.toLowerCase(c2);
                if (c1 != c2) {
                    // No overflow because of numeric promotion
                    return c1 - c2;
                }
            }
        }
    }
    return n1 - n2;
}

6. equals()compareTo() 区别

  1. equals() 可以接收一个 Object 类型的参数,而 compareTo() 只能接收一个 String 类型的参数。
  2. equals() 返回值为 boolean,而 compareTo() 的返回值则为 int
  3. equals() 方法返回 true 时,或者是 compareTo() 方法返回 0 时,则表示两个字符串完全相同。

三、String 常用方法

1. length() 字符串长度

查询字符串的长度。


2. trim()

去掉字符串首尾空格,底层实现 substring()


3. concat() 拼接

字符串拼接。


4. contains() 包含

查询字符串中是否包含另一个字符串。


5. indexOf() 字符下标

查询字符串首次出现的下标位置。


6. lastIndexOf()

查询字符串最后出现的下标位置。


7. toLowerCase() 转换小写

把字符串全部转换成小写。

8. toUpperCase() 转换大写

把字符串全部转换成大写


9. replace() 替换

替换字符串中的某些字符


10. replaceFirst()

用给定的替换项,替换与给定的正则表达式匹配的,该字符串的第一个子字符串


11. replaceAll()

用给定的替换项,替换与给定的正则表达式匹配的,该字符串的每个子字符串


12. split()

把字符串分割并返回字符串数组。


13. join() 合并

把字符串数组转为字符串。

org.apache.commons.lang3.StringUtils

14. substring() 截取

字符串截取。


四、String 知识扩展


1. ==equals() 区别

  1. == 对于基本数据类型来说,是用于比较 值是否相等 的;而对于引用类型来说,是用于比较 引用地址是否相同 的。
  2. Object 中的 equals() 方法其实就是 ==,而 String 重写了 equals() 方法,把它修改成比较两个字符串的值是否相等。

2. final 修饰好处

  1. Java 语言之父 James Gosling 的回答是,他会更倾向于使用 final,因为它能够缓存结果。
  1. 当你在传参时不需要考虑谁会修改它的值。
  2. 如果是可变类的话,则有可能需要重新拷贝出来一个新值进行传参,这样在性能上就会有一定的损失。
  1. James Gosling 还说,迫使 String 类设计成不可变的另一个原因是 安全
  1. 当你在调用其他方法时,比如调用一些系统级操作指令之前,可能会有一系列校验。
  2. 如果是可变类的话,可能在你校验过后,它的内部的值又被改变了,这样有可能会引起严重的系统崩溃问题。
  3. 这是迫使 String 类设计成不可变类的一个重要原因。
  1. 总结来说,使用 final 修饰,第一个好处是 安全;第二个好处是 高效

2.1 JVM 中字符串常量池
String s1 = "java";
String s2 = "java";
  • JVM 中的字符串常量池。
  1. 只有字符串是不可变时,我们才能实现字符串常量池。
  2. 字符串常量池可以为我们缓存字符串,提高程序的运行效率。
  3. 如果 String 是可变的,当 s1 的值修改之后,s2 的值也跟着改变了,这样就和我们预期的结果不相符了,因此也就没有办法实现字符串常量池的功能了。
    在这里插入图片描述

3. StringStringBufferStringBuilder 区别


3.1 StringBuffer 安全
  • 因为 String 类型是不可变的,所以在字符串拼接的时候,如果使用 String 的话性能会很低。
  • 因此就需要使用另一个数据类型 StringBuffer
  1. 它提供了 append()insert() 方法可用于字符串的拼接。
  2. StringBuilder使用 synchronized 来保证线程安全
@Override
public synchronized StringBuffer append(String str) {
    toStringCache = null;
    super.append(str);
    return this;
}

@Override
public synchronized StringBuffer insert(int offset, String str) {
    toStringCache = null;
    super.insert(offset, str);
    return this;
}

3.2 StringBuilder
  • 因为 StringBuffer 使用了 synchronized 来保证线程安全,所以性能不是很高。
  • 于是在 JDK-1.5 就有了 StringBuilder
  1. 它同样提供了 append()insert() 的拼接方法。
  2. 但它没有使用 synchronized 来修饰,因此在性能上要优于 StringBuffer,所以在非并发操作的环境下可使用 StringBuffer 来进行字符串拼接。

4. StringJVM


4.1 String 两种创建方式
  • String 常见的创建方式有两种。
  1. new String() 和 直接赋值 的方式
  1. 直接赋值的方式会先去字符串常量池中查找是否已经有此值,如果有则把引用地址直接指向此值,否则会先在常量池中创建,然后再把引用指向此值。
  2. new String() 的方式一定会先在堆上创建一个字符串对象,然后再去常量池中查询此字符串的值是否已经存在,如果不存在会先在常量池中创建此字符串,然后把引用的值指向此字符串。
String s1 = new String("Java");
String s2 = s1.intern();
String s3 = "Java";

System.out.println(s1 == s2); // false
System.out.println(s2 == s3); // true

  • 在 JVM 存储的位置,如下图所示:
  1. 小贴士:JDK-1.7 之后,把 永生代 换成 元空间
  2. 字符串常量池方法区 移到了 Java堆
    在这里插入图片描述

4.2 编译器对 String 字符串优化
String s1 = "Ja" + "va";
String s2 = "Java";

System.out.println(s1 == s2);// true
  • 虽然 s1 拼接了多个字符串,但对比的结果却是 true
  • 我们使用反编译工具,看到的结果如下:
  1. 从编译代码 #2 可以看出,代码 "Ja"+"va" 被直接编译成了 "Java"
  2. 因此 s1==s2 的结果才是 true,这就是编译器对字符串优化的结果。
Compiled from "StringExample.java"
public class com.lagou.interview.StringExample {
  public com.lagou.interview.StringExample();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return
    LineNumberTable:
      line 3: 0

  public static void main(java.lang.String[]);
    Code:
       0: ldc           #2                  // String Java
       2: astore_1
       3: ldc           #2                  // String Java
       5: astore_2
       6: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
       9: aload_1
      10: aload_2
      11: if_acmpne     18
      14: iconst_1
      15: goto          19
      18: iconst_0
      19: invokevirtual #4                  // Method java/io/PrintStream.println:(Z)V
      22: return
    LineNumberTable:
      line 5: 0
      line 6: 3
      line 7: 6
      line 8: 22
}

5. String.intern() 方法


五、String 小结

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

骑士梦

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

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

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

打赏作者

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

抵扣说明:

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

余额充值