天天看點

String與BigDecimal兩個基礎類

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); //除法
           

總結:

  1. 在需要精确的小數計算時再使用BigDecimal,BigDecimal的性能比double和float差,在處理龐大,複雜的運算時尤為明顯。故一般精度的計算沒必要使用BigDecimal。
  2. 盡量使用參數類型為String的構造函數。
  3. BigDecimal都是不可變的(immutable)的, 在進行每一次四則運算時,都會産生一個新的對象 ,是以在做加減乘除運算時要記得要儲存操作後的值。
來源: https://maliaoblog.cn/2020/0916/