String源码:基于jdk1.8

public final class String implements Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final char[] value;
/** Cache the hash code for the string */
private int hash; public String() {
this.value = "".value;
} public String(String var1) {
this.value = var1.value;
this.hash = var1.hash;
} public String(char[] var1) {
this.value = Arrays.copyOf(var1, var1.length);
}
public String(byte bytes[], int offset, int length) {
checkBounds(bytes, offset, length);
this.value = StringCoding.decode(bytes, offset, length);
} }

主要参数:

  1、char[] value,用来存储数据的char类型数组

  2、int hash,字符串的hash

结论:

  1、String是final修饰的,证明是不可变的

  2、实现了Comparable接口,可以通过CompareTo()进行比较,实现里Serializable接口,可以在网络中传输

  3、通过数组保存数据

常用方法:

//获取String中第index个字符,首位为0
public char charAt(int index) {
if ((index < 0) || (index >= value.length)) {
throw new StringIndexOutOfBoundsException(index);
}
return value[index];
}
//当前字符串和另一个字符串比较,比较同每个位置字符的大小,'A':65,'Z':90,'a':97,'z':122
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;
}
//拼接字符串
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);
}
//是否包含某个字符串
public boolean contains(CharSequence s) {
return indexOf(s.toString()) > -1;
}
//根据数组生成一个字符串
public static String copyValueOf(char data[]) {
return new String(data);
}
//是否以某个字符串结尾
public boolean endsWith(String suffix) {
return startsWith(suffix, value.length - suffix.value.length);
}
//比较两个字符串是否相等
public boolean equals(Object anObject) {
if (this == anObject) {                    //首先通过==比较,==比较的是对象,如果相等,返回true
return true;
}
if (anObject instanceof String) { //判断是否是String类型的实例,如果不是,返回false
String anotherString = (String)anObject;     //强转成String
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;
}
//返回该字符串的hash值
//空字符串返回0,否则计算公式如下:s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
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;
}
//判断是否包含字符串,和contains相同,包含返回0,否则-1
public int indexOf(int ch) {
return indexOf(ch, 0);
}
//本地方法
public native String intern();
//判断是否为空," "并不是空字符串
public boolean isEmpty() {
return value.length == 0;
}
//匹配正则表达式
public boolean matches(String regex) {
return Pattern.matches(regex, this);
}
//将字符串中某个字符替换为另一个字符
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[],并把原字符之前的值复制进去
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);    //生成一个新的String
}
}
return this;
}
//截取字符串,首位为0
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);
}
//字符串中字符转小写
public String toLowerCase() {
return toLowerCase(Locale.getDefault());
}
//字符串中字符转大写
public String toUpperCase() {
return toUpperCase(Locale.getDefault());
}
//剔除字符串两端的空格字符
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;//截取这两个下标的字符串
}
//生成一个字符串
public static String valueOf(char c) {
char data[] = {c};
return new String(data, true);
}

结论:所有改变字符串的操作都是重新生成一个新的字符串,包括+,+=

深入理解:

public static void main(String[] args) {
String str1 = "abc";
String str2 = new String("abc");
String str3 = "abc";
String str4 = new String("abc"); System.out.println(str1==str2);
System.out.println(str1==str3);
System.out.println(str2==str4);
}

结果:

false
true
false

我们首先讲一下class常量池和字符串常量池的概念,之前在jvm已经讲过了,这里再简单讲一下

class常量池(Class Constant Pool):

  我们写的每一个Java类被编译后,就会形成一份class文件,class文件中除了包含类的版本、字段、方法、接口等描述信息外,还有一项信

息就是常量池(constant pool table),用于存放编译器生成的各种字面量(Literal)和符号引用(Symbolic References);

字面量包括:

  1.String 2.八种基本类型的值 3.被声明为final的常量等;

符号引用包括:

  1.类和方法的全限定名 2.字段的名称和描述符 3.方法的名称和描述符。

字符串常量池(String Constant Pool):

  在HotSpot VM里实现的string pool功能的是一个StringTable类,被所有的类共享。字符串常量由一个一个字符组成,放在了StringTable上。

Class常量池里面的字符串在类加载过程放到字符串常量池

在JDK1.7版本,字符串常量池从方法区移到了堆中了。

解释:

  String str1 = "abc";和String str3 = "abc; 都在编译期间生成了字面量,保存在字符串常量池中。jvm每次先到常量池中查找是否存在这个

字符串,如果存在str3直接指向这个字符串

  而通过new生成的是对象,保存在堆中。每次new都会生成一个对象,所以是不相等的

String、StringBuffer、StringBuilder

public static void main(String[] args) {
String s = "aaa" ;
s += "bbb";
System.out.println(s);
}

反编译查看字节码

public class com.it.Test {
public com.it.Test();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return public static void main(java.lang.String[]);
Code:
0: ldc #2 // String aaa
2: astore_1
3: new #3 // class java/lang/StringBuilder
6: dup
7: invokespecial #4 // Method java/lang/StringBuilder."<init>":()V
10: aload_1
11: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
14: ldc #6 // String bbb
16: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
19: invokevirtual #7 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
22: astore_1
23: getstatic #8 // Field java/lang/System.out:Ljava/io/PrintStream;
26: aload_1
27: invokevirtual #9 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
30: return
}

第三行能看到遇到+的时候,会new一个StringBuilder,然后append这个字符串,然后调用toString(),如果多次用+=,就会生成很多

StringBuilder对象,就会浪费内存

如果是这样使用+,就没问题

public static void main(String[] args) {
String s = "aaa" + "bbb" + "ccc";
}  

在编译期间直接生成一个字符串"aaabbbccc",是因为"aaa","bbb","ccc",都是编译器可知的常量,当然一般也不会这样用的

public static void main(String[] args) {
String s1 = "bbb" ;
String s = "aaa" + s1 + "ccc";
}

如果这样使用,也是不行的

String本身contact()效率也不高,每次生成一个新的char[],然后通过数组new一个String,所以需要拼接字符串的时候,可以使用StringBuilder

public static void main(String[] args) {
StringBuilder builder = new StringBuilder("aaa");
builder.append("bbb");
}
public static void main(java.lang.String[]);
Code:
0: new #2 // class java/lang/StringBuilder
3: dup
4: ldc #3 // String aaa
6: invokespecial #4 // Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
9: astore_1
10: aload_1
11: ldc #5 // String bbb
13: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
16: pop
17: return
}

而StringBuffer和StringBuilder都继承了AbstractStringBuilder,也是通过char[]进行保存数据,所以效率会好很多

StringBuilder和StringBuffer区别就是是否保证线程安全,StringBuffer通过synchronized保证线程安全

性能比较:

"aaa"+"ccc"这样直接字符串叠加,性能肯定是最好的

而其余情况下:StringBuilder>StringBuffer>String

所以字符串改动较少使用String,否则使用StringBuilder,多线程环境下使用StringBuffer

PS;这里说下,我们使用Hibernate/JPA的时候,需要在Dao层拼接SQL,没必要用StringBuffer好吗,不要老是想着并发的问题。一般的Dao都是无状态的bean,不存在

线程安全问题。而且方法的执行在栈中进行的,栈是线程私有,没有特殊情况,还是要使用StringBuilder。

面试题:来自下面的链接

public static void main(String[] args) {
String s1 = "aaa2";
final String s2 = "aaa";
String s3 = "aaa";
String s4 = "aaa" + 2;
String s5 = s3 + 2;
String s6 = s2 + 2;
System.out.println(s1 == s4);
System.out.println(s1 == s5);
System.out.println(s1 == s6); final String s7 = getHello();
String s8 = s6 + 2;
System.out.println(s1 == s8);
} public static String getHello() {
return "hello";
}

结果:

true
false
true
false

s1==s4为true:

  "hello"+2在编译期间就已经被优化成"hello2",因此在运行期间,变量a和变量b指向的是同一个对象

s1==s5为false:

  由于有符号引用的存在,所以  String c = b + 2;不会在编译期间被优化,不会把b+2当做字面常量来处理的,因此这种方式生

成的对象事实上是保存在堆上的。因此a和c指向的并不是同一个对象。javap -c得到的内容

s1==s6为true:

  对于被final修饰的变量,会在class文件常量池中保存一个副本,也就是说不会通过连接而进行访问,对final变量的访问在编译

期间都会直接被替代为真实的值。那么String c = b + 2;在编译期间就会被优化成:String c = "hello" + 2;

s1==s8为false:

  这里面虽然将b用final修饰了,但是由于其赋值是通过方法调用返回的,那么它的值只能在运行期间确定,因此a和c指向的不是同一个对象。

再来一个栗子:

public static void main(String[] args) {
String a = "hello";
String b = new String("hello");
String d = b.intern(); System.out.println(b==d);
System.out.println(a==d);
}

结果:

false
true

解释:

  这里面涉及到的是String.intern方法的使用。在String类中,intern方法是一个本地方法,在JAVA7之前,intern方法会在运行时常量池

中查找是否存在内容相同的字符串,如果存在则返回指向该字符串的引用,如果不存在,则会将该字符串入池,并返回一个指向该字符串的引用。

因此,a和d指向的是同一个对象。

String str = new String("abc")创建了多少个对象?

反编译:

public static void main(java.lang.String[]);
Code:
0: new #2 // class java/lang/String
3: dup
4: ldc #3 // String abc
6: invokespecial #4 // Method java/lang/String."<init>":(Ljava/lang/String;)V
9: astore_1
10: return
}

结果是只创建一个对象

  而这道题目让人混淆的地方就是这里,这段代码在运行期间确实只创建了一个对象,即在堆上创建了"abc"对象。

  而为什么大家都在说是2个对象呢,这里面要澄清一个概念,该段代码执行过程和类的加载过程是有区别的。在类加载的过程中,确实在运行时

常量池中创建了一个"abc"对象,而在代码执行过程中确实只创建了一个String对象。

  因此,这个问题如果换成 String str = new String("abc")涉及到几个String对象?合理的解释是2个。

  个人觉得在面试的时候如果遇到这个问题,可以向面试官询问清楚”是这段代码执行过程中创建了多少个对象还是涉及到多少个对象“再根

据具体的来进行回答。

内容参考:https://www.cnblogs.com/dolphin0520/p/3778589.html和Java编程思想

Java基础(八)--String(源码)、StringBuffer、StringBuilder的更多相关文章

  1. 【java基础之jdk源码】Object

    最新在整体回归下java基础薄弱环节,以下为自己整理笔记,若有理解错误,请批评指正,谢谢. java.lang.Object为java所有类的基类,所以一般的类都可用重写或直接使用Object下方法, ...

  2. 【java基础之jdk源码】集合类

    最近在整理JAVA 基础知识,从jdk源码入手,今天就jdk中 java.util包下集合类进行理解 先看图 从类图结构可以了解 java.util包下的2个大类: 1.Collecton:可以理解为 ...

  3. java基础集合类——ArrayList 源码略读

    ArrayList是java的动态数组,底层是基于数组实现. 1. 成员变量 public class ArrayList<E> extends AbstractList<E> ...

  4. Java基础try-with-resource语法源码分析

    众所周知,所有被打开的系统资源,比如流.文件或者Socket连接等,都需要被开发者手动关闭,否则随着程序的不断运行,资源泄露将会累积成重大的生产事故. 在Java的江湖中,存在着一种名为finally ...

  5. java面试之String源码中equals具体实现

    废话不多说,直接看代码,注释已经写在上面了: public boolean equals(Object anObject) { if (this == anObject) {//比较两个对象的地址 r ...

  6. Java基础之String、StringBuffer、StringBuilder浅析

    Java基础之String.StringBuffer.StringBuilder浅析 一.前言: 位于java.lang包下的String.StringBuilder.StringBuffer一般都是 ...

  7. (转)Java中的String为什么是不可变的? -- String源码分析

    背景:被问到很基础的知识点  string  自己答的很模糊 Java中的String为什么是不可变的? -- String源码分析 ps:最好去阅读原文 Java中的String为什么是不可变的 什 ...

  8. 程序兵法:Java String 源码的排序算法(一)

    摘要: 原创出处 https://www.bysocket.com 「公众号:泥瓦匠BYSocket 」欢迎关注和转载,保留摘要,谢谢! 这是泥瓦匠的第103篇原创 <程序兵法:Java Str ...

  9. Java源码解析|String源码与常用方法

    String源码与常用方法 1.栗子 代码: public class JavaStringClass { public static void main(String[] args) { Strin ...

随机推荐

  1. js_html_input中autocomplete="off"在chrom中失效的解决办法 使用JS模拟锚点跳转 js如何获取url参数 C#模拟httpwebrequest请求_向服务器模拟cookie发送 实习期学到的技术(一) LinqPad的变量比较功能 ASP.NET EF 使用LinqPad 快速学习Linq

    js_html_input中autocomplete="off"在chrom中失效的解决办法 分享网上的2种办法: 1-可以在不需要默认填写的input框中设置 autocompl ...

  2. Html.RenderPartial使用三个参数

    Html.RenderPartial("usercontrolurl", model, ViewDataDictionary) 当使用三个参数时可以这样使用: var data = ...

  3. Aaron Swartz Rewriting Reddit中关于web.py的创建思路

    这天才少年居然自杀了,哎 原文点这 So how should things work? The first principle is that code should be clear and si ...

  4. iOS中打包.a静态库

    1.新建.a静态库工程 需要选择Static Library静态库工程模板新建工程,如下图: 新建静态库工程 实现需要打包的类,如下图: 实现需要打包的类 2.设置需要暴露的头文件 添加Headers ...

  5. ADT20 混淆编译

    注意:一定要 android代码混淆,整了两天怎么也弄不去来,百度翻遍了也都是怎么配置cfg文件,怎么混淆成功的喜悦,我就气死了,怎么都不成功.真是气死了,不过功夫不负有心人,终究还是弄出来了. 不能 ...

  6. 4.7.4 Constructing LALR Parsing Tables

    4.7.4 Constructing LALR Parsing Tables We now introduce our last parser construction method, the LAL ...

  7. 第十二周 Leetcode 354. Russian Doll Envelopes(HARD) LIS问题

    Leetcode354 暴力的方法是显而易见的 O(n^2)构造一个DAG找最长链即可. 也有办法优化到O(nlogn) 注意 信封的方向是不能转换的. 对第一维从小到大排序,第一维相同第二维从大到小 ...

  8. Python---NumPy模块---矩阵操作

    1.NumPy访问[数组&矩阵] 2.矩阵的运算 3.NumPy通用函数 4.NumPy矩阵的合并和分割 print "**********Numpy访问(数组&矩阵)*** ...

  9. Geometry Shader 实现 Wireframe 绘制边线的Shader

    最终效果: 参考了一个免费插件 https://assetstore.unity.com/packages/vfx/shaders/directx-11/ucla-wireframe-shader-2 ...

  10. bzoj 1666: [Usaco2006 Oct]Another Cow Number Game 奶牛的数字游戏【模拟】

    模拟 #include<iostream> #include<cstdio> using namespace std; int n,ans; int main() { scan ...