不惑JAVA之JAVA基础 - String

 本文适合有一定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
......
}    

String类名前面是final修复,变量char value[]也是final修饰。
可以得到三点:

  • String实质是字符数组;
  • 该类不可被继承;
  • 不可变性(immutable)。

常量池

Java中的常量池,实际上分为两种形态 : 静态常量池运行时常量池

  • 静态常量池,即*.class文件中的常量池,class文件中的常量池不仅仅包含字符串(数字)字面量,还包含类、方法的信息,占用class文件绝大部分空间。
  • 运行时常量池,则是jvm虚拟机在完成类装载操作后,将class文件中的常量池载入到内存中,并保存在方法区中,我们常说的常量池,就是指方法区中的运行时常量池。
public class StringTest {
    public static void main(String[] args) {
        String s = new String("abc");
        String s1 = "abc";
        String s2 = "abc";
        String s3 = s.intern();
        System.out.println(s == s1);//false
        System.out.println(s == s2);//false
        System.out.println(s == s3);//false
        System.out.println(s1 == s3);//true      
    }
}

分析:
输出结果如注释所示,前两个结果前面已经说的很清楚了,现在拿最后一个说明,首先看看s3 = s.intern()这句,当调用s.intern()这句的时候,先去字符串常量池中找,是否有abc这个串,如果没有,则新增,同时返回引用,如果有,则返回已经存在的引用,此处s1和s2都指向常量池中的abc对象,所以此处是存在的,调用s.intern()后,s3和s1、s2指向同一个对象,所以s1==s3返回的是true。
intern()做到了一个很不寻常的行为:在运行期动态的在方法区创建对象,一般只有像new关键字可以在运行期在堆上面创建对象,所以此处比较特殊。属于及时编译的概念。

如对常量池感兴趣可以参看:《Java虚拟机原理图解》 2.2、常量池详解(上)

常用方法

equals 方法
public boolean equals(Object anObject) {
    if (this == anObject) {
        return true;
    }
    if (anObject instanceof String) {
        String anotherString = (String)anObject;
        int n = count;
        if (n == anotherString.count) {
        char v1[] = value;  //---------1---------
        char v2[] = anotherString.value;//-------2----------
        int i = offset;
        int j = anotherString.offset;
        while (n-- != 0) { // --------3------------
            if (v1[i++] != v2[j++])
            return false;
        }
        return true;
        }
    }
    return false;
    }

通过1和2获得两个字符串的字符数组,循环3进行对比。

后期文章会单独详解 重写equals和hashcode的

charAt

获取指定位置的字符.

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

用于截取字符串

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

有一个Arrays.copyOfRange(value, offset, offset+count); ,这个方法的大致意思是通过原字符数组拷贝一份新的字符串。

如果还需要了解那个方法的底层代码可以给我留言。

StringBuffer与StringBuilder的区别

StringBuffer和StringBuilder都继承了抽象类AbstractStringBuilder,这个抽象类和String一样也定义了char[] value和int count,但是与String类不同的是,它们没有final修饰符。因此得出结论:String、StringBuffer和StringBuilder在本质上都是字符数组,不同的是,在进行连接操作时,String每次返回一个新的String实例,而StringBuffer和StringBuilder的append方法直接返回this,所以这就是为什么在进行大量字符串连接运算时,不推荐使用String,而推荐StringBuffer和StringBuilder。

    public synchronized StringBuffer append(String str) {
        super.append(i);
        return this;
    }
    public StringBuilder append(String str) {
        super.append(str);
        return this;
    }

由于StringBuffer方法上有synchronized所有是现成安全的。
继续看 super.append(i);源码

    public AbstractStringBuilder append(String str) {
        if (str == null) str = "null";
        int len = str.length();
        ensureCapacityInternal(count + len);
        str.getChars(0, len, value, count);
        count += len;
        return this;
    }

已StringBuilder为例,它集成AbstractStringBuilder抽象类。ensureCapacityInternal(count + len);是检查添加前字符数组长度是否满足append后数组的长度。如满足不做任何处理,如不满足创建一个新的字符数组,长度是现有数组的2倍,并将现有数组拷贝到新创建的数组中。来看一下str.getChars(0, len, value, count);源码

    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.arraycopy(value, srcBegin, dst, dstBegin, srcEnd - srcBegin); 是将新字符串拷贝到原有字符串中。

arraycopy 方法说明:

    public static void arraycopy(Object src, int srcPos, Object dest, int destPos, int length)
  • src:源数组;
  • srcPos:源数组要复制的起始位置;
  • dest:目的数组;
  • destPos:目的数组放置的起始位置; l
  • ength:复制的长度。
    注意:src and dest都必须是同类型或者可以进行转换类型的数组. 有趣的是这个函数可以实现自己到自己复制,比如: int[] fun ={0,1,2,3,4,5,6}; System.arraycopy(fun,0,fun,3,3); 则结果为:{0,1,2,0,1,2,6}; 实现过程是这样的,先生成一个长度为length的临时数组,将fun数组中srcPos 到srcPos+length-1之间的数据拷贝到临时数组中,再执行System.arraycopy(临时数组,0,fun,3,3)。

有意思的思考题

有时间补上。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值