ZB-034-01字符串原理

Java字符串详解

字符串是最重要的引用类型之一

  • 互联网基本只干一件事:处理字符串
    • 你随便打开一个网址
    • 为什么很少听说 拿 C/C++ 去写一个互联网应用 ,因为 它们的字符串处理简直是灾难
  • 能处理好字符串是web服务器的基本要求
    • Php
    • Python
    • Java
    • Ruby

非常推荐把 Java的 String 类 通读一遍

  • 不要怕它是英文的注释 仔细想想 谁一开始就什么都会的呢? 谁一开始就会英语的呢?
  • 一个东西如果难 说明它是一个机遇,说明别人干不了这事,而你能干这事说明你比别人厉害
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69

/**
* The {@code String} class represents character strings. All
* string literals in Java programs, such as {@code "abc"}, are
* implemented as instances of this class.
* <p>
* Strings are constant; their values cannot be changed after they
* are created. String buffers support mutable strings.
* Because String objects are immutable they can be shared. For example:
* <blockquote><pre>
* String str = "abc";
* </pre></blockquote><p>
* is equivalent to:
* <blockquote><pre>
* char data[] = {'a', 'b', 'c'};
* String str = new String(data);
* </pre></blockquote><p>
* Here are some more examples of how strings can be used:
* <blockquote><pre>
* System.out.println("abc");
* String cde = "cde";
* System.out.println("abc" + cde);
* String c = "abc".substring(2,3);
* String d = cde.substring(1, 2);
* </pre></blockquote>
* <p>
* The class {@code String} includes methods for examining
* individual characters of the sequence, for comparing strings, for
* searching strings, for extracting substrings, and for creating a
* copy of a string with all characters translated to uppercase or to
* lowercase. Case mapping is based on the Unicode Standard version
* specified by the {@link java.lang.Character Character} class.
* <p>
* The Java language provides special support for the string
* concatenation operator (&nbsp;+&nbsp;), and for conversion of
* other objects to strings. String concatenation is implemented
* through the {@code StringBuilder}(or {@code StringBuffer})
* class and its {@code append} method.
* String conversions are implemented through the method
* {@code toString}, defined by {@code Object} and
* inherited by all classes in Java. For additional information on
* string concatenation and conversion, see Gosling, Joy, and Steele,
* <i>The Java Language Specification</i>.
*
* <p> Unless otherwise noted, passing a <tt>null</tt> argument to a constructor
* or method in this class will cause a {@link NullPointerException} to be
* thrown.
*
* <p>A {@code String} represents a string in the UTF-16 format
* in which <em>supplementary characters</em> are represented by <em>surrogate
* pairs</em> (see the section <a href="Character.html#unicode">Unicode
* Character Representations</a> in the {@code Character} class for
* more information).
* Index values refer to {@code char} code units, so a supplementary
* character uses two positions in a {@code String}.
* <p>The {@code String} class provides methods for dealing with
* Unicode code points (i.e., characters), in addition to those for
* dealing with Unicode code units (i.e., {@code char} values).
*
* @author Lee Boynton
* @author Arthur van Hoff
* @author Martin Buchholz
* @author Ulf Zibis
* @see java.lang.Object#toString()
* @see java.lang.StringBuffer
* @see java.lang.StringBuilder
* @see java.nio.charset.Charset
* @since JDK1.0
*/

@see 的意思就是为了了解当前的类,你可能需要看看别的类(参考文献/友情链接)

在 IDEA 里 一个对象长什么样 是由它的 toString 决定的

1
2
3
4
5
6
class MyObject{
@Override
public String toString() {
return "hahaha";
}
}

字符串的不可变性(它是引用类型)

  • 为什么字符串是不可变的?
    • 安全:线程安全,存储安全
  • 缺点,每当修改的时候都需要重复创建新的对象

String 为什么不可变

API 层面

  • 它肚子里有个 字符数组的容器 private final char value[]
  • class String 它是 final
  • char value[] 它是 final 的 这个数组不能改变指向
  • String 类中 所有公开的 API 方法中 都没提供改变 value 值的操作,所以 String 是不可变的
1
2
3
4
5
6
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final char value[];
...
}

不可变的真实原因

  1. 在一个 HashMap 里,它有哈希值,它有若干个 每个桶里存固定的 hashCode
  2. 假如你声明了一个 HashMap<String,Object> 会把这个 String 算一个 hashCode() 会被当作 key 存到 桶里去
  3. Object.hashCode() java世界约定 把一个对象映射成一个整数,方便进行高效的哈希表查找,它有三个约定

    • 同一对象无论何时,在 java的整个生命周期里,必须一致性的返回相同的整数 就因为这个规定,导致String不可变
    • 如果两个对象相等那么必须返回一样的 hashCode
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      // 代码证明

      String x = "a" // 假设此时 hashCode 是 1
      x = "ab" // 此时我们让他的 HashCode 变吗?
      // 如果变 就违反了 hashCode() 的第一个约定
      // 如果不变 就违反了 第二个约定

      // 所以
      String x = "a" 此时它是一个对象 地址 111
      x = "ab" 此时重新生成了一个对象 地址(引用)从111指向了 200
  4. 为了让我们在哈希桶中 安全的使用 String , String必须不可变

我就要修改 String 怎么办?

千万不要这样

1
2
3
4
5
6
7
String s = "0";
for (int i = 0; i < 10; i++) {
s = s + i;
}

// 每次循环都会创建 零碎的小对象,会给内存带来压力
// 所以这是不可变性带来的缺陷

StringBuilder 字符可变序列

  • 不能当作 Hash桶的 key,因为它是可变的
  • 线程不安全
1
2
3
4
5
StringBuilder s = new StringBuilder("0");
for (int i = 0; i < 10; i++) {
// 可以级联 操作
s.append(i).append("-").append("-");
}

判断回文

1
2
3
System.out.println(
new StringBuilder("ABA").reverse().toString().equals("ABA")
);

StringBuffer

  • 可变
  • 线程安全(有 同步问题 慢),可变字符序列

StringBuffer 和 StringBuilder 区别

  • 它们都可变
  • StringBuffer 线程安全 / StringBuilder 线程不安全

那我要修改怎么办?

  • StringBuilder
    • 优先使⽤,线程不安全,速度快
  • StringBuffer
    • 线程安全,速度相对较慢

字符串的内部构造和常⽤API

  • 不可变性是如何保证的?
  • hash值是如何存储的?

    • 缓存 因为已经不可变了,没必要重新生成
    • String 源码里 的 hash 变量 就是用来缓存 hash值的
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      /** Cache the hash code for the string */
      private int hash; // Default to 0 默认是0

      public int hashCode() {
      int h = hash;
      // 如果 hash 是默认值0 就计算一下,不等于0 代表已经计算过了
      if (h == 0 && value.length > 0) {
      char val[] = value;

      for (int i = 0; i < value.length; i++) {
      h = 31 * h + val[i];
      }
      hash = h;
      }
      return h;
      }
      /*
      第一次访问的时候 hash=0(默认值) ,
      然后它会被创建出来 hash 被计算出来 ,以后在被访问就直接用
      这就是 hashCode 在 String 中的实现


      重点来了
      第一次访问的时候 它会被创建出来 ,以后在被访问就直接用

      如果多线程呢?
      潜在问题 假如两个线程 同时访问 时候 当前这俩都是 hash = 0
      然后同时计算,并创建 hash = xxx

      虽然有线程安全问题,但是不要紧
      因为 即使存在100个线程同时访问,但是 算出的结果都是一样的

      虽然执行过程不是线程安全的,但是 算出的 hash 结果一致。
      */
  • 我如何使⽤字符串随⼼所欲地完成⼯作?

  • 还不够的,StringUtils补上