Java JDK1.8源码学习之路 2 String
写在最前
String 作为我们最常使用的一个Java类,注意,它是一个引用类型,不是基本类型,并且是一个不可变对象,一旦定义 不再改变
经常会定义一段代码:
String temp = "Hello";
这里具体的含义是:定义了一个类型为String的引用类型变量 temp 指向 “Hello” 这个存在内存当中的对象。
这里列举一些面试中常见的问题,简单但也很容易绕进去
String a = "hello";
String b = "hello"; System.out.println(a.hashCode());//99162322
System.out.println(b.hashCode());//99162322 System.out.println(a==b);//true
这里用 == 比较两个相同的字符串时候,会返回true,原因是 JAVA会把两个内容相同的字符串看作是同一个对象,具有相同的哈希值;
new 一定会产生新对象,分配新的内存地址
String a = "hello";
String b = new String("hello"); System.out.println(a.hashCode());//99162322
System.out.println(b.hashCode());//99162322 System.out.println(a==b);//false
得出: == 双目运算符比较的是内存地址,new 产生了一个新的对象,有不同的内存地址
equals 方法与 == 运算符的比较 以及 + 运算符
String a = "hello";
String b = "word";
String c = "helloword"; String d = "hello"+"word"; System.out.println(c == a+b);//false
System.out.println(c == "hello"+"word");//true
System.out.println(c == d);//true
字符串如果是变量相加,先开空间,在拼接
如果是常量相加,是先加,然后在常量池找,如果有就直接返回,否则,就创建。
a+b 先进行开辟内存,在进行拼接,所以他们的内存地址肯定不相同
"hello"+"word" 常量相加,拼接后发现在常量池存在,则直接返回的就是c的内存地址,所以返回true
结论:引用类型使用 == 比较时候,比较的是内存地址,而equals 方法比较的是对象的值,在String 对象里面,会对两个字符串的每个字符进行一一比较,而平时我们创建的对象默认实现的是
Object 类的equals 方法,Object 类的equals方法默认使用的是 ==比较,所以比较的是内存地址。所以我们在比较自己创建的对象时候,要重写equals方法。
开始分析常用源码
构造器部分
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence { //定义了一个存放字符串字符的数组
private final char value[]; //字符串本身的哈希值
private int hash; // Default to 0 //理解为 是一个序列化的ID
private static final long serialVersionUID = -6849794470754667710L; //io包下面 实例化出一个长度为1的串行化字段的类
private static final ObjectStreamField[] serialPersistentFields =
new ObjectStreamField[0]; //构造一个空的字符串 这个空说的是""
public String() {
this.value = "".value;
} //传入一个字符串进行构造,
public String(String original) {
this.value = original.value;
this.hash = original.hash;
} //将传入一个字符数组,通过数组复制的方式,将字符数组的值复制到value字符数组
public String(char value[]) {
this.value = Arrays.copyOf(value, value.length);
}
}
传入一个StringBuffer进行构造一个字符串对象,通过复制字符序列进行构造,并且在构造的过程中加入同步锁
public String(StringBuffer buffer) {
synchronized(buffer) {
this.value = Arrays.copyOf(buffer.getValue(), buffer.length());
}
}
常用方法分析
length() 字符串长度方法
public int length() {
return value.length;
}
isEmpty() 判断是否为空
public boolean isEmpty() {
return value.length == 0;
}
CharAt(int index) 取出指定位置的字符,若下标小于零或者超出长度,则会抛出异常
public char charAt(int index) {
if ((index < 0) || (index >= value.length)) {
throw new StringIndexOutOfBoundsException(index);
}
return value[index];
}
codePointAt 取出指定下标位置字符的Unicode值 Unicode详见文章后部
public int codePointAt(int index) {
if ((index < 0) || (index >= value.length)) {
throw new StringIndexOutOfBoundsException(index);
}
return Character.codePointAtImpl(value, index, value.length);
}
getChars 复制字符串的字符数组到一个新的字符数组dst, 复制到dst数组dstBegin位置
void getChars(char dst[], int dstBegin) {
System.arraycopy(value, 0, dst, dstBegin, value.length);
}
getBytes 将这个字符串通过指定的编码名称编码后,返回字节数组
public byte[] getBytes(String charsetName)
throws UnsupportedEncodingException {
if (charsetName == null) throw new NullPointerException();
return StringCoding.encode(charsetName, value, 0, value.length);
}
equals 方法,最常用的方法之一 判断两个字符串是否相等
public boolean equals(Object anObject) {
// == 判断内存地址是否相等,相等则返回true
if (this == anObject) {
return true;
}
//传入的对象是否是String类型的子类
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
//长度相等在开始判断相等,不相等的长度直接返回false
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
//循环判断每一个字符,只要一个不相等则false
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
compareTo(String another) 字典顺序比较两个字符串,相等则返回0
a.compareTo(b) a>b 返回负整数 反之则是正整数
public int compareTo(String anotherString) {
int len1 = value.length;
int len2 = anotherString.value.length;
int lim = Math.min(len1, len2);
char v1[] = value;
char v2[] = anotherString.value; int k = 0;
while (k < lim) {
char c1 = v1[k];
char c2 = v2[k];
if (c1 != c2) {
return c1 - c2;
}
k++;
}
return len1 - len2;
}
regionMatches(int toffset, String other, int ooffset,int len)
other字符串是否是源字符串的子串,比较位置从源字符串的toffset位置开始,other字符串的ooffset位置开始,比较的长度是len
public boolean regionMatches(int toffset, String other, int ooffset,
int len) {
char ta[] = value;
int to = toffset;
char pa[] = other.value;
int po = ooffset;
// Note: toffset, ooffset, or len might be near -1>>>1.
if ((ooffset < 0) || (toffset < 0)
|| (toffset > (long)value.length - len)
|| (ooffset > (long)other.value.length - len)) {
return false;
}
while (len-- > 0) {
if (ta[to++] != pa[po++]) {
return false;
}
}
return true;
}
startsWith(String prefix, int toffset) 源字符串的toffset位置是否包含prefix字符串
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.
if ((toffset < 0) || (toffset > value.length - pc)) {
return false;
}
while (--pc >= 0) {
if (ta[to++] != pa[po++]) {
return false;
}
}
return true;
}
indexOf(int ch,int index) 方法 从字符串的formIndex开始索引,找出首次出现ch(Unicode 值)的位置,
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:被搜索的字符序列target, 源字符序列source ,从源字符序列的sourceOffset开始,源字符序列的长度sourceCount
formIndex表示从源字符序列的那个位置开始搜索
static int indexOf(char[] source, int sourceOffset, int sourceCount,
char[] target, int targetOffset, int targetCount,
int fromIndex) {
//搜索开始的位置大于等于源字符串的长度
if (fromIndex >= sourceCount) {
//如果被搜索字符的长度==0 就是空字符串 就返回源字符串的长度,否则就直接返回-1搜索不到
return (targetCount == 0 ? sourceCount : -1);
}
//对开始位置若小于零则归零
if (fromIndex < 0) {
fromIndex = 0;
}
//被搜索字符序列长度为0,直接返回搜索开始的位置
if (targetCount == 0) {
return fromIndex;
}
//首个比较的字符,从target偏移位置开始
char first = target[targetOffset];
// 搜索的最后一个位置
int max = sourceOffset + (sourceCount - targetCount);
//从偏移位置+开始搜索的位置开始搜索 搜索次数 max
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;
}
substring 返回一个子字符串,从beginIndex下标开始,到结束
这里如果下标取0则返回的是字符串本身,否则就新new一个
public String substring(int beginIndex) {
if (beginIndex < 0) {
throw new StringIndexOutOfBoundsException(beginIndex);
}
int subLen = value.length - beginIndex;
if (subLen < 0) {
throw new StringIndexOutOfBoundsException(subLen);
}
return (beginIndex == 0) ? this : new String(value, beginIndex, subLen);
}
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填充
str.getChars(buf, len);
//产生一个新的String对象
return new String(buf, true);
}
replace 将源字符串中的指定字符替换为新的字符
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;
}
}
//找到旧的字符则继续 因为i<len
if (i < len) {
char buf[] = new char[len];
//复制数组到 buf里面
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;
}
UniCode 万国码简介
Unicode 万国码,又叫做统一码,是计算机的一项业界标准,他为每种语言中的字符设定了统一的
并且是唯一的二进制码,满足文本的转换,处理。
UTF-8
计算机里面最常见的一种编码格式,它的基本单位是字节,UTF-8最大长度是4个字节。我们使用的汉字,每一个汉字占用三个字节来表示
UTF-8的特点是对不同范围的字符使用不同长度的编码。对于0x00-0x7F之间的字符,UTF-8编码与ASCII编码完全相同。
意思就是说平时使用到的英文字母以及常用半角符号。来自于键盘上的符号都是由ASCII 在Unicode里面使用一个字节去表示。
举个例子
这里打印出小写h所对应的万国码 用整数表示出就是104
String a = "hello";
System.out.println(a.codePointAt(0));//104
我们查阅ASCII表
果然一个字节表示的字符与ASCII码表是对应的
Java JDK1.8源码学习之路 2 String的更多相关文章
- Java JDK1.8源码学习之路 1 Object
写在最前 对于一个合格的后端程序员来说,现行的流行框架早已经能胜任基本的企业开发,Springboot 任何的框架都把重复的工作更佳简单/优化的解决掉,但是完全陷入在这样的温水里面, 好比温水煮青蛙, ...
- 【JDK1.8】 Java小白的源码学习系列:HashMap
目录 Java小白的源码学习系列:HashMap 官方文档解读 基本数据结构 基本源码解读 基本成员变量 构造器 巧妙的tableSizeFor put方法 巧妙的hash方法 JDK1.8的putV ...
- Java并发包源码学习系列:JDK1.8的ConcurrentHashMap源码解析
目录 为什么要使用ConcurrentHashMap? ConcurrentHashMap的结构特点 Java8之前 Java8之后 基本常量 重要成员变量 构造方法 tableSizeFor put ...
- JDK1.8源码学习-String
JDK1.8源码学习-String 目录 一.String简介 String类是Java中最常用的类之一,所有字符串的字面量都是String类的实例,字符串是常量,在定义之后不能被改变. 二.定义 p ...
- JDK1.8源码学习-Object
JDK1.8源码学习-Object 目录 一.方法简介 1.一个本地方法,主要作用是将本地方法注册到虚拟机中. private static native void registerNatives() ...
- JDK1.8源码学习-LinkedList
JDK1.8源码学习-LinkedList 目录 一.LinkedList简介 LinkedList是一个继承于AbstractSequentialList的双向链表,是可以在任意位置进行插入和移除操 ...
- JDK1.8源码学习-ArrayList
JDK1.8源码学习-ArrayList 目录 一.ArrayList简介 为了弥补普通数组无法自动扩容的不足,Java提供了集合类,其中ArrayList对数组进行了封装,使其可以自动的扩容或缩小长 ...
- JDK1.8源码学习-HashMap
JDK1.8源码学习-HashMap 目录 一.HashMap简介 HashMap 主要用来存放键值对,它是基于哈希表的Map接口实现的,是常用的Java集合之一. 我们都知道在JDK1.8 之前 的 ...
- Java并发包源码学习系列:挂起与唤醒线程LockSupport工具类
目录 LockSupport概述 park与unpark相关方法 中断演示 blocker的作用 测试无blocker 测试带blocker JDK提供的demo 总结 参考阅读 系列传送门: Jav ...
随机推荐
- 思科 DHCP服务器配置及DHCP中继
思路: 1.配置 DHCP 客户端 确保每个 PC 为 自动获取IP地址的方式: 2.配置 SW1 # 创建 VLAN 10 , 20 # 将相关的端口,放入到对应的 VLAN : # 配置交换机之间 ...
- Linux 磁盘的分区
如果我们想在系统中新增一块硬盘,需要做什么呢? 1. 对磁盘进行分区,新建可用分区 2. 对该分区进行格式化,以创建系统可用的文件系统 3. 若想要仔细一点,可以对刚才新建好的文件系统进行检验 4. ...
- 【大数据应用期末总评】Hadoop综合大作业
作业要求来自:https://edu.cnblogs.com/campus/gzcc/GZCC-16SE2/homework/3339 一.Hadoop综合大作业 要求: 1.将爬虫大作业产生的csv ...
- Docker部署web项目-war包
一.部署springmvc(war包)项目 1.手工方式 ①搜索tomcat镜像 docker search tomcat ②拉取tomcat基础镜像至本地仓库 docker pull tomcat ...
- 【软工实践】Alpha冲刺(5/6)
链接部分 队名:女生都队 组长博客: 博客链接 作业博客:博客链接 小组内容 恩泽(组长) 过去两天完成了哪些任务 描述 任务界面设计,任务功能后端实现 任务计时功能及界面实现 展示GitHub代码签 ...
- python项目总结--学生选课
题目要求: 根据业务需求,现要对慕课学院(1)班的所有学员进行选修课程分配,使得每一名学生都可以选修到一门课程.具体要求如下: 1.自定义学生信息.课程信息.教师信息三者的具体描述 2.自定义exam ...
- CEF 远程调试
转载:https://www.cnblogs.com/TianFang/p/9906786.html 转载:https://stackoverflow.com/questions/29117882/d ...
- Win10 LTSC 2019 安装和卸载 linux 子系统
一.开启 二.下载 https://docs.microsoft.com/zh-cn/windows/wsl/install-manual 手动下载适用于 Linux 的 Windows 子系统发 ...
- 关于将汉语拼音字母“ü”改成“v”的设想和建议
http://bbs.tianya.cn/post-free-1667253-1.shtml?_t=t -- 徐州工业职业技术学院 孙生强 <汉语拼音方案>为中国人的语言文字学习带来极大方 ...
- php中heredoc与nowdoc的使用方法、定界符<<<的使用方法
一.heredoc结构及用法 Heredoc 结构就象是没有使用双引号的双引号字符串,这就是说在 heredoc 结构中单引号不用被转义.其结构中的变量将被替换,但在 heredoc 结构中含有复杂的 ...