目錄
- 1.String 為什麼是不可變的?
- 2.字元串拼接用“+” 和 StringBuilder 有什麼差別?
- 3.String、StringBuffer 和 StringBuilder 的差別是什麼?
- 4.String 中的 equals() 和 Object 中的 equals() 有何差別?
- 5.Object 類有哪些常用的方法?
- 6.如何擷取目前系統的剩餘記憶體、總記憶體及最大堆記憶體?
- 7.LocalDateTime & Calendar
-
- 7.1.如何取目前的年、月、日、時、分、秒、毫秒?
- 7.2.如何擷取從 1970 年 1 月 1 日 0 時 0 分 0 秒到現在的毫秒數?
- 7.3.如何擷取某年某月的最後一天?
- 7.4.如何列印昨天的目前時刻?
- 7.5.如何格式化日期?
1.String 為什麼是不可變的?
(1)String 類中使用 final 關鍵字修飾的字元數組來儲存字元串,如下面的代碼所示:
public final class String implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final char value[];
...
}
(2)我們知道被 final 關鍵字修飾的類不能被繼承,修飾的方法不能被重寫,修飾的變量是基本資料類型則值不能改變,修飾的變量是引用類型則不能再指向其他對象。是以,final 關鍵字修飾的數組儲存字元串并不是 String 不可變的根本原因,因為這個數組儲存的字元串是可變的(final 修飾引用類型變量的情況)。
(3)String 真正不可變的原因如下:
① 儲存字元串的數組被 final 修飾且為私有的,并且String 類沒有提供/暴露修改這個字元串的方法。
② String 類被 final 修飾導緻其不能被繼承,進而避免了子類破壞 String 的不可變性。
2.字元串拼接用“+” 和 StringBuilder 有什麼差別?
(1)Java 語言本身并不支援運算符重載,“+”和“+=”是專門為 String 類重載過的運算符,也是 Java 中僅有的兩個重載過的運算符。
public static void main(String[] args) {
String str1 = "he";
String str2 = "llo";
String str3 = "world";
String str4 = str1 + str2 + str3;
}
上述代碼對應的位元組碼如下:

可以看出,字元串對象通過 “+” 的字元串拼接方式,實際上是通過 StringBuilder 調用 append() 方法實作的,拼接完成之後調用 toString() 得到一個 String 對象。不過在循環内使用“+”進行字元串的拼接的話,存在比較明顯的缺陷:編譯器不會建立單個 StringBuilder 以複用,會導緻建立過多的 StringBuilder 對象,進而比較占用記憶體空間,并且效率較低。
public static void main(String[] args) {
String[] arr = {"he", "llo", "world"};
String s = "";
for (int i = 0; i < arr.length; i++) {
s += arr[i];
}
System.out.println(s);
}
上述代碼對應的位元組碼如下所示:
(2)如果直接使用 StringBuilder 對象進行字元串拼接的話,就不會存在這個問題了。
public static void main(String[] args) {
String[] arr = {"he", "llo", "world"};
StringBuilder s = new StringBuilder();
for (String value : arr) {
s.append(value);
}
System.out.println(s);
}
上述代碼對應的位元組碼如下所示:
如果在 IDEA 中使用 “+” 來拼接字元串的話,會出現建議使用 StringBuilder 的提示,如下圖所示:
檢視位元組碼的方式之一:
在控制台使用指令 javap -v xxx.class 即可,不過在此之前需要先将 xxx.java 進行編譯得到 xxx.class 檔案才行。
3.String、StringBuffer 和 StringBuilder 的差別是什麼?
(1)底層資料結構
- String 是隻讀字元串,它并不是基本資料類型,而是一個對象。從底層源碼來看是一個 final 類型的字元數組,所引用的字元串不能被改變,一經定義,無法再增删改。每次對 String 的操作都會生成新的 String 對象。例如每次拼接操作(即兩個字元串相加): 隐式地在堆上 new 了一個跟原字元串相同的 StringBuilder 對象,再調用 append() 拼接後面的字元。
- StringBuffer 和 StringBuilder 都繼承了 AbstractStringBuilder 抽象類,在 AbstractStringBuilder 中也是使用字元數組儲存字元串,不過沒有使用 final 和 private 關鍵字修飾(如下面的代碼所示),最關鍵的是這個 AbstractStringBuilder 類還提供了很多修改字元串的方法,例如 append 方法。是以在進行頻繁的字元串操作時,建議使用 StringBuffer 和 StringBuilder 來進行操作。
/**
* The value is used for character storage.
*/
char[] value;
(2)線程安全性
- String 中的對象是不可變的,也就可以了解為常量,是以是線程安全的、
- StringBuffer 對方法加了同步鎖或者對調用的方法加了同步鎖,是以是線程安全的。
- StringBuilder 并沒有對方法進行加同步鎖,是以是非線程安全的。
(3)性能
- 每次對 String 類型進行改變的時候,都會生成一個新的 String 對象,然後将指針指向新的 String 對象。
- StringBuffer 每次都會對 StringBuffer 對象本身進行操作,而不是生成新的對象并改變對象引用。
- 相同情況下使用 StringBuilder 相比使用 StringBuffer 僅能獲得 10%~15% 左右的性能提升,但卻要冒多線程不安全的風險。
- 性能:StringBuilder > StringBuffer > String
(4)使用場景
- 操作少量的資料:建議使用 String;
- 單線程操作字元串緩沖區下操作大量資料: 建議使用 StringBuilder;
- 多線程操作字元串緩沖區下操作大量資料: 建議使用 StringBuffer;
4.String 中的 equals() 和 Object 中的 equals() 有何差別?
String 中的 equals 方法是被重寫過的,比較的是 String 字元串的值是否相等。Object 的 equals 方法是比較的對象的記憶體位址。
5.Object 類有哪些常用的方法?
(1)Object 是所有類的根,是所有類的父類,所有對象包括數組都實作了 Object 的方法。Object 類結構如下圖所示:
(2)各種方法介紹如下:
① clone()
保護方法,實作對象的淺複制,隻有實作了 Cloneable 接口才可以調用該方法,否則抛出 CloneNotSupportedException 異常,深拷貝也需要實作 Cloneable,同時其成員變量為引用類型的也需要實作 Cloneable,然後重寫 clone()。
② finalize()
該方法和垃圾收集器有關系,判斷一個對象是否可以被回收的最後一步就是判斷是否重寫了此方法。
③ equals()
該方法使用頻率非常高。equals 和 == 的差別見上面的4.1題,但是在 Object 中兩者是一樣的。子類一般都要重寫這個方法。
④ hashCode()
該方法用于哈希查找,重寫了 equals() 一般都要重寫 hashCode(),這個方法在一些具有哈希功能的 Collection 中用到。
⑤ notify()
配合 synchronized 使用,該方法喚醒在該對象上等待隊列中的某個線程(同步隊列中的線程是給搶占 CPU 的線程,等待隊列中的線程指的是等待喚醒的線程)。
⑥ notifyAll()
配合 synchronized 使用,該方法喚醒在該對象上等待隊列中的所有線程。
⑦ wait()
配合 synchronized 使用,wait() 就是使目前線程等待該對象的鎖,目前線程必須是該對象的擁有者,也就是具有該對象的鎖。wait() 一直等待,直到獲得鎖或者被中斷。wait(long timeout) 設定一個逾時間隔,如果在規定時間内沒有獲得鎖就傳回。
調用該方法後目前線程進入睡眠狀态,直到以下事件發生:
- 其他線程調用了該對象的 notify 方法;
- 其他線程調用了該對象的 notifyAll 方法;
- 其他線程調用了 interrupt 中斷該線程;
- 時間間隔到了。此時該線程就可以被排程了,如果是被中斷的話就抛出一個 InterruptedException 異常。
6.如何擷取目前系統的剩餘記憶體、總記憶體及最大堆記憶體?
(1)可以通過 java.lang.Runtime 類中與記憶體相關方法來擷取剩餘的記憶體、總記憶體及最大堆記憶體。通過下面方法可以擷取到堆使用的百分比及堆記憶體的剩餘空間。
freeMemory() 方法傳回剩餘空間的位元組數
totalMemory() 方法總記憶體的位元組數
maxMemory() 傳回最大記憶體的位元組數
(2)示例如下:
class Solution {
public static void main(String[] args) {
System.out.println("JVM 從操縱系統那裡挖到的最大的記憶體 maxMemory : " + Runtime.getRuntime().maxMemory() / 1024 / 1024 + "M");
System.out.println("JVM 已經從作業系統那裡挖過來的記憶體 totalMemory : " + Runtime.getRuntime().totalMemory() / 1024 / 1024 + "M");
System.out.println("JVM 從操縱系統挖過來還沒用上的記憶體 freeMemory : " + Runtime.getRuntime().freeMemory() / 1024 / 1024 + "M");
System.out.println("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
byte[] b1 = new byte[3 * 1024 * 1024];
System.out.println("JVM 從操縱系統那裡挖到的最大的記憶體 maxMemory " + Runtime.getRuntime().maxMemory() / 1024 / 1024 + "M");
System.out.println("JVM 已經從作業系統那裡挖過來的記憶體 totalMemory : " + Runtime.getRuntime().totalMemory() / 1024 / 1024 + "M");
System.out.println("JVM 從操縱系統挖過來還沒用上的記憶體 freeMemory : " + Runtime.getRuntime().freeMemory() / 1024 / 1024 + "M");
}
}
輸出結果如下:
JVM 從操縱系統那裡挖到的最大的記憶體 maxMemory : 3154M
JVM 已經從作業系統那裡挖過來的記憶體 totalMemory : 213M
JVM 從操縱系統挖過來還沒用上的記憶體 freeMemory : 208M
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
JVM 從操縱系統那裡挖到的最大的記憶體 maxMemory 3154M
JVM 已經從作業系統那裡挖過來的記憶體 totalMemory : 213M
JVM 從操縱系統挖過來還沒用上的記憶體 freeMemory : 205M
7.LocalDateTime & Calendar
7.1.如何取目前的年、月、日、時、分、秒、毫秒?
建立 java.util.Calendar 執行個體,調用其 get() 傳入不同的參數即可獲得參數所對應的值。
import java.util.Calendar;
public class TestDateAndTime {
public static void main(String[] args) {
//擷取目前的年、月、日、時、分、秒、毫秒
Calendar calendar = Calendar.getInstance();
//年
System.out.println(calendar.get(Calendar.YEAR));
//月,需要注意的是 Calendar.MONTH 是從 0 開始的
System.out.println(calendar.get(Calendar.MONTH) + 1);
//天
System.out.println(calendar.get(Calendar.DATE));
//時
System.out.println(calendar.get(Calendar.HOUR));
//分
System.out.println(calendar.get(Calendar.MINUTE));
//秒
System.out.println(calendar.get(Calendar.SECOND));
//毫秒
System.out.println(calendar.get(Calendar.MILLISECOND));
}
}
7.2.如何擷取從 1970 年 1 月 1 日 0 時 0 分 0 秒到現在的毫秒數?
import java.util.Calendar;
public class TestDateAndTime {
public static void main(String[] args) {
Calendar calendar = Calendar.getInstance();
//以下 2 種方法均可以擷取從 1970 年 1 月 1 日 0 時 0 分 0 秒到現在的毫秒數
System.out.println(System.currentTimeMillis());
System.out.println(Calendar.getInstance().getTimeInMillis());
}
}
7.3.如何擷取某年某月的最後一天?
import java.time.LocalDate;
import java.util.Calendar;
public class TestDateAndTime {
public static void main(String[] args) {
Calendar calendar = Calendar.getInstance();
//某月最後一天
//2018-05月最後一天,6月1号往前一天
calendar.set(Calendar.YEAR, 2018);
calendar.set(Calendar.MONTH, 5);
calendar.set(Calendar.DAY_OF_MONTH, 1);
calendar.add(Calendar.DAY_OF_MONTH, -1);
System.out.println(calendar.get(Calendar.YEAR) + "-" + (calendar.get(Calendar.MONTH) + 1) + "-" + calendar.get(Calendar.DAY_OF_MONTH));
//JDK 1.8 java.time 包
LocalDate date = LocalDate.of(2019, 6, 1).minusDays(1);
System.out.println(date.getYear() + "-" + date.getMonthValue() + "-" + date.getDayOfMonth());
}
}
7.4.如何列印昨天的目前時刻?
import java.time.LocalDateTime;
public class YesterdayCurrent {
public static void main(String[] args) {
LocalDateTime today = LocalDateTime.now();
LocalDateTime yesterday = today.minusDays(1);
System.out.println(yesterday);
}
}
或者使用以下方式:
import java.util.Calendar;
public class YesterdayCurrent {
public static void main(String[] args) {
Calendar cal = Calendar.getInstance();
cal.add(Calendar.DATE, -1);
System.out.println(cal.getTime());
}
}
7.5.如何格式化日期?
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Date;
public class TestDateAndTime {
public static void main(String[] args) {
Date date = new Date();
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
//格式化日期
System.out.println(simpleDateFormat.format(date));
//JDK 1.8 java.time 包
System.out.println(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
}
}