欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

String

程序员文章站 2022-05-09 22:21:21
...
String - JDK 1.8.0131


一、类定义

1.源码
public final class String implements java.io.Serializable, Comparable<String>, CharSequence


2.分析

a.类定义由final修饰,String 类不可继承
b.实现  Serializable 接口,表示 String 是序列化,String 修饰的内容的状态会保存在内存中
c.实现  Comparable 接口,实现该接口中的字符串比较方法
d.实现  CharSequence 接口,字符串实质是一个 char[]

3.总结
  • String 对象的内容是不可变的,所以String是线程安全的
  • 任何字符串内容的改变都将返回新的字符串,而原字符串保持不变


4.设计模式
享元模式

二、类的注释

1.源码
/**
 * <p><blockquote><pre>
 *     String str = "abc";
 * </pre></blockquote><p>
 * is equivalent to:
 * <p><blockquote><pre>
 *     char data[] = {'a', 'b', 'c'};
 *     String str = new String(data);
 * </pre></blockquote><p>
 */



String str = "abc" ;
<==>
char data[] = {'a', 'b', 'c'};
String str = new String(data);

2.分析
a.str 是引用,存在栈中,其存储指向对象"abc"在堆中的位置;
b.堆中的对象"abc",实质为拥有如下属性的对象
  • char [] data
  • int offset
  • int count
  • int hash

c.char [] data 依然是一个引用,其存储的是指向堆中另外一块存储 {'a','b','c'}的堆中空间的地址


三、成员变量

1.源码
    /** The value is used for character storage. */
    // 字符串实质是 char 类型的数组
    private final char value[];

    /** The offset is the first index of the storage that is used. */
    // 指定 数组中需要使用的元素的第一个位置,默认 0,截取使用子串时,指定起始位置 
    private final int offset;

    /** The count is the number of characters in the String. */
    // 字符串的长度
    private final int count;

    /** Cache the hash code for the string */
    // 字符串的 hash 值
    private int hash; // Default to 0

    /** use serialVersionUID from JDK 1.0.2 for interoperability */
    // 继承 Serializable 时,Eclipse 编译器提示生成唯一性的标识
    private static final long serialVersionUID = -6849794470754667710L;



2.分析

2.1 String的对象不可变但引用可变

字符串实质是 char [] 即char 类型的数据,且使用 final 修饰,说明字符串是不可变的

字符串不可改变是指字符串的对象不可变;而字符串的引用是可以改变的;
String str = "a" ;
str = "b" ;


对象 "a" 的内容并没有改变,而是 str 中存放的指向堆中对象的地址的值变了。即原来存储的是 "a" 所在的地址,现在指向了 "b" 的地址。
每次的重新赋值都会生成一个新的String对象。原有的String对象等待GC回收。
生成新对象的 char [] value 是对原参数(传入参数)的copy后进行操作,所以对原有字符串的修改不会影响到调用String类中方法得到的返回值。

2.2 实现 Serializable 接口作用

Serializable

2.3 hash值计算


    /**
     * Returns a hash code for this string. The hash code for a
     * <code>String</code> object is computed as
     * <blockquote><pre>
     * s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
     * </pre></blockquote>
     * using <code>int</code> arithmetic, where <code>s[i]</code> is the
     * <i>i</i>th character of the string, <code>n</code> is the length of
     * the string, and <code>^</code> indicates exponentiation.
     * (The hash value of the empty string is zero.)
     *
     * @return  a hash code value for this object.
     */
    public int hashCode() {
	int h = hash;
	if (h == 0) {
	    int off = offset;
	    char val[] = value;
	    int len = count;

            for (int i = 0; i < len; i++) {
                h = 31*h + val[off++];
            }
            hash = h;
        }
        return h;
    }



总结:
String str ==> char[] data
hash = data[0] * 31 ^ (n-1 )+ data[1]*31^(n-2)+...+data[n-1]
data[0] 表示该字符的 ascii 值对应的十进制数值

	public static void main(String[] args) {
		
		String a = "123";
		System.out.println(a.hashCode());
                // 48690
       }

分析:
h1 = 31 * 0 + 49 (49 是 1 对应的 ascii 值)
h2 = 31 * 49 + 50
h3 = 31 * ( 31 * 49 + 50 ) + 51
   = 31 ^ (3 -1) * 49 + 31 ^(3 -2)*50 + 31 ^(3-3)*51

作用:
相同前缀的字符串生成的hash值要相邻,便于比较、查找

关于hashCode()计算过程中,为什么使用了数字31,主要有以下原因:
1、使用质数计算哈希码,由于质数的特性,它与其他数字相乘之后,计算结果唯一的概率更大,哈希冲突的概率更小。

2、使用的质数越大,哈希冲突的概率越小,但是计算的速度也越慢;31是哈希冲突和性能的折中,实际上是实验观测的结果。

3、JVM会自动对31进行优化:31 * i == (i << 5) – i

详解 equals() 方法和 hashCode() 方法

四、构造方法

1.无参数构造方法
    /**
     * Initializes a newly created {@code String} object so that it represents
     * an empty character sequence.  Note that use of this constructor is
     * unnecessary since Strings are immutable.
     */
    // 无参构造函数
    public String() {
	this.offset = 0;
	this.count = 0;
	this.value = new char[0];
    }



2.参数为字符串
    /**
     * Initializes a newly created {@code String} object so that it represents
     * the same sequence of characters as the argument; in other words, the
     * newly created string is a copy of the argument string. Unless an
     * explicit copy of {@code original} is needed, use of this constructor is
     * unnecessary since Strings are immutable.
     *
     * @param  original
     *         A {@code String}
     */
    public String(String original) {
	int size = original.count;
	char[] originalValue = original.value;
	char[] v;
  	if (originalValue.length > size) {
 	    // The array representing the String is bigger than the new
 	    // String itself.  Perhaps this constructor is being called
 	    // in order to trim the baggage, so make a copy of the array.
            int off = original.offset;
            v = Arrays.copyOfRange(originalValue, off, off+size);
 	} else {
 	    // The array representing the String is the same
 	    // size as the String, so no point in making a copy.
	    v = originalValue;
 	}
	this.offset = 0;
	this.count = size;
	this.value = v;
    }    



解析:originalValue.length > size 的情况

    /**
     * Returns a new string that is a substring of this string. The
     * substring begins at the specified <code>beginIndex</code> and
     * extends to the character at index <code>endIndex - 1</code>.
     * Thus the length of the substring is <code>endIndex-beginIndex</code>.
     * <p>
     * Examples:
     * <blockquote><pre>
     * "hamburger".substring(4, 8) returns "urge"
     * "smiles".substring(1, 5) returns "mile"
     * </pre></blockquote>
     *
     * @param      beginIndex   the beginning index, inclusive.
     * @param      endIndex     the ending index, exclusive.
     * @return     the specified substring.
     * @exception  IndexOutOfBoundsException  if the
     *             <code>beginIndex</code> is negative, or
     *             <code>endIndex</code> is larger than the length of
     *             this <code>String</code> object, or
     *             <code>beginIndex</code> is larger than
     *             <code>endIndex</code>.
     */
    public String substring(int beginIndex, int endIndex) {
	if (beginIndex < 0) {
	    throw new StringIndexOutOfBoundsException(beginIndex);
	}
	if (endIndex > count) {
	    throw new StringIndexOutOfBoundsException(endIndex);
	}
	if (beginIndex > endIndex) {
	    throw new StringIndexOutOfBoundsException(endIndex - beginIndex);
	}
	return ((beginIndex == 0) && (endIndex == count)) ? this :
	    new String(offset + beginIndex, endIndex - beginIndex, value);
    }



当调用截取字符串的子串的方法时,
return new String 时  value 依然是原字符串的 char [] 数组

举例:


String str = "abcde";
// char [] value = {'a','b','c','d','e'};
// offset = 0 ; 起始位置
// count = 5 
String strSub = str.substring(0,3);
// 此时的 char [] value 依然是 str 的 char [] value 
// offset = 0 
// count = 3 
String strSubNew = new String(strSub);
// char [] value 的长度 为 5 
// count = 3 
// 所以 char [] data 的长度会大于 string str 的长度



3.参数为字符数组

    public String(char value[]) {
	int size = value.length;
	this.offset = 0;
	this.count = size;
	this.value = Arrays.copyOf(value, size);
    }


返回的是当前字符数组的拷贝,原数组的变更,不会对新的字符串产生影响。

4.参数为字符数组、起始位置、截止位置
    public String(char value[], int offset, int count) {
        if (offset < 0) {
            throw new StringIndexOutOfBoundsException(offset);
        }
        if (count < 0) {
            throw new StringIndexOutOfBoundsException(count);
        }
        // Note: offset or count might be near -1>>>1.
        if (offset > value.length - count) {
            throw new StringIndexOutOfBoundsException(offset + count);
        }
        this.offset = 0;
        this.count = count;
        this.value = Arrays.copyOfRange(value, offset, offset+count);
    }



相比与上一个构造方法,多了一步起始位置是否大于  字符串长度减去需要截止长度
的校验

五、substring()

截取子字符串

1.一个参数:起始位置
public String substring(int beginIndex) {
// 结束位置:默认字符串长度
return substring(beginIndex, count);
}

String s = "0123456789";
System.out.println(s.substring(2));// 2 - 9



2.两个参数:起始位置,结束位置
public String substring(int beginIndex, int endIndex) {
if (beginIndex < 0) {
    throw new StringIndexOutOfBoundsException(beginIndex);
}
if (endIndex > count) {
    throw new StringIndexOutOfBoundsException(endIndex);
}
if (beginIndex > endIndex) {
    throw new StringIndexOutOfBoundsException(endIndex - beginIndex);
}
return ((beginIndex == 0) && (endIndex == count)) ? this :
	    new String(offset + beginIndex, endIndex - beginIndex, value);
}


String s = "0123456789";
System.out.println(s.substring(2));// 2 - 9
		
System.out.println(s.substring(3,6)); // 3 -5



总结:
0.起始位置,从0开始
1.字符串截取,包含头部,不包含尾部,即[a,b) 左开右闭的集合

分析:System.out.println(s.substring(3,6)); 的结果不是 3 到 6 ,而是 3 到 5

由 源码上分析  new String(0+3,3,value);

调用 new String

String(int offset, int count, char value[]) {
this.value = value;
this.offset = offset;
this.count = count;
}



public String(String original) {
int size = original.count;
char[] originalValue = original.value;
char[] v;
if (originalValue.length > size) {
    // The array representing the String is bigger than the new
    // String itself.  Perhaps this constructor is being called
    // in order to trim the baggage, so make a copy of the array.
    int off = original.offset;
    v = Arrays.copyOfRange(originalValue, off, off+size);
} else {
   // The array representing the String is the same
   // size as the String, so no point in making a copy.
    v = originalValue;
}
this.offset = 0;
this.count = size;
this.value = v;
}


v = Arrays.copyOfRange(originalValue, off, off+size);
==>  Arrays.copyOfRange(originalValue, 3, 6);

调用

public static char[] copyOfRange(char[] original, int from, int to) {
int newLength = to - from;
if (newLength < 0)
   throw new IllegalArgumentException(from + " > " + to);
   char[] copy = new char[newLength];
   System.arraycopy(original, from, copy, 0,
               Math.min(original.length - from, newLength));
    return copy;
}


System.arraycopy(original, from, copy, 0,
                         Math.min(original.length - from, newLength));
==》 System.arraycopy(original, from, copy, 0,3 ); // 长度是3

即:char [] data = {'0','1','2','3','4','5','6','7','8','9'};
substring(3,6);是从 data[3] 开始取值,长度是 6 -3 ,data[3] data[4] data[5],
而不包含 data[6]

六、两种构造方式

1.
String str = "a" ;
String str = new String("a");

2.
前者生成一个对象,判断字符串常量池是否已存在 "a" ,若不存在,则在常量池中存入 "a";若存在则直接取到常量池中 "a"  的地址

后者
String str = "a" ; str = new String("a");
先去常量值中查找是否已经存在 "a" ,无则生成一个对象 "a" ; 有则,直接取出;
在堆中开辟一个空间,内容为 "a" , str 中存放堆中对象 "a" 的地址;
常量池中的 "a" 对象,等待GC回收。

七、intern()

1.源码
// 如果字符串常量池中已经有了此字符串,则直接返回;否则,在常量池中加入此字符串,并返回此对象的引用
public native String intern();


2.测试
String str2 = new String("a");
String str1 = "a" ;
System.out.println(str1 == str2 );
System.out.println(str1 == str2.intern());



str1 == str2 ; 结果为 false ,
str1 存储的是常量池中 "a" 的地址引用
str2 存储的是堆中对象 "a" 的地址引用,一定不等;

str1 == str2.intern() ; 从常量池中取出 str2 指向的 "a" 的地址
str1.equals(str2) 两者 的内容相同,所以在常量池中都指向了 "a" 的地址,所以为true

八、+ 连接操作

1.源码
String s1 = "a" ;
String s2 = "a" + "b" ;
String s3 = "a" + 1 + 2  ;
// 编译期不可确认内容:
String s4 = "a" + s1 ; // s1 存放对象的地址,无法确认地址中的内容
// 相当于
StringBuilder s5 = new StringBuilder("a");
s5.append(s1) ;
// 即 	字符串常量与变量相互拼接时,内部的操作实质为 StringBuilder 进行相应的 append 操作
// + 一次产生一个 StringBuilder 对象


// 多次拼接,每一次拼接  产生一个 StringBuilder 对象
String s6 = "" ;
for(int index = 0 ; index < 100 ; index++){
s6 = s6 + index ;
}
// 只产生一个 StringBuilder 对象
StringBuilder s7 = new StringBuilder("");
for(int index = 0 ; index < 100 ; index++){
s7.append(index);
}


2.总结
运行期能够确认的内容,存放堆中;
变量与变量或常量、字面量的组合拼接,等于new新创建对象;
因为在编译期无法确认变量所代表的常量值

九、trim()
1.源码
    public String trim() {
	int len = count;
	int st = 0;
	int off = offset;      /* avoid getfield opcode */
	char[] val = value;    /* avoid getfield opcode */

	while ((st < len) && (val[off + st] <= ' ')) {
	    st++;
	}
	while ((st < len) && (val[off + len - 1] <= ' ')) {
	    len--;
	}
	return ((st > 0) || (len < count)) ? substring(st, len) : this;
    }



2.分析

u0020 ascii 表中 表示空格,其前面有31个字符;<= 32(十进制) 的 ascii 均认为是空格

ascii 中 0 表示 空白字符 ;1 ~ 32 为控制字符;

起始位置:正数连续的空格的数量
截止位置:倒数连续的空格的数量

若  st != 0 说明起始位置有连续的空格
若  len < count 说明尾部有连续的空格

生成子串,即生成新的字符串。

3.总结

'\u0020' 表示空格,\r  \n  \t 等等 均小于 '\u0020' ;

十、length()

1.源码
    /**
     * Returns the length of this string.
     * The length is equal to the number of <a href="Character.html#unicode">Unicode
     * code units</a> in the string.
     *
     * @return  the length of the sequence of characters represented by this
     *          object.
     */
// 字符串中代码单元的长度
    public int length() {
        return count;
    }




    public String(char value[]) {
	int size = value.length;
	this.offset = 0;
	this.count = size;
	this.value = Arrays.copyOf(value, size);
    }




count 即为  char[] value 的长度,size

2.总结

length 方法,是指字符串中代码单元的数量;

代码点指编码表(比如Unicode)中某个字符的代码值(数字),书写时前面加U+,比如U+0041是字母A的代码点

java中的代码单元指表示编码表字符的最小存储单元,用16位表示。
UTF16编码
代码点与代码单元
相关标签: String