`

String

阅读更多
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编码
代码点与代码单元
分享到:
评论

相关推荐

    C语言头文件 STRING.H

    C语言头文件 STRING.HC语言头文件 STRING.HC语言头文件 STRING.HC语言头文件 STRING.HC语言头文件 STRING.HC语言头文件 STRING.HC语言头文件 STRING.HC语言头文件 STRING.HC语言头文件 STRING.HC语言头文件 STRING....

    StringtoList和StringtoMap和StringtoObject和StringtoArray

    Map&lt;String, String&gt; map = gson.fromJson(jsonString, new TypeToken&lt;Map&lt;String, String&gt;&gt;(){}.getType()); ``` 4. **String to Object** 如果JSON字符串代表的是一个自定义Java对象,你可以创建一个对应的类...

    C#中char[]与string之间的转换 string 转换成 Char[]

    C#中char[]与string之间的转换 C#中char[]与string之间的转换是一种常见的操作,我们经常需要在这两种数据类型之间进行转换。今天,我们将探讨C#中char[]与string之间的转换,包括string转换成Char[]和Char[]转换成...

    C# String 的各种转换

    ### C# String 的各种转换 在C#编程语言中,字符串与数字之间的转换是非常常见的操作。本文将详细介绍如何在C#中实现字符串与其他数据类型(如整数、浮点数等)之间的转换,并特别关注字符串与十六进制之间的转换。...

    StringAPI.java

    Java String 类型 API 测试代码 1.String和char[]之间的转换 toCharArray(); 2.String和byte[]之间的转换 getBytes() Arrays工具类 : Arrays.toString(names) String类 String replace(char oldChar, ...

    String和string区别以及string详解.doc

    标题与描述中提到的知识点是关于C#编程语言中`String`与`string`的区别,以及`string`类型的深入解析。以下是对这些知识点的详细解释: ### `String`与`string`的区别 #### 1. **位置与来源** - `String`是.NET ...

    String对象创建问题

    在Java编程语言中,`String`对象的创建是开发者经常遇到的问题,因为它涉及到内存管理和效率。`String`类在Java中被广泛使用,因为它代表不可变的字符序列,这使得它在很多场景下非常安全。这里我们将深入探讨`...

    string和char*

    string 和 char* 的区别和联系 在 C++ 编程中,字符串是一种常用的数据类型,string、CString 和 char*都是字符串的 представители,每种类型都有其特点和使用场景。下面我们将详细介绍 string、...

    JavaBean与JsonString的相互转换

    在Java开发中,JavaBean和JsonString是两种常见的数据表示形式。JavaBean是Java对象的一种规范,用于封装数据,而JsonString是一种轻量级的数据交换格式,常用于前后端交互。本篇将深入探讨JavaBean与JsonString之间...

    List转换成String数组

    ### List转换成String数组 在Java编程中,我们经常需要对集合进行操作,尤其是在处理大量字符串数据时。本文将详细介绍如何将一个`List&lt;String&gt;`类型的集合转换为`String[]`数组,并通过不同的方法来实现这一过程。...

    String[] list 相互转化

    ### String[] 与 List 相互转化 在 Java 编程语言中,`String[]` 数组和 `List` 集合之间的相互转换是非常常见的需求。这两种数据结构各有优势:数组提供了固定长度且访问效率高的特性,而列表则支持动态调整大小...

    c++中double与string相互转换算法

    本文将详细讨论如何在C++中将`double`类型的数值转换为`std::string`字符串,以及如何将`std::string`转换回`double`。我们将基于提供的`stringtodouble`工程文件进行讨论。 首先,让我们探讨`double`转`string`的...

    C++实现string存取二进制数据的方法

    在C++编程中,STL(Standard Template Library)的string类是用于处理文本字符串的强大工具。然而,在处理二进制数据时,需要注意string类的一些特性,因为它通常与文本字符串关联,而二进制数据可能包含特殊的字符...

    java基础String类选择题练习题

    根据提供的信息,我们可以总结出这份Java基础String类选择题练习题主要聚焦于String及StringBuffer类的使用。尽管具体的题目内容未给出,但从所展示的信息中可以推断出该练习题集涵盖了以下几方面的知识点: ### 一...

    list转换成string数组

    ### List转换成String数组 在Java编程语言中,经常需要将`List&lt;String&gt;`类型的数据转换为`String[]`数组类型,以便于进行某些特定的操作或适应某些方法的要求。本文将详细探讨这一转换过程,并通过几个具体的示例来...

    public static String[] split(String s, String regex)

    public static String[] split(String s, String regex) s参数为待拆分字符串, regex参数有两种格式: 单字符的字符串(长度1),功能如下:split(“ab#12#453”, “#”) 返回带5个元素的数组:ab, #, 12, #, 453 ...

    String型的不可变性

    "String型的不可变性" Java 中的 String 型是一个特殊的包装类数据,它具有不可变性。什么是不可变性呢?简单来说,就是 String 对象一旦被创建,不能被修改。那么,为什么 String 对象不能被修改呢?这就需要从 ...

    字符串数组转换成string类型的

    在编程领域,尤其是在使用C++、Java或C#等面向对象的语言时,经常需要将字符串数组转换为单一的string类型。这种操作在处理数据输入、输出或者格式化时非常常见。下面我们将详细讨论如何在不同语言中实现这个过程,...

    string 对象 与json互转

    与此相关的,`String`对象是Java编程语言中的基础类型,用于存储和处理文本数据。在实际开发中,我们经常需要在`String`对象与JSON对象之间进行转换,以满足不同的需求。本文将深入探讨`String`与JSON的互转方法,并...

    HexString和Base64String的相互转换

    `HexString`和`Base64String`是两种常见的二进制数据的文本表示形式。理解它们的特性和转换方法对于开发人员来说是非常基础且实用的知识。 首先,`HexString`(十六进制字符串)是一种将二进制数据表示为十六进制...

Global site tag (gtag.js) - Google Analytics