`
小尧Eric
  • 浏览: 2352 次
  • 性别: Icon_minigender_1
  • 来自: 济南
社区版块
存档分类
最新评论

Java类库复习——java.lang.String

阅读更多

    今年终于重新回到开发岗位继续我的程序员生涯,但时隔一年半,很多都有些生疏了,仔细读了一遍<Thinking In Java>,发现很多基本技术和思想其实从一开始就没研究明白。决定重新对照着书本,jdk和源码把Java中基本的类库研读总结下。

    第一篇就先从最简单最基本的String开始吧!

    博客的对代码的排版似乎不好,附件里附上txt格式的内容,可读性会好些。

 

1.String的基本特性

    我们在class中找到java.lang.String,先来看String的基本特性。

 

    String类的定义是这样的:public final class String implements java.io.Serializable, Comparable<String>, CharSequence

    从以上定义,我们可以看出String的几个基本特性:

    ·String是一个类。(似乎是废话。主要与其他基本类型分开,Java中的类型似乎只有String与Enum是类形式定义的。)

    ·String是只读的常量,具有不可变性;不可被继承。(修饰为final)

    ·String类型是可序列化的,可比较的字符序列。(implements java.io.Serializable, Comparable<String>, CharSequence)

    再来看一下Sting类中的定义的几个基本field

    private final char value[];//String的值,String的本质就是char型数组

    private final int offset;//子数组第一个字符的索引

    private final int count;//字符串中的字符数量

    private int hash; //对象的hash code


    经过上面的分析,其实我们已经能看出String对象的本质是一个经过封装的char型数组,封装后提供了一些方便操作的方法。

 

    

2.Sting常用方法及实现(大部分注释选自JavaSE5中文API说明)

    2.1.equals(Object anObject)

 

 

            /**
	 * 重写equals方法 比较此字符串与指定的对象。 
	 * 当且仅当该参数不为 null,并且是表示与此对象相同的字符序列的 String对象时,结果才为true。
	 * @param anObject 与此 String 进行比较的对象。
	 * @return 如果 String 相等,则返回 true;否则返回 false。
	 * @see java.lang.Object#equals(java.lang.Object)
	 */
	public boolean equals(Object anObject) {
		if (this == anObject) {
			return true;
		}
		if (anObject instanceof String) {// 先判断类型
			String anotherString = (String) anObject;
			int n = count;
			if (n == anotherString.count) {// 再判断数组容量是否相同
				char v1[] = value;
				char v2[] = anotherString.value;
				int i = offset;
				int j = anotherString.offset;
				while (n-- != 0) {// 再遍历判断每一个字符
					if (v1[i++] != v2[j++])
						return false;
				}
				return true;
			}
		}
		return false;
	}

    2.2.compareTo(String anotherString)方法

 

       /**
	 * 重写compreTo方法 按字典顺序比较两个字符串。 该比较基于字符串中各个字符的 Unicode 值。 将此 String
	 * 对象表示的字符序列与参数字符串所表示的字符序列进行比较。如果按字典顺序此 String 对象在参数字符串之前,则比较结果为一个负整数。
	 * 如果按字典顺序此 String 对象位于参数字符串之后,则比较结果为一个正整数。 如果这两个字符串相等,则结果为 0;compareTo
	 * 只有在方法 equals(Object) 返回 true 时才返回 0。 这是字典排序的定义。
	 * 如果这两个字符串不同,则要么它们在某个索引处具有不同的字符,该索引对二者均为有效索引;要么它们的长度不同;或者同时具备上述两种情况。
	 * 如果它们在一个或多个索引位置上具有不同的字符,
	 * 假设 k 是这类索引的最小值;则按照 < 运算符确定的那个字符串在位置 k上具有较小的值,其字典顺序在其他字符串之前。
	 * 这种情况下,compareTo 返回这两个字符串在位置 k 处的两个不同的 char 值,即值:
	 * this.charAt(k)-anotherString.charAt(k)
	 * 如果它们没有不同的索引位置,则较短字符串在字典顺序上位于较长字符串的前面。 这种情况下,compareTo 返回这两个字符串长度的不同,即值:
	 * this.length()-anotherString.length()
	 * @param anotherString 要比较的 String。
	 * @return 如果参数字符串等于此字符串,则返回 0 值;
	 * 			如果按字典顺序此字符串小于字符串参数,则返回一个小于 0 的值;
	 * 			如果按字典顺序此字符串大于字符串参数,则返回一个大于 0 的值。
	 */
    	public int compareTo(String anotherString) {
		int len1 = count;
		int len2 = anotherString.count;
		int n = Math.min(len1, len2);// 获得两个字符串中较短的长度
		char v1[] = value;
		char v2[] = anotherString.value;
		int i = offset;
		int j = anotherString.offset;
		/* 在一个或多个索引位置上具有不同的字符 */
		if (i == j) {// 偏移量相同
			int k = i;
			int lim = n + i;
			while (k < lim) {
				char c1 = v1[k];
				char c2 = v2[k];
				if (c1 != c2) {
					return c1 - c2;
				}
				k++;
			}
		} else {
			while (n-- != 0) {
				char c1 = v1[i++];
				char c2 = v2[j++];
				if (c1 != c2) {
					return c1 - c2;
				}
			}
		}
		/* 没有不同的索引位置 */
		return len1 - len2;
	}

    前面的注释中,已经把String的排序规则说的很明白了。简单来说就是:1.先判断每个索引处的字符都相同,找到第一个字符不同处的索引,返回此索引处两个字符的差;2.如果每个索引处的字符都相同,判断两个数组的长度,返回两个字符串的长度差。

 

    2.3.startsWith(String prefix, int toffset)

 

    /**
     * 测试此字符串是否以指定前缀开始,该前缀以指定索引开始。
     * @param prefix 前缀
     * @param toffset 在字符串中开始查找的位置。 
     * @return 如果该参数表示的字符序列是此对象从索引 toffset 处开始的子字符串,则返回 true;否则返回 false。
     * 			如果 toffset 为负或大于此 String 对象的长度,则结果为 false;否则结果与该表达式的结果相同。
     */
    public boolean startsWith(String prefix, int toffset) {
		char ta[] = value;
		int to = offset + toffset;
		char pa[] = prefix.value;
		int po = prefix.offset;
		int pc = prefix.count;
		// Note: toffset might be near -1>>>1.
		if ((toffset < 0) || (toffset > count - pc)) {
			return false;
		}
		while (--pc >= 0) {//遍历前缀的字符,与当前字符串进行比较
			if (ta[to++] != pa[po++]) {
				return false;
			}
		}
		return true;
	}
 

    这个方法的实现很简单,需要注意的就是toffset的值不要为负或者大于String对象的长度。


    2.4.String substring(int beginIndex, int 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);
	}

    很常用的一个方法,会截取从beginIndex处到endIndex处的字符串,生成一个新的字符串。从方法实现可以看出,要注意的有两点:1.beginIndex和endIndex两个参数要符合要求,否则会抛出StringIndexOutOfBoundsException。2.截取的字符串包括beginIndex处的字符,不包括endIndex处的字符。

 

     2.5.String trim()

 

   /**
     * 返回字符串的副本,忽略前导空白和尾部空白。 
	  * 如果此 String 对象表示一个空字符序列,或者此 String 对象表示的字符序列的第一个和最后一个字符的代码都大于 '\u0020'(空格字符),
	  * 则返回对此 String 对象的引用。 
	  * 否则,若字符串中没有代码大于 '\u0020' 的字符,则创建并返回一个表示空字符串的新的 String 对象。 
     * 否则,假定 k 为代码大于 '\u0020' 的第一个字符的索引,m 为代码大于 '\u0020' 的最后一个字符的索引。
     * 创建一个新的 String 对象,它表示此字符串中从索引 k 处的字符开始,到索引 m 处的字符结束的子字符串,也就是 this.substring(k, m+1) 的结果。 
     * 此方法用于截去字符串从头到尾的空白(如上面所定义)。 
     * @return 此字符串移除了前导和尾部空白的副本,如果没有前导和尾部空白,则返回此字符串。
     */
    public String trim() {
		int len = count;
		int st = 0;
		int off = offset; 
		char[] val = value; 
		while ((st < len) && (val[off + st] <= ' ')) {
			st++;
		}
		while ((st < len) && (val[off + len - 1] <= ' ')) {
			len--;
		}
		return ((st > 0) || (len < count)) ? substring(st, len) : this;
	}
 
    另一个常用的方法,去除空字符串。可以看到,这个方法的实现就是定位到非空字符串的索引处,然后调用substring方法截取字符串中的非空字符串。(Sun的工程师写代码真是简洁,但读起来有点累啊~)

    2.6.String valueOf()
    我们经常使用String.valueOf()方法将基本类型转换为String型,其中的操作也很简单,就是调用了基本类型的包装类中的toString()方法。比如
public static String valueOf(float f) {
		return Float.toString(f);
}
 
3.String的几点使用须知
    
    3.1.String的不变性。String对象是不可变的,只读的,任何对他的引用都无法改变他的值。我们从上面看的几个String内置方法的实现也可以看出,任何需要改变字符串内容的方法,实际在实现时都是创建了新的String对象作为返回值,而不是改变原有的String对象。(比如substring,trim)
    3.2.String和StringBuilder(StringBuffer)的区别。这是个老生常谈的问题了,在频繁调用的循环中,StringBuilder无疑是有优势的(tips:如果能预先估算字符串的上限,那么预先为StringBuilder分配空间大小是提高效率的好办法),但是正常的简单拼接中,使用String的"+"和"+="真的没有想象的那么“效率低下”,因为编译器会自动调用StringBuilder来帮你实现。如果只是String newString = oldString + "test"这样的语句的话,那么真的没有必要使用StringBuilder实现,在大多数情况下,让“人”舒服比让“机器”舒服更为重要。
    3.3.String的对象引用机制。每个字符串都会在内存中分配地址,String对象实际上是对内存中字符串的引用。比如我们现在有个String a = "a";那么当我们创建一个新的String对象String b = new String("a");时,并没有在内存中分配新的空间来再存储"a",而是将b的引用也指向了原来"a"在内存中的位置。同样的,当没有对象引用指向"a"时,垃圾回收器会对他进行接管。
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics