一、String类型

引用博文连接:  https://blog.csdn.net/ylyg050518/article/details/52352993

一、成员变量

 //用于存储字符串
private final char value[]; //缓存String的hash值
private int hash; // Default to 0 /** use serialVersionUID from JDK 1.0.2 for interoperability */
private static final long serialVersionUID = -6849794470754667710L;

二、构造方法

//不含参数的构造函数,一般没什么用,因为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);
}

三、常用方法

1,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是一样的。

Object的hashCode方法,

 public native int hashCode();

源码翻译:

返回该对象的哈希码值。支持此方法是为了提高哈希表(例如 java.util.Hashtable 提供的哈希表)的性能。
hashCode 的常规协定是: 在 Java 应用程序执行期间,在对同一对象多次调用 hashCode 方法时,必须一致地返回相同的整数,前提是将对象进行 equals 比较时所用的信息没有被修改。从某一应用程序的一次执行到同一应用程序的另一次执行,该整数无需保持一致。
如果根据 equals(Object) 方法,两个对象是相等的,那么对这两个对象中的每个对象调用 hashCode 方法都必须生成相同的整数结果。
如果根据 equals(java.lang.Object) 方法,两个对象不相等,那么对这两个对象中的任一对象上调用 hashCode 方法不 要求一定生成不同的整数结果。但是,程序员应该意识到,为不相等的对象生成不同整数结果可以提高哈希表的性能。

实际上,由 Object 类定义的 hashCode 方法确实会针对不同的对象返回不同的整数。(这一般是通过将该对象的内部地址转换成一个整数来实现的,但是 JavaTM 编程语言不需要这种实现技巧。)

2,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;
}
1. 内存地址相同,则为真。
2. 如果对象类型不是String类型,则为假。否则继续判断。
3. 如果对象长度不相等,则为假。否则继续判断。
4. 从后往前,判断String类中char数组value的单个字符是否相等,有不相等则为假。如果一直相等直到第一个数,则返回真。

由此可以看出,如果对两个超长的字符串进行比较还是非常费时间的。

3,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;
}

equals方法是重写了Object的equals方法,而compareTo是因为String implements  Comparable<String>,重写了Comparable的compareTo方法。

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

4,String concat(String str)

 public String concat(String str) {
int otherLen = str.length();
//如果被添加的字符串为空,返回对象本身
if (otherLen == 0) {
return this;
}
int len = value.length;
//复制指定的数组,截取或用 null 字符填充(如有必要),以使副本具有指定的长度。
char buf[] = Arrays.copyOf(value, len + otherLen);
str.getChars(buf, len);
return new String(buf, true);
}

5,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;
}
其中的val[st] <= ' ',比较的是字符在字典位置,String trim()只能去掉字符串的前后空格,中间空格去不掉!
可以通过 char[] toCharArray() 得到字符串的字符数组

  public static void main(String[] args) {
String str = " klp m ";
//得到字符串字符数组
char[] val = str.toCharArray(); System.out.println("字符是"+val[4]+"!");
System.out.println("字符字典位置"+Integer.valueOf(val[4])+"!");
System.out.println("字符是"+val[0]+"!");
System.out.println("字符字典位置"+Integer.valueOf(val[1])+"!");
}

结果:

 字符是p!
字符字典位置112!
字符是 !
字符字典位置32!

6,String intern()

  public native String intern();

源码解释:

返回字符串对象的规范化表示形式。
一个初始为空的字符串池,它由类 String 私有地维护。 当调用 intern 方法时,如果池已经包含一个等于此 String 对象的字符串(用 equals(Object) 方法确定),则返回池中的字符串。
否则,将此 String 对象添加到池中,并返回此 String 对象的引用。 它遵循以下规则:对于任意两个字符串 s 和 t,当且仅当 s.equals(t) 为 true 时,s.intern() == t.intern() 才为 true。 所有字面值字符串和字符串赋值常量表达式都使用 intern 方法进行操作。字符串字面值在 Java Language Specification 的 §3.10.5 定义。 返回:
一个字符串,内容与此字符串相同,但一定取自具有唯一字符串的池。

详细使用方法及解释参考链接:  https://blog.csdn.net/wjzhang5514/article/details/70209403

 public static void main(String[] args) {
String s1 = "HelloWorld";
String s2 = new String("HelloWorld");
String s3 = "Hello";
String s4 = "World";
String s5 = "Hello" + "World";
String s6 = s3 + s4; System.out.println(s1 == s2);
System.out.println(s1 == s5);
System.out.println(s1 == s6);
System.out.println(s1 == s6.intern());
System.out.println(s2 == s2.intern());
}

结果:

 false
true
false
true
false

解释:

s1 创建的 HelloWorld 存在于方法区中的常量池其中的字符串池,而 s2 创建的 HelloWorld 存在于堆中,故第一条 false 。

s5 的创建是先进行右边表达式的字符串连接,然后才对 s5 进行赋值。赋值的时候会搜索池中是否有 HelloWorld 字符串,
若有则把 s5 引用指向该字符串,若没有则在池中新增该字符串。显然 s5 的创建是用了 s1 已经创建好的字面量,故 true 。 第三个比较容易弄错,s6 = s3 + s4; 其实相当于 s6 = new String(s3 + s4); s6 是存放于堆中的,不是字面量。所以 s1 不等于 s6 。 第四个 s6.intern() 首先获取了 s6 的 HelloWorld 字符串,然后在字符串池中查找该字符串,找到了 s1 的 HelloWorld 并返回。
这里如果事前字符串池中没有 HelloWorld 字符串,那么还是会在字符串池中创建一个 HelloWorld 字符串再返回。总之返回的不是堆中的 s6 那个字符串。 第四条也能解释为什么第五条是 false 。s2是堆中的 HelloWorld,s2.intern() 是字符串池中的 HelloWorld 。String s6 = (s3 + s4).intern();

String s7 = (s3 + s4).intern(); 则s7 存储的 HelloWorld 是存放字符串池中!!!

四、关于String类不是可变类

所谓不可变类,就是创建该类的实例后,该实例的属性是不可改变的,java提供的包装类和java.lang.String类都是不可变类。当创建它们的实例后,其实例的属性是不可改变的。但是需要注意的是:

String s="abc"; s="def";

s是字符串对象的”abc”引用,即引用是可以变化的,跟对象实例的属性变化没有什么关系,

 String类被设计成不可变的原因

1,字符串常量池的需要

字符串常量池(String pool, String intern pool, String保留池) 是Java方法区中一个特殊的存储区域, 当创建一个String对象时,假如此字符串值已经存在于常量池中,

则不会创建一个新的对象,而是引用已经存在的对象。 如下面的代码所示,将会在字符串常量池中只创建一个实际String对象. 
代码如下:

 String s1 = "abcd";
String s2 = "abcd";

假若字符串对象允许改变,那么将会导致各种逻辑错误,比如改变一个对象会影响到另一个独立对象. 严格来说,这种常量池的思想,是一种优化手段.

 String s1= "ab" + "cd";
String s2= "abc" + "d";

也许这个问题违反新手的直觉, 但是考虑到现代编译器会进行常规的优化, 所以他们都会指向常量池中的同一个对象. 或者,你可以用 jd-gui 之类的工具查看一下编译后的class文件.

2,允许String对象缓存HashCode

Java中String对象的哈希码被频繁地使用, 比如在hashMap 等容器中。

字符串不变性保证了hash码的唯一性,因此可以放心地进行缓存.这也是一种性能优化手段,意味着不必每次都去计算新的哈希码.

3, 安全性

String被许多的Java类(库)用来当做参数,例如 网络连接地址URL,文件路径path,还有反射机制所需要的String参数等, 假若String不是固定不变的,将会引起各种安全隐患。 
假如有如下的代码:

 boolean connect(string s){
if (!isSecure(s)) {
throw new SecurityException();
}
// 如果在其他地方可以修改String,那么此处就会引起各种预料不到的问题/错误
causeProblem(s);
}

4,线程安全

因为字符串是不可变的,所以是多线程安全的,同一个字符串实例可以被多个线程共享。这样便不用因为线程安全问题而使用同步。字符串自己便是线程安全的。

总体来说, String不可变的原因包括 设计考虑,效率优化问题,以及安全性这三大方面.

----------------------------------------------------若有不正之处,欢迎大家指正,不胜感激!!!!!

JDK1.8源码之String的更多相关文章

  1. JDK1.8源码学习-String

    JDK1.8源码学习-String 目录 一.String简介 String类是Java中最常用的类之一,所有字符串的字面量都是String类的实例,字符串是常量,在定义之后不能被改变. 二.定义 p ...

  2. 【JUC】JDK1.8源码分析之ArrayBlockingQueue(三)

    一.前言 在完成Map下的并发集合后,现在来分析ArrayBlockingQueue,ArrayBlockingQueue可以用作一个阻塞型队列,支持多任务并发操作,有了之前看源码的积累,再看Arra ...

  3. JDK1.8源码阅读系列之三:Vector

    本篇随笔主要描述的是我阅读 Vector 源码期间的对于 Vector 的一些实现上的个人理解,用于个人备忘,有不对的地方,请指出- 先来看一下 Vector 的继承图: 可以看出,Vector 的直 ...

  4. 深入源码剖析String,StringBuilder,StringBuffer

    [String,StringBuffer,StringBulider] 深入源码剖析String,StringBuilder,StringBuffer [作者:高瑞林] [博客地址]http://ww ...

  5. 集合之TreeSet(含JDK1.8源码分析)

    一.前言 前面分析了Set接口下的hashSet和linkedHashSet,下面接着来看treeSet,treeSet的底层实现是基于treeMap的. 四个关注点在treeSet上的答案 二.tr ...

  6. 集合之LinkedHashSet(含JDK1.8源码分析)

    一.前言 上篇已经分析了Set接口下HashSet,我们发现其操作都是基于hashMap的,接下来看LinkedHashSet,其底层实现都是基于linkedHashMap的. 二.linkedHas ...

  7. 集合之HashSet(含JDK1.8源码分析)

    一.前言 我们已经分析了List接口下的ArrayList和LinkedList,以及Map接口下的HashMap.LinkedHashMap.TreeMap,接下来看的是Set接口下HashSet和 ...

  8. 【1】【JUC】JDK1.8源码分析之ArrayBlockingQueue,LinkedBlockingQueue

    概要: ArrayBlockingQueue的内部是通过一个可重入锁ReentrantLock和两个Condition条件对象来实现阻塞 注意这两个Condition即ReentrantLock的Co ...

  9. 【1】【JUC】JDK1.8源码分析之ReentrantLock

    概要: ReentrantLock类内部总共存在Sync.NonfairSync.FairSync三个类,NonfairSync与FairSync类继承自Sync类,Sync类继承自AbstractQ ...

随机推荐

  1. C++ STL 常用排序算法

    C++ STL 常用排序算法 merge() 以下是排序和通用算法:提供元素排序策略 merge: 合并两个有序序列,存放到另一个序列. 例如: vecIntA,vecIntB,vecIntC是用ve ...

  2. 注册系统所有的dll文件.bat

    @echo off echo 运行后,能重新注册系统所有的dll文件, echo 能解决内存读写错误的问题 pause echo on for %%1 in (%windir%/system32/*. ...

  3. 【JQuery】Ajax

    一.前言        接着上一章的内容,继续本章的学习.本章知识来自于https://www.cnblogs.com/jach/p/5709175.html 二.内容 $.ajax({ url:'/ ...

  4. BZOJ 2745: [HEOI2012]Bridge

    2745: [HEOI2012]Bridge Time Limit: 30 Sec  Memory Limit: 128 MBSubmit: 199  Solved: 90[Submit][Statu ...

  5. 【BZOJ3105】【CQOI2013】新Nim游戏

    Description 传统的Nim游戏是这样的:有一些火柴堆,每堆都有若干根火柴(不同堆的火柴数量可以不同).两个游戏者轮流操作,每次可以选一个火柴堆拿走若干根火柴.可以只拿一根,也可以拿走整堆火柴 ...

  6. Gym 101933

    Gym 101933 B. Baby Bites水题直接模拟即可 #include <cstdio> #include <cstring> #include <queue ...

  7. python学习(24) 使用Xpath解析并抓取美女图片

    Xpath最初用来处理XML解析,同样适用于HTML文档处理.相比正则表达式更方便一些 Xpath基本规则 nodename 表示选取nodename 节点的所有子节点 / 表示当前节点的直接子节点 ...

  8. Logistic Ordinal Regression

    sklearn实战-乳腺癌细胞数据挖掘(博客主亲自录制视频教程) https://study.163.com/course/introduction.htm?courseId=1005269003&a ...

  9. 转:苹果Xcode帮助文档阅读指南

    一直想写这么一个东西,长期以来我发现很多初学者的问题在于不掌握学习的方法,所以,Xcode那么好的SDK文档摆在那里,对他们也起不到什么太大的作用.从论坛.微博等等地方看到的初学者提出的问题,也暴露出 ...

  10. Java 多线程实现

    第一种方式 package demo3; public class Threddemo { public static void main(String[] args) { MyThred mt = ...