String

阅读更多
String - JDK 1.8.0131


一、类定义

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


2.分析

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

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


4.设计模式
享元模式

二、类的注释

1.源码
/**
 * 

 *     String str = "abc";
 * 

* is equivalent to: *

 *     char data[] = {'a', 'b', 'c'};
 *     String str = new String(data);
 * 

*/



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
     * String object is computed as
     * 
     * s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
     * 
* using int arithmetic, where s[i] is the * ith character of the string, n is the length of * the string, and ^ 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 beginIndex and
     * extends to the character at index endIndex - 1.
     * Thus the length of the substring is endIndex-beginIndex.
     * 

* Examples: *

     * "hamburger".substring(4, 8) returns "urge"
     * "smiles".substring(1, 5) returns "mile"
     * 
* * @param beginIndex the beginning index, inclusive. * @param endIndex the ending index, exclusive. * @return the specified substring. * @exception IndexOutOfBoundsException if the * beginIndex is negative, or * endIndex is larger than the length of * this String object, or * beginIndex is larger than * endIndex. */ 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 Unicode
     * code units 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)