JDK1.8源码学习-String

  JDK1.8源码学习-String

目录

一、String简介

String类是Java中最常用的类之一,所有字符串的字面量都是String类的实例,字符串是常量,在定义之后不能被改变。

二、定义

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

1.String类是由final修饰的,表明String类不能被继承,并且String类中的成员方法都默认是final方法。

2.String类是由final修饰的,表明String类一旦创建,就无法被改变,对String对象的任何操作都不会影响到原对象,任何的change操作都会产生新的String对象。

3.java.io.Servializable,标识序列化,Comparable<String>中只有一个compareTo(T o)方法,用于两个实例化对象比较大小,CharSequence表示一个char值的可读序列,CharSequence与String都能用于定义字符串,但CharSequence的值是可读可写序列,而String的值是只读序列。

三、属性

    private final char value[];

    private int hash; // Default to 0

    private static final long serialVersionUID = -6849794470754667710L;

    private static final ObjectStreamField[] serialPersistentFields =
        new ObjectStreamField[0];

1.String的内容本质上是使用不可变的char类型的数组来存储的。

2.hash是String实例化对象的hashcode的一个缓存值,这是因为String对象经常被用来进行比较,如果每次比较都重新计算hashcode值的话,是比较麻烦的,保存一个缓存值能够进行优化。

3.serialVersionUID为序列化ID.

4.serialPersistentFields属性用于指定哪些字段需要被默认序列化,具体用法为:

private static final ObjectStreamField[] serialPersistentFields = {
new ObjectStreamField("name", String.class),
new ObjectStreamField("age", Integer.Type)
}

transient用于指定哪些字段不会被默认序列化,两者同时使用时,transient会被忽略。

四、构造方法

4.1、无参构造函数

 public String() {
        this.value = "".value;
    }

空字符串" "

4.2、参数为String类型

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

其实就是一个克隆的过程,但是String是不可变的,所以没有太多必要。

4.3、参数为char数组

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

传入一个字符数组,将该数组拷贝一份给value。

4.4、参数为char数组

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

从char数组中的offset位置开始,截取count个字符,拷贝到value。

4.5、参数为bytes数组

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

从bytes数组中的offset位置开始,将长度为length的字节,以charsetName格式编码,拷贝到value

五、创建String对象

1.直接使用" ",也就是使用"字面量"赋值

String name = "张三";

2.使用连接符"+"来赋值

String name = "张" + "三";

3.使用关键字new来创建对象

String name = new String("张三");

六、常用方法

  6.1、equals方法

public boolean equals(Object anObject) {
    //如果引用的是同一个对象,则返回真
    if (this == anObject) {
        return true;
    }
    //如果不是String类型的数据,返回假
    if (anObject instanceof String) {
        String anotherString = (String) anObject;
        int n = value.length;
        //如果char数组长度不相等,返回假
        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;
}

这里重写了Object中的equals方法,用来判断两个对象实际意义上是否相等

6.2、compareTo方法

public int compareTo(String anotherString) {
    //自身对象字符串长度len1
    int len1 = value.length;
    //被比较对象字符串长度len2
    int len2 = anotherString.value.length;
    //取两个字符串长度的最小值lim
    int lim = Math.min(len1, len2);
    char v1[] = value;
    char v2[] = anotherString.value;

    int k = 0;
    //从value的第一个字符开始到最小长度lim处为止,如果字符不相等,返回自身(对象不相等处字符-被比较对象不相等字符)
    while (k < lim) {
        char c1 = v1[k];
        char c2 = v2[k];
        if (c1 != c2) {
            return c1 - c2;
        }
        k++;
    }
    //如果前面都相等,则返回(自身长度-被比较对象长度)
    return len1 - len2;
}

用于比较两个字符串的大小,如果两个字符串长度相等则返回0,如果长度不相等,则返回当前字符串的长度减去被比较的字符串的长度。

6.3、hashCode方法

public int hashCode() {
    int h = hash;
    //如果hash没有被计算过,并且字符串不为空,则进行hashCode计算
    if (h == 0 && value.length > 0) {
        char val[] = value;

        //计算过程
        //s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
        for (int i = 0; i < value.length; i++) {
            h = 31 * h + val[i];
        }
        //hash赋值
        hash = h;
    }
    return h;
}

这里重写了hashCode方法,采用多项式进行计算,可以通过不同的字符串得到相同的hash,所以像个String对象的hashCode相同,并不代表两个String是相同的。

算法:假设n = 3

i=0 -> h = 31 * 0 + val[0]

i=1 -> h = 31 * (31 * 0 + val[0]) + val[1]

i=2 -> h = 31 * (31 * (31 * 0 + val[0]) + val[1]) + val[2]

h = 31*31*31*0 + 31*31*val[0] + 31*val[1] + val[2]

h = 31^(n-1)*val[0] + 31^(n-2)*val[1] + val[2]

6.4、startWith方法

public boolean startsWith(String prefix, int toffset) {
    char ta[] = value;
    int to = toffset;
    char pa[] = prefix.value;
    int po = 0;
    int pc = prefix.value.length;
    // Note: toffset might be near -1>>>1.
    //如果起始地址小于0或者(起始地址+所比较对象长度)大于自身对象长度,返回假
    if ((toffset < 0) || (toffset > value.length - pc)) {
        return false;
    }
    //从所比较对象的末尾开始比较
    while (--pc >= 0) {
        if (ta[to++] != pa[po++]) {
            return false;
        }
    }
    return true;
}

public boolean startsWith(String prefix) {
    return startsWith(prefix, 0);
}

public boolean endsWith(String suffix) {
    return startsWith(suffix, value.length - suffix.value.length);
}

startsWith和endWith方法也是比较常用的方法,常用来判断字符串以特定的字符开始或结尾。

6.5、concat方法

public String concat(String str) {
    int otherLen = str.length();
    //如果被添加的字符串为空,则返回对象本身
    if (otherLen == 0) {
        return this;
    }
    int len = value.length;
    char buf[] = Arrays.copyOf(value, len + otherLen);
    str.getChars(buf, len);
    return new String(buf, true);
}

concat方法用于将指定的字符串参数连接到字符串上。

6.6、replace方法

public String replace(char oldChar, char newChar) {
    //新旧值先对比
    if (oldChar != newChar) {
        int len = value.length;
        int i = -1;
        char[] val = value; 

        //找到旧值最开始出现的位置
        while (++i < len) {
            if (val[i] == oldChar) {
                break;
            }
        }
        //从那个位置开始,直到末尾,用新值代替出现的旧值
        if (i < len) {
            char buf[] = new char[len];
            for (int j = 0; j < i; j++) {
                buf[j] = val[j];
            }
            while (i < len) {
                char c = val[i];
                buf[i] = (c == oldChar) ? newChar : c;
                i++;
            }
            return new String(buf, true);
        }
    }
    return this;
}

replace的参数是char和charSequence,即可以支持字符的替换,也支持字符串的替换(charSequence即字符串序列的意思)

replaceAll的参数是regex,即基于规则表达式的替换,比如可以通过replaceAll("\\d","*")把一个字符串所有的数字字符都替换成星号;

相同点:都是全部替换,即把源字符串中的某一字符或者字符串全部替换成指定的字符或者字符串。

不同点:replaceAll支持正则表达式,因此会对参数进行解析(两个参数均是),如replaceAll("\\d","*"),而replace则不会,replace("\\d","*")就是替换"\\d"的字符串,而不会解析为正则。

6.7、trim方法

public String trim() {
    int len = value.length;
    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;
}

trim用于删除字符串的头尾的空格。

原文地址:https://www.cnblogs.com/liudblog/p/11196293.html

时间: 2024-10-16 23:28:49

JDK1.8源码学习-String的相关文章

JDK1.8源码学习之 HashMap.java

///JDK1.8源码学习之HashMap.java package java.util; import java.io.IOException; import java.io.InvalidObjectException; import java.io.Serializable; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.function.BiConsu

JDK1.8源码学习-Object

JDK1.8源码学习-Object 目录 一.方法简介 1.一个本地方法,主要作用是将本地方法注册到虚拟机中. private static native void registerNatives(); static { registerNatives(); } 2.获取类的字节码对象 public final native Class<?> getClass(); 3.返回当前对象的hash值 public native int hashCode(); 4.比较党当前对象的引用是否和要比较的

JDK源码学习--String篇(二) 关于String采用final修饰的思考

JDK源码学习String篇中,有一处错误,String类用final[不能被改变的]修饰,而我却写成静态的,感谢CTO-淼淼的指正. 风一样的码农提出的String为何采用final的设计,阅读JDK源码的时候,有粗略的思考过,今天下班后又把<Thinking in Java>中关于final的内容重新看了一遍,对此写下一些关于自己的理解和想法. String类中final关键字的使用 final关键字,用来描述一块数据不能被改变,两种可能理由:设计.效率 final使用的三种情况:数据.方

JDK1.8源码学习-ArrayList

JDK1.8源码学习-ArrayList 目录 一.ArrayList简介 为了弥补普通数组无法自动扩容的不足,Java提供了集合类,其中ArrayList对数组进行了封装,使其可以自动的扩容或缩小长度,相当于动态数组. ArrayList封装了一个动态的可以重新分配的Object[]数组,其中每一个类的对象都有一个capacity属性,表示了它们所封装的Object[]数组的长度,当向ArrayList中添加元素的时候,该属性会自动的添加.如果想要添加大量元素的时候,可以使用ensureCap

JDK源码学习--String篇(三) 存储篇

在进一步解读String类时,先了解下内存分配和数据存储的. 数据存储 1.寄存器:最快的存储区,位于处理器的内部.由于寄存器的数量有限,所以寄存器是按需分配. 2.堆栈:位于RAM中,但是通过堆栈指针可以从处理器哪里获得直接支持.堆栈指针向下移动,则分配新的内存:堆栈指针向上移动释放内存. 注:堆栈中存储基本的数据类型和[对象引用],但是Java对象存储在堆中. 3.堆:通用内存池,位于RAM中,用于存放所有的Java对象. 注:堆中存储的 new创建的对象和数组. 4.常量存储:存放常量.

JDK1.8源码学习-String-hashCode方法为什么选择数字31作为乘子

1. 背景 某天,我在写代码的时候,无意中点开了 String hashCode 方法.然后大致看了一下 hashCode 的实现,发现并不是很复杂.但是我从源码中发现了一个奇怪的数字,也就是本文的主角31.这个数字居然不是用常量声明的,所以没法从字面意思上推断这个数字的用途.后来带着疑问和好奇心,到网上去找资料查询一下.在看完资料后,默默的感叹了一句,原来是这样啊.那么到底是哪样呢?在接下来章节里,请大家带着好奇心和我揭开数字31的用途之谜. 2. 选择数字31的原因 在详细说明 String

JDK1.8源码学习之Map.java

package java.util; import java.util.function.BiConsumer; import java.util.function.BiFunction; import java.util.function.Function; import java.io.Serializable; /** * 一个可以将关键字映射为值的对象; * 一个map可以包含重复的关键字keys * 但一个关键字(key)最多只能映射到一个值(value) * *这个接口取代了Dict

【JDK1.8】 Java小白的源码学习系列:HashMap

目录 Java小白的源码学习系列:HashMap 官方文档解读 基本数据结构 基本源码解读 基本成员变量 构造器 巧妙的tableSizeFor put方法 巧妙的hash方法 JDK1.8的putVal方法 JDK1.8的resize方法 初始化部分 数组搬移部分 Java小白的源码学习系列:HashMap 春节拜年取消,在家花了好多天时间啃一啃HashMap的源码,同样是找了很多很多的资料,有JDK1.7的,也有JDK1.8的,当然本文基于JDK1.8.将所学到的东西进行整理,希望回过头再看

Hadoop源码学习笔记(4) ——Socket到RPC调用

Hadoop源码学习笔记(4) ——Socket到RPC调用 Hadoop是一个分布式程序,分布在多台机器上运行,事必会涉及到网络编程.那这里如何让网络编程变得简单.透明的呢? 网络编程中,首先我们要学的就是Socket编程,这是网络编程中最底层的程序接口,分为服务器端和客户端,服务器负责监听某个端口,客户端负责连接服务器上的某个端口,一旦连接通过后,服务器和客户端就可以双向通讯了,我们看下示例代码: ServerSocket server = new ServerSocket(8111); S