String, StringBuffer and StringBuilder
string有11種構造方法
1. 可變性
- String不可變
- StringBuffer 和 StringBuilder 可變
在 Java 9 之後,String 類的實作改用 byte 數組存儲字元串,同時使用
coder
來辨別使用了哪種編碼。
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final byte[] value;
/** The identifier of the encoding used to encode the bytes in {@code value}. */
private final byte coder;
}
value 數組被聲明為 final,這意味着 value 數組初始化之後就不能再引用其它數組。并且 String 内部沒有改變 value 數組的方法,是以可以保證 String 不可變。
2. 線程安全
- String 不可變,是以是線程安全的
- StringBuilder 不是線程安全的
- StringBuffer 是線程安全的,内部使用 synchronized 進行同步
String Pool
字元串常量池(String Pool)儲存着所有字元串字面量(literal strings),這些字面量在編譯時期就确定。不僅如此,還可以使用 String 的 intern() 方法在運作過程将字元串添加到 String Pool 中。
當一個字元串調用 intern() 方法時,如果 String Pool 中已經存在一個字元串和該字元串值相等(使用 equals() 方法進行确定),那麼就會傳回 String Pool 中字元串的引用;否則,就會在 String Pool 中添加一個新的字元串,并傳回這個新字元串的引用。
下面示例中,s1 和 s2 采用 new String() 的方式建立了兩個不同字元串,而 s3 和 s4 是通過 s1.intern() 方法取得同一個字元串引用。intern() 首先把 s1 引用的字元串放到 String Pool 中,然後傳回這個字元串引用。是以 s3 和 s4 引用的是同一個字元串。
String s1 = new String("aaa");
String s2 = new String("aaa");
System.out.println(s1 == s2); // false
String s3 = s1.intern();
String s4 = s1.intern();
System.out.println(s3 == s4); // true
如果是采用 “bbb” 這種字面量的形式建立字元串,會自動地将字元串放入 String Pool 中。
String s5 = "bbb";
String s6 = "bbb";
System.out.println(s5 == s6); // true
在 Java 7 之前,String Pool 被放在運作時常量池中,它屬于永久代。而在 Java 7,String Pool 被移到堆中。這是因為永久代的空間有限,在大量使用字元串的場景下會導緻 OutOfMemoryError 錯誤。
new String(“abc”)
使用這種方式一共會建立兩個字元串對象(前提是 String Pool 中還沒有 “abc” 字元串對象)。
- “abc” 屬于字元串字面量,是以編譯時期會在 String Pool 中建立一個字元串對象,指向這個 “abc” 字元串字面量;
- 而使用 new 的方式會在堆中建立一個字元串對象。
建立一個測試類,其 main 方法中使用這種方式來建立字元串對象。
public class NewStringTest {
public static void main(String[] args) {
String s = new String("abc");
}
}
使用 javap -verbose 進行反編譯,得到以下内容:
// ...
Constant pool:
// ...
#2 = Class #18 // java/lang/String
#3 = String #19 // abc
// ...
#18 = Utf8 java/lang/String
#19 = Utf8 abc
// ...
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=3, locals=2, args_size=1
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
// ...
在 Constant Pool 中,#19 存儲這字元串字面量 “abc”,#3 是 String Pool 的字元串對象,它指向 #19 這個字元串字面量。在 main 方法中,0: 行使用 new #2 在堆中建立一個字元串對象,并且使用 ldc #3 将 String Pool 中的字元串對象作為 String 構造函數的參數。
以下是 String 構造函數的源碼,可以看到,在将一個字元串對象作為另一個字元串對象的構造函數參數時,并不會完全複制 value 數組内容,而是都會指向同一個 value 數組。
public String(String original) {
this.value = original.value;
this.hash = original.hash;
}
BigDecimal
Java在java.math包中提供的API類BigDecimal,用來對超過16位有效位的數進行精确的運算。雙精度浮點型變量double可以處理16位有效數。在實際應用中,需要對更大或者更小的數進行運算和處理。float和double隻能用來做科學計算或者是工程計算,在商業計算中要用java.math.BigDecimal。BigDecimal所建立的是對象,我們不能使用傳統的+、-、*、/等算術運算符直接對其對象進行數學運算,而必須調用其相對應的方法。方法中的參數也必須是BigDecimal的對象。構造器是類的特殊方法,專門用來建立對象,特别是帶有參數的對象。
BigDecimal一共有4個構造方法:
BigDecimal(int) 建立一個具有參數所指定整數值的對象。
BigDecimal(double) 建立一個具有參數所指定雙精度值的對象。(不建議采用)
BigDecimal(long) 建立一個具有參數所指定長整數值的對象。
BigDecimal(String) 建立一個具有參數所指定以字元串表示的數值的對象
這裡不建議采用第二種,原因:
1、參數類型為double的構造方法的結果有一定的不可預知性。有人可能認為在Java中寫入 newBigDecimal(0.1)所建立的BigDecimal正好等于 0.1,但是它實際上等于0.1000000000000000055511151。這是因為0.1無法準确地表示為 double(或者說對于該情況,不能表示為任何有限長度的二進制小數)。這樣,傳入到構造方法的值不會正好等于 0.1(雖然表面上等于該值)。
2、另一方面,String 構造方法是完全可預知的:寫入 newBigDecimal(“0.1”) 将建立一個 BigDecimal,它正好等于預期的 0.1。是以,比較而言,通常建議優先使用String構造方法 。
當double必須用作BigDecimal的源時,請使用Double.toString(double)轉成String,然後使用String構造方法,或使用BigDecimal的靜态方法valueOf,如下:
對于常用的加,減,乘,除,BigDecimal類提供了相應的成員方法
public BigDecimal add(BigDecimal value); //加法
public BigDecimal subtract(BigDecimal value); //減法
public BigDecimal multiply(BigDecimal value); //乘法
public BigDecimal divide(BigDecimal value); //除法
總結:
- 在需要精确的小數計算時再使用BigDecimal,BigDecimal的性能比double和float差,在處理龐大,複雜的運算時尤為明顯。故一般精度的計算沒必要使用BigDecimal。
- 盡量使用參數類型為String的構造函數。
- BigDecimal都是不可變的(immutable)的, 在進行每一次四則運算時,都會産生一個新的對象 ,是以在做加減乘除運算時要記得要儲存操作後的值。
來源: https://maliaoblog.cn/2020/0916/