String源码分析

栏目: Java · 发布时间: 6年前

内容简介:其实String方面的面试题往深了延申的话,还是会延伸到JVM,所以还是希望读者对JVM有一定的了解,这样更便于理解String的设计。解析byte类型的方法有8个,两个过时的

其实String方面的面试题往深了延申的话,还是会延伸到JVM,所以还是希望读者对JVM有一定的了解,这样更便于理解String的设计。

String源码分析

String结构

/*
Strings are constant; their values can not be changed after they are created.
Stringbuffers support mutable strings.Because String objects are immutable they can be shared. Forexample:
*/
public final class String implements java.io.Serializable, 
        Comparable<String>, CharSequence 
复制代码
源码里可以看到Stringfinal修饰并继承了三个接口
源码注释也说到字符串是不变的; 它们的值在创建后无法更改.Stringbuffers支持可变字符串。
因为String对象是不可变的,所以它们可以共享
复制代码
String源码分析

final

修饰类:类不可被继承,也就是说,String类不可被继承了
 修饰方法:把方法锁定,以访任何继承类修改它的涵义
 修饰遍历:初始化后不可更改
复制代码

Comparable和Serializable

Serializable接口里为空
Comparable接口里只有一个public int compareTo(T o);方法
这两个接口不做叙述.
复制代码

CharSequence

接口中的方法
    length(): int
    charAt(): char
    subSequence(int,int):CharSwquence 
    toString(): String 
    chars(): IntStream
    codePoints(): IntStream
复制代码
我们发现这个接口中的方法很少,没有我们常用的String方法,
那么它应该是一个通用接口,会有很多实现类,包括StringBuilderStringBuffer
复制代码

String构造方法

空参构造

public String() {
     this.value = "".value;
 }
复制代码

解析

String str=new String("abc");
1.先创建一个空的String对象
2.常量池中创建一个abc,并赋值给第二个String
3.将第二个String的引用传递给第一个String
注:如果常量池中有abc,则不用创建,直接把引用传递给第一个String
复制代码

String类型初始化

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

案例:  String str=new String("str");  
复制代码

字符数组初始化

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

注:将传过来的char数组copyvalue数组里
复制代码

字节数组初始化

byte类型的方法有8个,两个过时的

剩下六个又分为指定编码和不指定编码

不指定编码

public String(byte bytes[]) {
        this(bytes, 0, bytes.length);
    }

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);
    }
复制代码

指定编码

String(byte bytes[], Charset charset)
String(byte bytes[], String charsetName)
String(byte bytes[], int offset, int length, Charset charset)
String(byte bytes[], int offset, int length, String charsetName)
复制代码

解析

byte是网络传输或存储的序列化形式,
所以在很多传输和存储的过程中需要将byte[]数组和String进行相互转化,
byte是字节,char是字符,字节流和字符流需要指定编码,不然可能会乱码,
bytes字节流是使用charset进行编码的,想要将他转换成unicodechar[]数组,
而又保证不出现乱码,那就要指定其解码方法
复制代码

StringBUilder构造

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

注:很多时候我们不会这么去构造,因为StringBuilderStringBuffertoString方法
如果不考虑线程安全,优先选择StringBuilder
复制代码

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;
    }
复制代码
String重写了父类Objectequals方法
    先判断地址是否相等(地址相等的情况下,肯定是一个值,直接返回true)
    在判断是否是String类型,不是则返回false
    如果都是String,先判断长度,
    再比较值,把值赋给char数组,遍历两个char数组比较
    
复制代码

hashcode方法

public int hashCode() {
        int h = hash;
        if (h == 0 && value.length > 0) {
            char val[] = value;

            for (int i = 0; i < value.length; i++) {
                h = 31 * h + val[i];
            }
            hash = h;
        }
        return h;
    }
复制代码
如果Stringlength==0或者hash值为0,则直接返回0
如果上述条件不满足,则通过算法计算hash
复制代码

intern方法

public native String intern();

注:方法注释会有写到,意思就是调用方法时,
如果常量池有当前String的值,就返回这个值,没有就加进去,返回这个值的引用
复制代码
String str1="a";
        String str2="b";
        String str3="ab";
        String str4 = str1+str2;
        String str5=new String("ab");

        System.out.println(str5==str3);//堆内存比较字符串池
        //intern如果常量池有当前String的值,就返回这个值,没有就加进去,返回这个值的引用
        System.out.println(str5.intern()==str3);//引用的是同一个字符串池里的
        System.out.println(str5.intern()==str4);//变量相加给一个新值,所以str4引用的是个新的
        System.out.println(str4==str3);//变量相加给一个新值,所以str4引用的是个新的
        
        false
        true
        false
        false
复制代码

重点: -- 两个字符串常量或者字面量相加,不会new新的字符串 ,其他相加则是新值,(如 String str5=str1+"b";)

因为在jvm翻译为二进制代码时,会自动优化,把两个值后边的结果先合并,再保存为一个常量。
复制代码

String里还有很多方法,substring,replace等等,我们就不一一举例了

StringBuilder

StringBuilderStringbuffer这两个类的方法都很想一样,因此我们就那StringBuilder的源码作分析
等下再去看三者之间的关系和不同
复制代码

结构和构造

public final class StringBuilder  extends AbstractStringBuilder
    implements java.io.Serializable, CharSequence{
    
    //空构造,初始大小16
    public StringBuilder() {
        super(16);
    }
    
    //给予一个初始化容量
    public StringBuilder(int capacity) {
        super(capacity);
    }
    
    //使用String进行创建
    public StringBuilder(String str) {
        super(str.length() + 16);
        append(str);
    }
    
    //String创建和CharSequence类型创建,额外多分配16个字符的空间,
    //然后调用append将参数字符添加进来,(字符串缓冲区)
    public StringBuilder(CharSequence seq) {
        this(seq.length() + 16);
        append(seq);
    }
}
复制代码

解析

我们可以看到方法内部都是在调用父类的方法,
通过继承关系,我们是知道它的父类是AbstractStringBuilder,
父类里实现类AppendableCharSequence 接口,所以它能够跟String相互转换
复制代码

父类AbstractStringBuilder

AbstractStringBuilder() {
    }
    
AbstractStringBuilder(int capacity) {
        value = new char[capacity];
    }
复制代码

解析

父类里是只有两个构造方法,一个为空实现,一个为指定字符数组的容量,
如果事先知道String的长度小于16,就可以节省内存空间,
他的数组和String的不一样,因为成员变量value数组没有被final修饰,
所以可以修改他的引用变量的值,即可以引用到新的数组对象,
所以StringBuilder对象是可变的
复制代码

append

String源码分析
append有很多重载方法,原理都差不多
我们以String举例

//传入要追加的字符串
public AbstractStringBuilder append(String str) {
        //判断字符串是否为null
        if (str == null)        
            return appendNull();
            
        //不为null,获得它的长度
        int len = str.length(); 
        
        //调用方法,把原先长度+追加字符串长度的和传入方法
        ensureCapacityInternal(count + len);    
        str.getChars(0, len, value, count);
        count += len;
        return this;
    }
    
    
    
    //判断是否满足扩展要求
private void ensureCapacityInternal(int minimumCapacity) {
        // 和-原先字符串长度是否>0  肯定是大于0的
        if (minimumCapacity - value.length > 0)
            //调用复制空间的方法,和当参数
            expandCapacity(minimumCapacity);
    }
    
    
    //开始扩充
void expandCapacity(int minimumCapacity) {
        //先把原先长度复制2倍多2
        int newCapacity = value.length * 2 + 2;
        
        //判断newCapacity-和是否<0
        if (newCapacity - minimumCapacity < 0)
            //小于0的情况就是你复制的长度不够,那就把和的长度给复制的长度
            newCapacity = minimumCapacity;
            
            //正常逻辑怎么着都走不到这一步,新长度肯定是大于0
        if (newCapacity < 0) {
            if (minimumCapacity < 0) // overflow
                throw new OutOfMemoryError();
            newCapacity = Integer.MAX_VALUE;
        }
        
        //将数组扩容拷贝
        value = Arrays.copyOf(value, newCapacity);
    }
    
复制代码

insert

String源码分析
insert同样有很多重载方法,下面以charString为例
insertensureCapacityInternal(count + 1);和上面一样,不做讲解了

public AbstractStringBuilder insert(int offset, char c) {
        //检查是否满足扩充条件
        ensureCapacityInternal(count + 1);
        //拷贝数组
        System.arraycopy(value, offset, value, offset + 1, count - offset);
        //进行复制
        value[offset] = c;
        count += 1;
        return this;
    }
    
    
public AbstractStringBuilder insert(int offset, String str) {
        //判断要插入的坐标是否在字符串内,不再则报数组下标越界
        if ((offset < 0) || (offset > length()))
            throw new StringIndexOutOfBoundsException(offset);
        //判断要插入的是否为null
        if (str == null)
            str = "null";
        //获得要插入的字符串长度    
        int len = str.length();
        //检查是否满足扩充条件
        ensureCapacityInternal(count + len);
        //拷贝数组
        System.arraycopy(value, offset, value, offset + len, count - offset);
        str.getChars(value, offset);
        count += len;
        return this;
    }
复制代码

StringBuffer

public final class StringBuffer extends AbstractStringBuilder
    implements java.io.Serializable, CharSequence
{
    
}

StringBuilder差不多,只不过在所有的方法上面加了一个同步锁
复制代码

equals与==

equals

String类重写了父类equals的方法

我们先看下父类的

//直接判断地址
public boolean equals(Object obj) {
        return (this == obj);
    }
复制代码

再看下String类的equals

public boolean equals(Object anObject) {
    //地址相等肯定为true,就不用继续往下走了
        if (this == anObject) {
            return true;
        }
    //地址不相等的情况下,比较两个字符的内容是否一样
    //把字符串方法char[]数组里,遍历比较
        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;
    }
复制代码

==

==比较的是内存地址

基本数据类型比较值
引用数据类型比较地址值
    (对象的引用,在堆空间,String在字符串池,newString在堆空间)
复制代码

根据下面案例分析一下源码

创建方式 对象个数 引用指向
String a="abc"; 1 常量池
String b=new String("abc");; 1 堆内存 (abc则是复制的常量池里的abc)
String c=new String() 1 堆内存
String d="a"+"bc"; 3 常量池(a一次,bc一次,和一次,d指向和)
String e=a+b; 3 堆内存

重点-- 两个字符串常量或者字面量相加,不会new新的字符串,

变量相加则是会new新的字符串,new出来的都在堆

总结

Stringfinal修饰,一旦创建无法更改,每次更改则是在新创建对象
StringBuilderStringBuffer则是可修改的字符串

StringBuilderStringBuffer的区别
    StringBuffer synchronized 修饰,同步,线程安全
    StringBuilder并没有对方法进行加同步锁,所以是非线程安全的。
    如果程序不是多线程的,那么使用StringBuilder效率高于StringBuffer
复制代码

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

The Everything Store

The Everything Store

Brad Stone / Little, Brown and Company / 2013-10-22 / USD 28.00

The definitive story of Amazon.com, one of the most successful companies in the world, and of its driven, brilliant founder, Jeff Bezos. Amazon.com started off delivering books through the mail. Bu......一起来看看 《The Everything Store》 这本书的介绍吧!

在线进制转换器
在线进制转换器

各进制数互转换器

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

HTML 编码/解码
HTML 编码/解码

HTML 编码/解码