/**
* 印 this 的時候,會調用 Object.toString()
* 匿名類別的 this:會先調用自己匿名類別的 toString,沒有才去 Object.toString
* Lambda 的 this:會先調用「當下」類別的 toString,沒有才去 Object.toString
*/
public class ThisTest {
Animal a1 = new Animal() {
@Override
public void m(String str) {
System.out.println(this);
}
// @Override
// public String toString() {
// return "anonymous toString";
// }
};
Animal a2 = s -> System.out.println(this);
// @Override
// public String toString() {
// return "this toString";
// }
public static void main(String[] args) {
ThisTest thisTest = new ThisTest();
thisTest.a1.m("xxx");
thisTest.a1.m("aaa");
thisTest.a2.m("ooo");
thisTest.a2.m("bbb");
}
@FunctionalInterface
public interface Animal {
void m(String str);
}
}
2022年8月11日 星期四
lambda 的 this (java8 八)
2022年7月14日 星期四
Java 的 floorMod 和 %
// System.out.println(Math.floorMod(113, 30)); // 23
System.out.println(Math.floorMod(113, -30)); // -7
System.out.println(Math.floorMod(-113, 30)); // 7
System.out.println(Math.floorMod(-113, -30)); // -23
System.out.println("-----------------------");
// System.out.println(113 % 30); // 23
System.out.println(113 % -30); // 23
System.out.println(-113 % 30); // -23
System.out.println(-113 % -30); // -23
System.out.println("=======================");
// System.out.println(Math.floorDiv(113, 30)); // 3
System.out.println(Math.floorDiv(113, -30)); // -4
System.out.println(Math.floorDiv(-113, 30)); // -4
System.out.println(Math.floorDiv(-113, -30)); // 3
System.out.println("-----------------------");
// System.out.println(113 / 30); // 3
System.out.println(113 / -30); // -3
System.out.println(-113 / 30); // -3
System.out.println(-113 / -30); // 3
除數和被除數全正數和全負數時,% 和 mod 一模一樣
※全負數時,除法是負負得正;
※有負數時, % 的正負號看左邊,mod 的正負號看右邊,如 -113 % -30,-113 是左邊;-30 是右邊,一正一負時也是一樣
※113 / 30 是 3...23
floorDiv 時,一正一負為負,(3 + 1) * -1 = -4,固定加1乘-1
floorMod 時,30 - 23 = 7,然後看 30 是正或負,7 的正負號就和它一樣
目前不知道 mod 這樣有什麼用
2022年2月17日 星期四
檢查服務是否啟動、grep、sed、awk
檢查服務是否啟動
lsof -i:8080
netstat -lntup |grep 8080
ss -lntup |grep 8080
ps -ef |grep 8080
grep
過濾文字
-A 之前x行(不包括搜尋的行)
-B 之後x行(不包括搜尋的行)
-C 前後x行(不包括搜尋的行)
-c 有幾行,類似 wc -l 的功能
-i 忽略大小寫
-w word,單字匹配
-v 回傳相反的匹配
grep -A3 5 fileName:找到 5 後,將前3行顯示出來,所以總共會顯示 4 行
seq 10 |grep -A3 5 ==> 5~8
seq 10 |grep -B3 5 ==> 2~5
seq 10 |grep -C3 5 ==> 2~8
seq 20 |grep 5 |wc -l
seq 20 |grep -c 5
grep -i a test.txt
seq 50 |grep 5
seq 50 |grep -w 5
seq 20 |grep -v 5
sed
針對行的增刪改查,stream edit 的縮寫
-n:sed 會一行一行執行,預設每行都會輸出,所以匹配到的會出現兩次,而加這個只會將匹配成功的輸出
-i:顯示的結果,預設不會覆蓋原檔案,如果要覆蓋要加這個參數
-r -E:加上正則表達式支援
增刪改查
c a i:replace想成 ce的c/append/insert
d:delete
s:substitute
p:print
p:
'3p' 印出第3行
'1,5p' 印出1~5行
'/xxx/p' 找 xxx
'/10:00/,/11:00/p' 找 10:00~11:00 之間的資料
增刪改查範例:
sed -n '/7/,/10/p' test.txt
sed '/7/,/10/d' test.txt // 加 -n 會看不到結果,每次的7~10都會刪除
sed 's/a/aaa/' test.txt // s寫在前面,s/a/aaa/、s#a#aaa#,分隔符不要太奇怪都可以
sed 's#[0-9]##g' test.txt // 將數字取代成空,但仍然有換行,最後不加 g,只會取代每行的第一個數字
sed '6a xxxxxx' test.txt // 在第6行之後加一行 xxxxxx
sed '6i xxxxxx' test.txt // 在第6行之前加一行 xxxxxx
sed '6c xxxxxx' test.txt // 取代第6行,變成 xxxxxx
awk
發明人的名字第一個字母組成的
record:行(左右),預設 \n,-v RS 可改
field:列(上下),預設空隔,-F 或 -v FS 可改
$數字:第x列,$0是整行
NF:最後一列的數字,可以加減,但要用圓括號包起來
NR:行號
-F分隔符
FS:-v FS=分隔符,給 awk 的分隔符,相當於 -F,預設是空格
OFS:-v OFS=分隔符,awk 返回結果的分隔符,預設是空格
RS:-v RS=換行符,預設 \n
ORS:-v ORS=換行符,預設 \n
print 會換行
printf 不會換行
NR
Number of Record
NR==1 取出第一行
NR >=1 && NR <= 5 取出 1~5 行,還有 == != 可用
awk 'NR >= 12 && NR <= 15' test.txt
假設有一行 1,2,3,4,5,6,7,8,9,10
awk -F, 'NR==1{print $NF}' test.txt // 取第一行,然後以,分隔,再取最後一列,所以是10
awk -F, 'NR==1{print $(NF-1)}' test.txt // 取到數第二列
ls -l |awk '{print $5, $NF}' |column -t // 取第五列和最後一列,並排版
ls -l |awk '{for(i=5;i<=NF;i++){printf $i" "};printf "\n"}' |column -t // 取第五列到最後一列
正則
awk '$10~/^1/{print $10}' fileName // 第 10 個欄位是 1 開頭就印出來
~ 包含
!~ 不包含
^$ 空
//,//
//,//{print $2}
BEGIN、END
BEGIN{} 內容在 awk 讀檔案之前執行
END{} 內容在 awk 讀檔案之後執行
seq 100 |awk 'BEGIN {pring 0} {sum=sum+$1; print sum} END{print "finish"}'
印出 0~100 相加的過程,BEGIN 和 END 可以只用一個
陣列
非數字要用字串包起來
awk 'BEGIN{x[0]=123; x[1]="xxx"; print x[0], x[1]}'
awk 'BEGIN{x[0]=123; x[1]="xxx"; for(i in x) print x[i]}'
2021年12月12日 星期日
rocketMQ 4.x
控制台:就是有畫面可以操作
※啟動注意事項
此文章使用的是 Windows 的 rocketMQ,使用 java8 可以跑得起來,但不能安裝在有空格的路徑,如 Program Files,網上有說用字串包起來可以解決,但我試不成功,但可以用 PROGRA~1 解決
9 10 不是 LTS 版,我沒試過,而 11 會出現 JVM 參數已經沒有的問題,會跑不起來,雖然網路上也有將沒有的 JVM 參數註解掉,但我試還是不成功
windows 要執行副檔名是 cmd 的;linux 要執行 sh 的
mqnamesrv.cmd 啟動 name server,內部會執行 runserver.cmd
mqbroker.cmd 啟動 broker,內部會執行 runbroker.cmd
以上這兩個用文件編輯器可打開查看,其中需要 ROCKETMQ_HOME 變數,所以要設環境變數
-Xms2g -Xmx2g 可以設小一點,設 256m 就夠測試了
我設的是 ROCKETMQ_HOME=C:\Users\bruce\Downloads\rocketmq-4.9.2,要在 bin 的上一層,最後在放到 path 裡,然後後面加上 bin
然後先啟動 name server,後啟動 broker,啟動成功畫面如下:
以上的命令可在官網的文件複製
※測試生產消費
啟好了之後,試試能不能生產和消費,官網說要設定 NAMESRV_ADDR="localhost:9876"
所以再開啟命令提示字元,set NAMESRV_ADDR=localhost:9876,官網前後有字串,但我試的結果不行,注意 set 設定的變數只有在這個視窗有效,要永久就要在環境變數裡設定
設定好之後,tools.cmd org.apache.rocketmq.example.quickstart.Producer 就會看到畫面一直跑,
接著是消費者,再開一個命令提示字元,如果變數是暫時的,還要在設定 NAMESRV_ADDR,打上 tools.cmd org.apache.rocketmq.example.quickstart.Consumer,就會看到開始消費了
※啟動主控台
就是這文章最上面的控制台,使用 ide 打開後,可改兩個地方,如下:
rocketmq.config.namesrvAddr 要設定 name server 的地址; server.port 可以改成這個主控台想要的 port
server.port 預設是 8080;rocketmq.config.namesrvAddr 預設是 localhost:9876
然後下 mvn clean package -Dmaven.test.skip=true,成功後會在 target 裡生成一個 jar
執行這個 jar,java -jar xxx.jar 即可啟動,在瀏覽器打上 localhost:8080
看到這畫面表示成功了啟動了,再生產一次資料後,可在 Message 活頁標籤裡搜尋 Topic 是 TopicTest 的,可以看到生產的資料
※關閉 name server 和 broker
和啟動相反,先關閉 broker,再關閉 name server
mqshutdown.cmd broker
mqshutdown.cmd namesrv
※broker 的雙主雙從等
在 rocketMQ 目錄有個 conf,裡面有三個資料夾
2m-2s-async:雙主雙從加異步
2m-2s-sync:雙主雙從加同步
2m-noslave:雙主
在啟動 broker 時可以加上 -c 的參數,後面加檔案路徑,就可以抓到裡面的設定,但裡面的設定還不是很多,可到官網查看
※Message
這個是傳訊息要用到的 class
tag 可以再 topic 裡在過濾一層,如生產時 topic 是 animal,tag 有 cat、dog…等,消費者可以針對某一個 tag 進行過濾,如 tag 給 dog,就只抓 dog,給 * 會全抓
key 是用戶端自己生成的,可以把它寫成唯一 id
※持久化資料
windows 在 %userProfile%\store
commitlog:真正存資料的地方,預設一個檔案 1G
consumequeue:用 topic 區分,連到 commitlog 的索引,消費者群組來這裡找資料的
index:如果在生產時有給 key 就可以利用這個 key 和時間區間來找資料
※Queue 數量
預設的數量是 4,可以使用 producer.setDefaultTopicQueueNums 修改,但如果已經有 topic 改不了,所以是 setDefault,因為資料在讀或寫的時候,更改這個值會有影響,要用下面要說的 writeQueueNums 和 readQueueNums
已經有 topic,可以刪除或修改,但要在控制台操作,如下:
每個橫條都是一個 topic,最右的按鈕可以刪除 topicstatus 按鈕可以看有幾個 queue
topic config 按鈕可以修改幾個 queue,如下:
perm 有三種值 2 4 6,分別是可寫可讀可讀寫
writeQueueNums 是 Queue 的數量,改好可到 status 查看
通常 writeQueueNums 和 readQueueNums 都是設成一樣的,只有在擴容或縮容時才會不一樣
假設 writeQueueNums 4; readQueueNums 8 會有 4 個消費者消費不到,想擴容時可暫時這樣設定
假設 writeQueueNums 8; readQueueNums 4 會有 4 個 queue 沒有消費,想縮容時可暫時這樣設定
※消費者組、主題、tag
當設定完消費者組、主題、tag 後,消費者組就會開始監聽,但只要同一個消費者組,不管主題、tag 是什麼都會收到資料,所以有可能收錯資料,而另一個資料丟失
正確的做法是消費者組、主題、tag 都要一樣,要訂閱不同主題時,也要用新的消費者組
※刷盤和複製策略
在下載好的 rocketmq\conf\broker.conf 有如下的設定
brokerRole = ASYNC_MASTER,表示異步複製,改同步只要去掉 A 即可,而 SLAVE 是接受方
flushDiskType = ASYNC_FLUSH,表示異步刷盤,改同步只要去掉 A 即可
這兩個預設都是異步的
複製策略
brokerRole 表示 MASTER 複製給 SLAVE 用什麼方式
1. ASYNC_MASTER 消息在記憶體並寫到 master 的硬碟後就回應,然後再寫到 slave
所以在還沒寫到硬碟時,忽然網路延遲或斷電,但生產者會以為已經送到 broker 了,這時會造成 slave 資料丟失
2. SYNC_MASTER 收到消息並寫到 master 和 slave 的硬碟後才回應已完成
3. SLAVE 接收 MASTER 的資料
刷盤策略
flushDiskType 表示消息在記憶體,寫入硬碟的方式
1. ASYNC_FLUSH 消息在記憶體就回應,然後再寫到碟碟
所以忽然網路延遲或斷電,就會認為沒有刷盤成功,然後再送出資料給消費者,造成消息重覆
2. SYNC_FLUSH 記憶體寫到硬碟後再回應
※Queue 和消費者
1.同個消費者組下的消費者可以消費同個主題的不同 queue;但同個主題的同個 queue 不能被多個消費者消費
@RocketMQMessageListener(
consumerGroup = "myConsumerGroup",
topic = "myTopic",
selectorExpression = "myTag",
consumeMode = ConsumeMode.ORDERLY,
consumeThreadMax = 1)
ConsumeMode.ORDERLY 表示一個 queue 對應一個消費者,注意原碼的註解 one queue, one thread,並不是指一個 thread,要指定一個 thread 要用下面的 consumeThreadMax = 1
consumeThreadMax 的數量是指藍色的線
2.不同消費者組下的消費者可以同時消費同個主題的同個 queue
3.同個消費者組下的消費者不可以同時消費不同 topic 的 queue
2021年12月10日 星期五
Mongodb
enterprise 需要錢,community 不用錢
下載解壓安裝,安裝過程會問要不要再安裝 compass,這個是 gui 工具,看要不要安裝或者安裝自己熟悉的 gui 工具,mongodb://127.0.0.1:27017 可以連自己的,但下面的 mongod 要執行成功
完成後,看要不要設定環境變數
啟動用 mongod,但會出現錯誤,說路徑要有 c:\data\db,自行創建後,啟動成功
或者用參數 mongod --dbpath 資料夾路徑名,這樣就可以使用自己喜歡的資料夾
port 預設是 27017,想改可用 mongod --port 2222,這要就可以改 port
然後再開一個視窗,打上 mongo 或 mongo --port 27017 即可連線,出現「>」表示連現成功
database 資料庫
collection 集合
document 文件
資料庫和集合不用手動創建,直接 use 名稱即可,在新增文件時,會自動新增資料庫和文件
show dbs 或 shw databases 看有什麼資料庫
db 看目前在哪個資料庫
show collections 或 show tables看當前資料庫有什麼集合
新增文件 db.<collection>.insertOne insertMany
db.zoo.insertOne(name:"monkey"); zoo 是這行才開始新增的 collection,如果資料庫沒新增也會一併新增,insertOne 裡的 bson 就是 document
其他 deleteOne、deleteMany、updateOne、updateMany、find 都看官方文件即可
※NoSQLBooster for MongoDB
這也是個 IDE
projection 是投影的意思,在這裡表示哪些欄位要顯示,1顯示,0不顯示
另一種方法是將 document 寫在 find 的第二個參數裡,這樣就不用寫 projection 了
※索引
預設 _id 是 PK,就已經是索引了
db.<collection>.createIndex(
{ field1: 1 } ,
{ field2: -1 }
);
對 field1 做正序,對 field2 做倒序
查看 index: db.<collection>.getIndexes();
如圖,表示有兩個索引,key 為索引的欄位名稱,第二個是我建的複合索引
查看執行計畫,官方文檔
db.<collection>.explain("queryPlanner");
裡面可以寫三種值 queryPlanner、executionStats、allPlansExecution
看結果的 winningPlan,有個 stage,如下
stage 包括五種
COLLSCAN:掃描 collection,全表掃描的意思
IXSCAN:掃描 index,表示吃到索引了
FETCH:用於檢索文件
SHARD_MERGE:用於合併分片的結果
SHARDING_FILTER:用於從分片中過濾掉孤立文件
2021年10月2日 星期六
LBCC 和 MVCC
LBCC (Lock Base Concurrency Control) 基於鎖的併發控制
※英文文件、中文文件
※行鎖鎖的是索引,沒索引會變成表鎖,有 PK 會自動有主鍵索引
※自動提交
SET AUTOCOMMIT=0; # 關閉自動提交
show variables like 'autocommit';
※關閉不可寫的指令
SET SQL_SAFE_UPDATES=0; # 這樣才可以新增、修改、刪除
show variables like 'sql_safe_updates';
※開啟事務
START TRANSACTION 或 BEGIN
※回滾和提交
ROLBACK、COMMIT
※隔離等級
# READ-UNCOMMITTED、READ-COMMITTED、REPEATABLE-READ、SERIALIZABLE
mysql 預設是 REPEATABLE-READ
set session transaction_isolation='REPEATABLE-READ'; # 不寫 session 也行,預設就是 session
set global transaction_isolation='REPEATABLE-READ';
SELECT @@global.transaction_isolation;
SELECT @@session.transaction_isolation;
●兩個 session 隔離等級不同,如 A 是 READ-UNCOMMITTED,B 是 READ-COMMITTED,以自己設定的為準,A 讀 B 的會有髒資料;相反則不會
紅框為同一個 session,如果改了活頁籤其中一個,同一個 session 都會改到;綠框為不同 session,mysql workbench 要在圖的下方再按一次就會出現
※準備測試資料
CREATE TABLE `bruce_tbl` (
`id` int DEFAULT NULL,
`name` varchar(10) DEFAULT NULL,
KEY `id_index` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
INSERT INTO bruce_test.bruce_tbl (`id`,`name`) VALUES (1,'abc');
INSERT INTO bruce_test.bruce_tbl (`id`,`name`) VALUES (5,'bbb');
INSERT INTO bruce_test.bruce_tbl (`id`,`name`) VALUES (9,'ccc');
INSERT INTO bruce_test.bruce_tbl (`id`,`name`) VALUES (11,'ddd');
以下的測試是用 8.0.23 版
以下的測試都是用鎖的,如 selec * from table 是沒有鎖的,不管怎樣都可以執行的,不在這裡說明的範圍
●以下的測試第一次都要先 SET AUTOCOMMIT=0; 而且還要執行 start transaction 或 begin 再執行以下要測試的語句,然後都測試完後,再 rollback 或 commit
※exclusive locks 排他鎖、shared locks 共享鎖
排他鎖:在 select 最後加 for update、增刪改
共享鎖:在 select 最後加 for share 或 lock in share mode,但後者還可以有更多選項,如會阻塞就直接返回
排他鎖只能一個,一個 sql 取得了排他鎖,其他的鎖都會阻塞,要查的出資料才有鎖
共享鎖可以多個,但不能有排他鎖,要查的出資料才有鎖
測試排他鎖:
A session:
SELECT * FROM bruce_test.bruce_tbl where id = 1 for update; # 執行到這裡先停,然後執行 B session
B session:
SELECT * FROM bruce_test.bruce_tbl where id = 1 for update; # 會阻塞
SELECT * FROM bruce_test.bruce_tbl where id = 5 for update; # 不會阻塞
# 資料庫的 id 有四筆,1 5 9 11,如果不是這四個值也不會阻塞
測試共享鎖:
A session:
SELECT * FROM bruce_test.bruce_tbl where id = 1 for share;
B session:
SELECT * FROM bruce_test.bruce_tbl where id = 1 for share; # 不會阻塞
SELECT * FROM bruce_test.bruce_tbl where id = 1 for update; # 會阻塞
※Intentioin locks 意向鎖
分成意向共享鎖和意向排他鎖,都是表鎖,無法人工增加,是 mysql 自動幫我們加上的
事務要取得排他鎖前要先取得意向鎖排他鎖;事務要取得排他鎖前要先取得意向共享鎖
意向鎖目的是為了加快效能,如某一行被 for update,就表示有意向排他鎖和排他鎖,另一個 session 想鎖這張表 (lock tables talble_name read) 會發現有意向排他鎖,就知道有其中一行被鎖了,這樣就不用一行一行去找哪一行被鎖了
這兩個鎖都可以多個而且互相都是相容的
以下是官網截出來的表,IX 表示意向排他鎖;IS 表示意向共享鎖,指的都是表鎖,共享和排他的例子鎖的是行,mysql 是可以同時有表鎖和行鎖的
首先看直的,排他鎖只能有一個,所以全部都是 confilict
再來是共享鎖,可以多個共享鎖和意向共享鎖,其他有排他的都不行
意向共享除了排他外都是可以的
意向排他只能和意向排他和意向共享,其他都不行,連共享鎖也不行
※record locks 記錄鎖
測試方法同測試排他鎖
※gap locks 間隙鎖
隔離等級為 RR 才有用,範圍條件裡沒有命中記錄時為間隙鎖,不能新增記錄,但可以用排他鎖查詢,因為都是沒有記錄的範圍
● A 是 RR,B 是 RC,A 的範圍沒有記錄,會有間隙鎖,這時 B 想新增範圍裡的資料會等待
● 資料庫的 id 是 1 5 9 11
測試一 A session:
SELECT * FROM bruce_test.bruce_tbl where id > 5 and id < 9 for update; # 這個範圍裡資料庫沒有資料才能叫間隙鎖
測試一 B session:
SELECT * FROM bruce_test.bruce_tbl where id = 8 for update;
# 主鍵索引:都沒鎖
# 唯一索引:5不會鎖,鎖的是 9
# 普通索引:5不會鎖,鎖的是 9
insert into bruce_test.bruce_tbl (id, name) values (5, 'xxx');
# 主鍵索引:鎖的是 6~8,也就是 where 的範圍
# 唯一索引:鎖的是 6~9
# 普通索引:鎖的是 5~8
測試二 A session:
SELECT * FROM bruce_test.bruce_tbl where id > 15 for update;
測試二 B session:
insert into bruce_test.bruce_tbl (id, name) values (11, 'xxx');
# 主鍵索引:鎖 12(含)之後
# 唯一索引:鎖 12(含)之後
# 普通索引:鎖 11(含)之後
※next-key locks 臨鍵鎖
為記錄鎖 + 間隙鎖,隔離等級為 RR 才有用
● 假設資料庫的 id(index) 是 1 5 9 11,這時 id > 5 and id < 10,除了不能新增外也不可用排他鎖
A session:
SELECT * FROM bruce_test.bruce_tbl where id > 5 and id < 10 for update;
B session:
SELECT * FROM bruce_test.bruce_tbl where id = 11 for update;
# 主鍵、唯一、普通索引都是鎖 9 和 11,不鎖 5
insert into bruce_test.bruce_tbl (id, name) values (8, 'xxx');
# 主鍵索引:鎖 6~10
# 唯一索引:鎖 6~11
# 普通索引:鎖 5~10
測試二 A session:
SELECT * FROM bruce_test.bruce_tbl where id > 5 for update;
測試二 B session:
SELECT * FROM bruce_test.bruce_tbl where id = 9 for update;
# 主鍵、唯一、普通索引都是鎖 9 和 11,不鎖 5
insert into bruce_test.bruce_tbl (id, name) values (9, 'xxx');
# 主鍵索引:鎖 6(含) 以上
# 唯一索引:鎖 6(含) 以上
# 普通索引:鎖 5(含) 以上
MVCC (Multi-Version Concurrency Control) 多版本併發控制
snapshot read 快照讀:像照相機一樣照起來,以後資料都從這邊拿
current read 當前讀:直接去資料庫拿,所以拿的都是最新的資料
在兩個讀之間有增刪改其中之一,會觸發當前讀
目的是想解決幻讀,但必竟隔離等級是 RR,不是 SERIALIZABLE,所以只解決了一部分,在第一次讀的時候用的是快照讀,之後其他 session 新增了記錄,此時在查詢一次並不會產生新的記錄,會有問題的如下:
測試:
A session:# 執行第一行後時先停一下,然後執行 B session,然後再繼續
SELECT * FROM bruce_test.bruce_tbl where id = 1; # 假設查出來的 name 是 aaa
update bruce_test.bruce_tbl set name = CONCAT(name, '123') where id = 1; # 這行產生了讀已提交的資料
SELECT * FROM bruce_test.bruce_tbl where id = 1; # sesion 還沒結束,name 應該要是 aaa123,但確變成了 abc123,因為觸發了當前讀
B session:
update bruce_test.bruce_tbl set name = 'abc' where id = 1; # 直接提交
2021年8月15日 星期日
浮點數
整數的 1/0 會出 by zero 的例外,但浮點數不會,有三種型式,「/」左右邊其中一個是 .0 就不會出錯了
1.0/0.0 = Infinity (正無限)
-1.0/0.0 = -Infinity (負無限)
0.0/0.0 = NaN (非數字)
Math.sqrt(-2) = NaN (只要是負數就會 NaN,但 0 沒關係)
其中只有 NaN 不等於自己,所以 Float 和 Double 的 isNaN 原碼如下:
public static boolean isNaN(float v) {return (v != v);}
public static boolean isNaN(double v) {return (v != v);}
isInfinite 是否是正無限或負無限 (不包括 NaN)
isFinite 只要不是正無限、負無限、NaN 其中之一就會回傳 true
IEEE 754
程式用的是 IEEE 754 的規則,維基連結
會將浮點數分成三部分
sign + exponent + fraction => 正負數+指數+小數,sign 佔一位,0是正數;1是負數
Float 32 位元為 1 + 8 + 23,指數(10進位)要加 127
Double 64位元為 1 + 11 + 52,指數(10進位)要加 1023
指數的正規化:整數只留一位,而且一定是 1,假設 10 進位是 0.5,轉成二進位是 0.1,第一個必需是 1,所以要改成 1 * 2^-1 次方
0 01111110 00000000000000000000000 中間的是 -1 + 127 = 126 的二進位
Infinity 正無限:0 + 指數全是 1 + 小數全是 0
-Infinity 負無限:1 + 指數全是 1 + 小數全是 0
NaN 非數字:指數全是 1 + 小數不是 0,java 定義為 指數全是 1 + 小數第一個是 1 後面都是 0
※驗證:
一、以 8.5 為例,轉二進制不會循環,1000.1 轉成左邊只有一位 => 1.0001 * 2^3 (二的3次方,3是指數)
32 位元 (可用 Float.floatToIntBits(8.5F) 驗證):
0 3+127 0001000...
0 10000010 0001+19個0 => 01000001000010000000000000000000 => 1091043328
64位元(可用 Double.doubleToLongBits(8.5) 驗證)
0 3+1023 00010000000...
0 10000000010 (1後8個0) 0001+48個0 => 0100000000100001000000000000000000000000000000000000000000000000 => 4620974692658839552
二、以 12.4 為例,轉二進制會循環,1100.0110 0110 0110...(0110 沒完沒了) => 1.1000110 0110 0110...*2^3
只舉例 32 位元,看懂了自己算算看 64 位元
0 3+127 1000110 0110 0110 0110....
0 10000010 1000110 0110 0110 0110 0110 => 01000001010001100110011001100110 => 1095132774
三、正負無限
32位元
0 + 8個1 + 23個0 01111111100000000000000000000000 --> 7F800000
1 + 8個1 + 23個0 11111111100000000000000000000000 --> FF800000
64位元
0 + 11個1 + 52個0 0111111111110000000000000000000000000000000000000000000000000000 --> 7FF0000000000000
1 + 11個1 + 52個0 1111111111110000000000000000000000000000000000000000000000000000 --> FFF0000000000000
四、NaN
32位元
0 + 8個1 + 頭一個是1後面都是0
01111111110000000000000000000000 --> 7FC00000
64位元
0 + 11個1 + 頭一個是1後面都是0
0111111111111000000000000000000000000000000000000000000000000000
7FF8000000000000






