Java基础(八)--String(源码)、StringBuffer、StringBuilder
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的更多相关文章
- 【java基础之jdk源码】Object
最新在整体回归下java基础薄弱环节,以下为自己整理笔记,若有理解错误,请批评指正,谢谢. java.lang.Object为java所有类的基类,所以一般的类都可用重写或直接使用Object下方法, ...
- 【java基础之jdk源码】集合类
最近在整理JAVA 基础知识,从jdk源码入手,今天就jdk中 java.util包下集合类进行理解 先看图 从类图结构可以了解 java.util包下的2个大类: 1.Collecton:可以理解为 ...
- java基础集合类——ArrayList 源码略读
ArrayList是java的动态数组,底层是基于数组实现. 1. 成员变量 public class ArrayList<E> extends AbstractList<E> ...
- Java基础try-with-resource语法源码分析
众所周知,所有被打开的系统资源,比如流.文件或者Socket连接等,都需要被开发者手动关闭,否则随着程序的不断运行,资源泄露将会累积成重大的生产事故. 在Java的江湖中,存在着一种名为finally ...
- java面试之String源码中equals具体实现
废话不多说,直接看代码,注释已经写在上面了: public boolean equals(Object anObject) { if (this == anObject) {//比较两个对象的地址 r ...
- Java基础之String、StringBuffer、StringBuilder浅析
Java基础之String.StringBuffer.StringBuilder浅析 一.前言: 位于java.lang包下的String.StringBuilder.StringBuffer一般都是 ...
- (转)Java中的String为什么是不可变的? -- String源码分析
背景:被问到很基础的知识点 string 自己答的很模糊 Java中的String为什么是不可变的? -- String源码分析 ps:最好去阅读原文 Java中的String为什么是不可变的 什 ...
- 程序兵法:Java String 源码的排序算法(一)
摘要: 原创出处 https://www.bysocket.com 「公众号:泥瓦匠BYSocket 」欢迎关注和转载,保留摘要,谢谢! 这是泥瓦匠的第103篇原创 <程序兵法:Java Str ...
- Java源码解析|String源码与常用方法
String源码与常用方法 1.栗子 代码: public class JavaStringClass { public static void main(String[] args) { Strin ...
随机推荐
- Canvas: trying to use a recycled bitmap android.graphics.Bitmap@XXX
近期在做和图片相关显示的出现了一个问题,整理一下思路.分享出来给大家參考一下: Exception Type:java.lang.RuntimeException java.lang.RuntimeE ...
- SpringMVC学习指南-Spring框架
Spring框架主要使用依赖注入.实际上,很多牛叉的框架如Google的Guice都是使用依赖注入. ------------------------------------------------- ...
- IEDA-maven引用本地jia包
简单说下为啥用maven引用本地jar包:当在pom文件中配置需要引用了jar的坐标,但是maven引用不了(原因未知情况下),这种情况下就需要找开发提供相关依赖的的jar文件打成一个jar包发送过来 ...
- Canny算法源码,欢迎交流
http://blog.csdn.net/jianxiong8814/article/details/1563109 http://blog.csdn.net/assuper/article/deta ...
- HDU 1018 Big Number (log函数求数的位数)
Problem Description In many applications very large integers numbers are required. Some of these app ...
- 操作系统学习笔记:CPU调度
CPU调度的目的在于提高CPU利用率,不让CPU闲着.CPU是宝贵的资源,如果有一个进程,本来在CPU中运行,忽然因为要使用IO资源,于是转而请求IO,这边CPU挂起,造成就绪队列中的其他进程等待,这 ...
- the largest value you actually can transmit between the client and server is determined by the amount of available memory and the size of the communications buffers.
the largest value you actually can transmit between the client and server is determined by the amoun ...
- Vijos 1451 圆环取数 【区间DP】
背景 小K攒足了路费来到了教主所在的宫殿门前,但是当小K要进去的时候,却发现了要与教主守护者进行一个特殊的游戏,只有取到了最大值才能进去Orz教主…… 描述 守护者拿出被划分为n个格子的一个圆环,每个 ...
- android MediaRecorder start failed:-38【转】
本文转载自:http://blog.csdn.net/fnuwfnh/article/details/46698509 最近在学习android 录音方面的知识,发现在部分手机正常运行的APP,在华为 ...
- Java 工程与 Eclipse 高级用法
0. 显示各种视图(perspective)及工具窗口 [Window]⇒ [Perspective]⇒ [Open perspective]⇒ other(如下图所示): [Window]⇒ [Sh ...