利用 Android File Transfer 這個官方工具,嘗試將 Pixel 6a 連接到我的 macbook,但一直發生『無法與裝置連接』的錯誤訊息。依照官方建議,重新安裝 Android File Transfer、電腦重新開機、手機重新開機、連接線材都更換了都沒用。
最後發現是 Google Drive 在搞鬼,將其結束後 Android File Transfer 就可以正常看到 Pixel 6a 的資料夾。
利用 Android File Transfer 這個官方工具,嘗試將 Pixel 6a 連接到我的 macbook,但一直發生『無法與裝置連接』的錯誤訊息。依照官方建議,重新安裝 Android File Transfer、電腦重新開機、手機重新開機、連接線材都更換了都沒用。
最後發現是 Google Drive 在搞鬼,將其結束後 Android File Transfer 就可以正常看到 Pixel 6a 的資料夾。
更新 openjdk 後,無法開啟 Eclipse/SpringToolSuites4,錯誤訊息如下 :
The JVM shared library "/Users/foo/Library/Java/JavaVirtualMachines/adopt-openjdk-11.0.12/Contents/Home/bin/../lib/server/libjvm.dylib" does not contain the JNI_CreateJavaVM symbol.
看起來應該是更新的 openjdk(adopt-openjdk-11.0.12)有問題,編輯 Eclipse/SpringToolSuites4 的 Info.plist,指定要執行的 Java 路徑(原先正常的 adopt-openjdk-11.0.11)即可。
<!-- Info.plist 搜尋 -vm 字串 -->
<string>-vm</string><string>/Users/foo/Library/Java/JavaVirtualMachines/adopt-openjdk-11.0.11/Contents/Home/bin/java</string>
本文假設讀者你已經用過 Optional 或是介接過回傳 Optional 的 API,知道 Optional 主要是解決 java.lang.NullPointerException(剛好是本站用的網址 XD),但還是不太知道為什麼要用 Optional!
我覺得站在開發 API 的角度來看這個問題,就很清楚為何要使用 Optional!
使用你 API 的人,就可以根據這個資訊,知道呼叫這個方法需不需要處理 null 的問題,而不是每個方法都要多寫處理 null 的邏輯,或是沒處裡就會發生 java.lang.NullPointerException。
// 回傳 People 代表這方法絕對會回傳一個我爸
public People getMyFather()
// 回傳 <Optional>People 代表可能回傳空物件
public <Optional>People getMyFather()
如果今天你寫的系統,每個人都必定會有個爸爸,那回傳 People 的 getMyFather() 符合你的意圖,而回傳 <Optional>People 的 getMyFather() 因為有可能回傳空物件的語意,所以就不適合用此方法。
若你寫的系統允許父不詳的狀況,那回傳 <Optional>People 的 getMyFather() 就是比較好的寫法。
我看到第一個回傳 People 的 getMyFather(),我很清楚我不用處理 null 的狀況,也就不用多寫處理 null 的邏輯。而第二個方法我就需要用 isPresent() 檢查回傳的 Optional<People>是否為一個空物件。
下面程式碼說明 of、ofNullable 這兩個方法:
Foo foo = null;
// of()不接受傳入 null,此處會發生 NullPointerException
Optional<Foo> optionalFoo1 = Optional.of(foo);
// ofNullable()可以傳入 null,此處會得到一個空物件
Optional<Foo> optionalFoo2 = Optional.ofNullable(foo);
下面程式碼說明 ifPresent、get、getElse、orElseGet、orElseThrow:
// 從 getBar 取得一個 Optional<Bar>
// 由回傳 Optional<Bar> 來看
// 我們預期有可能收到空物件
Optional<Bar> optionalBar = getBar();
// 用 ifPresent + get 的寫法跟原本檢查 null 的寫法一樣差
if (optionalBar.ifPresent()) // 檢查是否為空物件
{
Bar bar = optionalBar.get(); // 取得包在裏頭的 bar
bar.doSometing();
}
// 當 optionalBar 裏頭包的是 null,會建立一個新的 Bar 物件並回傳
Bar bar = optionalBar.getElse(new Bar());
// 當 optionalBar 裏頭包的是 null,會建立一個新的 Bar 物件並回傳
Bar bar = optionalBar.orElseGet(() -> {
// 這邊可以多做一些事,比如印 Log
return new Bar();
}));
// 當 optionalBar 裏頭包的是 null,會丟出一個自訂的例外
Bar bar = optionalBar.orElseThrow(() -> new MyException("WTF")));
開發 API 的人使用 Optional 來說明回傳的物件是不是有可能為 null,讓使用 API 的人看到介面就知道是否要處理 null。
而 getElse() 能改善以往檢查是否為空,如果不為空就...的寫法,讓程式碼更簡潔更容易閱讀。
Encode 編碼 | Encrypt 加密 | Hash 雜湊 | |
---|---|---|---|
簡單定義 | 原本的資料用不同的詮釋方式產生新的資料 | 利用金鑰來保護資料 | 將資料用公式算出一個無法反推回原本資料的結果 |
是否能還原資料? | 知道編碼方式即可還原資料 | 知道加密方式 + 取得金鑰即可還原資料 | 無法還原資料 |
舉例 | 摩斯密碼 網址編碼 Base64 | AES(對稱加密) RSA(非對稱加密) | md5 SHA1 SHA256 |
如果今天在資料庫要保存一個密碼:
你可以制定密碼規則,強迫使用者建立比較難想到的密碼(比如要有大小寫、特殊符號、數字),這樣的密碼就比較不會從對照表被反查出來,但會造成使用者要建立自己也記不太住的密碼。
另外一種方式,就是使用者可以建立他們記得住的密碼,然後系統會在密碼後方加上由系統產生的特殊字串(就是所謂的 Salt),達到不造成使用者困擾,又能建立少見密碼的效果。
結論:
將密碼加鹽,雜湊後保存。
之前用 Windows 工作排程器 都是用來執行會自動關閉的程式/Script,所以沒有設定上圖的這個選項『如果工作已在執行中,下列規則將會套用:(If the task is already running, then the following rule applies)』都沒有關係,因為舊工作很快就執行完成,執行新工作的時候不會看到還在執行中的舊工作。
這次嘗試要每天重啟一個會持續執行的程式,因為該程式發生問題並不會自己關閉,在設定上遇到了一些問題,先來解釋一下上面圖片的四個選項:
首先要注意的是,不要用手動執行工作的方式來驗證,你會發現每次新工作都會執行成功,但實際上自動跑的時候,行為結果都跟你想的不一樣。可以利用反覆設定僅一次的觸發時間,等到觸發時間到來驗證看看新工作是否有順利執行。
以下針對我這次的問題做紀錄,我想要定期重新執行一個 exe 檔案(因為它會發生錯誤且不會結束),四個選項觀察到的結果如下:
最終的做法是,在排程工作中加入 taskkill 指令(用來刪除原先執行的 exe 檔案) + 佇列新執行個體(Queue a new instance),確保新工作總是會被執行,而執行新工作的第一個步驟就是刪除執行中的 exe 檔案,再重新執行 exe 檔案。
介接 MultiBio 門禁考勤人臉指紋機時,利用官網提供的 zkemkeeper SDK,能正確讀取設備上的打卡資料,但想要即時接收打卡事件時,完全照抄官方範例,卻怎樣都收不到設備即時回傳的事件。最大的差別在於官方範例是一個 Windows Forms 程式,我正在開發的是一個主控台(Console)程式。
可能原因是 zkemkeeper SDK 收到設備即時回傳事件後,使用的訊息派送機制跟 Windows Forms 程式會用到的 Message Loop 相關。所以嘗試在一開始就用 Application 提供的 Run() 方法,執行目前執行緒的標準應用程式 Message Loop。
記得要用 .NET Framwork 專案,而不是 .NET / .NET Core 專案(C# 工程師可能才知道我在說什麼),要不然沒有 System.Windows.Forms.Application 可以用,完整程式碼如下:
Thread thread = new Thread(() =>
{
zkemkeeper.CZKEMClass axCZKEM1 = new zkemkeeper.CZKEMClass();
bool bIsConnected = false; // the boolean value identifies whether the device is connected
int idwErrorCode = 0;
bIsConnected = axCZKEM1.Connect_Net("x.x.x.x", Convert.ToInt32("4370"));
if (bIsConnected == true)
{
iMachineNumber = 1;// In fact,when you are using the tcp/ip communication,this parameter will be ignored,that is any integer will all right.Here we use 1.
bool regEventResult = axCZKEM1.RegEvent(iMachineNumber, 65535);//Here you can register the realtime events that you want to be triggered(the parameters 65535 means registering all)
}
else
{
axCZKEM1.GetLastError(ref idwErrorCode);
Console.WriteLine(String.Format("Unable to connect the device, ErrorCode={1}", idwErrorCode.ToString()));
}
axCZKEM1.OnAttTransactionEx += new zkemkeeper._IZKEMEvents_OnAttTransactionExEventHandler(axCZKEM1_OnAttTransactionEx); // 註冊監聽事件
Application.Run();
});
thread.IsBackground = true;
//thread.SetApartmentState(ApartmentState.STA); // 我註解掉也可以運作!
thread.Start();
Console.ReadLine(); // 接收到輸入才會結束程式
在看 Functional Interface 之前,先看看以下例子,如果我們只能寫一個方法,要在一個字串集合內,找到預期的字串,同時要能支援完全比對/忽略大小寫的話,寫出來的程式碼大概長的是以下這個樣子:
/*
* expect:是要比對的字串。
* ignoreCase:true 代表比對可以忽略大小寫。
*/
public boolean findAnyMatch(List<String> list, String expect, boolean ignoreCase)
{
if (ignoreCase) // 若任一個字串符合傳入的字串(忽略大小寫),就回傳 true。
{
for (String string : list)
{
if (expect.equalsIgnoreCase(string))
{
return true;
}
}
}
else // 若任一個字串符合傳入的字串(要完全一樣),就回傳 true。
{
for (String string : list)
{
if (expect.equals(string))
{
return true;
}
}
}
return false;
}
...
System.out.println(foo.findAnyMatch(list, "Moe", false)); // true
System.out.println(foo.findAnyMatch(list, "Jack", false)); // false
System.out.println(foo.findAnyMatch(list, "moe", true)); // true
原本的寫法除了又臭又長之外,還有多重巢狀結構,不容易閱讀。
同樣的方法改用 lamda 表達式作為 findAnyMatch 傳入參數的寫法,程式碼如下,是不是短了很多?甚至還可以在不更動 findAnyMatch 的情況下,找出是否有包含 "o" 的字串。
/*
* predicate:是一個只定義一個抽象方法 interface,告訴傳入的 Lambda 表達式會收到一個參數,然後要回傳一個 boolean。
*/
public boolean findAnyMatch(List<String> list, Predicate<String> predicate)
{
// 若任一個字串符合傳入的 predicate 檢查的條件,就回傳 true。
return list.stream()
.filter(predicate) // 傳入的 lambda 會收到一個字串,回傳 boolean
.findFirst()
.isPresent();
}
...
System.out.println(foo.findAnyMatch(list, (s) -> s.equals("Moe"))); // true
System.out.println(foo.findAnyMatch(list, (s) -> s.equals("Jack"))); // false
System.out.println(foo.findAnyMatch(list, (s) -> s.equalsIgnoreCase("moe"))); // true
System.out.println(foo.findAnyMatch(list, (s) -> s.contains("o"))); // true
所以大致的演進流程是:用傳入的參數來影響方法內的行為 > 直接把行為(lambda)當作參數。同樣的效果用傳入匿名類別當作參數也可以做到,但傳入 lambda 的寫法看起來比較短、比較好閱讀。
Java 把 lambda 寫法常用到的 Interface 先定義出來,我們在導入 lamda 寫法改寫原有方法時,就不用重複定義這些常出現的介面。比如上面用到的 Predicate 介面,它預期接收一個字串類型物件,然後返回一個 boolean 值。
以下整理出程式碼常見到的 Functional Interface:
Functional Interface | 輸入 | 回傳 |
---|---|---|
Predicate | T 類型物件 | boolean |
Consumer | T 類型物件 | 不用回傳 |
Fuction | T 類型物件 | R 類型物件 |
Supplier | 無 | T 類型物件 |
BiPredicate | T 類型物件 + U 類型物件 | boolean |
BiConsumer | T 類型物件 + U 類型物件 | 不用回傳 |
BiFuction | T 類型物件 + U 類型物件 | R 類型物件 |
升級完 Intellij IDEA 後,要開啟 Intellij IDEA 就跳出了 Unsupported Java Version 這個警告,在 Console 下查看 Java 版本是 openjdk version "11.0.2" 2019-01-15,版本大於 Java 11,應該不是環境的問題。
執行以下指令排除此問題:
rm ~/Library/Application\ Support/JetBrains/IntelliJIdea2020.3/idea.jdk
最早接觸到這些特殊註解是在寫 Eclipse 的時候,有些自動產生的程式碼就會自己補上 //TODO,在 Tasks 分頁就能顯示出這些備註,點擊就直接跳到程式碼上相當方便。
實際上在 Preferences > Java > Compiler > Task Tags 下可以看到預設只支援 TODO、FIXME 和 XXX 這三種特殊註釋。
除了 TODO、FIXME 和 XXX 外,我也列出了我比較少用,但在其他地方有看到的特殊註釋:
因為開發環境的 SpringToolSuite4 要求更新 Java,更新到 Java 11 之後,反而 SpringToolSuite3 就打不開了,啟動就跳出下圖:
按照指示查看 log,看起來是跟 Java 9 引入的 module 功能相關,造成找不到這個 PreDestroy 這個類別。
java.lang.NoClassDefFoundError: javax/annotation/PreDestroy
at org.eclipse.e4.core.internal.di.InjectorImpl.disposed(InjectorImpl.java:426)
at org.eclipse.e4.core.internal.di.Requestor.disposed(Requestor.java:154)
at org.eclipse.e4.core.internal.contexts.ContextObjectSupplier$ContextInjectionListener.update(ContextObjectSupplier.java:78)
at org.eclipse.e4.core.internal.contexts.TrackableComputationExt.update(TrackableComputationExt.java:111)
at org.eclipse.e4.core.internal.contexts.TrackableComputationExt.handleInvalid(TrackableComputationExt.java:74)
at org.eclipse.e4.core.internal.contexts.EclipseContext.dispose(EclipseContext.java:176)
at org.eclipse.e4.core.internal.contexts.osgi.EclipseContextOSGi.dispose(EclipseContextOSGi.java:106)
at org.eclipse.e4.core.internal.contexts.osgi.EclipseContextOSGi.bundleChanged(EclipseContextOSGi.java:139)
at org.eclipse.osgi.internal.framework.BundleContextImpl.dispatchEvent(BundleContextImpl.java:903)
at org.eclipse.osgi.framework.eventmgr.EventManager.dispatchEvent(EventManager.java:230)
at org.eclipse.osgi.framework.eventmgr.ListenerQueue.dispatchEventSynchronous(ListenerQueue.java:148)
at org.eclipse.osgi.internal.framework.EquinoxEventPublisher.publishBundleEventPrivileged(EquinoxEventPublisher.java:213)
at org.eclipse.osgi.internal.framework.EquinoxEventPublisher.publishBundleEvent(EquinoxEventPublisher.java:120)
at org.eclipse.osgi.internal.framework.EquinoxEventPublisher.publishBundleEvent(EquinoxEventPublisher.java:112)
at org.eclipse.osgi.internal.framework.EquinoxContainerAdaptor.publishModuleEvent(EquinoxContainerAdaptor.java:156)
at org.eclipse.osgi.container.Module.publishEvent(Module.java:476)
at org.eclipse.osgi.container.Module.doStop(Module.java:634)
at org.eclipse.osgi.container.Module.stop(Module.java:498)
at org.eclipse.osgi.container.SystemModule.stop(SystemModule.java:202)
at org.eclipse.osgi.internal.framework.EquinoxBundle$SystemBundle$EquinoxSystemModule$1.run(EquinoxBundle.java:165)
at java.base/java.lang.Thread.run(Thread.java:834)
Caused by: java.lang.ClassNotFoundException: javax.annotation.PreDestroy cannot be found by org.eclipse.e4.core.di_1.6.1.v20160712-0927
at org.eclipse.osgi.internal.loader.BundleLoader.findClassInternal(BundleLoader.java:410)
at org.eclipse.osgi.internal.loader.BundleLoader.findClass(BundleLoader.java:372)
at org.eclipse.osgi.internal.loader.BundleLoader.findClass(BundleLoader.java:364)
at org.eclipse.osgi.internal.loader.ModuleClassLoader.loadClass(ModuleClassLoader.java:161)
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:521)
... 21 more
解決方法:以下方法為 Eclipse / SpringToolSuite 通用,到 Eclipse / SpringToolSuite 資料夾下編輯 eclipse.ini 或 STS.ini,在 -vmargs 之前加上 -vm 參數指定啟動使用的 Java,下方範例我指定到我原本安裝的 Java 8 其 bin 資料夾下的 java 檔案。
另外要注意,-vm 是一列,指定路徑也是單獨一列,記得換行!
... 忽略
-vm
/Library/Java/JavaVirtualMachines/jdk1.8.0_40.jdk/Contents/Home/bin/java
-vmargs
... 忽略
在 Mac 上用 iterm2 管理所有會連線的主機,這樣就不用每次在記 IP / 帳號 / 密碼,首先先安裝 sshpass,再去 iterm2 設定 Profile(一個 Profile 可以想成一個主機),詳細步驟如下:
打開命令列,利用 homebrew 來安裝 sshpass:
brew install https://raw.githubusercontent.com/kadwanev/bigboybrew/master/Library/Formula/sshpass.rb
打開 iTerm2,Preferences > Profiles 分頁,在 Command 區域將選項設為 Command ,旁邊文字欄填入連線資訊。
# 連線資訊格式說明
# /usr/local/bin/sshpass -p 密碼 ssh -p Port 帳號@IP
# 範例:連線到 192.168.8.8,Port 是 22,帳號是 root,密碼是 iampassword
/usr/local/bin/sshpass -p iampassword ssh -p 22 root@192.168.8.8
設定畫面如下:
假設我們要測試資料庫,除了連接真正的資料庫或是本地端的測試資料庫以外,我們也可以生成假的物件來測試,以下列出 Stub / Mock 這兩種做法的測試案例,大家可以自行體會其中的差別。
/** 能連接至 MySQL 的實作 */
public class DatabaseMySql implements Database
{
public boolean connect()
{
// 省略
return true; // 連接真正的的資料庫,真的連上才回傳 true。
}
}
/** 為了測試用的實作 */
public class DatabaseStub implements Database
{
public boolean connect()
{
return true; // 直接回傳 true。
}
}
// 分隔線
public void testStub()
{
Database databaseStub = new DatabaseStub(); // 建立 stub
System.out.println(databaseStub.connect()); // 印出 true
}
public void testMock()
{
Database databaseMock = mock(Database.class); // 使用 mockito 建立 mock
when(databaseMock.connect()).thenReturn(true); // 當呼叫 connect() 就回傳 true
System.out.println(databaseStub.connect()); // 印出 true
}
對我來說如果用 Mock 寫法的話:
我實務上比較常用 Stub 的做法:
關於更專業的解釋,可以參考 Mocks Aren't Stubs 這一篇經典文章。
在 Java 8 之後,可以用 Stream API 來處理資料集合,最大的好處就是巢狀結構消失了,比較容易閱讀、看出商業邏輯,以下紀錄比較常用的操作:
// 示範 of
Stream<String> fooBarBazStream = Stream.of("foo", "bar", "baz");
// 示範 empty
Stream<String> emptyStream = Stream.empty();
// 示範 concat
Stream<String> stream1 = Stream.of("foo", "bar");
Stream<String> stream2 = Stream.of("baz");
Stream<String> stream3 = Stream.concat(stream1, stream2);
// 示範 generate
Stream<String> helloStream = Stream.generate(() -> "hello"); // 總是回傳 "hello"
String<Double> randomStream = Stream.generate(Math::random); // 每次回傳一個亂數
// 示範 iterate
Integer seed = 1;
Stream<Integer> oneTwoFourEghitStream = Stream.iterate(seed, n -> n*2); // 1, 2, 4, 8...
// 示範 distinct
Stream.of("a", "b", "a", "b")
.distinct()
.forEach(e -> System.out.print(e)); // 印出 "ab"
// 示範 map
employees.stream()
.map(e -> e.getName())
.forEach(name -> System.out.println(name)); // 印出所有員工的姓名
// 示範 flatMap
employees.stream()
.flatMap(e -> e.getPhoneList().stream()) // 傳入一位員工,發送出多筆電話
.forEach(phone -> System.out.println(phone)); // 印出所有員工的電話
終止操作完了以後,Stream 就不能再使用了。
// 示範 collect
List<String> nameList =
employees.stream()
.map(e -> e.getName())
.collect(Collectors.toList());
Docker VM 的記憶體使用量很高,進去看16G 的記憶體的機器,才裝12個 Docker 容器,一個一個檢查才發現幾個是跑 Java APP 的容器吃的記憶體特別高(1G~3G 之間),但有些容器都是 Serverless 類型的服務,不應該佔用那麼多記憶體。
在容器內部執行 JVM,預設 Max Heap Size 會是主機記憶體的1/4,而非容器記憶體的1/4(所以我 docker run 設定 memory 參數也沒用)。
在Java 8u131 之後支援了 Docker CPU 和記憶體的限制,在 Java 8 上要使用以下參數:
# 建立一個記憶體500 MB 有 JRE 8 的容器,執行 Java 指令印出 Max. Heap Size
# 調整 docker run 的 memory 參數,可以影響容器內 JVM 使用的記憶體大小。
docker run --memory 500MB openjdk:8-jre java \
-XX:+UnlockExperimentalVMOptions \
-XX:+UseCGroupMemoryLimitForHeap \
-XX:MaxRAMFraction=1 \
-XshowSettings:vm \
-version
參考:
了解上一章節的參數後,首先記得 docker run 一定要加上 memory 參數來限制容器的記憶體。
再來就是將原本執行 Java 程式的命令列(看是寫在 docker run 指令還是寫在 Dockerfile 裡頭)加上參數。這樣就能解決 Docker 容器跑 Java 程式佔用太多記憶體的問題。
# 執行 app.jar
docker run --memory 500MB openjdk:8-jre java \
-XX:+UnlockExperimentalVMOptions \
-XX:+UseCGroupMemoryLimitForHeap \
-XX:MaxRAMFraction=1 \
-jar path/to/your/app.jar
# Dockerfile
# 省略
CMD ["java", "-Dspring.profiles.active=test", "-XX:+UnlockExperimentalVMOptions", "-XX:+UseCGroupMemoryLimitForHeap", "-XX:MaxRAMFraction=1", "-XshowSettings:vm", "-jar", "path/to/your/app.jar"]
在 Console 視窗按 ⌘ + F 不會跳出搜尋功能。
因為開發環境會使用 Eclipse 和 Intellij IDEA,不想記兩套快捷鍵,所以 keymap 就設定為 Eclipse(macOS),Eclipse(macOS) 預設 Find 和 Replace 都是空的,原本只替 Replace(通常找到就是想要替代)設定 ⌘ + F,Find 沒有設定熱鍵所以就沒作用。
Preferences > Keymap ,選取 Main menu > Edit > Find,指定 Find... 和 Replace... 的熱鍵即可。
上次換機是2020/8,距今約兩年半,上一台 MacBook Pro 13" 是未代的 intel i5 / 16G Ram / 500G SSD,當初沒有等一下換 M1 版本,主要是怕程式開發工具有支援度問題,另外我有外接三螢幕需求,需要買到最貴的 M1 Max 才支援三螢幕輸出,這次買的是整修品(M1 Max / 64G Ram / 1T SSD)價格為85300元。
現在這台 MacBook Pro 13" 開發上很夠用,缺點大概是沒用的 touch bar 還有比較容易熱,然後這一兩年有了剪片需求,整個剪輯流程大概要輸出長度20分鐘的1080P影片三次,每次都要等20分鐘以上,現在換上 M1 Max 大概兩分鐘左右就輸出完成了,大大減少我等待的時間。
這次兩台電腦轉移的體驗也很好,同個網段下開啟系統移轉輔助程式,兩個多小時完成,大部分的 APP 跟個人資料都轉移過去了,我是碰到 Chrome 轉移失敗、MS Offiece 沒有轉移這兩個問題而已,重新安裝就解決了!
關於多螢幕部分,完全沒問題可以輸出三個螢幕,目前外接的做法是:
以上供有想要升級 M1 的朋友參考,有問題也可以留言問看看,我懂就幫忙測看看。
2023/2/16 更新
VirtualBox 6.1 不支援 M1/M2,VirtualBox 7.0 才有 Developer preview for macOS / Arm64 (M1/M2) hosts,但根據如何评价 VirtualBox 7.0 Beta 1 支持Apple Silicon?和我實際所有 VM (CentOS、Ubuntu)都無法啟動看來,目前 VirtualBox 並不支援!
2023/6/30 更新
VirtualBox-7.0.8_BETA4-156879-macOSArm64 不支援 M1/M2
先說結論,訂閱 Intellij IDEA 前先下載社群版,試著開個專案打打字(中英文都要),看看是否會卡頓,CPU 使用率是否會超過100%,會卡頓就不要訂閱。
# 檔案路徑在 /Applications/IntelliJ IDEA.app/Contents/bin/idea.vmoptions
# 預設值如下:
-Xms128m
-Xmx750m
-XX:ReservedCodeCacheSize=240m
預設值等於只允許 IntelliJ IDEA 使用750m 的記憶體,我筆電記憶體再大都沒有用,修改成最大可使用2g 的記憶體。# 修改後的值如下:
-Xms1024m
-Xmx2048m
-XX:ReservedCodeCacheSize=1024m
# 檔案路徑在 /Applications/IntelliJ IDEA.app/Contents/bin/idea.vmoptions
# 加入此列
-XX:TieredStopAtLevel=1
JIT Compiler 的 CPU 使用量有明顯降低,但 IntelliJ IDEA 只有微幅改善還是頓(因為還有其他問題占用 CPU)。
The DATETIME type is used when you need values that contain both date and time information. MySQL retrieves and displays DATETIME values in ‘YYYY-MM-DD HH:MM:SS’ format. The supported range is ‘1000-01-01 00:00:00’ to ‘9999-12-31 23:59:59’.
簡單來說,兩個差別:The TIMESTAMP data type has a range of ‘1970-01-01 00:00:01’ UTC to ‘2038-01-09 03:14:07’ UTC. It has varying properties, depending on the MySQL version and the SQL mode the server is running in.
MySQL converts TIMESTAMP values from the current time zone to UTC for storage, and back from UTC to the current time zone for retrieval. (This does not occur for other types such as DATETIME.)
-- 在 MySQL 執行以下 SQL 指令,顯示 MySQL 時區以及 MySQL 主機時區
show variables like "%time_zone%";
-- 發現 system_time_zone 是 CST,代表 MySQL 主機時區是 CST
-- 發現 time_zone 是 SYSTEM,代表 MySQL 時區參考 MySQL 主機時區
發生原因在於 CST 可能是中國標準時間(China Standard Time)或是美國中部時間(Central Standard Time),造成 Java 認為 MySQL 時區是美國中部時間。
# 修改 my.cnf,在 [mysqld] 下增加:
default-time-zone = '+08:00'
# 需要重新啟動 MySQL
-- 重起後,重新在 MySQL 執行以下 SQL 指令,顯示 MySQL 時區以及 MySQL 主機時區
show variables like "%time_zone%";
-- system_time_zone 是 CST
-- time_zone 是 +08:00