一、概要
无论是在java语言还是C语言等等高级计算机语言中,字符串都是最常使用的类型,所以字符串的类的结构设计应该是经过严格论证的。下面我们就来看看java中的String类是如何设计的?这样设计的优缺点有哪些?
二、源码
先来看看String类的源码
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final char value[];
/** Cache the hash code for the string */
private int hash; // Default to 0
/** use serialVersionUID from JDK 1.0.2 for interoperability */
private static final long serialVersionUID = -6849794470754667710L;
/**
* Class String is special cased within the Serialization Stream Protocol.
*
* A String instance is written into an ObjectOutputStream according to
* <a href="{@docRoot}/../platform/serialization/spec/output.html">
* Object Serialization Specification, Section 6.2, "Stream Elements"</a>
*/
private static final ObjectStreamField[] serialPersistentFields =
new ObjectStreamField[0];
/**
* Allocates a new {@code String} so that it represents the sequence of
* characters currently contained in the character array argument. The
* contents of the character array are copied; subsequent modification of
* the character array does not affect the newly created string.
*
* @param value
* The initial value of the string
*/
public String(char value[]) {
this.value = Arrays.copyOf(value, value.length);
}
/**
* Converts this string to a new character array.
*
* @return a newly allocated character array whose length is the length
* of this string and whose contents are initialized to contain
* the character sequence represented by this string.
*/
public char[] toCharArray() {
// Cannot use Arrays.copyOf because of class initialization order issues
char result[] = new char[value.length];
System.arraycopy(value, 0, result, 0, value.length);
return result;
}
//-----其它代码
public static final Comparator<String> CASE_INSENSITIVE_ORDER
= new CaseInsensitiveComparator();
private static class CaseInsensitiveComparator
implements Comparator<String>, java.io.Serializable {
// use serialVersionUID from JDK 1.2.2 for interoperability
private static final long serialVersionUID = 8575799808933029326L;
public int compare(String s1, String s2) {
int n1 = s1.length();
int n2 = s2.length();
int min = Math.min(n1, n2);
for (int i = 0; i < min; i++) {
char c1 = s1.charAt(i);
char c2 = s2.charAt(i);
if (c1 != c2) {
c1 = Character.toUpperCase(c1);
c2 = Character.toUpperCase(c2);
if (c1 != c2) {
c1 = Character.toLowerCase(c1);
c2 = Character.toLowerCase(c2);
if (c1 != c2) {
// No overflow because of numeric promotion
return c1 - c2;
}
}
}
}
return n1 - n2;
}
/** Replaces the de-serialized object. */
private Object readResolve() { return CASE_INSENSITIVE_ORDER; }
}
/**
* Compares two strings lexicographically, ignoring case
* differences. This method returns an integer whose sign is that of
* calling {@code compareTo} with normalized versions of the strings
* where case differences have been eliminated by calling
* {@code Character.toLowerCase(Character.toUpperCase(character))} on
* each character.
* <p>
* Note that this method does <em>not</em> take locale into account,
* and will result in an unsatisfactory ordering for certain locales.
* The java.text package provides <em>collators</em> to allow
* locale-sensitive ordering.
*
* @param str the {@code String} to be compared.
* @return a negative integer, zero, or a positive integer as the
* specified String is greater than, equal to, or less
* than this String, ignoring case considerations.
* @see java.text.Collator#compare(String, String)
* @since 1.2
*/
public int compareToIgnoreCase(String str) {
return CASE_INSENSITIVE_ORDER.compare(this, str);
}
}
可以看到String类有如下特性:
- 类是final的,不可以被继承作为父类出现
- 属性value[],是final的其值一旦确定则不能被修改
- 属性hash可以缓存当前字符串的hashCode
- String类实现了序列化、比较和字节序列接口,也就是说 String类可以被序列化并存储,并且提供了compareTo方法与另一个String比较。
- 有一个静态内部类CaseInsensitiveComparator实现了Comparator接口负责忽略大小写的比较
三、为什么被设计成Immutable(不可变的)
通过上面的代码特征,我们可以知道,String是一个不可改变的类,即使我们使用它的构造函数传入参数,也是通过Arrays.copyOf重新生成一个新的char数据(避免引入同一地址,当外部变量改变时影响此字符串),包括toCharArray返回的也是一个copy,同样也是出现这种考虑。这样做的优点:
- 线程安全,不可改变的量在多线程操作时,不需要引入同步机制,它自己的不可变性就已经保证了线程安全。
- 可以放在常量池中,统一引用,避免重复创建相同的对象,节省空间。
- 我们看到它有一个hash属性,用来缓存此字符串的hashCode,比如在把字符串放入HashMap中等等操作,会经常使用到字符串的hashCode值,这样就不用重复的计算,提高效率。
- 出于安全方法的考虑,比如,java中的反射,路径等等都会使用字符串对象,如果字符串可以改变的话在实现实用时,就不能保证值的准确性。
四、字符串的比较
一般的,定义字符串有两种方式:
String s = "abc";String s1= "abc"或String s2 = new String("abc");
这两种方式区别,相信大家应该很熟悉了,前者是把"abc"放入了常量池中,后者是把字符串对象放入了堆中。此时,s==s1为true而s==s2则为false。因为他们引用的地址不一样。
而equals比较的是引用地址是否相同,相同则为true(这也是因为字符串的不变性),否则比较字符串的值,如果值相同则为true,否则为false。例如,上面的s.equals(s2)为true,虽然引用地址不同,但值是相同的。
再细心一点,我们还可以看到String类实现了Comparable<String>,但它还有一个静态内部类实现的却是Comparator<String>,这两个接口的区别就在于:
- Comparable,可以看作是内部比较器,其方法是public int compareTo(T o);把自身与传入的参数进行比较,是一种比较自然的比较方式,如果把元素放入一个集合后又想使用Collections.sort进行排序时,那么加入此集合的对象就需要实现Comparable接口了。
- Comparator,可以看作是一个外部比较器。其方法是int compare(T o1, T o2);比较传入的两个参数对象,不需要此对象一定要实现什么接口,具体的比较方法由自己来实现。
一般来说,如果实现了Comparable,但另一种比较场景又对此比较方法不满意的话,可以再实现Comparator。比如String类实现了Comparable,比较一个字符串与自己是否完全相同,但对于忽略大小写的比较就不适合,所以新增加了一个内部来实现Comparator的比较方法。
五、StringBuffer与StringBuilder
java中的String类被设计成了Immutable的,如果对字符串进行拼接操作例如:String s = "hello "+"world"+"!",这样会在内存中保存四个字符串,而有用的只有一个,其它的可能会被gc掉,这样不方便对字符串的拼接等等操作,同时,效率也也比较慢,如果有大量的拼接操作,那对内存或cpu都是不友好的。所以,java又提供了另外的两个类来操作字符串。
StringBuffer线程安全的字符串操作类,它内部的方法大都是加了synchronized的。适用于多线程操作同一资源的场景,同时,由于有线程安全的考虑,所以效率上会比StringBuilder慢一点。
总结
类被设计成不可变性,带来的好处就是线程安全等等,但同时也带来了对象变更的开销很大,每改变一次就要重新创建一个新的对象并把原对象copy到新对象中。针对这种情况我们可以考虑使用其它的方法来应对变更,比如字符串的变更我们可以使用StringBuffer或者StringBuilder来操作字符串。在jdk内部不光是String,像Integer、Long、Double等等也都是Immutable的。