Android研发中对String的思考(源码分析)

1、常用创建方式思考:

String text = "this is a test text ";

上面这一句话实际上是执行了三件事

1、声明变量 String text;

2、在内存中开辟空间 (内存空间一)

3、将变量的text的引用指向开辟的内存空间

当有

text = "this is a change text";

这一句话执行了两件事

1、在内存中开辟空间

2、将变量text 的引用指向 新开辟的内存空间

3、内存空间一此时依然存在,这就是说明了String的不可变性

测试实践一:

String text = "oo";
 //循环体系将字符串拼接
for (int i = 0; i < 90000; i++) {
      text+=i;
}

这段小程序在我的应用中执行了8s的时间 ,太长了,原因很简单,就是不断的在重复创建新的对象与内存空间,同时还要不时的释放未使用的内存空间

测试实践二:

String text = "oo";
//创建字符缓冲区
StringBuilder builder = new StringBuilder(text);
//循环体系将字符串拼接
for (int i = 0; i < 100000; i++) {
      builder.append(i);
 }

执行这一小段代码,执行的次数远远超于实践一中,然而时间只使用了 30 ms,大优化了性能,原因只是因为其在只是开辟了一个缓存空间,每次只把新的数据添加到缓存中。

2、创建String 的构造简析

2.1、String string = new String ();

这种方法是创建了一个空的String,在内存空间中开辟了一个空内容的地址,实际上也没有什么用,源码中有这样的描述

/**
     * 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.value = "".value;
    }

虽然是空的String(是""而不是null,“”出是占有内存空间,只不过内容是空的) ,但在其构造方法中依然调用了value方法,也就是空字符串的value,

所谓value

 /** The value is used for character storage. */
    private final char value[];

2.2、String string = new String (" test ");

这种方法来创建String,与我们常用创建方式分析中的思路一至,声明变量,开辟空间,赋值引用

源码中这样操作

public String(String original) {
        this.value = original.value;
        this.hash = original.hash;
    }

毫无疑问,它的构造方法中首先将其转为了对应的字节数组中的数据。

同时 获取其对应的Hash值

 /** Cache the hash code for the string */
    private int hash; // Default to 0

2.3、将char数组中的内容构造为String类型

 char[] chars = new char[]{"q","e","e","q","e","e"};
        //将char数组中的内容构造为String类型
        String string = new String(chars);

        //构造的字符串为 qeeqee

这里是依赖字符数组来创建构造String  ,从源码的角度来看,毫无疑问在其对应的构造中首先将其转为了对应字符数组

 public String(char value[]) {
            this.value = Arrays.copyOf(value, value.length);
        }

可以看到这里并没有直接通过 Arrays的copyOf创建了一个新的字符数组,并赋值 于this.value,

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;
        }

2.4、 将字符数组中的部分数据构造为String

 char[] chars = new char[]{"q","e","e","q","e","e"};
        //构造字符串
        String string = new String(chars,0,2);

        //构造的字符串为 qe
        //也就是说将字符数组中的部分字符 构造成String

从其构造源码来看 :

public String(char value[], int offset, int count) {
            if (offset < 0) {
                throw new StringIndexOutOfBoundsException(offset);
            }
             if (count <= 0) {
                 if (count < 0) {
                    throw new StringIndexOutOfBoundsException(count);
                }
            if (offset <= value.length) {
                    this.value = "".value;
                    return;
            }
        }
        // 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);
        }

也就是说当我们通过这种方法来构造String 的时候 , 传入的参数中构造字符起始位置 offset小于零,那么将会出异常,构造字符个数 count 小于0,

那么意味取出的字符数组中的长度为0,也就是构造一个 ""字符串,同时赋值其字符数组,

当构造起始位置 字符个娄都不小于0的时候 ,当起始位置与取出的字符长度设置不合理(所谓不合理指的是 例 字符数组长度为 5,

构造字符串的时候传入的 起始位置 为3 ,构造长度为5,也就是说在字符数组中从3号位置取值,向后延取5个值,原字符数组长度必然不够)的时候,

抛出异常

当传入的参数合理的时候,则通过Arrays的copyOfRange方法创建一个新的字符数组空间,并赋值 this.value

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;
        }

2.6、 通过 unicode数组来构造String

int[] charIntArray = new int[]{67,68,69,70};

String  string = new String (charIntArray,0,charIntArray.length);

//对应的字符串 CEDF 

从源码角度来看:(具体的关于unicode将在下文中简述)

public String(int[] codePoints, int offset, int count) {
                if (offset < 0) {
                    throw new StringIndexOutOfBoundsException(offset);
                }
                if (count <= 0) {
                    if (count < 0) {
                        throw new StringIndexOutOfBoundsException(count);
                    }
                    if (offset <= codePoints.length) {
                        this.value = "".value;
                        return;
                    }
                }
                // Note: offset or count might be near -1>>>1.
                if (offset > codePoints.length - count) {
                    throw new StringIndexOutOfBoundsException(offset + count);
                }

                final int end = offset + count;

                // Pass 1: Compute precise size of char[]
                int n = count;
                for (int i = offset; i < end; i++) {
                    int c = codePoints[i];
                    if (Character.isBmpCodePoint(c))
                        continue;
                    else if (Character.isValidCodePoint(c))
                        n++;
                    else throw new IllegalArgumentException(Integer.toString(c));
                }

                // Pass 2: Allocate and fill in char[]
                final char[] v = new char[n];

                for (int i = offset, j = 0; i < end; i++, j++) {
                    int c = codePoints[i];
                    if (Character.isBmpCodePoint(c))
                        v[j] = (char)c;
                    else
                        Character.toSurrogates(c, v, j++);
                }

                this.value = v;
            }

2.7、将字节数组构建为String

byte[] newByte = new byte[]{4,5,6,34,43};
        //构造字符
        String string = new String (newByte,0,newByte,"UTF-8");
        //参数一 对应的字节数组
        //参数二 参数三 构造字符串的字节范围
        //参数四 构造字符串的编码方式 这里使用的 为UTF - 8;

从其源码的角度来看

public String(byte bytes[], int offset, int length, String charsetName)
                throws UnsupportedEncodingException {
                    if (charsetName == null)
                        throw new NullPointerException("charsetName");
                    checkBounds(bytes, offset, length);
                    this.value = StringCoding.decode(charsetName, bytes, offset, length);
            }

如果传入的编码方式为空,则直接抛出异常

而下面的checkBounds方法只是做了一些安全性检验

private static void checkBounds(byte[] bytes, int offset, int length) {
                if (length < 0)
                    throw new StringIndexOutOfBoundsException(length);
                if (offset < 0)
                    throw new StringIndexOutOfBoundsException(offset);
                if (offset > bytes.length - length)
                    throw new StringIndexOutOfBoundsException(offset + length);
            }

然后通过 方法StringCoding.decode 方法创建了一个新的字符数组,并赋值与 this.value

2.8、 将字节数组中的一部分数据构建为String

        byte[] newByte = new byte[]{4,5,6,34,43};
        //构造字符
        String string = new String (newByte,"UTF-8");

3、常用处理String操作的方法分析

3.1、 取出一个String中的指定角标下的一个字符

 String text = "thisisapicture";
        //取出角标索引为0位置的字符
        char cahrString = text . charAt(0);

        //这里取出来的字符为 t 

从源码角度来看

public char charAt(int index) {
            if ((index < 0) || (index >= value.length)) {
                throw new StringIndexOutOfBoundsException(index);
            }
            return value[index];
        }

从其源码看来,当我们刚刚创建一个String 对象的时候,String对象的内容则会被转为内存空间中的相应的字数组,而在这里,

则是从其对应的内存数组中拿到对应角标下的相应字符

3.2、 对字符串的分割

3.2.1、 split方法进行分割

 String textString = "q,q,w,e,e,r,f,g3,g";
        //将字符串以“,”为基准进行分割
        String[] stringArray = textString.split(",");
        //得到相应的字符串数组
        // [q, q, w, e, e, r, f, g3, g]

从源码的角度来看

public String[] split(String regex) {
            return split(regex, 0);
        }

实际中调用了两个参数的重载方法

public String[] split(String regex, int limit) {
            /* fastpath if the regex is a
             (1)one-char String and this character is not one of the
                RegEx's meta characters ".$|()[{^?*+\\", or
             (2)two-char String and the first char is the backslash and
                the second is not the ascii digit or ascii letter.
             */
            char ch = 0;
            if (((regex.value.length == 1 &&
                     ".$|()[{^?*+\\".indexOf(ch = regex.charAt(0)) == -1) ||
                     (regex.length() == 2 &&
                      regex.charAt(0) == '\\' &&
                      (((ch = regex.charAt(1))-'0')|('9'-ch)) < 0 &&
                      ((ch-'a')|('z'-ch)) < 0 &&
                      ((ch-'A')|('Z'-ch)) < 0)) &&
                    (ch < Character.MIN_HIGH_SURROGATE ||
                     ch > Character.MAX_LOW_SURROGATE))
                {
                    int off = 0;
                    int next = 0;
                    boolean limited = limit > 0;
                    ArrayList<String> list = new ArrayList<>();
                    while ((next = indexOf(ch, off)) != -1) {
                        if (!limited || list.size() < limit - 1) {
                            list.add(substring(off, next));
                            off = next + 1;
                        } else {    // last one
                            //assert (list.size() == limit - 1);
                            list.add(substring(off, value.length));
                            off = value.length;
                            break;
                        }
                    }
                    // If no match was found, return this
                    if (off == 0)
                        return new String[]{this};

                    // Add remaining segment
                    if (!limited || list.size() < limit)
                        list.add(substring(off, value.length));

                    // Construct result
                    int resultSize = list.size();
                    if (limit == 0) {
                        while (resultSize > 0 && list.get(resultSize - 1).length() == 0) {
                            resultSize--;
                        }
                    }
                    String[] result = new String[resultSize];
                    return list.subList(0, resultSize).toArray(result);
                }
                return Pattern.compile(regex).split(this, limit);
            }

当看到这段源码的时候,有点头疼,因为它有点长,其实仔细一看,也不过如此

可以看到方法内有一个if,如果条件为true,那么就使用indexOf()打开循环体系,判断后substring()截取,

//第一步部分:当regex的长度为1且不是“.$|()[{^?*+\\”中的时,为真

(regex.value.length == 1 &&".$|()[{^?*+\\".indexOf(ch = regex.charAt(0)) == -1)

//第二部分:当长度为2时且第一个字符为“\”转义字符,第二个字符不是字符0-9 a-z A-Z 以及utf-16之间的字符

从if可以看出如果regex内容为一个非正则匹配符或者是转以后的特殊字符时,采用indexOf()+substring()处理,

否则使用正则表达式   Pattern.compile(regex).split(this, limit)

也就是说,我们在使用Split方法对String字符串进行分割的时候,不仅可以以一个普通的字符为标准进行分割,

还可以使用一个正则表达式进行分割

而当使用的分割符号正好为正则表达式中的某些符号的时候,需要正则转义才能够得到正确的结果

3.3 获取字符串中指定角标字符的Unicode编码

String text = "ABCD";
    //获取0号位置也就是'A'字符的编码
    int uniCode = text.codePointAt(0);

从源码来看 :

public int codePointAt(int index) {
        if ((index < 0) || (index >= value.length)) {
            throw new StringIndexOutOfBoundsException(index);
        }
        return Character.codePointAtImpl(value, index, value.length);
    }

实际的操作体则是Character这个类在发挥作用:Character.codePointAtImpl(value, index, value.length)

也就是直接将String text对应的字符数组,以及要获取字符角标,以及字符数组长度传参

Unicode给世界上每个字符分配了一个编号,编号范围从0x000000到0x10FFFF。

编号范围在0x0000到0xFFFF之间的字符,为常用字符集,称BMP(Basic Multilingual Plane)字符。

编号范围在0x10000到0x10FFFF之间的字符叫做增补字符(supplementary character)。

Unicode主要规定了编号,但没有规定如果把编号映射为二进制,UTF-16是一种编码方式,或者叫映射方式,

它将编号映射为两个或四个字节,对BMP字符,它直接用两个字节表示,

对于增补字符,使用四个字节,前两个字节叫高代理项(high surrogate),范围从0xD800到0xDBFF,

后两个字节叫低代理项(low surrogate),范围从0xDC00到0xDFFF,UTF-16定义了一个公式,可以将编号与四字节表示进行相互转换。

Java内部采用UTF-16编码,char表示一个字符,但只能表示BMP中的字符,对于增补字符,需要使用两个char表示,一个表示高代理项,一个表示低代理项。

使用int可以表示任意一个Unicode字符,低21位表示Unicode编号,高11位设为0。整数编号在Unicode中一般称为代码点(Code Point),表示一个Unicode字符,与之相对,还有一个词代码单元(Code Unit)表示一个char。

而在Character这个类中,对应的静态操作方法实则为:

static int codePointAtImpl(char[] a, int index, int limit) {
        char c1 = a[index];
        if (isHighSurrogate(c1) && ++index < limit) {
            char c2 = a[index];
            if (isLowSurrogate(c2)) {
                return toCodePoint(c1, c2);
            }
        }
        return c1;
    }

实际 在这个方法中的第一步是从text对应的字符数组中取出对应的角标的字符,如果不符合其中if的条件,那么将直接获取的是字符本身,

也就是字符本身的unicode编码是本身

在if的判断条件中.执行判断取出的字符是否在isHighSurrogate这个方法所限定的范围内,

public static boolean isHighSurrogate(char ch) {
        // Help VM constant-fold; MAX_HIGH_SURROGATE + 1 == MIN_LOW_SURROGATE
        return ch >= MIN_HIGH_SURROGATE && ch < (MAX_HIGH_SURROGATE + 1);
    }

MIN_HIGH_SURROGATE utf-16 编码中的 unicode 高代理项代码单元的最小值。高代理项也称为前导代理项。

MAX_HIGH_SURROGATE utf-16 编码中的 unicode 高代理项代码单元的最大值

如果在,则执行 isLowSurrogate方法的限定判断

public static boolean isLowSurrogate(char ch) {
        return ch >= MIN_LOW_SURROGATE && ch < (MAX_LOW_SURROGATE + 1);
    }

此方法可以理解为 确定给定char值是否为一个Unicode低代理项代码单元

也可以理解为 判断对应字符是否在  0xDC00到0xDFFF 范围的 低代理项。

如果 在,则执行toCodePoint方法计算其对应的Unicode值,也就是根据高代理项high和低代理项low生成代码单元

public static int toCodePoint(char high, char low) {
        // Optimized form of:
        // return ((high - MIN_HIGH_SURROGATE) << 10)
        //         + (low - MIN_LOW_SURROGATE)
        //         + MIN_SUPPLEMENTARY_CODE_POINT;
        return ((high << 10) + low) + (MIN_SUPPLEMENTARY_CODE_POINT
                                       - (MIN_HIGH_SURROGATE << 10)
                                       - MIN_LOW_SURROGATE);
    }

3.4、获取字符串中指定角标索引前一个元素的代码点

String textString = "ABCDEF";
        //这里是获取三号位置前面一位,也就是二号位 C 的uniCode编码
        int uniCode = textString.codePointBefore(3);

从源码来看:实际实操作的还是字符串对应的字符数组,操作方法依然是Character这个类所定义的静态方法

public int codePointBefore(int index) {
            int i = index - 1;
            if ((i < 0) || (i >= value.length)) {
                throw new StringIndexOutOfBoundsException(index);
            }
            return Character.codePointBeforeImpl(value, index, 0);
        }

而在Character这个类中的方法中则是直接从对应的字符数组中取出角标前一位的字符,

然后判定是否在编码区中的 高 低代理区,如果 在,那么就计算返回对应的编码

static int codePointBeforeImpl(char[] a, int index, int start) {
        char c2 = a[--index];
        if (isLowSurrogate(c2) && index > start) {
            char c1 = a[--index];
            if (isHighSurrogate(c1)) {
                return toCodePoint(c1, c2);
            }
        }
        return c2;
        }

3.5、获取字符串中指定范围内的字符串Unicode代码点数

String textString = "ABCDEF";
        //这里获取的是整个字符串所对应的 代码点数
        int uniCodeCount = textString.codePointCount(0, textString.length());

从源码角度来看 实际还是在操作String对应的字符数组,负责操作的是 Character这个类

public int codePointCount(int beginIndex, int endIndex) {
        if (beginIndex < 0 || endIndex > value.length || beginIndex > endIndex) {
            throw new IndexOutOfBoundsException();
        }
            return Character.codePointCountImpl(value, beginIndex, endIndex - beginIndex);
        }

这里直接传入字符串对应的字符数组,以及范围的开始点,范围长度

static int codePointCountImpl(char[] a, int offset, int count) {
            int endIndex = offset + count;
            int n = count;
            for (int i = offset; i < endIndex; ) {
                if (isHighSurrogate(a[i++]) && i < endIndex &&
                    isLowSurrogate(a[i])) {
                    n--;
                    i++;
                }
            }
                return n;
        }

可以看出来 在这里面也是一个循环判断每一个字符是否在高代理项区与低代理项区,然后进行计数

3.6、比较两个字符串是否相等

        String text1 = "ABCDEF";
        String text2 = "ABCDEFG";
        //比较
        int compareNumber = text1.compareTo(text2);

其实就是依次比较两个字符串ASC码。如果两个字符的ASC码相等则继续后续比较,否则直接返回两个ASC的差值。如果两个字符串完全一样,则返回0

从源码角度来看:

 public int compareTo(String anotherString) {
            int len1 = value.length;
            int len2 = anotherString.value.length;
            int lim = Math.min(len1, len2);
            char v1[] = value;
            char v2[] = anotherString.value;

            int k = 0;
            while (k < lim) {
                char c1 = v1[k];
                char c2 = v2[k];
                if (c1 != c2) {
                    return c1 - c2;
                }
                k++;
            }
            return len1 - len2;
        }

在这里先字符串本身对应的字符数组的长度,然后再通过 方法anotherString.value.length方法获取比较对象String的长度

3.7 忽略大小写比较两个字符串

String text1 = "ABCDEF";
        String text2 = "ABCDEFG";
        //比较
        int compareNumber = text1.compareToIgnoreCase(text2);

从源码角度来看

public int compareToIgnoreCase(String str) {
            return CASE_INSENSITIVE_ORDER.compare(this, str);
        }

这里直接使用了CASE_INSENSITIVE_ORDER的compare方法来比较两个字符串,

而CASE_INSENSITIVE_ORDER是String类中定义的一个比较器

(1.2版本开始使用)

public static final Comparator<String> CASE_INSENSITIVE_ORDER

3.8 将两个字符串拼接为一个字符串

 String text1 = "ABCDEF";
        String text2 = "ABCDEFG";
        //这里将 text2 拼接到text1后面
        String newString  = text1.concat(text2);

将两个字符串拼接到一起,可以使用 text1+text2这个方法,只不过是在性能上有一点点的不合适

从源码角度来看

public String concat(String str) {
            int otherLen = str.length();
            if (otherLen == 0) {
                return this;
            }
            int len = value.length;
            char buf[] = Arrays.copyOf(value, len + otherLen);
            str.getChars(buf, len);
            return new String(buf, true);
        }

第一步 在这里先进行字符串判定,如果要拼接的字符串是"",那么拼接后依然是本身,所在这里直接return,

第二步 通过 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;
            }
            //这里创建一个新长度的数组空间并将原来的字符数组COPY进去

第三步 通过 String 的 getChars方法将 两个字符串对应的字符数组拼接

第四步 构造新的字符串返回

3.9 查找一个字符在字符串中第一次出现的位置

 String text = "ABCD ";
        //查找 "A" 在字符串text中第一次出现的位置
        int index = text.indexOf("A");

从源码角度来看

 public int indexOf(String str) {
            return indexOf(str, 0);
        }

        //再看indexOf(String str, int fromIndex)方法

 public int indexOf(String str, int fromIndex) {
         return indexOf(value, 0, value.length,
                    str.value, 0, str.value.length, fromIndex);
        }

上述indexfOf(String str)方法中调用indexOf(String str,int fromIndex)方法,然后传入0,也就是说将从String的开始位置查找指定字符在字符串中出现的位置,

上述indexfOf(String str)方法中调用indexOf(String str,int fromIndex)方法,然后传入0,也就是说将从String的开始位置查找指定字符在字符串中出现的位置,

而在indexOf(String str,int fromIndex)这个方法中则调用了indexOf的多参数方法,这时分别传入

value   父字符串的字符数组

0       父字符串的有效字符起始角标索引 这里传入0,也就是整个父字符串都可作为操作对象

value.length 父字符串的字符数组长度

str.value  子字符串的字符数组

0          子字符串的有效字符起始角标索引

str.value.length 子字符串的字符数组长度

fromIndex   在父字符串的查找的开始位置

再看多参数方法

static int indexOf(char[] source, int sourceOffset, int sourceCount,
                char[] target, int targetOffset, int targetCount,
                int fromIndex) {
            if (fromIndex >= sourceCount) {
                return (targetCount == 0 ? sourceCount : -1);
            }
            if (fromIndex < 0) {
                fromIndex = 0;
            }
            if (targetCount == 0) {
                return fromIndex;
            }

            char first = target[targetOffset];
            int max = sourceOffset + (sourceCount - targetCount);

            for (int i = sourceOffset + fromIndex; i <= max; i++) {
                /* Look for first character. */
                if (source[i] != first) {
                    while (++i <= max && source[i] != first);
                }

                /* Found first character, now look at the rest of v2 */
                if (i <= max) {
                    int j = i + 1;
                    int end = j + targetCount - 1;
                    for (int k = targetOffset + 1; j < end && source[j]
                            == target[k]; j++, k++);

                    if (j == end) {
                        /* Found whole string. */
                        return i - sourceOffset;
                    }
                }
            }
            return -1;
        }

在这个方法中,参数最多,那么说明处理的方法就在这里面

先分析参数

char[] source,         父String 对应字符数组

int sourceOffset,      父String 被使用起始索引

int sourceCount,       父String 对应字符数组长度

char[] target,         子String 对应字符数组

int targetOffset,      子String 被使用超始索引

int targetCount,       子String 对应字符数组长度

int fromIndex           检索起始位置

从上到下

if (fromIndex >= sourceCount) {

return (targetCount == 0 ? sourceCount : -1);

}

如果查找子String标识的在父View中的起始位置 大于等于 父String的长度

则终止操作,返回0 或者 -1

当子字符串的长度为0的时候,则返加0,此时子字符串为”“ 内容为空的空字符串

当子字符串的长度不为0的时候,返回-1,也就是说没有查找到

if (fromIndex < 0) {

fromIndex = 0;

}

当检索起始位置小于0,将起始位置默认设为0

if (targetCount == 0) {

return fromIndex;

}

当检索的子String对应的字符数组长度为0时,返回传入的检索位置

char first = target[targetOffset];

取出检索子String的第一个字符

int max = sourceOffset + (sourceCount - targetCount);

父String被检索起始位 + (父String对应字符数组长度 - 子String对应字符数组长度)

for (int i = sourceOffset + fromIndex; i <= max; i++) {

。。。。

}

如果循环体系中的条件没有被满足,那说明没有查找到对应的字符,返回-1

在循环体系中

/* Look for first character. */

if (source[i] != first) {

while (++i <= max && source[i] != first);

}

上面这段代码让我感觉到无语

/* Found first character, now look at the rest of v2 */

if (i <= max) {

int j = i + 1;

int end = j + targetCount - 1;

for (int k = targetOffset + 1; j < end && source[j]

== target[k]; j++, k++);

if (j == end) {

/* Found whole string. */

return i - sourceOffset;

}

}

3.10 判断一个字符串中是否包含指定的某一个字符

String text = "ABCE";
        //判断text中是否包含 "A"
        boolean isContans = text.contains("A");

从源码的角度来看:

public boolean contains(CharSequence s) {
            return indexOf(s.toString()) > -1;
        }

可以看到其实实际中还是String 的indexOf在发挥实际的作用,这里调用的indexOf(String str)方法,当查找到对应的字符时,会返回这个字符在字符串中的角标索引,角标索引从0 开始 肯定大于-1,与-1相比较,返回true

如果没有找到,indexOf方法返回的是 -1,最终contains方法返回的是 false,如果传入的是 ""空字符串,indexOf方法会返回0,最终contains方法返回的是true

3.11 比较字符串 到指定的CharSequence 序列是否相同

(2016-9-22 18:43 更新)

 String text1 = "ABCDEF";
        //比较序列
        boolean isExit = text1.contentEquals("AABCDEF");

从源码的角度来看

        public boolean contentEquals(StringBuffer sb) {
            return contentEquals((CharSequence)sb);
        }
        public boolean contentEquals(CharSequence cs) {
            // Argument is a StringBuffer, StringBuilder
            if (cs instanceof AbstractStringBuilder) {
                if (cs instanceof StringBuffer) {
                    synchronized(cs) {
                       return nonSyncContentEquals((AbstractStringBuilder)cs);
                    }
                } else {
                    return nonSyncContentEquals((AbstractStringBuilder)cs);
                }
            }
            // Argument is a String
            if (cs instanceof String) {
                return equals(cs);
            }
            // Argument is a generic CharSequence
            char v1[] = value;
            int n = v1.length;
            if (n != cs.length()) {
                return false;
            }
            for (int i = 0; i < n; i++) {
                if (v1[i] != cs.charAt(i)) {
                    return false;
                }
            }
            return true;
        }

这里两个方法重载,传类型不一样,CharSequence类型和StringBuffer类型

而实际操作起作用的是contentEquals(CharSequence cs)方法

而在这个方法中

if (cs instanceof AbstractStringBuilder) {
                     if (cs instanceof StringBuffer) {
                          synchronized(cs) {
                            return nonSyncContentEquals((AbstractStringBuilder)cs);
                       }
            } else {
                 return nonSyncContentEquals((AbstractStringBuilder)cs);
          }
    }

可以看出来,当我们传入的参数 cs 是AbstractStringBuilder的实例的时候 执行if内部的方法&#8;并且 如果是StringBuffer的实例,加同步锁,

而在方法nonSyncContentEquals中

private boolean nonSyncContentEquals(AbstractStringBuilder sb) {
                    char v1[] = value;
                    char v2[] = sb.getValue();
                    int n = v1.length;
                    if (n != sb.length()) {
                        return false;
                    }
                    for (int i = 0; i < n; i++) {
                        if (v1[i] != v2[i]) {
                            return false;
                        }
                    }
                    return true;
                }

这个方法中吧,感觉到无语,首先先获取参数的对应字符数组的长度,然后比较字符数组的长度,如果长度不相同,那么直接返回false,也就是这两个字符串的序列号肯定不一样,

如果长度一样,再一循环比较每一个字符

如果不满足if中的判断语句后,在向下执行,

// Argument is a String
                if (cs instanceof String) {
                    return equals(cs);
                }

如果传参是String的实例的时候

则调用了String的equals方法进行比较

如果不满足上面的条件,再向下执行

 // Argument is a generic CharSequence
                    char v1[] = value;
                    int n = v1.length;
                    if (n != cs.length()) {
                        return false;
                    }

可以看到这一步是直接比较 的字符参数的长度 ,如果长度不一样,那么其对应的序列号肯定也就不一样了

如果不满足if条件

   for (int i = 0; i < n; i++) {
                        if (v1[i] != cs.charAt(i)) {
                            return false;
                        }
                    }

可以看到继续向下,则是比较每一个字符,不相同返回false;

如果不满足上述方法,则最后返回true

3.12 感觉无力的 copyValueOf方法

(2016-9-22 18:43 更新)

        String string = "4444";

        char[] charTest = new char[]{'d','r','w','q'};

        String newString = string.copyValueOf(charTest);

构建结果尽然是: drwq

不忍心看源码,但是还得看下,一看,更是无语

         /**
         * Equivalent to {@link #valueOf(char[])}.
         *
         * @param   data   the character array.
         * @return  a {@code String} that contains the characters of the
         *          character array.
         */
        public static String copyValueOf(char data[]) {
            return new String(data);
        }

擦,尽然是调用了String的一个构造来构造了一个新的 String

3.13 判断字符串是否以指定的字符或者字符串开头

(2016-9-23 8:33 更新)

 String string = "andThisIs4";
        //判断是否以a开头
        boolean flag = string.startsWith("a");
        //输出结果为 true

从源码角度来看:

        public boolean startsWith(String prefix) {
            return startsWith(prefix, 0);
        }

这里调用其重载方法

对于这个方法来说,传入两个参数

String string = "thisIsAndAnd";

//判断从二号索引位置 是否是以this开头

boolean flag = string.startsWith("this",2);

//结果 为 flase

public boolean startsWith(String prefix, int toffset) {
            char ta[] = value;
            int to = toffset;
            char pa[] = prefix.value;
            int po = 0;
            int pc = prefix.value.length;
            // Note: toffset might be near -1>>>1.
            if ((toffset < 0) || (toffset > value.length - pc)) {
                return false;
            }
            while (--pc >= 0) {
                if (ta[to++] != pa[po++]) {
                    return false;
                }
            }
            return true;
        }

从上到下 :

//获取 父String 对应的字符数组

char ta[] = value;

//赋值 检索父String的起始位置

int to = toffset;

//获取 子String 对应的字符数组

char pa[] = prefix.value;

//定义变量标识

int po = 0;

//获取 子String 对应字符数组的长度

int pc = prefix.value.length;

//如果检索 父String的位置 小于0 返回false

//如果

if ((toffset < 0) || (toffset > value.length - pc)) {

return false;

}

//循环比较

while (--pc >= 0) {

if (ta[to++] != pa[po++]) {

return false;

}

}

return true

3.14 判断字符串是否以指定的字符或者字符串&#8;结尾

(2016-9-23 8:33 更新)

String string = "andrThisIs4";
        //判断是否是以 4 结尾
        boolean flag = string.endsWith("4");
        //结果 为true

从源码角度来看

public boolean endsWith(String suffix) {
                return startsWith(suffix, value.length - suffix.value.length);
            }

不忍直视,它尽然调用了 startsWith方法来执行判断

3.15 比较两个字符串中的内容是否相同

(2016-9-23 8:33 更新)

        String text1 = "ABCD";
        String text2 = "ABCE";
        //比较两个字符串是否相等
        boolean flage = text1.equals(text2);

text1 text2 分别指向两个堆内存空间

当使用 text1 == text2 来判断时,比较的是text1 与 text2这两个变量 对应的引用地址是否相等,也就是说其指向的内存地址是否一样

从源码角度来看 equals方法

public boolean equals(Object anObject) {
             if (this == anObject) {
                    return true;
                }
            if (anObject instanceof String) {
                String anotherString = (String)anObject;
                int n = value.length;
                if (n == anotherString.value.length) {
                    char v1[] = value;
                    char v2[] = anotherString.value;
                    int i = 0;
                    while (n-- != 0) {
                        if (v1[i] != v2[i])
                            return false;
                        i++;
                    }
                    return true;
                }
            }
            return false;
        }

参数格式要求是 Object类型

在这个方法中,其先比较的两个变量指向的堆内存空间地址是否是一样,如果一样,那么直接返回true ,

当 String text1 = "ABC";在内存空间开辟空间,并赋值变量text1

当定义 Sring text2 ="ABC";时,虚拟机会先去内存空间的常量区中寻找是否有对应内容的空间,如果有,那么就直接指向那边,在这里,因创建的text1 内容与 text2的内容一致,所以在text2创建的时候,会将text2的引用指向直接指向text1指向的内存空间

在接下来的一步中,如果传入的比较参数是String实例,将其强转为String 类型

然后通过循环体制 一一比较每一个字符,

3.16 获取String默认格式下的字节编码

(2016-9-27 14:33 更新)

        String text1 = "ABCDEF";
        //获取默认编码下的字节数组
        byte[] bytes = text1.getBytes();

从源码角度来看:

        public byte[] getBytes() {
            return StringCoding.encode(value, 0, value.length);
        }

这里间接使用了StringCoding类的静态方法 encode方法

        static byte[] encode(char[] ca, int off, int len) {
            String csn = Charset.defaultCharset().name();
            try {
                // use charset name encode() variant which provides caching.
                return encode(csn, ca, off, len);
            } catch (UnsupportedEncodingException x) {
                warnUnsupportedCharset(csn);
            }
            try {
                return encode("ISO-8859-1", ca, off, len);
            } catch (UnsupportedEncodingException x) {
                // If this code is hit during VM initialization, MessageUtils is
                // the only way we will be able to get any kind of error message.
                MessageUtils.err("ISO-8859-1 charset not available: "
                                 + x.toString());
                // If we can not find ISO-8859-1 (a required encoding) then things
                // are seriously wrong with the installation.
                System.exit(1);
                return null;
            }
        }

在这个方法中首先通过 String csn = Charset.defaultCharset().name(); 获取默认的编码方式(“UTF-8”)

然后调用其重载的方法来进行编译 ,

如果出现异常,则使用 ISO-8859-1 的编码方式来进行解析,如果再出现异常,系统异常退出

3.17 获取String指定编码格式下的字节数组

(2016-9-27 14:33 更新)

        String text1 = "abef";
        byte[] bytes = null;

        try {
            bytes = text1.getBytes("UTF-8");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }

可见这个方法是获取默认编码格式下的字节数组方法的重载方法

从源码角度来看

        public byte[] getBytes(String charsetName)
            throws UnsupportedEncodingException {
                if (charsetName == null) throw new NullPointerException();
                return StringCoding.encode(charsetName, value, 0, value.length);
        }

可以看到这里直接调用的是 StringCoding 的四个参数的重载方法(getBytes()方法调用的是三个参数的encode方法)

3.18 将String 中的部分字符复制到指定的字符数组中去

(2016-9-27 14:33 更新)

        String text1  ="ABCDEF";
        //目标字符数组
        char[] chars = new char[10];
        //拷贝
        text1.getChars(0,text1.length(),chars,0);
        //参数一 参数二  拷贝String中字符的范围
        //参数三  目标字符数组
        //参数四  目标字符数组中的存储起始位置

从源码角度来看

public void getChars(int srcBegin, int srcEnd, char dst[], int dstBegin) {
            if (srcBegin < 0) {
                throw new StringIndexOutOfBoundsException(srcBegin);
            }
            if (srcEnd > value.length) {
                throw new StringIndexOutOfBoundsException(srcEnd);
            }
            if (srcBegin > srcEnd) {
                throw new StringIndexOutOfBoundsException(srcEnd - srcBegin);
            }
            System.arraycopy(value, srcBegin, dst, dstBegin, srcEnd - srcBegin);
        }

其直接调用 System 的拷贝数组的方法将数据拷贝

        public static native void arraycopy(Object src,  int  srcPos,
                                        Object dest, int destPos,
                                        int length);

对于System的arraycopy方法来说,其直接调用 JNI层方法实现

在上述调用 System的arraycopy方法的时候 传入参数

参数一  (value) 父级String对应的字符数组

参数二  (srcBegin) 父级String开始拷贝数据的起始位置

参数三  (dst)   目标数组

参数四  (dstBegin) 目标数组存储数据的起始位置

参数五  (srcEnd - srcBegin) 截取父String中字符的长度

时间: 2024-11-09 02:02:04

Android研发中对String的思考(源码分析)的相关文章

Android研发中对String的思考(源代码分析)

1.经常使用创建方式思考: String text = "this is a test text "; 上面这一句话实际上是运行了三件事 1.声明变量 String text; 2.在内存中开辟空间 (内存空间一) 3.将变量的text的引用指向开辟的内存空间 当有 text = "this is a change text"; 这一句话运行了两件事 1.在内存中开辟空间 2.将变量text 的引用指向 新开辟的内存空间 3.内存空间一此时依旧存在,这就是说明了St

Android 中View的绘制机制源码分析 三

到目前为止,measure过程已经讲解完了,今天开始我们就来学习layout过程,不过在学习layout过程之前,大家有没有发现我换了编辑器,哈哈,终于下定决心从Html编辑器切换为markdown编辑器,这里之所以使用"下定决心"这个词,是因为毕竟Html编辑器使用好几年了,很多习惯都已经养成了,要改变多年的习惯确实不易,相信这也是还有很多人坚持使用Html编辑器的原因.这也反应了一个现象,当人对某一事物非常熟悉时,一旦出现了新的事物想取代老的事物时,人们都有一种抵触的情绪,做技术的

JDK中String类的源码分析(二)

1.startsWith(String prefix, int toffset)方法 包括startsWith(*),endsWith(*)方法,都是调用上述一个方法 1 public boolean startsWith(String prefix, int toffset) { 2 char ta[] = value; 3 int to = toffset; 4 char pa[] = prefix.value; 5 int po = 0; 6 int pc = prefix.value.l

Android 中View的绘制机制源码分析 二

尊重原创:http://blog.csdn.net/yuanzeyao/article/details/46842891 本篇文章接着上篇文章的内容来继续讨论View的绘制机制,上篇文章中我们主要讲解了View的measure过程,今天我们就来学习ViewGroup的measure过程,由于ViewGroup只是一个抽象类,所以我们需要以一个具体的布局来分析measure过程,正如我上篇文章说的,我打算使用LinearLayout为例讲解measure过程,如果你还没有读过上篇文章,那么建议你先

在Android Studio中为jar添加source源码

下面的步骤将介绍如何在Android Studio中为jar库绑定源码!!! 1. 在build.gradle中添加jar依赖: dependencies { compile fileTree(dir: 'libs', include: ['*.jar'])               //针对libs下的.jar库 compile files('libs/xxxx.Final.jar')                            // 针对指定文件 } 2. Rebuild后点进j

Android异步消息处理机制详解及源码分析

PS一句:最终还是选择CSDN来整理发表这几年的知识点,该文章平行迁移到CSDN.因为CSDN也支持MarkDown语法了,牛逼啊! [工匠若水 http://blog.csdn.net/yanbober 转载烦请注明出处,尊重分享成果] 最近相对来说比较闲,加上养病,所以没事干就撸些自己之前的知识点为博客,方便自己也方便别人. 1 背景 之所以选择这个知识点来分析有以下几个原因: 逛GitHub时发现关注的isuss中有人不停的在讨论Android中的Looper , Handler , Me

caffe中HingeLossLayer层原理以及源码分析

输入: bottom[0]: NxKx1x1维,N为样本个数,K为类别数.是预测值. bottom[1]: Nx1x1x1维, N为样本个数,类别为K时,每个元素的取值范围为[0,1,2,-,K-1].是groundTruth. 输出: top[0]: 1x1x1x1维, 求得是hingeLoss. 关于HingeLoss: p: 范数,默认是L1范数,可以在配置中设置为L1或者L2范数. :指示函数,如果第n个样本的真实label为k,则为,否则为-1. tnk: bottom[0]中第n个样

ym——Android仿网易新闻导航栏PagerSlidingTabStrip源码分析

转载请注明本文出自Cym的博客(http://blog.csdn.net/cym492224103),谢谢支持! 前言 最近工作比较忙,所以现在才更新博文,对不住大家了~!言归正传,我们来说说这个PagerSlidingTabStrip,它是配合ViewPager使用的导航栏,网易新闻就是用的这个导航,我们仔细观察这个导航栏不仅他是跟着ViewPager滑动而滑动,而且指示器还会随着标题的长度而动态的变化长度,还可以改变多种样式哦~! · 下载地址: Github:https://github.

OpenSSL 中 RSA 加密解密实现源码分析

1.RSA 公钥和私钥的组成,以及加密和解密的公式: 2.模指数运算: 先做指数运算,再做模运算,如 5^3 mod 7 = 125 mod 7 = 6 3.RSA加密算法流程: 选择一对不同的.并且足够大的素数 p 和 q 计算 n = p * q 计算欧拉函数 f(n) = (p-1) * (q-1),p 和 q 需要保密 寻找与 f(n) 互质的数 e,并且 1 < e < f(n) 计算 d,使得 d * e ≡ 1 mod f(n) 公钥 KU = (e , n)   私钥 KR =