java中的String类

本文详细解析了Java中String类的设计原理,探讨了其不可变性带来的线程安全、节省空间及提高效率等优势,对比了字符串常量池与堆区创建的不同,以及String、StringBuffer和StringBuilder在字符串拼接上的应用差异。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 

一、概要

       无论是在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,同样也是出现这种考虑。这样做的优点:

  1. 线程安全,不可改变的量在多线程操作时,不需要引入同步机制,它自己的不可变性就已经保证了线程安全。
  2. 可以放在常量池中,统一引用,避免重复创建相同的对象,节省空间。
  3. 我们看到它有一个hash属性,用来缓存此字符串的hashCode,比如在把字符串放入HashMap中等等操作,会经常使用到字符串的hashCode值,这样就不用重复的计算,提高效率。
  4. 出于安全方法的考虑,比如,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的。

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值