Java 8 的時間 API 用起來比之前的 Date、Calendar 不知道爽幾倍。但新 API 的時區轉換常常爆炸,因為邏輯和以前差很多。LinkedHashMap 是保持插入順序的 Map,常被小看但超實用。一次整理出來。
Java 8 時間 API 基礎
主要類
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| import java.time.*;
LocalDateTime now = LocalDateTime.now(); System.out.println(now);
LocalDate today = LocalDate.now(); System.out.println(today);
LocalTime time = LocalTime.now(); System.out.println(time);
Instant instant = Instant.now(); System.out.println(instant);
ZoneId taipeiZone = ZoneId.of("Asia/Taipei");
ZonedDateTime taipeiTime = ZonedDateTime.now(taipeiZone); System.out.println(taipeiTime);
|
時間轉換
最常見的坑:UTC 時間轉台灣時間。
1 2 3 4 5 6 7 8 9 10 11 12
| String utcTimeStr = "2026-02-20T06:30:00Z";
Instant instant = Instant.parse(utcTimeStr); ZonedDateTime taipeiTime = instant.atZone(ZoneId.of("Asia/Taipei")); System.out.println(taipeiTime);
LocalDateTime utcLocal = LocalDateTime.parse(utcTimeStr.replace("Z", "")); Instant inst = utcLocal.toInstant(ZoneOffset.UTC); ZonedDateTime taipeiTime = inst.atZone(ZoneId.of("Asia/Taipei"));
|
關鍵的坑
UTC 時間轉本地時間的常見錯誤:
1 2 3 4 5 6 7 8 9 10 11 12 13
| long utcMillis = System.currentTimeMillis(); long taipeiMillis = utcMillis + 8 * 60 * 60 * 1000;
String utcStr = "2026-02-20T06:30:00"; LocalDateTime ldt = LocalDateTime.parse(utcStr);
Instant inst = LocalDateTime.parse(utcStr).toInstant(ZoneOffset.UTC); ZonedDateTime taipeiTime = inst.atZone(ZoneId.of("Asia/Taipei"));
|
格式化和解析
1 2 3 4 5 6 7 8 9 10 11 12 13
| LocalDateTime now = LocalDateTime.now(); DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); String formatted = now.format(formatter); System.out.println(formatted);
LocalDateTime parsed = LocalDateTime.parse("2026-02-20 14:30:45", formatter);
ZonedDateTime taipei = ZonedDateTime.now(ZoneId.of("Asia/Taipei")); String tzFormatted = taipei.format(DateTimeFormatter.ISO_ZONED_DATE_TIME); System.out.println(tzFormatted);
|
和舊 Date 轉換
有些古老 library 還在用 java.util.Date:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| Instant instant = Instant.now(); Date date = Date.from(instant);
Date date = new Date(); Instant instant = date.toInstant();
LocalDateTime ldt = LocalDateTime.now(); Date date = Date.from(ldt.atZone(ZoneId.systemDefault()).toInstant());
Date date = new Date(); LocalDateTime ldt = date.toInstant() .atZone(ZoneId.systemDefault()) .toLocalDateTime();
|
LinkedHashMap 筆記
HashMap 不保證順序,但有時候需要保持插入順序。LinkedHashMap 就是這種場景。
基本用法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| Map<String, Integer> hashMap = new HashMap<>(); hashMap.put("Alice", 30); hashMap.put("Bob", 25); hashMap.put("Cathy", 28);
Map<String, Integer> linkedMap = new LinkedHashMap<>(); linkedMap.put("Alice", 30); linkedMap.put("Bob", 25); linkedMap.put("Cathy", 28);
linkedMap.forEach((k, v) -> System.out.println(k + ": " + v));
|
設定初始容量避免擴容
跟 HashMap 一樣,要預先設定容量避免頻繁 resize:
1 2 3 4 5 6 7 8 9 10
| Map<String, String> map = new LinkedHashMap<>();
Map<String, String> map = new LinkedHashMap<>(16);
Map<String, String> map = new LinkedHashMap<>(134);
|
LinkedHashMap 的內部結構
1 2 3 4 5 6 7 8
| HashMap: [bucket]─────> entry1 -> entry2 -> entry3 LinkedHashMap(保持插入順序): [bucket]─────> entry1 -> entry2 -> entry3 同時維護一條雙向鏈結串列: entry1 <-> entry2 <-> entry3
|
跟 HashMap 相比,多了一條雙向鏈結串列來記錄插入順序。查詢還是 O(1),但記憶體稍多。
LRU Cache 實現(特殊用法)
LinkedHashMap 有個特殊的 accessOrder 模式,記錄訪問順序而不是插入順序:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| Map<String, String> lruCache = new LinkedHashMap<String, String>(16, 0.75f, true) { @Override protected boolean removeEldestEntry(Map.Entry eldest) { return size() > 10; } };
lruCache.put("key1", "value1"); lruCache.put("key2", "value2"); lruCache.get("key1"); lruCache.put("key3", "value3");
|
accessOrder 參數:
false - 記錄插入順序(預設)
true - 記錄訪問順序(get、put 都會更新順序)
實戰組合:時間戳序列化
常見場景:API 回傳時間,要保持原始順序,還要轉成台灣時間。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| List<String> timeStrings = Arrays.asList( "2026-02-20T06:00:00Z", "2026-02-20T08:00:00Z", "2026-02-20T10:00:00Z" );
Map<String, ZonedDateTime> timeMap = new LinkedHashMap<>();
for (String utcStr : timeStrings) { Instant instant = Instant.parse(utcStr); ZonedDateTime taipeiTime = instant.atZone(ZoneId.of("Asia/Taipei")); timeMap.put(utcStr, taipeiTime); }
timeMap.forEach((utcStr, taipeiTime) -> { System.out.println(utcStr + " -> " + taipeiTime); });
|
實務建議
時間 API:
- 儘量用
Instant + ZoneId 的組合處理時區轉換
- 永遠記得
LocalDateTime.parse() 不包含時區資訊
- 和舊
Date 轉換時透過 Instant 作中介
- 格式化用
DateTimeFormatter,別自己組字串
LinkedHashMap:
- 需要保持順序就用 LinkedHashMap,別用 HashMap
- 預先設定容量避免擴容
- 簡單 LRU 緩存用 accessOrder = true
- 需要複雜 LRU 邏輯就用 Guava 的
CacheBuilder
大多數時候用 Java 8 的時間 API 和 LinkedHashMap,就能搞定九成的場景。剩下一成是奇怪的需求,那就另當別論了。
你的鼓勵將被轉換為我明天繼續加班的動力(真的)。 ❤️