Java源码之String
本文出自:http://blog.csdn.net/dt235201314/article/details/78330377
一丶概述
还记得那会的“Hello World”,第一个程序,输出的String,下面介绍String源码,颇有计算机二级考试习题的感觉。
二丶源码及案例
1.String是final类型的
在Java中,被 final 类型修饰的类不允许被其他类继承,被final修饰的变量赋值后不允许被修改。
什么是不可变类?
所谓不可变类,就是创建该类的实例后,该实例的属性是不可改变的,java提供的包装类和java.lang.String类都是不可变类。当创建它们的实例后,其实例的属性是不可改变的。
需要注意的是,对于如下代码
- String s="abc";
- s="def";
你可能会感到疑惑,不是说String是不可变类吗,这怎么可以改变呢,平常我也是这样用的啊。请注意,s是字符串对象的”abc”引用,即引用是可以变化的,跟对象实例的属性变化没有什么关系,这点请注意区分。
2.String类实现了Serializable, Comparable, CharSequence接口。
Comparable接口有compareTo(String s)方法,CharSequence接口有length(),charAt(int index),subSequence(int start,int end)方法,后面详解
3.成员变量
- //用于存储字符串
- private final char value[];
- //缓存String的hash值
- private int hash; // Default to 0
String类中包含一个不可变的char数组用来存放字符串,一个int型的变量hash用来存放计算后的哈希值。
4.构造函数
- //不含参数的构造函数,一般没什么用,因为value是不可变量
- public String() {
- this.value = new char[0];
- }
- //参数为String类型
- public String(String original) {
- this.value = original.value;
- this.hash = original.hash;
- }
- //参数为char数组,使用java.utils包中的Arrays类复制
- public String(char value[]) {
- this.value = Arrays.copyOf(value, value.length);
- }
- //从bytes数组中的offset位置开始,将长度为length的字节,以charsetName格式编码,拷贝到value
- 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);
- }
- //调用public String(byte bytes[], int offset, int length, String charsetName)构造函数
- public String(byte bytes[], String charsetName)
- throws UnsupportedEncodingException {
- this(bytes, 0, bytes.length, charsetName);
- }
- //StringBuffer 和 StringBuider 也可以被当做构造 String 的参数。
- //这两个构造方法是很少用到的,因为当我们有了 StringBuffer 或者 StringBuilfer 对象之后可以直接使用他们的 toString 方法来得到 String。
- 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());
- }
相关问题:
String两种不同的赋值方式
String str = new String("abc");
String str = "abc";
为什么String可以不用new就可以创建对象?这两种赋值方式有什么不同?
例:
- public class Test {
- public static void main(String[] args) {
- String a = "ok"; // 新建了一个String对象
- String b = "ok"; // 从缓冲池找
- String c = new String("ok"); // 新建一个String对象
- String d = new String("ok"); // 不从缓冲池找,新建一个
- System.out.println(a==b);//将输出"true";因为两个变量指向同一个对象。
- System.out.println(c==d);//将输出"flase";因为两个变量不指向同一个对象。虽然值相同,只有用c.equals(d)才能返回true.
- String e = "a"+"b"+1;
- String f = "ab1";
- System.out.println(e == f);//将输出"true";因为编译器识别 “e = "a"+"b"+1”等同于“e = "ab1"”
- String g = new String("ab1");
- String h = "ab1";
- System.out.println(g == h);////将输出"flase";因为两个变量不指向同一个对象
- }
- }
5.常用方法
boolean equals(Object anObject)
- boolean equals(Object anObject)
- 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;
- }
equals方法经常用得到,它用来判断两个对象从实际意义上是否相等,String对象判断规则:
1. 内存地址相同,则为真。
2. 如果对象类型不是String类型,则为假。否则继续判断。
3. 如果对象长度不相等,则为假。否则继续判断。
4. 从后往前,判断String类中char数组value的单个字符是否相等,有不相等则为假。如果一直相等直到第一个数,则返回真。
int compareTo(String anotherString)
- 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;
- }
理解:
java中的compareto方法,返回参与比较的前后两个字符串的asc码的差值,看下面一组代码
String a="a",b="b";
System.out.println(a.compareto.b);
则输出-1;
若a="a",b="a"则输出0;
若a="b",b="a"则输出1;
单个字符这样比较,若字符串比较长呢??
若a="ab",b="b",则输出-1;
若a="abcdef",b="b"则输出-1;
也就是说,如果两个字符串首字母不同,则该方法返回首字母的asc码的差值;
如果首字母相同呢??
若a="ab",b="a",输出1;
若a="abcdef",b="a"输出5;
若a="abcdef",b="abc"输出3;
若a="abcdef",b="ace"输出-1;
即参与比较的两个字符串如果首字符相同,则比较下一个字符,直到有不同的为止,返回该不同的字符的asc码差值,如果两个字符串不一样长,可以参与比较的字符又完全一样,则返回两个字符串的长度差值
int hashCode()
- int 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;
- }
String类重写了hashCode方法,Object中的hashCode方法是一个Native调用。String类的hash采用多项式计算得来,我们完全可以通过不相同的字符串得出同样的hash,所以两个String对象的hashCode相同,并不代表两个String是一样的。
boolean startsWith(String prefix,int toffset)
- boolean startsWith(String prefix,int toffset)
- 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);
- }
起始比较和末尾比较都是比较经常用得到的方法,例如在判断一个字符串是不是http协议的,或者初步判断一个文件是不是mp3文件,都可以采用这个方法进行比较
String concat(String str)
- 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方法也是经常用的方法之一,它先判断被添加字符串是否为空来决定要不要创建新的对象。
String replace(char oldChar,char newChar)
- public String replace(char oldChar, char newChar) {
- //新旧值先对比
- if (oldChar != newChar) {
- int len = value.length;
- int i = -1;
- char[] val = value; /* avoid getfield opcode */
- //找到旧值最开始出现的位置
- 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(String oldStr,String newStr)方法通过正则表达式来判断。
String 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;
- }
ASCII值比较,“”为32,字母都大于64,可参考ASCII值表
String substring()
- // 截取字符串
- public String substring(int beginIndex) {
- if (beginIndex < 0) { // 如果起始下标<0
- throw new StringIndexOutOfBoundsException(beginIndex);
- }
- int subLen = value.length - beginIndex; // 获取截取长度
- if (subLen < 0) { // 如果截取长度<0
- throw new StringIndexOutOfBoundsException(subLen);
- }
- return (beginIndex == 0) ? this : new String(value, beginIndex, subLen);
- }
- // 截取字符串
- public String substring(int beginIndex, int endIndex) {
- if (beginIndex < 0) { // 如果起始下标<0
- throw new StringIndexOutOfBoundsException(beginIndex);
- }
- if (endIndex > value.length) { // 如果末尾下标>字符数组长度
- throw new StringIndexOutOfBoundsException(endIndex);
- }
- int subLen = endIndex - beginIndex; // 获取截取长度
- if (subLen < 0) { // 如果截取长度<0
- throw new StringIndexOutOfBoundsException(subLen);
- }
- return ((beginIndex == 0) && (endIndex == value.length)) ? this
- : new String(value, beginIndex, subLen);
- }
如:
"hamburger".substring(4) returns "urger"
"hamburger".substring(4, 8) returns "urge"
"smiles".substring(1, 5) returns "mile"
indexOf():
- //该字符跟数组中的每个字符从左往右比较
- //lastIndexOf一样只不过是从右往左比较
- public int indexOf(int ch, int fromIndex) {
- final int max = value.length;
- if (fromIndex < 0) {
- fromIndex = 0;
- } else if (fromIndex >= max) {
- // Note: fromIndex might be near -1>>>1.
- return -1;
- }
- if (ch < Character.MIN_SUPPLEMENTARY_CODE_POINT) {
- // handle most cases here (ch is a BMP code point or a
- // negative value (invalid code point))
- final char[] value = this.value;
- for (int i = fromIndex; i < max; i++) {
- if (value[i] == ch) {
- return i;
- }
- }
- return -1;
- } else {
- return indexOfSupplementary(ch, fromIndex);
- }
- }
- //indexOf最终是调用下面的第二个static方法来进行求解的
- //求解步骤大概是:
- //首先搜索到第一个字符所在的位置,之后逐个比较;
- //这里并没有使用kmp算法因此是一个可以优化的地方
- public int indexOf(String str, int fromIndex) {
- return indexOf(value, 0, value.length,
- str.value, 0, str.value.length, fromIndex);
- }
- static int indexOf(char[] source, int sourceOffset, int sourceCount,
- char[] target, int targetOffset, int targetCount,
- int fromIndex) {
- if (fromIndex >= sourceCount) {
- return (targetCount == 0 ? sourceCount : -1);
- }
- if (fromIndex < 0) {
- fromIndex = 0;
- }
- if (targetCount == 0) {
- return fromIndex;
- }
- char first = target[targetOffset];
- int max = sourceOffset + (sourceCount - targetCount);
- for (int i = sourceOffset + fromIndex; i <= max; i++) {
- /* Look for first character. */
- if (source[i] != first) {
- while (++i <= max && source[i] != first);
- }
- /* Found first character, now look at the rest of v2 */
- if (i <= max) {
- int j = i + 1;
- int end = j + targetCount - 1;
- for (int k = targetOffset + 1; j < end && source[j]
- == target[k]; j++, k++);
- if (j == end) {
- /* Found whole string. */
- return i - sourceOffset;
- }
- }
- }
- return -1;
- }
split
- public String[] split(String regex, int limit) {
- char ch = 0;
- //1. 如果regex只有一位,且不为列出的特殊字符;
- //2.如regex有两位,第一位为转义字符且第二位不是数字或字母,“|”表示或,即只要ch小于0或者大于9任一成立,小于a或者大于z任一成立,小于A或大于Z任一成立
- //3.第三个是和编码有关,就是不属于utf-16之间的字符
- 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<>();
- //如果这个字符串中包含了用于分割的ch,则进行下一步操作
- while ((next = indexOf(ch, off)) != -1) {
- if (!limited || list.size() < limit - 1) {
- list.add(substring(off, next));
- off = next + 1;
- } else {
- 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);
- }
详解见:
String intern()
- public native String intern();
intern方法是Native调用,它的作用是在方法区中的常量池里通过equals方法寻找等值的对象,如果没有找到则在常量池中开辟一片空间存放字符串并返回该对应String的引用,否则直接返回常量池中已存在String对象的引用
例:
- String a = new String("ab1");
- String b = new String("ab1").intern();
a == b就为true
int hash32()
- int hash32()
- private transient int hash32 = 0;
- int hash32() {
- int h = hash32;
- if (0 == h) {
- // harmless data race on hash32 here.
- h = sun.misc.Hashing.murmur3_32(HASHING_SEED, value, 0, value.length);
- // ensure result is not zero to avoid recalcing
- h = (0 != h) ? h : 1;
- hash32 = h;
- }
- return h;
- }
在JDK1.7中,Hash相关集合类在String类作key的情况下,不再使用hashCode方式离散数据,而是采用hash32方法。这个方法默认使用系统当前时间,String类地址,System类地址等作为因子计算得到hash种子,通过hash种子在经过hash得到32位的int型数值。
其他方法:
- public int length() {
- return value.length;
- }
- public String toString() {
- return this;
- }
- 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 源码解析,深入认识String
Java源码之String的更多相关文章
- java源码阅读String
1类签名与注释 public final class String implements java.io.Serializable, Comparable<String>, CharSeq ...
- Java源码解析|String源码与常用方法
String源码与常用方法 1.栗子 代码: public class JavaStringClass { public static void main(String[] args) { Strin ...
- [Java源码解析] -- String类的compareTo(String otherString)方法的源码解析
String类下的compareTo(String otherString)方法的源码解析 一. 前言 近日研究了一下String类的一些方法, 通过查看源码, 对一些常用的方法也有了更透彻的认识, ...
- Java 源码学习线路————_先JDK工具包集合_再core包,也就是String、StringBuffer等_Java IO类库
http://www.iteye.com/topic/1113732 原则网址 Java源码初接触 如果你进行过一年左右的开发,喜欢用eclipse的debug功能.好了,你现在就有阅读源码的技术基础 ...
- Java 源码刨析 - String
[String 是如何实现的?它有哪些重要的方法?] String 内部实际存储结构为 char 数组,源码如下: public final class String implements java. ...
- Java源码赏析(五)再识 String 类
在 Java源码赏析(三)初识 String 类 中,我们已经大概理解了String的接口,接下来我们描述一下String的常用工具方法. /** * 为了精简的String结构,之前提到的方法省 ...
- Java源码赏析(三)初识 String 类
由于String类比较复杂,现在采用多篇幅来讲述 这一期主要从String使用的关键字,实现的接口,属性以及覆盖的方法入手.省略了大部分的字符串操作,比如split().trim().replace( ...
- 如何阅读Java源码 阅读java的真实体会
刚才在论坛不经意间,看到有关源码阅读的帖子.回想自己前几年,阅读源码那种兴奋和成就感(1),不禁又有一种激动. 源码阅读,我觉得最核心有三点:技术基础+强烈的求知欲+耐心. 说到技术基础,我打个比 ...
- 如何阅读Java源码
刚才在论坛不经意间,看到有关源码阅读的帖子.回想自己前几年,阅读源码那种兴奋和成就感(1),不禁又有一种激动.源码阅读,我觉得最核心有三点:技术基础+强烈的求知欲+耐心. 说到技术基础,我打个比方吧, ...
随机推荐
- iOS 转盘动画效果实现
代码地址如下:http://www.demodashi.com/demo/11598.html 近期公司项目告一段落,闲来无事,看到山东中国移动客户端有个转盘动画挺酷的.于是试着实现一下,看似简单,可 ...
- Oracle学习笔记(5)——查询
基本查询语句 SELECT [DISTINCT] column_name1,...|* FROM table_name [WHERE conditions] 在SQL*PLUS中设置格式 更改显示字段 ...
- oracle获取时间毫秒数
select (sysdate-to_date('1970-01-01','yyyy-mm-dd')-8/24)*24*60*60*1000-1* 60 * 60 * 1000 millisecon ...
- <转> lua: userdata的metatable使用
1 如何封装c++的指针 对于c++对象的lua包装,我们可以使用 template<typename T> struct luaUserdataWrapper { luaUserdat ...
- Redis Win平台安装
安装准备 Redis 官网:https://redis.io/ 中文官网:http://www.redis.cn/ Redis 在Window上并没有提供官方版本.所以可以去微软维护的包库上下载. 下 ...
- js标准化价钱
//标准化总价钱 s:总价钱,n:保留几位小数 function fmoney(s, n) { n = n > 0 && n <= 20 ? n : 2; s = pars ...
- 记一起和前端没什么卵关系的OPTION 405问题
记一起和前端没什么卵关系的后端405问题 问题的关键点在于本来是POST请求,会变成OPTION请求,并且提示405报错,会类似跨域.并且只有某些手机机型才会(如Oppo系列). 其实跨域的问题,如果 ...
- linux管理员工具
htop 任务管理器 bmon 网络监控 ### 详情 --------------------------------------------------- htop 任务管理器 bmon 网络监控
- ios 应用多语言自由切换实现
第一次进入应用获取本机语言.1.假设本机语言在软件实现语言列表里.则默认选中本机语言,可是用户还是能够选择其他语言.2.假设本机语言不在软件实现语言列表里,则默认选中英语.用户还是能够选择其他语言. ...
- atom安装插件
1. 由于墙的原因,在界面中安装不了 先安装 npm 可以下载node.js桌面版命令行 2.打开命令行中进入atom的安装目录,进入到packages 中 3. 进入github 官网 输入你要下载 ...