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 ...
随机推荐
- 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 ...
- Html.RenderPartial使用三个参数
Html.RenderPartial("usercontrolurl", model, ViewDataDictionary) 当使用三个参数时可以这样使用: var data = ...
- Aaron Swartz Rewriting Reddit中关于web.py的创建思路
这天才少年居然自杀了,哎 原文点这 So how should things work? The first principle is that code should be clear and si ...
- iOS中打包.a静态库
1.新建.a静态库工程 需要选择Static Library静态库工程模板新建工程,如下图: 新建静态库工程 实现需要打包的类,如下图: 实现需要打包的类 2.设置需要暴露的头文件 添加Headers ...
- ADT20 混淆编译
注意:一定要 android代码混淆,整了两天怎么也弄不去来,百度翻遍了也都是怎么配置cfg文件,怎么混淆成功的喜悦,我就气死了,怎么都不成功.真是气死了,不过功夫不负有心人,终究还是弄出来了. 不能 ...
- 4.7.4 Constructing LALR Parsing Tables
4.7.4 Constructing LALR Parsing Tables We now introduce our last parser construction method, the LAL ...
- 第十二周 Leetcode 354. Russian Doll Envelopes(HARD) LIS问题
Leetcode354 暴力的方法是显而易见的 O(n^2)构造一个DAG找最长链即可. 也有办法优化到O(nlogn) 注意 信封的方向是不能转换的. 对第一维从小到大排序,第一维相同第二维从大到小 ...
- Python---NumPy模块---矩阵操作
1.NumPy访问[数组&矩阵] 2.矩阵的运算 3.NumPy通用函数 4.NumPy矩阵的合并和分割 print "**********Numpy访问(数组&矩阵)*** ...
- Geometry Shader 实现 Wireframe 绘制边线的Shader
最终效果: 参考了一个免费插件 https://assetstore.unity.com/packages/vfx/shaders/directx-11/ucla-wireframe-shader-2 ...
- bzoj 1666: [Usaco2006 Oct]Another Cow Number Game 奶牛的数字游戏【模拟】
模拟 #include<iostream> #include<cstdio> using namespace std; int n,ans; int main() { scan ...