String,stringBuffer,StringBuilder区别和详细例子

String,stringBuffer,StringBuilder
阅读文章提问题,问题作为索引。
在这里插入图片描述
0,String定义初始化为null,会有什么影响?
1,为什么有了String类型,还需要StringBuffer和StringBuilder?
2,String和StringBuffer和StringBuilder区别?
3,Stringbuffer和StringBuilder的底层是什么数据结构?如何扩容的?Jdk8和jdk9对底层数据结构有哪些修改?
4,String链接字符串真的效率真的比不上StringBuffer和StringBuilder吗?编译器做了哪些优化?Jdk8和jdk9优化的区别?
5,为什么会有字符串缓存?缓存在哪里?如何进行缓存的?
6,Intern方法的作用和弊端?,
7,为什么没有调用intern方法有的字符串也会加入到常量池?
8,jdk1.8常量池的位置?常量池的参数调整?
9,如何设计一个immutable类?
10,为什么String内部数据进行任何修改,这种便利甚至体现在拷贝构造函数中,由于不可
变,Immutable对象在拷贝时不需要额外复制数据?
11,jdk8中新的排重(排除字符串常量重复的方法)机制?
12, 字符串操作,比如getBytes()/String(byte[] bytes)等都是隐含着使用平台默认编码,这是一种好的实践吗?是否有利于避免乱码?

问题0)为什么有了String类型,还需要StringBuffer和StringBuilder?,

因为String类型是不可变类型,字符串操作(拼接)造成会创建很多中间的对象。

问题1)string初始赋值为null,会有什么影响?

例子1:
public class StringNUll {
public static void main(String[] args) {
String str001 = null;
System.out.println(“str001===:”+str001);

    String str002 = "null";
    str002 = str002 + "321";
    System.out.println("str002===:"+str002);
}

}

str001===:null
str002===:null321

反编译:String拼接会优化为StringBuilder
53: new #3 // class java/lang/StringBuilder
56: dup
57: invokespecial #4 // Method java/lang/StringBuilder.""?)V
60: ldc #11 // String str002===:
62: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
65: aload_2
66: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
69: invokevirtual #7 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;

问题2)Java的字符串,String、StringBufer、StringBuilder有什么区别?

String是Java语言非常基础和重要的类,它是immutable类,它被声明为final class,所有属性也是final的。Immutable类表现出来的性质就是不可以通过它的实例修改属性值。所以字符串的拼接,裁剪等动作,都会产生新的String对象。

StringBuffer是为了解决String拼接产生太多的中间对象而提出的一个类,我们可以用append和add方法,把字符串添加到已有的序列的末尾或指定位置,StringBuffer是一个线程安全的类,它保证了线程安全,随之而来的是额外的开销,所以除非有线程安全的需要,不然推荐使用stringbuilder效率更高。

Stringbuilder的功能和Stringbuffer是一样的,它去掉了synchronize关键字修饰,减小了开销。绝大部分情况下进行字符串操作的首选。

问题3)如何设计一个immutable类?

参照String类

https://www.cnblogs.com/jaylon/p/5721571.html

一、不可变类简介
不可变类:所谓的不可变类是指这个类的实例一旦创建完成后,就不能改变其成员变量值。如JDK内部自带的很多不可变类:Interger、Long和String等。
可变类:相对于不可变类,可变类创建实例后可以改变其成员变量值,开发中创建的大部分类都属于可变类。

二、不可变类的优点
说完可变类和不可变类的区别,我们需要进一步了解为什么要有不可变类?这样的特性对JAVA来说带来怎样的好处?
线程安全
不可变对象是线程安全的,在线程之间可以相互共享,不需要利用特殊机制来保证同步问题,因为对象的值无法改变。可以降低并发错误的可能性,因为不需要用一些锁机制等保证内存一致性问题也减少了同步开销。
易于构造、使用和测试

三、不可变类的设计方法
对于设计不可变类,个人总结出以下原则:

  1. 类添加final修饰符,保证类不被继承。
    如果类可以被继承会破坏类的不可变性机制,只要继承类覆盖父类的方法并且继承类可以改变成员变量值,那么一旦子类以父类的形式出现时,不能保证当前类是否可变。
  2. 保证所有成员变量必须私有,并且加上final修饰,保证成员变量赋值之后不可变(不包括数组值)。
    通过这种方式保证成员变量不可改变。但只做到这一步还不够,因为如果是对象成员变量有可能再外部改变其值。所以第4点弥补这个不足。
    3.不提供改变成员变量的方法,包括setter。保证不能通过外部对象赋值。(防止数组赋值)
    避免通过其他接口改变成员变量的值,破坏不可变特性。
    public class StringConst {
    private final int[] myArray;
    public StringConst(int[] array) {
    this.myArray = array; // wrong
    }
    public void setMyArray(char b){
    this.myArray[0] = b;
    }
    }

4.通过构造器初始化所有成员,进行深拷贝(deep copy)
如果构造器传入的对象直接赋值给成员变量,还是可以通过对传入对象的修改进而导致改变内部变量的值。例如:
public final class ImmutableDemo {
private final int[] myArray;
public ImmutableDemo(int[] array) {
this.myArray = array; // wrong
}
}
5,在getter方法中,不要直接返回对象本身,而是克隆对象,并返回对象的拷贝
这种做法也是防止对象外泄,防止通过getter获得内部可变成员对象后对成员变量直接操作,导致成员变量发生改变。

这种方式不能保证不可变性,myArray和array指向同一块内存地址,用户可以在ImmutableDemo之外通过修改array对象的值来改变myArray内部的值。
为了保证内部的值不被修改,可以采用深度copy来创建一个新内存保存传入的值。正确做法:
public final class MyImmutableDemo {
private final int[] myArray;
public MyImmutableDemo(int[] array) {
this.myArray = array.clone();
}
}
四、String对象的不可变性
string对象在内存创建后就不可改变,不可变对象的创建一般满足以上5个原则,我们看看String代码是如何实现的。
public final class String
implements java.io.Serializable, Comparable, CharSequence{
/** The value is used for character storage. /
private final char value[];
/
* The offset is the first index of the storage that is used. /
private final int offset;
/
* The count is the number of characters in the String. /
private final int count;
/
* Cache the hash code for the string */
private int hash; // Default to 0

public String(char value[]) {
this.value = Arrays.copyOf(value, value.length); // deep copy操作
}

public char[] toCharArray() {
// Cannot use Arrays.copyOf because of class initialization order issues
char result[] = new char[value.length];
System.arraycopy(value, 0, result, 0, value.length);
return result;
}

}
如上代码所示,可以观察到以下设计细节:
String类被final修饰,不可继承
string内部所有成员都设置为私有变量
不存在value的setter
并将value和offset设置为final。
当传入可变数组value[]时,进行copy而不是直接将value[]复制给内部变量.
获取value时不是直接返回对象引用,而是返回对象的copy.
这都符合上面总结的不变类型的特性,也保证了String类型是不可变的类。

五、String对象的不可变性的优缺点
从上一节分析,String数据不可变类,那设置这样的特性有什么好处呢?我总结为以下几点:
1.字符串常量池的需要.
字符串常量池可以将一些字符常量放在常量池中重复使用,避免每次都重新创建相同的对象、节省存储空间。但如果字符串是可变的,此时相同内容的String还指向常量池的同一个内存空间,当某个变量改变了该内存的值时,其他遍历的值也会发生改变。所以不符合常量池设计的初衷。
2. 线程安全考虑。
同一个字符串实例可以被多个线程共享。这样便不用因为线程安全问题而使用同步。字符串自己便是线程安全的。
3. 类加载器要用到字符串,不可变性提供了安全性,以便正确的类被加载。譬如你想加载java.sql.Connection类,而这个值被改成了myhacked.Connection,那么会对你的数据库造成不可知的破坏。
4. 支持hash映射和缓存。
因为字符串是不可变的,所以在它创建的时候hashcode就被缓存了,不需要重新计算。这就使得字符串很适合作为Map中的键,字符串的处理速度要快过其它的键对象。这就是HashMap中的键往往都使用字符串。

缺点:
如果有对String对象值改变的需求,那么会创建大量的String对象。

六、String对象的是否真的不可变
虽然String对象将value设置为final,并且还通过各种机制保证其成员变量不可改变。但是还是可以通过反射机制的手段改变其值。例如:
//创建字符串"Hello World", 并赋给引用s
String s = “Hello World”;
System.out.println("s = " + s); //Hello World

//获取String类中的value字段
Field valueFieldOfString = String.class.getDeclaredField("value");
//改变value属性的访问权限
valueFieldOfString.setAccessible(true);

//获取s对象上的value属性的值
char[] value = (char[]) valueFieldOfString.get(s);
//改变value所引用的数组中的第5个字符
value[5] = '_';
System.out.println("s = " + s);  //Hello_World

打印结果为:
s = Hello Worlds = Hello_World
发现String的值已经发生了改变。也就是说,通过反射是可以修改所谓的“不可变”对象的
总结:
不可变类是实例创建后就不可以改变成员遍历的值。这种特性使得不可变类提供了线程安全的特性但同时也带来了对象创建的开销,每更改一个属性都是重新创建一个新的对象。JDK内部也提供了很多不可变类如Integer、Double、String等。String的不可变特性主要为了满足常量池、线程安全、类加载的需求。合理使用不可变类可以带来极大的好处。

问题4)Stringbuffer和StringBuilder的底层是什么数据结构?如何扩容的?Jdk8和jdk9对底层数据结构有哪些修改?

StringBuffer和StringBuilder底层都是利用可修改的(jdk8 char数组,jdk9是byte数组)数组。二者都继承了AbstractStringBuilder,里面包含了基本操作。区别是方法上是否加了synchronize。

底层是数组,自然就限制了字符串的长度就是数组的长度。那么数组应该创建多大呢?
初始化字符串长度是16。扩容会增加开销,因为需要创建新的数组,进行arraycopy。
扩容算法:
使用append()方法在字符串后面追加东西的时候,如果长度超过了该字符串存储空间大小了就需要进行扩容:构建新的存储空间更大的字符串,将久的复制过去;

再进行字符串append添加的时候,会先计算添加后字符串大小,传入一个方法:ensureCapacityInternal 这个方法进行是否扩容的判断,需要扩容就调用expandCapacity方法进行扩容:

尝试将新容量扩为大小变成2倍+2 if 判断一下 容量如果不够,直接扩充到需要的容量大小

immutable类能保证线程安全,因为无法进行任何修饰,拷贝构造函数。

问题5)对String拼接字符串的优化?

Jdk8 会将String拼接转换成StringBuilder操作。
Jdk9会直接使用StringConcat。
所以直接使用String也是可以的。

Jdk8编译器会将String连接优化成StringBuilder。Jdk9会优化为byte链接。

public class StringCon {
public static void main(String[] args) {
String str = “123”+“abc”+“456”;
str += “def”;
}
}

public static void main(java.lang.String[]);
Code:
0: ldc #2 // String 123abc456
2: astore_1
3: new #3 // class java/lang/StringBuilder
6: dup
7: invokespecial #4 // Method java/lang/StringBuilder.""?)V
10: aload_1
11: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
14: ldc #6 // String def
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: return
}

Jdk9会使用makeConcatWithConsants进行优化

public class StringConcat {
public satic void main(String[] args) {
String myStr = “aa” + “bb” + “cc” + “dd”;
Sysem.out.println(“My String:” + myStr);
}
}
反编译:
invokedynamic#4,0//InvokeDynamic #0:makeConcatWithConsants:(Ljava/lang/String;)Ljava/lang/String;

问题6)为什么Java 9 中字符串造成char数组改为了byte数组?

因为一个字符占用两个字节,但是有的语言就是一个字节就能表示。
如果你仔细观察过Java的字符串,在历史版本中,它是使用char数组来存数据的,这样非常直接。但是Java中的char是两个bytes大小,拉丁语系语言的字符,根本就不需要太宽
的char,这样无区别的实现就造成了一定的浪费。
而现在使用byte数组,加上coder,就可以根据不同的语言选择不同的编码方式,减少了很大一部分空间。

问题7)Intern方法的作用和弊端?,

Intern方法作用:如果常量池存在的该字符串,就返回该字符串。如果不存在就把该字符串加入到常量池中。
例子1:
public class StringCon {
public static void main(String[] args) {
String str = “123”+“abc”+“456”;
str += “def”;
str.intern();
String str1 = “123abc456def”;
System.out.println(str == str1); // true

 }

}
例子2:
public class StringCon {
public static void main(String[] args) {
String str = “123”+“abc”+“456”;
str += “def”;
String str1 = “123abc456def”;
System.out.println(str == str1); // false
}
}

Intern方法的弊端:
String在Java 6以后提供了intern()方法,目的是提示JVM把相应字符串缓存起来,以备重复使用。在我们创建字符串对象并调用intern()方法的时候,如果已经有缓存的字符串,
就会返回缓存里的实例,否则将其缓存起来。
看起来很不错?但实际情况估计会让你大跌眼镜。一般使用Java 6这种历史版本,并不推荐大量使用intern,为什么呢?魔鬼存在于细节中,被缓存的字符串是存在所
谓PermGen里的,也就是臭名昭著的“永久代”,这个空间是很有限的,也基本不会被FullGC之外的垃圾收集照顾到。所以,如果使用不当,OOM就会光顾。
Jdk1.7将字符串常量池移动到了堆内,不在永久区内了。Jdk1.8取消了永久区
在后续版本中,这个缓存被放置在堆中,这样就极大避免了永久代占满的问题,甚至永久代在JDK 8中被MetaSpace(元数据区)替代了。而且,默认缓存大小也在不断地扩大中,
从最初的1009,到7u40以后被修改为60013。

Intern是一种显式地排重机制,但是它也有一定的副作用,因为需要开发者写代码时明确调用,一是不方便,每一个都显式调用是非常麻烦的;另外就是我们很难保证效率,应用开发阶段很难清楚地预计字符串的重复情况,有人认为这是一种污染代码的实践。

问题8)为什么没有调用intern方法有的字符串也会加入到常量池?
同时我们会问我们使用String的时候也会自动的将字符串加入到常量池,是的。只有在一条语句上的字符串会加入到常量池。
例如:只有这个的才会自动加入到常量池,否则需要手动调用intern方法,将字符串加入到常量池。
String str005 = “333”;
String str004 = “111”+“222”;

问题9)jdk1.8常量池的位置?常量池的参数调整?

Number of buckets常量池大小,你可以使用下面的参数直接打印具体数字,可以拿自己的JDK立刻试验一下。
java -XX:+PrintStringTableStatistics

你也可以使用下面的JVM参数手动调整大小,但是绝大部分情况下并不需要调整,除非你确定它的大小已经影响了操作效率。
-XX:StringTableSize=N

问题10)jdk8中新的排重(排除字符串常量重复的方法)机制?

Intern是一种显式地排重机制,但是它也有一定的副作用,因为需要开发者写代码时明确调用,一是不方便,每一个都显式调用是非常麻烦的;另外就是我们很难保证效率,应用开发阶段很难清楚地预计字符串的重复情况,有人认为这是一种污染代码的实践。

幸好在Oracle JDK 8u20之后,推出了一个新的特性,也就是G1 GC下的字符串排重。它是通过将相同数据的字符串指向同一份数据来做到的,是JVM底层的改变,并不需
要Java类库做什么修改。

注意这个功能目前是默认关闭的,你需要使用下面参数开启,并且记得指定使用G1 GC:
-XX:+UseStringDeduplication

问题11)字符串操作,比如getBytes()/String(byte[] bytes)等都是隐含着使用平台默认编码,这是一种好的实践吗?是否有利于避免乱码?

在使用byte数组的时候,要进行设置编码。否则就会使用环境编码,造成乱码的情况。
环境编码指的是编译环境的编码。

问题12)为什么要有字符串缓存?缓存到哪里?

我们的程序中操作最多的是字符串,所以如果能避免重复的创建字符串对象,可以有效降低内存消耗和创建对象开销。
缓存到了字符串常量池当中。Jdk1,7之后常量池,不在永久区内,而在堆内存中。

Java6中提供intern方法,方法功能是从常量池中找该字符串,如果有就返回,如果没有就将该字符串缓存在常量池当中。

问题13)Jdk9的intrinsic机制,还需要分析一下。不太明白。

在Java 9中,我们引入了Compact Strings的设计,对字符串进行了大刀阔斧的改进。将数据存储方式从char数组,改变为一个byte数组加上一个标识编码的所谓coder,并且将
相关字符串操作类都进行了修改。另外,所有相关的Intrinsic之类也都进行了重写,以保证没有任何性能损失。
虽然底层实现发生了这么大的改变,但是Java字符串的行为并没有任何大的变化,所以这个特性对于绝大部分应用来说是透明的,绝大部分情况不需要修改已有代码。
当然,在极端情况下,字符串也出现了一些能力退化,比如最大字符串的大小。你可以思考下,原来char数组的实现,字符串的最大长度就是数组本身的长度限制,但是替换
成byte数组,同样数组长度下,存储能力是退化了一倍的!还好这是存在于理论中的极限,还没有发现现实应用受此影响。
在通用的性能测试和产品实验中,我们能非常明显地看到紧凑字符串带来的优势,即更小的内存占用、更快的操作速度。
今天我从String、StringBufer和StringBuilder的主要设计和实现特点开始,分析了字符串缓存的intern机制、非代码侵入性的虚拟机层面排重、Java 9中紧凑字符的改进,并且
初步接触了JVM的底层优化机制intrinsic

知识点:

1,什么是immutable类?

不可变类:所谓的不可变类是指这个类的实例一旦创建完成后,就不能改变其成员变量值。如JDK内部自带的很多不可变类:Interger、Long和String等。
可变类:相对于不可变类,可变类创建实例后可以改变其成员变量值,开发中创建的大部分类都属于可变类。

2,Final可以修饰什么?有什么特点?
Final修饰的类是不可继承的类。
Final修饰的变量值是不可变的。
Final修饰的方法不可重写,但是可以被继承。
阅读文章提问题,问题作为索引。
一起写程序

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值