Java基础类之String源码分析

简介

String类是Java中的不可变字符串类,这种不可变性主要是因为内部属性private final char value[]字段是用final关键字修饰的,以及没有任何修改char[]的方法。另外,String类也是用final修饰的,说明研发者不希望这个类被继承。学习和面试中也经常会问到String相关知识点。

继承结构

 这里可以看出String实现了Serializable、Comparable、CharSequence三个接口。

Compareable主要是用于比较两个字符串的操作

Serializable接口,这是一个序列化标志接口。

CharSequence 接口,表示是一个有序字符的集合。

主要属性

    //存储字符串的属性 
    private final char value[];

    //缓存字符串的哈希码
    private int hash; // Default to 0

    /** use serialVersionUID from JDK 1.0.2 for interoperability */
    private static final long serialVersionUID = -6849794470754667710L;

可以看出char类型的value值是用final修饰的,底层其实是一个char类型的数组存储字符串的。

String类是用final修饰的,所以我们认为其是不可变对象,但是真的不可变吗?

     每个字符串都是由许多单个字符串组成的,value被final修饰,只能保证引用不被改变,但是value所指向的堆中的数组,才是真实的数据,只要能够操作堆中的数组,依旧能够改变数据。而且value是基本类型构成的,那么即使被声明为private,我们也可以通过反射来改变。

public static void main(String[] args) throws IllegalAccessException, NoSuchFieldException 
   {
        String str = "hello";
        System.out.println(str);
        Field fieldStr = String.class.getDeclaredField("value");
        fieldStr.setAccessible(true);
        char[] value = (char[])fieldStr.get(str);
        value[0] = 'H';
        System.out.println(str);
    }

通过前后两次打印的结果,我们可以看到String被改变了,但是在代码里,几乎不会使用反射的机制去操作String字符串,所以,我们会认为String类型是不可变的。

那么,String类为什么要设计成不可变的呢?我们可以从性能以及安全方面来考虑:

安全

      引发安全问题,譬如,数据库的用户名、密码都是以字符串的形式传入来获得数据库的连接,或者在socket编程中,主机名和端口都是以字符串的形式传入。因为字符串是不可变的,所以它的值是不可改变的,否则黑客们可以钻到空子,改变字符串指向的对象的值,造成安全漏洞。

      保证线程安全,在并发场景下,多个线程同时读写资源时,会引竞态条件,由于 String 是不可变的,不会引发线程的问题而保证了线程。

      HashCode,当 String 被创建出来的时候,hashcode也会随之被缓存,hashcode的计算与value有关,若 String 可变,那么 hashcode 也会随之变化,针对于 Map、Set 等容器,他们的键值需要保证唯一性和一致性,因此,String 的不可变性使其比其他对象更适合当容器的键值。
性能

      当字符串是不可变时,字符串常量池才有意义。字符串常量池的出现,可以减少创建相同字面量的字符串,让不同的引用指向池中同一个字符串,为运行时节约很多的堆内存。若字符串可变,字符串常量池就失去了意义,基于常量池的String.intern()方法也失效,每次创建新的String将在堆内开辟出新的空间,占据更多的内存。

Java没有内置的字符串类型,标准的java类库中提供了一个预定义类,每个用双括号括起来的字符串都是String类的一个实例。与大多数程序设计语言一样,Java语言使用+号拼接两个字符串。当一个字符串与一个非字符串的值进行拼接时,后者被转换成字符串。每次拼接都会创建一个新对象,也就是每次都要去申请内存空间,因为做大量字符串拼接时性能很差,只适合做少量的字符串拼接。

构造方法

String类中提供了很多重载的构造方法,这里只分析一些常用的构造方法。

    //无参的构造    
    public String() {
        this.value = "".value;
    }
    
    public String(String original) {
        this.value = original.value;
        this.hash = original.hash;
    }
    
    public String(char value[]) {
        this.value = Arrays.copyOf(value, value.length);
    }
    public String(StringBuffer buffer) {
        synchronized(buffer) {
            this.value = Arrays.copyOf(buffer.getValue(), buffer.length());
        }
    }
    public String(StringBuilder builder) {
        this.value = Arrays.copyOf(builder.getValue(), builder.length());
    }

length()方法

返回字符串的长度(也就是对应的char数组的长度)

    public int length() {
        return value.length;
    }

isEmpty()方法

判断字符串是否为null (也就是判断char数组长度是否为0)

    public boolean isEmpty() {
        return value.length == 0;
    }

charAt(int  index)方法

截取index位置的字符并返回(返回char数组中的index位置的数据)

    public char charAt(int index) {
        //这里需要判断索引index是否越界
        if ((index < 0) || (index >= value.length)) {
            throw new StringIndexOutOfBoundsException(index);
        }
        return value[index];
    }

equals()方法

String类中的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;
    }

substring()方法

这个方法主要是截取从beginIndex到endIndex的字符

    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);
        }
        //这里返回的是新new的一个String   获取当前value的从beginIndex  截取sublen长度
        return ((beginIndex == 0) && (endIndex == value.length)) ? this
                : new String(value, beginIndex, subLen);
    }

通过这里我们可以看到,subString底层其实使用的构造方法,我们来看一下这个构造方法

    public String(char value[], int offset, int count) {
        //这里是判断偏移量和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);
        }
        //因为value是一个char 类型的数组   这里使用的是Arrays的copyOfRange方法
        //类似System.arraycopy方法     
        this.value = Arrays.copyOfRange(value, offset, offset+count);
    }

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值