Java 深入理解:String源码分析

本文深入剖析Java中String类的源码,详细解读其构造方法、成员变量及各种对外提供的方法,探讨String不可变性的原因及其对性能的影响。

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

因为String可以说是java中用的最多的一种类型,但是毕业三年还没看过源码,实在是惭愧,今天特意静下心来自己看看String的源码。

String源码分析

继承结构
public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {}
  • String是一个final类,所以不能被继承
  • 实现Serializable,可以被序列化
  • 实现Comparable,可以用于比较大小(按顺序比较单个字符的ASCII码)
  • 实现CharSequence,表示是一个有序字符的序列,(因为String的本质是一个char类型数组)
成员
// String底层实现是一个不可变char型数组
private final char value[];

// 存储字符串哈希值
private int hash; // Default to 0

// 实现序列化的标识
private static final long serialVersionUID = -6849794470754667710L;

private final char value[]这是String字符串的本质,是一个字符数组,被final修饰,是不可变的。

构造方法
   /** 01
	* 默认将""空字符串的value赋值给实例对象的value,也是空字符
	* 相当于深拷贝了空字符串""
	*/
	public String() {
        this.value = "".value;
    }

	/** 02
	* 这是一个有参构造函数,参数为一个String对象
	* 将形参的value和hash赋值给实例对象作为初始化
	* 相当于深拷贝了一个形参String对象
	*/
    public String(String original) {
        this.value = original.value;
        this.hash = original.hash;
    }

	/** 03
	* 这是一个有参构造函数,参数为一个char字符数组
	* 意义就是通过字符数组去构建一个新的String对象
	*/
    public String(char value[]) {
        this.value = Arrays.copyOf(value, value.length);
    }

	/** 04
	* 这是一个有参构造函数,参数为char字符数组,offset(起始位置,偏移量),count(个数)
	* 作用就是在char数组的基础上,从offset位置开始计数count个,构成一个新的String的字符串
	* 意义就类似于截取count个长度的字符集合构成一个新的String对象
	*/
    public String(char value[], int offset, int count) {
        if (offset < 0) {        //如果起始位置小于0,抛异常
            throw new StringIndexOutOfBoundsException(offset);
        }
        if (count <= 0) {
            if (count < 0) {     //如果个数小于0,抛异常
                throw new StringIndexOutOfBoundsException(count);
            }
            if (offset <= value.length) {      //在count = 0的前提下,如果offset<=len,则返回""
                this.value = "".value;
                return;
            }
        }
        // Note: offset or count might be near -1>>>1.
        //如果起始位置>字符数组长度 - 个数,则无法截取到count个字符,抛异常
        if (offset > value.length - count) { 
            throw new StringIndexOutOfBoundsException(offset + count);
        }
        //重点,从offset开始,截取到offset+count位置(不包括offset+count位置)
        this.value = Arrays.copyOfRange(value, offset, offset+count); 
    }

	/** 05
	* 这是一个有参构造函数,参数为int字符数组,offset(起始位置,偏移量),count(个数)
	* 作用跟04构造函数差不多,但是传入的不是char字符数组,而是int数组。
	* 而int数组的元素则是字符对应的ASCII整数值
	* 例子:new String(new int[]{97,98,99},0,3);   output: abc
	*/
    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);
        }  
		//以上都是为了处理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));
        }
		
		//上面关于BMP什么的,我暂时也没看懂,猜想关于验证int数据的正确性,通过上面的测试就进入下面的算法
		
        // Pass 2: Allocate and fill in char[]
        final char[] v = new char[n];

        for (int i = offset, j = 0; i < end; i++, j++) {  //从offset开始,到offset + count
            int c = codePoints[i];
            if (Character.isBmpCodePoint(c))
                v[j] = (char)c;   //将Int类型显式缩窄转换为char类型
            else
                Character.toSurrogates(c, v, j++);
        }

        this.value = v; //最后将得到的v赋值给String对象的value,完成初始化
    }


	/** 06
	* 这是一个有参构造函数,参数为byte数组,offset(起始位置,偏移量),长度,和字符编码格式
	* 就是传入一个byte数组,从offset开始截取length个长度,其字符编码格式为charsetName,如UTF-8
	* 例子:new String(bytes, 2, 3, "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);
    }

	/** 07
	* 类似06
	*/
    public String(byte bytes[], int offset, int length, Charset charset) {
        if (charset == null)
            throw new NullPointerException("charset");
        checkBounds(bytes, offset, length);
        this.value =  StringCoding.decode(charset, bytes, offset, length);
    }
	
	/** 08
	* 这是一个有参构造函数,参数为byte数组和字符集编码
	* 用charsetName的方式构建byte数组成一个String对象
	*/
    public String(byte bytes[], String charsetName)
            throws UnsupportedEncodingException {
        this(bytes, 0, bytes.length, charsetName);
    }

	/** 09
	* 类似08
	*/
    public String(byte bytes[], Charset charset) {
        this(bytes, 0, bytes.length, charset);
    }

	/** 10
	* 这是一个有参构造函数,参数为byte数组,offset(起始位置,偏移量),length(个数)
	* 通过使用平台的默认字符集解码指定的 byte 子数组,构造一个新的 String。
	* 
	*/
    public String(byte bytes[], int offset, int length) {
        checkBounds(bytes, offset, length);
        this.value = StringCoding.decode(bytes, offset, length);
    }

	/** 11
	* 这是一个有参构造函数,参数为byte数组
	* 通过使用平台默认字符集编码解码传入的byte数组,构造成一个String对象,不需要截取
	* 
	*/
    public String(byte bytes[]) {
        this(bytes, 0, bytes.length);
    }

	/** 12
	* 有参构造函数,参数为StringBuffer类型
	* 就是将StringBuffer构建成一个新的String,比较特别的就是这个方法有synchronized锁
	* 同一时间只允许一个线程对这个buffer构建成String对象
	*/
    public String(StringBuffer buffer) {
        synchronized(buffer) {
            this.value = Arrays.copyOf(buffer.getValue(), buffer.length()); //使用拷贝的方式
        }
    }

	/** 13
	* 有参构造函数,参数为StringBuilder
	* 同12差不多,只不过是StringBuilder的版本,差别就是没有实现线程安全
	*/
    public String(StringBuilder builder) {
        this.value = Arrays.copyOf(builder.getValue(), builder.length());
    }
	
	/** 14
	* 这个构造函数比较特殊,有用的参数只有char数组value,是一个不对外公开的构造函数,没有访问修饰符
	* 加入这个share的只是为了区分于String(char[] value)方法,用于重载,功能类似于03,我也在03表示过疑惑。
	* 为什么提供这个方法呢,因为性能好,不需要拷贝。为什么不对外提供呢?因为对外提供会打破value为不变数组的限制。
	* 如果对外提供这个方法让String与外部的value产生关联,如果修改外不的value,会影响String的value。所以不能
	* 对外提供
	*/
    String(char[] value, boolean share) {
        // assert share : "unshared not supported";
        this.value = value;
    }
对外方法
// 返回数组长度,也就是String长度
public int length() {
    return value.length;
}
// 数组长度为0则说明String是空字符串
public boolean isEmpty() {
    return value.length == 0;
}
// 返回字符串指定位置的字符,也就是返回底层数组指定下标的字符
public char charAt(int index) {
    if ((index < 0) || (index >= value.length)) {
        throw new StringIndexOutOfBoundsException(index);
    }
    return value[index];
}
// 返回String对象指定位置字符的ASSIC码,也就是返回底层数组指定下标的元素的ASSIC码(int类型)
public int codePointAt(int index) {
    if ((index < 0) || (index >= value.length)) {
        throw new StringIndexOutOfBoundsException(index);
    }
    return Character.codePointAtImpl(value, index, value.length);
}
// 返回String对象指定位置前一个字符的ASSIC码,也就是返回底层数组指定下标前一个的元素的ASSIC码(int类型)
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);
}
/**
* 方法返回的是代码点个数,是实际上的字符个数,功能类似于length()
* 对于正常的String来说,length方法和codePointCount没有区别,都是返回字符个数。
* 但当String是Unicode类型时则有区别了。
* 例如:String str = “/uD835/uDD6B” (即 'Z' ), length() = 2 ,codePointCount() = 1 
*/
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);
}
/**
* 也是相对Unicode字符集而言的,从index索引位置算起,偏移codePointOffset个位置,返回偏移后的位置是多少
* 例如,index = 2 ,codePointOffset = 3 ,maybe返回 5 
*/
public int offsetByCodePoints(int index, int codePointOffset) {
    if (index < 0 || index > value.length) {
        throw new IndexOutOfBoundsException();
    }
    return Character.offsetByCodePointsImpl(value, 0, value.length,
            index, codePointOffset);
}

   /**
	* 这是一个不对外的方法,是给String内部调用的,因为它是没有访问修饰符的,只允许同一包下的类访问
	* 参数:dst[]是目标数组,dstBegin是目标数组的偏移量,既要复制过去的起始位置(从目标数组的什么位置覆盖)
	* 作用就是将String的字符数组value整个复制到dst字符数组中,在dst数组的dstBegin位置开始拷贝
	* 
	*/
	void getChars(char dst[], int dstBegin) {
        System.arraycopy(value, 0, dst, dstBegin, value.length);
    }

   /**
	* 得到char字符数组,原理是getChars() 方法将一个字符串的字符复制到目标字符数组中。 
	* 参数:srcBegin是原始字符串的起始位置,srcEnd是原始字符串要复制的字符末尾的后一个位置(既复制区域不包括srcEnd)
	* dst[]是目标字符数组,dstBegin是目标字符的复制偏移量,复制的字符从目标字符数组的dstBegin位置开始覆盖。
	*/
    public void getChars(int srcBegin, int srcEnd, char dst[], int dstBegin) {
        if (srcBegin < 0) {           //如果srcBegin小于,抛异常
            throw new StringIndexOutOfBoundsException(srcBegin);
        }
    *    if (srcEnd > value.length) {  //如果srcEnd大于字符串的长度,抛异常
            throw new StringIndexOutOfBoundsException(srcEnd);
        }
        if (srcBegin > srcEnd) {      //如果原始字符串其实位置大于末尾位置,抛异常
            throw new StringIndexOutOfBoundsException(srcEnd - srcBegin);
        }
        System.arraycopy(value, srcBegin, dst, dstBegin, srcEnd - srcBegin);
    }

   /**
    * 获得charsetName编码格式的bytes数组
	*/
    public byte[] getBytes(String charsetName)
            throws UnsupportedEncodingException {
        if (charsetName == null) throw new NullPointerException();
        return StringCoding.encode(charsetName, value, 0, value.length);
    }

   /**
    * 与上个方法类似,但charsetName和charset的区别,我还没搞定,搞懂来再更新
	*/
    public byte[] getBytes(Charset charset) {
        if (charset == null) throw new NullPointerException();
        return StringCoding.encode(charset, value, 0, value.length);
    }

	/**
    * 使用平台默认的编码格式获得bytes数组
	*/
    public byte[] getBytes() {
        return StringCoding.encode(value, 0, value.length);
    }

写一些常见的方法吧

// 按某一个字符串分割,返回String 数组
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);
}
// 转为小写
public String toLowerCase() {
    return toLowerCase(Locale.getDefault());
}
public String toLowerCase(Locale locale) {
    if (locale == null) {
        throw new NullPointerException();
    }

    int firstUpper;
    final int len = value.length;

    /* Now check if there are any characters that need to be changed. */
    scan: {
        for (firstUpper = 0 ; firstUpper < len; ) {
            char c = value[firstUpper];
            if ((c >= Character.MIN_HIGH_SURROGATE)
                    && (c <= Character.MAX_HIGH_SURROGATE)) {
                int supplChar = codePointAt(firstUpper);
                if (supplChar != Character.toLowerCase(supplChar)) {
                    break scan;
                }
                firstUpper += Character.charCount(supplChar);
            } else {
                if (c != Character.toLowerCase(c)) {
                    break scan;
                }
                firstUpper++;
            }
        }
        return this;
    }

    char[] result = new char[len];
    int resultOffset = 0;  /* result may grow, so i+resultOffset
                            * is the write location in result */

    /* Just copy the first few lowerCase characters. */
    System.arraycopy(value, 0, result, 0, firstUpper);

    String lang = locale.getLanguage();
    boolean localeDependent =
            (lang == "tr" || lang == "az" || lang == "lt");
    char[] lowerCharArray;
    int lowerChar;
    int srcChar;
    int srcCount;
    for (int i = firstUpper; i < len; i += srcCount) {
        srcChar = (int)value[i];
        if ((char)srcChar >= Character.MIN_HIGH_SURROGATE
                && (char)srcChar <= Character.MAX_HIGH_SURROGATE) {
            srcChar = codePointAt(i);
            srcCount = Character.charCount(srcChar);
        } else {
            srcCount = 1;
        }
        if (localeDependent ||
            srcChar == '\u03A3' || // GREEK CAPITAL LETTER SIGMA
            srcChar == '\u0130') { // LATIN CAPITAL LETTER I WITH DOT ABOVE
            lowerChar = ConditionalSpecialCasing.toLowerCaseEx(this, i, locale);
        } else {
            lowerChar = Character.toLowerCase(srcChar);
        }
        if ((lowerChar == Character.ERROR)
                || (lowerChar >= Character.MIN_SUPPLEMENTARY_CODE_POINT)) {
            if (lowerChar == Character.ERROR) {
                lowerCharArray =
                        ConditionalSpecialCasing.toLowerCaseCharArray(this, i, locale);
            } else if (srcCount == 2) {
                resultOffset += Character.toChars(lowerChar, result, i + resultOffset) - srcCount;
                continue;
            } else {
                lowerCharArray = Character.toChars(lowerChar);
            }

            /* Grow result if needed */
            int mapLen = lowerCharArray.length;
            if (mapLen > srcCount) {
                char[] result2 = new char[result.length + mapLen - srcCount];
                System.arraycopy(result, 0, result2, 0, i + resultOffset);
                result = result2;
            }
            for (int x = 0; x < mapLen; ++x) {
                result[i + resultOffset + x] = lowerCharArray[x];
            }
            resultOffset += (mapLen - srcCount);
        } else {
            result[i + resultOffset] = (char)lowerChar;
        }
    }
    return new String(result, 0, len + resultOffset);
}
// 去头和尾空格
public String trim() {
    int len = value.length;//尾部指针标志,须-1
    int st = 0;//头指针
    char[] val = value;    /* avoid getfield opcode */

    while ((st < len) && (val[st] <= ' ')) {
        st++;
    }
    while ((st < len) && (val[len - 1] <= ' ')) {
        len--;
    }
    return ((st > 0) || (len < value.length)) ? substring(st, len) : this;
}

String中唯一一个native函数

public native String intern();

String类中唯一的一条本地方法
比如str.intern(),作用就是去字符串常量池中寻找str字符串,如果有则返回str在常量池中的引用,如果没有则在常量池中创建str对象

相关问题

1,String类为何为不可变类型?
  • 从String的定义,我们可以知道,String类是被final修饰的,是一个不可被继承的类
  • String底层实际上是一个私有的不可变数组,也没有对外公开的改变数组中值的方法
  • 从其他的方法中,我们也可以看到,如果是对字符串进行修改,其本质是new了一个新的String的对象
但是

可以通过反射修改String底层数组中的元素的值

2,为什么String已经有一个compareTo方法了,还需要一个静态内部类再实现compare?

因为String继承了comparable接口,所以可以重写compareTo(String str)方法,注意参数是String类型的。里面实现的方式不忽略大小写的比较方式。如果想要一个忽略大小写的比较方式,就需要一个比较器来实现,这时String提供一个内部比较器给我们用,我们就不用重写一个单独的类做String的比较器了

3,为什么在忽略大小写比较的时候,通常都会大小写都会比较一次?

也就是说,通常会同位置的字符会全部转为大写比较一次,又转为小写比较一次?这是为什么,不是转换为大写或小写比较一次既可吗?
同时比较了UpperCase和LowerCase,是为了兼容Georgian字符。
https://www.cnblogs.com/yanyichao/p/4493039.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值