Maison > Java > JavaBase > le corps du texte

Passez en revue le passé et apprenez le nouveau (2) Compréhension approfondie des chaînes en Java

coldplay.xixi
Libérer: 2020-09-19 09:36:20
avant
1644 Les gens l'ont consulté

Passez en revue le passé et apprenez le nouveau (2) Compréhension approfondie des chaînes en Java

Recommandations d'apprentissage associées : Tutoriel de base Java

Dans l'article précédent, nous avons effectué une analyse approfondie de La mémoire de String et ses quelques fonctionnalités. Dans cet article, nous analyserons en profondeur les deux autres classes liées à String, qui sont StringBuilder et StringBuffer. Quelle est la relation entre ces deux classes et String ? Tout d'abord, jetons un coup d'œil au diagramme de classes ci-dessous :

Passez en revue le passé et apprenez le nouveau (2) Compréhension approfondie des chaînes en Java

Comme vous pouvez le voir sur la figure, StringBuilder et StringBuffer héritent de AbstractStringBuilder et AbstractStringBuilder. Et String implémente une interface commune CharSequence.

Nous savons qu'une chaîne est composée d'une série de caractères. L'implémentation interne de String est basée sur un tableau de caractères (basé sur un tableau d'octets après jdk9), et le tableau est généralement une zone de mémoire continue. le tableau est initialisé Ensuite, vous devez spécifier la taille du tableau. Dans l'article précédent, nous savions déjà que String est immuable car son tableau interne est déclaré final. En même temps, l'épissage, l'insertion, la suppression et d'autres opérations des caractères String sont toutes implémentées en instanciant de nouveaux objets. Les StringBuilder et StringBuffer que nous allons découvrir aujourd'hui sont plus dynamiques que String. Ensuite, faisons connaissance ensemble avec ces deux catégories.

1. StringBuilder

Vous pouvez voir le code suivant dans la classe parent de StringBuilder, AbstractStringBuilder :

abstract class AbstractStringBuilder implements Appendable, CharSequence {    /**
     * The value is used for character storage.
     */
    char[] value;    /**
     * The count is the number of characters used.
     */
    int count;
}复制代码
Copier après la connexion

StringBuilder et String sont tous deux implémentés sur la base de tableaux de caractères. StringBuilder n'a pas de modification finale, ce qui signifie que StringBuilder peut être modifié dynamiquement. Jetons ensuite un coup d'œil à la méthode de construction sans paramètre de StringBuilder. Le code est le suivant :

 /**
     * Constructs a string builder with no characters in it and an
     * initial capacity of 16 characters.
     */
    public StringBuilder() {        super(16);
    }复制代码
Copier après la connexion

Dans cette méthode, la méthode de construction de la classe parent est appelée. Accédez à AbstractStringBuilder et voyez que sa méthode de construction. est la suivante :

    /**
     * Creates an AbstractStringBuilder of the specified capacity.
     */
    AbstractStringBuilder(int capacity) {
        value = new char[capacity];
    }复制代码
Copier après la connexion

AbstractStringBuilder Le constructeur initialise en interne un tableau avec une capacité. En d'autres termes, StringBuilder initialise un tableau char[] d'une capacité de 16 par défaut. En plus de la construction sans paramètre, StringBuilder fournit également plusieurs méthodes de construction. Le code source est le suivant :

 /**
     * Constructs a string builder with no characters in it and an
     * initial capacity specified by the {@code capacity} argument.
     *
     * @param      capacity  the initial capacity.
     * @throws     NegativeArraySizeException  if the {@code capacity}
     *               argument is less than {@code 0}.
     */
    public StringBuilder(int capacity) {        super(capacity);
    }    /**
     * Constructs a string builder initialized to the contents of the
     * specified string. The initial capacity of the string builder is
     * {@code 16} plus the length of the string argument.
     *
     * @param   str   the initial contents of the buffer.
     */
    public StringBuilder(String str) {        super(str.length() + 16);
        append(str);
    }    /**
     * Constructs a string builder that contains the same characters
     * as the specified {@code CharSequence}. The initial capacity of
     * the string builder is {@code 16} plus the length of the
     * {@code CharSequence} argument.
     *
     * @param      seq   the sequence to copy.
     */
    public StringBuilder(CharSequence seq) {        this(seq.length() + 16);
        append(seq);
    }复制代码
Copier après la connexion

La première méthode de ce code initialise un StringBuilder avec une capacité spécifiée. Les deux autres méthodes de construction peuvent transmettre respectivement String et CharSequence pour initialiser StringBuilder. La capacité de ces deux méthodes de construction sera ajoutée à la longueur de la chaîne transmise par 16.

1. Opération d'ajout et expansion de StringBuilder

Dans l'article précédent, nous savons déjà qu'un épissage de chaîne efficace peut être effectué via la méthode d'ajout de StringBuilder. En prenant append (String) comme exemple, vous pouvez voir que l'ajout de StringBuilder appelle la méthode append de la classe parent. En fait, non seulement append, mais presque toutes les méthodes d'exploitation des chaînes de la classe StringBuilder sont implémentées via la classe parent. Le code source de la méthode append est le suivant :

    // StringBuilder
    @Override
    public StringBuilder append(String str) {        super.append(str);        return this;
    }    
  // AbstractStringBuilder
  public AbstractStringBuilder append(String str) {        if (str == null)            return appendNull();        int len = str.length();
        ensureCapacityInternal(count + len);
        str.getChars(0, len, value, count);
        count += len;        return this;
    }复制代码
Copier après la connexion

Dans la première ligne de la méthode append, une vérification nulle est d'abord effectuée, et lorsqu'elle est égale à null, la méthode appendNull est appelée. Le code source est le suivant :

private AbstractStringBuilder appendNull() {        int c = count;
        ensureCapacityInternal(c + 4);        final char[] value = this.value;
        value[c++] = 'n';
        value[c++] = 'u';
        value[c++] = 'l';
        value[c++] = 'l';
        count = c;        return this;
    }复制代码
Copier après la connexion

La méthode AppendNull appelle d'abord EnsureCapacityInternal pour s'assurer que la capacité du tableau de chaînes est rechargée. La méthode EnsureCapacityInternal sera analysée en détail ci-dessous. Ensuite, vous pouvez voir que le caractère « null » est ajouté à la valeur du tableau char[].

Nous avons mentionné ci-dessus que la capacité par défaut du tableau interne de StringBuilder est de 16. Par conséquent, lors de l'épissage de chaînes, vous devez d'abord vous assurer que le tableau char[] a une capacité suffisante. Par conséquent, la méthode EnsureCapacityInternal est appelée à la fois dans la méthode appendNull et dans la méthode append pour vérifier si le tableau char[] a une capacité suffisante. Si la capacité est insuffisante, le code source de EnsureCapacityInternal est le suivant :

private void ensureCapacityInternal(int minimumCapacity) {        // overflow-conscious code
        if (minimumCapacity - value.length > 0)
            expandCapacity(minimumCapacity);
    }复制代码
Copier après la connexion

Voici l'interprétation si Si la longueur de la chaîne épissée est supérieure à la longueur du tableau de chaînes, expandCapacity sera appelé pour l'expansion.

void expandCapacity(int minimumCapacity) {        int newCapacity = value.length * 2 + 2;        if (newCapacity - minimumCapacity < 0)
            newCapacity = minimumCapacity;        if (newCapacity < 0) {            if (minimumCapacity < 0) // overflow
                throw new OutOfMemoryError();
            newCapacity = Integer.MAX_VALUE;
        }
        value = Arrays.copyOf(value, newCapacity);
    }复制代码
Copier après la connexion

La logique de expandCapacity est également très simple. Tout d'abord, multipliez la longueur du tableau d'origine par 2 et ajoutez 2 pour calculer la longueur du tableau étendu. Ensuite, on estime que si newCapacity est inférieur à minimumCapacity, la valeur minimumCapacity est attribuée à newCapacity. Comme la méthode expandCapacity est appelée à plusieurs endroits, ce code est ajouté pour garantir la sécurité.

La phrase suivante du code est très intéressante. Est-il possible que newCapacity et minimumCapacity soient inférieurs à 0 ? Lorsque minimumCapacity est inférieur à 0, une exception OutOfMemoryError est levée. En fait, il est inférieur à 0 car il est hors limites. Nous savons que tout ce qui est stocké dans l’ordinateur est binaire et que multiplier par 2 équivaut à décaler un bit vers la gauche. En prenant l'octet comme exemple, un octet a 8 bits dans un nombre signé, le bit le plus à gauche est le bit de signe. Le bit de signe des nombres positifs est 0 et celui des nombres négatifs est 1. Ensuite, la plage de tailles qu'un octet peut représenter est [-128 ~ 127], et si un nombre est supérieur à 127, il sera hors limites, c'est-à-dire que le bit de signe le plus à gauche sera remplacé par 1 dans le deuxième bit de à gauche, et un nombre négatif apparaîtra. Bien sûr, ce n'est pas byte mais int, mais le principe est le même.

另外在这个方法的最后一句通过Arrays.copyOf进行了一个数组拷贝,其实Arrays.copyOf在上篇文章中就有见到过,在这里不妨来分析一下这个方法,看源码:

 public static char[] copyOf(char[] original, int newLength) {        char[] copy = new char[newLength];
        System.arraycopy(original, 0, copy, 0,
                         Math.min(original.length, newLength));        return copy;
    }复制代码
Copier après la connexion

咦?copyOf方法中竟然也去实例化了一个对象!!那不会影响性能吗?莫慌,看一下这里仅仅是实例化了一个newLength长度的空数组,对于数组的初始化其实仅仅是指针的移动而已,浪费的性能可谓微乎其微。接着这里通过System.arraycopy的native方法将原数组复制到了新的数组中。

2.StringBuilder的subString()方法toString()方法

StringBuilder中其实没有subString方法,subString的实现是在StringBuilder的父类AbstractStringBuilder中的。它的代码非常简单,源码如下:

public String substring(int start, int end) {        if (start < 0)            throw new StringIndexOutOfBoundsException(start);        if (end > count)            throw new StringIndexOutOfBoundsException(end);        if (start > end)            throw new StringIndexOutOfBoundsException(end - start);        return new String(value, start, end - start);
    }复制代码
Copier après la connexion

在进行了合法判断之后,substring直接实例化了一个String对象并返回。这里和String的subString实现其实并没有多大差别。 而StringBuilder的toString方法的实现其实更简单,源码如下:

 @Override
    public String toString() {        // Create a copy, don&#39;t share the array
        return new String(value, 0, count);
    }复制代码
Copier après la connexion

这里直接实例化了一个String对象并将StringBuilder中的value传入,我们来看下String(value, 0, count)这个构造方法:

    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.value = Arrays.copyOfRange(value, offset, offset+count);
    }复制代码
Copier après la connexion

可以看到,在String的这个构造方法中又通过Arrays.copyOfRange方法进行了数组拷贝,Arrays.copyOfRange的源码如下:

   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;
    }复制代码
Copier après la connexion

Arrays.copyOfRange与Arrays.copyOf类似,内部都是重新实例化了一个char[]数组,所以String构造方法中的this.value与传入进来的value不是同一个对象。意味着StringBuilder在每次调用toString的时候生成的String对象内部的char[]数组并不是同一个!这里立一个Falg

3.StringBuilder的其它方法

StringBuilder除了提供了append方法、subString方法以及toString方法外还提供了还提供了插入(insert)、删除(delete、deleteCharAt)、替换(replace)、查找(indexOf)以及反转(reverse)等一些列的字符串操作的方法。但由于实现都非常简单,这里就不再赘述了。

二、StringBuffer

在第一节已经知道,StringBuilder的方法几乎都是在它的父类AbstractStringBuilder中实现的。而StringBuffer同样继承了AbstractStringBuilder,这就意味着StringBuffer的功能其实跟StringBuilder并无太大差别。我们通过StringBuffer几个方法来看

     /**
     * A cache of the last value returned by toString. Cleared
     * whenever the StringBuffer is modified.
     */
    private transient char[] toStringCache;    @Override
    public synchronized StringBuffer append(String str) {
        toStringCache = null;        super.append(str);        return this;
    }    /**
     * @throws StringIndexOutOfBoundsException {@inheritDoc}
     * @since      1.2
     */
    @Override
    public synchronized StringBuffer delete(int start, int end) {
        toStringCache = null;        super.delete(start, end);        return this;
    }  /**
     * @throws StringIndexOutOfBoundsException {@inheritDoc}
     * @since      1.2
     */
    @Override
    public synchronized StringBuffer insert(int index, char[] str, int offset,                                            int len)
    {
        toStringCache = null;        super.insert(index, str, offset, len);        return this;
    }@Override
    public synchronized String substring(int start) {        return substring(start, count);
    }    
// ...复制代码
Copier après la connexion

可以看到在StringBuffer的方法上都加上了synchronized关键字,也就是说StringBuffer的所有操作都是线程安全的。所以,在多线程操作字符串的情况下应该首选StringBuffer。 另外,我们注意到在StringBuffer的方法中比StringBuilder多了一个toStringCache的成员变量 ,从源码中看到toStringCache是一个char[]数组。它的注释是这样描述的:

toString返回的最后一个值的缓存,当StringBuffer被修改的时候该值都会被清除。

我们再观察一下StringBuffer中的方法,发现只要是操作过操作过StringBuffer中char[]数组的方法,toStringCache都被置空了!而没有操作过字符数组的方法则没有对其做置空操作。另外,注释中还提到了 toString方法,那我们不妨来看一看StringBuffer中的 toString,源码如下:

   @Override
    public synchronized String toString() {        if (toStringCache == null) {
            toStringCache = Arrays.copyOfRange(value, 0, count);
        }        return new String(toStringCache, true);
    }复制代码
Copier après la connexion

这个方法中首先判断当toStringCache 为null时会通过 Arrays.copyOfRange方法对其进行赋值,Arrays.copyOfRange方法上边已经分析过了,他会重新实例化一个char[]数组,并将原数组赋值到新数组中。这样做有什么影响呢?细细思考一下不难发现在不修改StringBuffer的前提下,多次调用StringBuffer的toString方法,生成的String对象都共用了同一个字符数组--toStringCache。这里是StringBuffer和StringBuilder的一点区别。至于StringBuffer中为什么这么做其实并没有很明确的原因,可以参考StackOverRun 《Why StringBuffer has a toStringCache while StringBuilder not?》中的一个回答:

1.因为StringBuffer已经保证了线程安全,所以更容易实现缓存(StringBuilder线程不安全的情况下需要不断同步toStringCache) 2.可能是历史原因

三、 总结

本篇文章到此就结束了。《深入理解Java中的字符串》通过两篇文章深入的分析了String、StringBuilder与StringBuffer三个字符串相关类。这块内容其实非常简单,只要花一点时间去读一下源码就很容易理解。当然,如果你没看过此部分源码相信这篇文章能够帮助到你。不管怎样,相信大家通过阅读本文还是能有一些收获。解了这些知识后可以帮助我们在开发中对字符串的选用做出更好的选择。同时,这块内容也是面试常客,相信大家读完本文去应对面试官的问题也会绰绰有余。

Si vous souhaitez en savoir plus sur la programmation, faites attention à la rubrique Formation php !

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Étiquettes associées:
source:juejin.im
Déclaration de ce site Web
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
Tutoriels populaires
Plus>
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal
À propos de nous Clause de non-responsabilité Sitemap
Site Web PHP chinois:Formation PHP en ligne sur le bien-être public,Aidez les apprenants PHP à grandir rapidement!