String类常用方法源码分析


title: String类常用方法源码分析
date: 2019-10-24 13:48:58
tags: java
categories: java

环境: JDK8

String

先看String类的定义

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence

可以看到String类被final修饰,因此不能被继承.String类还实现了序列化接口Serializable,可比较的接口Comparable并指定泛型String,该接口必须实现int compareTo方法,最后还实现了字符序列CharSequence的接口,该接口常用的方法如chatAt(int index),length(),toString()等等.

构造字符串

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

其中value定义:

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

该构造函数创建了一个空的字符换并存在字符数组value中.

再看看一个有参的构造函数:

/**
     * 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);
    }

  1. 该构造函数指定一个字符数组来创建一个字符序列,是通过Arrays的copyOf方法将字符数组拷贝到当前数组.
    这样当修改字符数组的子串时,不会影响新字符数组.

下面经过一个简单的测试来验证下

 public static void main(String[] args) {
        char[] c1 = {'a', 'b'};
        char[] c2 = c1;
        for (int i = 0; i < c1.length; i++) {
            if (i==1){
                c1[1]='c';
            }
        }
        for (char c : c1) {
            System.out.println("c1:"+c);
        }
        for (char c : c2) {
            System.out.println("c2:"+c);
        }

    }

结果

c1:a
c1:c
c2:a
c2:c

由上可知,在c1赋值给c2后,改变了c1中的值c2的值也会随之变化,这显然与我们想要的相悖,所以才会使用拷贝的方式,将c1与c2分开.

经过以上分析可以看出,下面两个语句是等价的,因为String类底层使用char[]数组来存储字符序列.

char data[] = {'a', 'b', 'c'};
String str = new String(data);
  1. string.value.length的代码等价于string.length
public int length() {
        return value.length;
    }

使用字节数组构造一个String

在Java中,String实例中保存有一个char[]字符数组,char[]字符数组是以unicode码来存储的,String 和 char 为内存形式,byte是网络传输或存储的序列化形式。所以在很多传输和存储的过程中需要将byte[]数组和String进行相互转化。所以,String提供了一系列重载的构造方法来将一个字符数组转化成String,提到byte[]和String之间的相互转换就不得不关注编码问题。

String(byte[] bytes, Charset charset) 是指通过charset来解码指定的byte数组,将其解码成unicode的char[]数组,够造成新的String。

这里的bytes字节流是使用charset进行编码的,想要将他转换成unicode的char[]数组,而又保证不出现乱码,那就要指定其解码方式。

如果我们在使用byte[]构造String的时候,使用的是下面这四种构造方法(带有charsetName或者charset参数)的一种的话,那么就会使用StringCoding.decode方法进行解码,使用的解码的字符集就是我们指定的charsetName或者charset。 我们在使用byte[]构造String的时候,如果没有指明解码使用的字符集的话,那么StringCoding的decode方法首先调用系统的默认编码格式,如果没有指定编码格式则默认使用ISO-8859-1编码格式进行编码操作。主要体现代码如下:

static byte[] encode(String charsetName, char[] ca, int off, int len)
        throws UnsupportedEncodingException
    {
        StringEncoder se = deref(encoder);
        String csn = (charsetName == null) ? "ISO-8859-1" : charsetName;
        if ((se == null) || !(csn.equals(se.requestedCharsetName())
                              || csn.equals(se.charsetName()))) {
            se = null;
            try {
                Charset cs = lookupCharset(csn);
                if (cs != null)
                    se = new StringEncoder(cs, csn);
            } catch (IllegalCharsetNameException x) {}
            if (se == null)
                throw new UnsupportedEncodingException (csn);
            set(encoder, se);
        }
        return se.encode(ca, off, len);
}

上面是编码清单,下面是解码清单:

static char[] decode(String charsetName, byte[] ba, int off, int len)
        throws UnsupportedEncodingException
    {
        StringDecoder sd = deref(decoder);
        String csn = (charsetName == null) ? "ISO-8859-1" : charsetName;
        if ((sd == null) || !(csn.equals(sd.requestedCharsetName())
                              || csn.equals(sd.charsetName()))) {
            sd = null;
            try {
                Charset cs = lookupCharset(csn);
                if (cs != null)
                    sd = new StringDecoder(cs, csn);
            } catch (IllegalCharsetNameException x) {}
            if (sd == null)
                throw new UnsupportedEncodingException(csn);
            set(decoder, sd);
        }
        return sd.decode(ba, off, len);
}

charAt

再看charAt(int index)方法源码:

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

该方法返回字符序列中下标为index的字符。并且index的范围:(0,value.length].

concat

先看源码:

public String concat(String str) {
        int otherLen = str.length(); //取得str字符串长度
        if (otherLen == 0) { //如果传入的字符串内容为空,则返回原字符串
            return this;
        }
        int len = value.length; //取得原字符串内容的长度
        char buf[] = Arrays.copyOf(value, len + otherLen); //复制字符串的内容到char数组,并扩展长度
        str.getChars(buf, len); //将str的内容复制到buf数组中,位置从len开始。在这一步完成两个字符串的拼接
        return new String(buf, true); //将buf数组转化为新的String实例并返回
    }

步骤含义基本都在上面,这里着重描述一下getChars方法

void getChars(char dst[], int dstBegin) {
        System.arraycopy(value, 0, dst, dstBegin, value.length);
    }

可以看出,链接字符换操作实际是字符串的拷贝.最后返回连接成功后的字符串.

最后是一个特殊的私有包范围类型的构造方法,String除了提供了很多公有的供程序员使用的构造方法以外,还提供了一个包范围类型的构造方法(Jdk 8),我们看一下他是怎么样的:

String(char[] value, boolean share) {
        // assert share : "unshared not supported";
        this.value = value;
}

从代码中我们可以看出,该方法和 String(char[] value)有两点区别:

  1. 第一个,该方法多了一个参数: boolean share,其实这个参数在方法体中根本没被使用,也给了注释,目前不支持使用false,只使用true。那么可以断定,加入这个share的只是为了区分于String(char[] value)方法,不加这个参数就没办法定义这个函数,只有参数不能才能进行重载。

  2. 第二个区别就是具体的方法实现不同。

在这里插入图片描述
那么也就是说,这个方法构造出来的String和参数传过来的char[] value共享同一个数组。 那么,为什么Java会提供这样一个方法呢?

首先,我们分析一下使用该构造函数的好处:

  1. 首先,性能好,这个很简单,一个是直接给数组赋值(相当于直接将String的value的指针指向char[]数组),一个是逐一拷贝。当然是直接赋值快了。

  2. 其次,共享内部数组节约内存。

  3. 该方法之所以设置为包范围,是因为一旦该方法设置为公有,在外面可以访问的话,那就破坏了字符串的不可变性。

subString

substring有两个重载方法:

public String substring(int beginIndex) {
        if (beginIndex < 0) {
            throw new StringIndexOutOfBoundsException(beginIndex);
        }
        int subLen = value.length - beginIndex;
        if (subLen < 0) {
            throw new StringIndexOutOfBoundsException(subLen);
        }
        return (beginIndex == 0) ? this : new String(value, beginIndex, subLen);
    }
public String substring(int beginIndex, int endIndex) {
        if (beginIndex < 0) {
            throw new StringIndexOutOfBoundsException(beginIndex);
        }
        if (endIndex > value.length) {
            throw new StringIndexOutOfBoundsException(endIndex);
        }
        int subLen = endIndex - beginIndex;
        if (subLen < 0) {
            throw new StringIndexOutOfBoundsException(subLen);
        }
        return ((beginIndex == 0) && (endIndex == value.length)) ? this
                : new String(value, beginIndex, subLen);
    }

这两个重载方法都是先计算要截取的子串长度,判断边界最后返回调用new String(value, beginIndex, subLen)方法,我们来看一下这个方法:

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

offset指第一个匹配的字符序列的索引,count指子串的长度。
最终该子串会被拷贝到字符数组value中,并且后续的字符数组的修改并不影响新创建的字符串。

contains

先看一下源码:

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

可以看出,直接调用indexOf方法:

public int indexOf(String str) {
        return indexOf(str, 0);
}
  public int indexOf(String str, int fromIndex) {
        return indexOf(value, 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;
    }

首先判断开始索引如果大于源字符串则返回,若目标字符串长度为0返回源字符串长度,否则返回-1.

然后迭代查找字符,若全部源字符串都找到则返回第一个匹配的索引,否则返回-1.
所以在public boolean contains(CharSequence s)方法中,若indexOf方法返回-1则返回false,否则返回true。

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

该方法首先判断this == anObject ?,也就是说判断要比较的对象和当前对象是不是同一个对象,如果是直接返回true,如不是再继续比较,然后在判断anObject是不是String类型的,如果不是,直接返回false,如果是再继续比较,到了能终于比较字符数组的时候,他还是先比较了两个数组的长度,不一样直接返回false,一样再逐一比较值。

join

public static String join(CharSequence delimiter, CharSequence... elements) {
        Objects.requireNonNull(delimiter);
        Objects.requireNonNull(elements);
        // Number of elements not likely worth Arrays.stream overhead.
        StringJoiner joiner = new StringJoiner(delimiter);
        for (CharSequence cs: elements) {
            joiner.add(cs);
        }
        return joiner.toString();
}
  1. StringJoiner 类也是jdk1.8开始加入的通过分隔符或前缀或后缀来构造字符串的,底层是字符序列的拷贝。

  2. requireNonNull方法校验参数不能为空,否则抛异常.源码:

public static <T> T requireNonNull(T obj) {
        if (obj == null)
            throw new NullPointerException();
        return obj;
}

==============================================

参考文章: https://blog.csdn.net/u011726984/article/details/51326697

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值