顯示具有 Java 標籤的文章。 顯示所有文章
顯示具有 Java 標籤的文章。 顯示所有文章

2019年9月2日 星期一

java 11 功能

※HttpClient

org.apache.http.client.HttpClient.HttpClient;
java.net.HttpURLConnection;
java.net.http.HttpClient;

apache 也有 HttpClient,但聽說效能沒 HttpURLConnection 好,所以就越來越少人用了,但這一版新增了替代的抽象類別,名字也叫 HttpClient,可以支援 http 2.0 的,至於寫法,API 已有提供範例


※Optional 增加 1 個方法

.isEmpty
Optional<String> op = Optional.ofNullable(null);
if (op.isEmpty()) {
System.out.println("空的");
}



※String 新增 6 個方法

.strip、stripLeading、stripTrailing
System.out.println(" \t \n \r".strip().length()); // 0,去掉空格(全半形)
String s = " a\t \n \ra  ";
System.out.println(s.stripLeading().length()); // 9,去掉前空格(全半形)
System.out.println(s.stripTrailing().length()); // 8,去掉後空格(全半形)

.isBlank
System.out.println("".isBlank()); // true
System.out.println(" \t \n \r".isBlank()); // false

.repeat、lines
System.out.println("abc".repeat(3)); // abcabcabc
System.out.println("asdf\r\nzxcv\r\n".lines().count()); // 2
System.out.println("asdf\r\nzxcv\r\nqwer".lines().count()); // 3
lines() 回傳 Stream<String>

2019年9月1日 星期日

java 10 功能

※var

var i = 11;
只能寫在區域變數且一定要賦非 null 的值
var j = new int[]{1,2,3}; // new int[] 不能省略
可以寫在 lambda 的參數,但 lambda 本來就可以省略,多此一舉
可以寫在迴圈,新舊都可以
不能寫在方法的參數和回傳值


※Optional 增加 1 個方法

.orElseThrow
Integer integer = Optional.ofNullable(1).orElseThrow(NullPointerException::new);

如果是 null 就會報 orElseThrow 裡寫的 exception



※List.copyOf

List<Integer> list1 = List.of(1, 2, 3);
List<Integer> copyList1 = List.copyOf(list1);
System.out.println(list1 == copyList1); // true

List<Integer> list2 = Stream.of(1, 2, 3).collect(Collectors.toList());
List<Integer> copyList2 = List.copyOf(list2);
System.out.println(list2 == copyList2); // false

※此方法只會複製不可變的,所以如果本來就是不可變的,底層會判斷能不能 instanceof成 AbstractImmutableList,傳回來會不一樣

※copyOf 是淺複製

class Animal {
    private int id;

// getter/setter

    public Animal(int id) {
        this.id = id;
    }
}

List<Animal> animals = List.of(new Animal(1), new Animal(2), new Animal(3));
List<Animal> copyAnimals = List.copyOf(animals);
System.out.println(animals == copyAnimals); // true

animals.forEach(a -> System.out.println(a.getId())); // 1 2 3
animals.get(0).setId(3);
animals.forEach(a -> System.out.println(a.getId())); // 3 2 3
copyAnimals.forEach(a -> System.out.println(a.getId())); // 3 2 3
System.out.println(animals == copyAnimals); // true

Java 9 功能

※Module



新建一個專案,然後增加兩個 module,如上圖,Module1 和 Module2
在 Module1 增加 class M1;Module2 增加 class M2
M1 想用 M2 預設是沒辦法的,所以在兩個 module 的 src 按右鍵增加 module-info

1.M2 要給別人用,所以要匯出,都是以 package 為單位
module Module2 {
    exports xxx.ooo;
}

2.M1 要用別人的,所以要要求使用,如果要用 java 內鍵的,按下 content assist 會有提示
module Module1 {
    requires Module2;
}

3.此時還是會編譯錯誤,還得如下使用:

4.好文章


※介面可用 private 方法


※String 和 AbstractStringBuilder 

從 char[] 改成 byte[],AbstractStringBuilder 是 StringBuffer 和 StringBuilder 的抽象父類別


※jshell

java 的 shell,在安裝目錄的 bin 裡,每次要測簡單的東西,都要寫 class,然後在寫個 main 方法是很累人的,所以直接在裡面打即可
1.除了 java.lang 外,打 /imports 是預設有 import 的
2.不用 try catch
3.屬性和方法會後者蓋前者



※省略泛型和增強 try-with-resource

new Comparable<>(){
@Override
public int compareTo(Object o) {
return 0;
}
};

※在 java 7 就可以省略 <> 裡的東西了,但匿名類別不行,在這一版可以了


InputStreamReader isr = new InputStreamReader(System.in);
try(isr) {

} catch(Exception e) {

}

※在 java 7 就可以有 try () 了,但宣告都要在裡面才行,在這一版可以寫在外面,裡面寫變數名稱即可

※不能改 try 裡的東西,裡面是 final 的



※不可變的可讀集合

List.of(1, 2, 3, 4, 5);
Set.of(1, 1, 2, 2, 3);
Map.of("a", 1, "b", 2);
Map.ofEntries(Map.entry("c", 3), Map.entry("d", 4));



※Optional 增加 3 個方法

.stream
List<Integer> list = List.of(1, 2, 3);
Optional<List<Integer>> oList = Optional.ofNullable(list);
Stream<List<Integer>> stream = oList.stream();

※回傳的和 Optional 的泛型一樣

.or
Optional<Integer> op = Optional.ofNullable(null);
op = op.or(() -> Optional.of(7));
System.out.println(op.get());

※如果不是 null 就回傳,否則就回傳 7


.ifPresentOrElse
Optional<Integer> op = Optional.ofNullable(null);
op.ifPresentOrElse(x -> System.out.println("x=" + x), () -> System.out.println("hahaha"));

※回傳 x 或 hahaha


※Stream 增加 4 個方法

Stream.iterate(0, n -> n < 10, n -> ++n).forEach(System.out::println);

※這是個 overloading 方法,java 8 只有一個,要加 limit 才不會變成無限流,現在有了中間參數是 Predicate,可以不用 limit 了


Stream.of(1, 2, 6, 7, 4, 3).takeWhile(n -> n < 6).forEach(System.out::println); // 1 2
Stream.of(1, 2, 6, 7, 4, 3).dropWhile(n -> n < 6).forEach(System.out::println); // 6 7 4 3
takeWhile:從第一個開始判斷,如果條件成立( < 6),就抓,但只要條件不成立,馬上返回 (雖然後面也有條件成立的,但不管)

dropWhile:takeWhile 的相反,從第一個開始判斷,如果條件成立就刪除,但只要條件不成立,馬上返回 (雖然後面也有條件成立的,但不管)

Stream.of(null, null).count(); // 2 這個不是新增的,如果有兩個以上 (包括兩個) 的元素,不會報錯,但如果只有一個 null,會報空指針,所以可以用新方法 ofNullable,但回傳的是 0
Stream.ofNullable(null).count(); // 0



※ElementType 增加 MODULE

使用在 module-info 裡,例子可看這篇最下面

2019年8月15日 星期四

java 的 ~、^、<<、>>、>>>、<<=、>>=、>>>=

※

final byte x = 40;
final byte y = -40;
    
System.out.println(~x); // -41
System.out.println(~y); // 39
    
System.out.println(5 ^ 6); // 3
System.out.println(x ^ y); // -16
System.out.println(Integer.toBinaryString(x)); // 101000
System.out.println(Integer.toBinaryString(y)); // 11111111111111111111111111011000
System.out.println(Integer.toBinaryString(-16)); // 11111111111111111111111111110000
    
System.out.println(x << 2); // 160
System.out.println(x >> 2); // 10
System.out.println(x >>> 2); // 10
    
System.out.println(y << 2); // -160
System.out.println(y >> 2); // -10
System.out.println(y >>> 2); // 1073741814
System.out.println(y >>> 4); // 268435453

※這裡的符號都會轉換成二進制,可用 Integer.toBinaryString 或 Long.toBinaryString 查看,這兩個差在 32 位和64 位


40 的二進制:(前面沒有視同 0,總共 32 或 64 位) 10 1000
-40 的二進制:1111 1111 1111 1111 1111 1111 1101 1000


※正二進制轉負二進制

二進制取反 +1
如:-40 的二進制就是 40 的二進制取反 +1
40 -> 10 1000 取反 --> 01 0111 加 1 -> (前面很多1)  01 1000

※負的二進制轉成 10 進制

取反後 +1 的 10 進制乘 -1

※~ 取反(簡單公式就是 (x+1)*-1)

正數:二進制+1後轉10進制,然後乘-1
負數:二進制取反
.40 轉二進制 -> 10 1000 -> 10 1001 = 41 * -1 -> -41

.-40 轉二進制 -> (前面很多1) 01 1000 取反->10 0111 = 39



※ ^ XOR 互相排斥,一正一反為 true,兩正兩反為 false

.5 ^ 6
5 -> 101
6-> 110
XOR 後,為 011 -> 3

.40 ^ -40
40 -> 101000
-40 -> 1111 1111 1111 1111 1111 1111 1101 1000
XOR 後,為 (前面很多1) 11 0000

驗證:
(前面很多1) 11 0000 取反 -> 1111 + 1 -> 1 0000 -> 16 * -1 = -16

※有兩個數想要互換,但不能用中間的 temp 變數,有以下兩種方法

一個數對另一個數互斥兩次,值不會變
var x = 66;
var y = 77;
x = x ^ y;
y = x ^ y;
x = x ^ y;
※要小心 x 和 y 的值一樣時,不能用這招,直接 return 即可
----------------------------------
先取得兩數的和再進行減法運算
var x = 66;
var y = 77;
x = x + y;
y = x - y;
x = x - y;

※要小心 x+y 超過類型的範圍就不行了


※ << 左移運算符

.40 -> 10 1000
<< 2 往左二位,就相當於在最右邊增加 2 個 0
1010 0000 -> 2 的 7 次方 + 2 的 5 次方 -> 128 + 32 = 160
以十進位來說, <<2 就是乘 2 的 2 次方;<<3 就是乘 2 的 3 次方

.-40 ->  (前面很多1)  01 1000 -> 0110 0000

驗證:
(前面很多1) 0110 0000 取反 -> 1001 1111 + 1 -> 1010 0000 -> 32 + 128 -> 160 * -1 = -160


可以背快速的用法:
例一:40 << 4:4 表示 2 的 4 次方,結果為 40 *16
例二:1 << 3:1 * 8

※ >> 右移運算符

.40 -> 10 1000
>> 2 往右二位,就相當於最右邊刪除 2 位,如果是正數,最左邊補 2 個 0
1010 = 10
以十進位來說, >>2 就是除 2 的 2 次方;>>3 就是除 2 的 3 次方,如果有小數點都是無條件捨去

.-40 -> (前面很多1)  01 1000 ->  (前面很多1) 0110,負數最左邊是補 1

驗證:
(前面很多1) 0110 取反 -> 1001 + 1 -> 1010 = 10 * -1 = -10


※左、右移運算符的應用可看這篇




※ >>> 無符號右移運算符 (就是只有正數,正數結果和 >> 一樣)

※就算宣告成 byte,結果還是 32 位,也不會報錯,可以正常使用

※因為只有正數,左邊一定是 0,至於有幾個 0,要看 >>> 3 給 3 那就是 3 個 0;右邊的處理和 >> 一樣,刪 3 個最右邊的

※>> 右移運算符的負數是最左邊補 1,無符號是補 0,最左邊補0後,換算時就不是負的了,所以說只有正數,不用再取反+1了

-40 -> (前面很多1)  01 1000

.>>> 2,就相當於最右邊刪除 2 位,最左邊 2 個改 0
‭0011 1111 1111 1111 1111 1111 1111 0110‬
最左邊的 0 可以刪除


.>>> 4,就相當於最右邊刪除 4 位,最左邊 4 個改 0
‭0000 1111 1111 1111 1111 1111 1111 1101
最左邊的 0 可以刪除‬,最下面小算盤的圖就是省略了

從最右邊的 2的0次方到最左邊的 2 的 31 次方,一個一個加起來就是 10 進位了,但這個用人工算太累了,可用小算盤,如下:




※<<=、>>=、>>>=


int x = 40 << 2;

int o = 40;
o <<= 2; // o = o << 2;

此時 x 和 o 是一樣的意思,一定要分兩行,否則編譯錯誤,右移和無符號右移也是一樣,這在 jdk7 的 HashMap 原碼看到的

2019年5月16日 星期四

AtomicXxx、ABA

※AtomicInteger


AtomicInteger ai = new AtomicInteger(); // 建構子可給初始值,不給預設為 0
System.out.println(ai.get()); //取得當前值 0
System.out.println(ai.getAndIncrement()); // 後置遞增 0
System.out.println(ai.incrementAndGet()); // 前置遞增 2
System.out.println(ai.getAndAdd(3)); // 2
System.out.println(ai.addAndGet(3)); // 8
System.out.println(ai.getAndSet(7)); // 8
System.out.println(ai.get()); // 7
ai.set(9); // 回傳 void
System.out.println(ai.get()); // 9

※increment、decrement 為加減1,想加減更多用 addAndGet、getAndSet

※AtomicXxx Xxx 為 Wrapper 類別,都類似這樣的用法



※AtomicIntegerArray

int[] ints = {1, 2};
AtomicIntegerArray arr = new AtomicIntegerArray(ints);
arr.getAndSet(0, 9); // 第0個改成9
System.out.println(arr.get(0));//成功修改
System.out.println(ints[0]); // 原本的陣列並不會被改

※針對陣列的其中一個索引修改



※AtomicReference

MyClass c1 = new MyClass(1, "Tom");
MyClass c2 = new MyClass(2, "Jerry");
    
AtomicReference<MyClass> ref = new AtomicReference<>();
ref.set(c1);
ref.compareAndSet(c1, c2);
System.out.println(ref.get().getId());
System.out.println(ref.get().getName());
    
    
    
@Getter
@Setter
class MyClass {
    private int id;
    private String name;
    public MyClass(int id, String name){
        this.id = id;
        this.name = name;
    }
}

※針對整體物件的修改



※AtomicIntegerFieldUpdater

MyClass c1 = new MyClass(1, "Tom");
    
AtomicIntegerFieldUpdater<MyClass> atomic = AtomicIntegerFieldUpdater.newUpdater(MyClass.class, "id");
System.out.println(atomic.getAndIncrement(c1));
System.out.println(atomic.get(c1));
    
class MyClass {
    volatile int id;
    private String name;
    MyClass(int id, String name){
        this.id = id;
        this.name = name;
    }
}

※針對物件裡的 Integer 修改,當然替換 Integer 也有其他 Wrapper 類別

※注意 Myclass 的欄位必需不是 private 且為 volatile 才可以,會有錯誤提示



※AtomicReferenceFieldUpdater

MyClass c1 = new MyClass(1, "Tom");
    
AtomicReferenceFieldUpdater<MyClass, String> atomic = AtomicReferenceFieldUpdater.newUpdater(MyClass.class, String.class, "name");
atomic.set(c1, "Jerry");
// System.out.println(atomic.getAndSet(c1, "Jerry"));
// System.out.println(atomic.compareAndSet(c1, "Tom", "Jerry")); // 舊值如果不是 Tom 就不更新,回傳 boolean
System.out.println(atomic.get(c1));
    
    
    
class MyClass {
    private int id;
    volatile String name;
    MyClass(int id, String name){
        this.id = id;
        this.name = name;
    }
}

※針對物件裡的物件修改

※使用此方式更新的欄位,一定要使用 Wrapper 類別,如此例 id 不是 Integer 會報錯,必需將 MyClass 和 newUpdate 的第二個參數都使用 Integer 才行



※AtomicStampedReference

Integer i1 = 10;
AtomicStampedReference<Integer> atomic = new AtomicStampedReference<>(i1, 200);
System.out.println(atomic.compareAndSet(10, 11, 200, 201)); // true,預期舊值是 10 且 stamp 是 200 就更新值為11,新的 stamp 為201
System.out.println(i1); // 10
System.out.println(atomic.getReference()); // 11
System.out.println(atomic.getStamp()); // 201
System.out.println(atomic.get(new int[]{i1})); // 11

※針對 ABA 的問題,用此類可解決

※前兩個參數 -128~127 因有 cache,看起來不會有問題,但超過就不行了,永遠是false,就算 i1 改用 int 也會自動裝箱成 Integer
Byte、Short、Integer、Long、Character 都有這個問題,可看 XxxCache
後兩個參數沒有這個問題,因為底層是 int


.較好的寫法

AtomicStampedReference<Integer> atomic = new AtomicStampedReference<>(1000, 200);
System.out.println(atomic.compareAndSet(atomic.getReference(), 11, 200, 201)); // true,以 atomic 為主就是同個記憶體,這樣就不會有問題了
System.out.println(atomic.getReference()); // 11
System.out.println(atomic.getStamp()); // 201

※這樣子寫就不用管 -128~127 的問題了


.使用 AtomicStampedReference 針對整個物件修改

MyClass c1 = new MyClass(1, "Tom");
MyClass c2 = new MyClass(2, "Jerry");
//Integer i1 = 10;
AtomicStampedReference<MyClass> atomic = new AtomicStampedReference<>(c1, 200);
System.out.println(atomic.compareAndSet(atomic.getReference(), c2, 200, 201)); // true
MyClass ref = atomic.getReference();
System.out.println(ref.getId()); // 2
System.out.println(ref.getName()); // Jerry
System.out.println(atomic.getStamp()); // 201
    
    
    
@Getter
@Setter
class MyClass {
    private int id;
    private String name;
    
    MyClass(int id, String name) {
        this.id = id;
        this.name = name;
    }
}

※

2019年4月30日 星期二

小心使用 split

String s1 = ",,,";
System.out.println(s1.split(",").length); // 0
String s2 = "a,,,";
System.out.println(s2.split(",").length); // 1
String s3 = ",a,,";
System.out.println(s3.split(",").length); // 2
String s4 = ",,a,";
String[] split = s4.split(",");
System.out.println(split.length); // 3
Arrays.stream(split).forEach(System.out::println); // 空空a
    
String s5 = "abcde";
System.out.println(s5.split("").length); // 5


※只會分割非分割符的前面,如果将 a 改成空格也是一樣的結果

※分割符什麼都沒有時,會每個字符都分割

※s1 長度是 0,所以使用 s1.split(",")[0] 會報 ArrayIndexOutOfBoundsException

2019年3月27日 星期三

Lombok 的使用

lombok 有很多註解,可以節省代碼,方便開發,但使用 Lombok 時,IDE 必須設定,否則編譯會過,但沒有效果(叫出 outline 活頁標籤可知道)


※Eclipse

maven:
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.6</version>
    <scope>provided</scope>
</dependency>
    
    
    
gradle:
provided group: 'org.projectlombok', name: 'lombok', version: '1.18.6'


※

maven 或 gradle 下載後,注意下載的位置

找到 jar 檔後,點兩下打開,然後 install/update,但安裝完成也沒提示,重啟 eclipse 才可以

可參考 官方文檔,比較重要的幾乎都在 lombok 包中

@Getter:可寫在 field 和 type,寫在 field 表示生成 getter;寫在 type 表示為所有 field 生成 getter
@Setter:可寫在 field 和 type,寫在 field 表示生成 setter;寫在 type 表示為所有 field 生成 setter
@RequiredArgsConstructor:只能寫在 type,如果有 final 欄位,建構子就必須要加,如果有多個 final 欄位,依寫的順序;都沒有 final 欄位,就等於空的建構子,不能與 @NoArgsConstructor 一起使用
@ToString:只能寫在 type,預設格式是「類名(欄位一=值, 欄位二=值…)」
@EqualsAndHashCode:生成 equals(Object)、hashCode()、canEqual(Object) 三個方法
@Data:以上五個註解合起來

@AllArgsConstructor:只能寫在 type,生成全部 field 的建構子,順序以寫的先後為主
@NoArgsConstructor:只能寫在 type,生成無參數的建構子,不能與@RequiredArgsConstructor 一起使用
@Accessors(chain=true):使用 setter 時,可以一直 set,這個和上面幾個註解是不同的包



※Intellij

1.Settings/Plugins 搜尋 lombok 並安裝好後重啟 Intellij
2.
3.增加 maven 或 gradle jar 檔

2018年8月11日 星期六

Executors

※基本的執行緒池

// ExecutorService es = Executors.newFixedThreadPool(3);
// ExecutorService es = Executors.newCachedThreadPool();
ExecutorService es = Executors.newSingleThreadExecutor();
    
for (int i = 1; i <= 7; i++) {
    es.execute(() -> {
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        for (int j = 1; j <= 5; j++) {
            System.out.println(Thread.currentThread().getName() + "->" + j);
        }
    });
}
es.shutdown();

※newFixedThreadPool:固定大小的執行緒
newCachedThreadPool:無限大小的執行緒
newSingleThreadExecutor:單一的執行緒

※shutdown():不增加新的執行緒,當前執行緒不會關閉
shutdownNow():不增加新的執行緒,當前執行緒也會關閉


※排程

ScheduledExecutorService ses = Executors.newScheduledThreadPool(3);
ses.schedule(() -> {
    System.out.println("yeah");
}, 2L, TimeUnit.SECONDS);
ses.shutdown();
    
ScheduledExecutorService sess = Executors.newScheduledThreadPool(3);
sess.scheduleAtFixedRate(() -> {
    System.out.println("xxx");
}, 2L, 1L, TimeUnit.SECONDS);
try {
    Thread.sleep(5000L);
    sess.shutdown();
} catch (InterruptedException e) {
    e.printStackTrace();
}
    
ScheduledExecutorService seas = Executors.newSingleThreadScheduledExecutor();
seas.scheduleAtFixedRate(() -> {
    System.out.println("xxx");
}, 2L, 1L, TimeUnit.SECONDS);
try {
    Thread.sleep(5000L);
    seas.shutdown();
} catch (InterruptedException e) {
    e.printStackTrace();
}

※newScheduledThreadPool、newSingleThreadScheduledExecutor 回傳 ScheduledExecutorService,裡面有執行一次的 schedule() 和重覆執行的 scheduleAtFixedRate()

※重覆執行要注意太早 shutdown() 就不執行了



※1.8 的 newWorkStealingPool

Executors.newWorkStealingPool();

※底層第一行就是 new ForkJoinPool,ForkJoinPool 是 1.7 增加的類別,但這個執行緒池是 1.8 才新增的,可參考這裡


※ThreadPoolExecutor

Executors 底層使用的就是這個,共有7個參數
new ThreadPoolExecutor(5,
20,
10,
TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>(),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());

表示一開始就有 5 個執行緒可以用,最多20個
假設有30個 request 進來,5個先執行,然後25個會放在第5個參數的 queue 裡
再來15個執行緒去 queue 抓來執行,最後10個看第7個參數的放棄策略
執行完的15個執行緒,過10毫秒 (第3和4的參數) 就關閉
第六個參數是將 Runnable 包裝成 Thread

以下是第 5~7 可用的參數
BlockingQueue
ArrayBlockingQueue
DelayedWorkQueue 基於 heap 的資料結構,按照時間順序將每個任務進行排序
BlockingDeque
LinkedBlockingDeque 預設是 Integer.MAX_VALUE,所以有 OOM 的風險
SynchronousQueue 一生產就消費,如沒有消費者就阻塞,所以沒有容量大小
DelayQueue 只能消費已過期的
TransferQueue
LinkedTransferQueue 很像 SynchronousQueue,但有容量,消費者沒取到就會阻塞
LinkedBlockingQueue 預設是 Integer.MAX_VALUE,所以有 OOM 的風險
PriorityBlockingQueue 按照自定義的優先級消費
ThreadFactory
DaemonThreadFactory 創建背景執行的執行緒,之後出來的執行緒都是背景執行緒
DefaultThreadFactory 預設的執行緒工廠
PrivilegedThreadFactory 創建與當前執行緒具有相同權限的新執行緒
RejectedExecutionHandler
DiscardOldestPolicy 丟棄最老的任務,就是在 queue 裡面的
AbortPolicy 新來的任務直接丟棄並報異常
CallerRunsPolicy 同步調用,start() 調用後執行 run(),但這個策略是直接調用 run()
DiscardPolicy 新來的任務直接丟棄但不報異常 

ThreadLocal

※
public class Test {
    // private Map<Thread, Double> map = new HashMap<>();
    private ThreadLocal<Double> threadLocal = new ThreadLocal<>();
    
    public static void main(String[] args) {
        new Test().test();
    }
    
    private void test() {
        for (int i = 0; i < 3; i++) {
            new Thread(() -> {
                Double v = Math.random();
                System.out.println(Thread.currentThread().getName() + " 的 值 是 =" + v);
                // map.put(Thread.currentThread(), v);
                threadLocal.set(v);
    
                new innerAClass().ia();
                new innerBClass().ib();
            }).start();
    
        }
    }
    
    class innerAClass {
        public void ia() {
            // System.out.println(Thread.currentThread().getName() + "(a)取出的值是=" +
            // map.get(Thread.currentThread()));
            System.out.println(Thread.currentThread().getName() + "(a)取出的值是=" + threadLocal.get());
        }
    }
    
    class innerBClass {
        public void ib() {
            // System.out.println(Thread.currentThread().getName() + "(b)取出的值是=" +
            // map.get(Thread.currentThread()));
            System.out.println(Thread.currentThread().getName() + "(b)取出的值是=" + threadLocal.get());
        }
    }
}

※

Callable、Future、CompletionService

※Callable、Future

ExecutorService es = Executors.newSingleThreadExecutor();
Future<String> future = es.submit(() -> "yeah!");
    
System.out.println("等一兮呢");
try {
    System.out.println(future.get());
    es.shutdown();
} catch (InterruptedException | ExecutionException e) {
    e.printStackTrace();
}

※submit 裡的參數就是 Callable

※Callable 和 Runnable 類似,但有回傳值

※Future 可以用 get() 取得 Runnable 的回傳值


※get 參數

ExecutorService es = Executors.newSingleThreadExecutor();
Future<String> future = es.submit(() -> {
    Thread.sleep(1000L);
    return "yeah!";
});
    
System.out.println("等一兮呢");
try {
    System.out.println(future.get(500L, TimeUnit.MILLISECONDS));
} catch (InterruptedException | ExecutionException | TimeoutException e) {
    e.printStackTrace();
} finally {            
    es.shutdown();
}

※如果超過 get 設定的時間,就會出現 java.util.concurrent.TimeoutException



※CompletionService

ExecutorService 有多個 Callable 時,每個 Callable 都會回傳一個Future,使用 get 取得結果時,不一定會是第一個 Future,使用 CompletionServce 可以解決這個問題

ExecutorService es = Executors.newFixedThreadPool(3);
CompletionService<String> ecs = new ExecutorCompletionService<>(es);
    
for (int i = 0; i < 3; i++) {
    int temp = i;
    ecs.submit(() -> {
        Thread.sleep(new Random().nextInt(10000));
        return "嘻嘻嘻" + temp;
    });
}
    
for (int i = 0; i < 3; i++) {
    Future<String> future;
    try {
        future = ecs.take();
        System.out.println(future.get());
    } catch (InterruptedException e) {
        e.printStackTrace();
    } catch (ExecutionException e) {
        e.printStackTrace();
    }
    
    if(i == 2) {
        es.shutdown();
    }
}

※一組執行緒,誰先執行完誰就先執行

2018年6月11日 星期一

五種 synchronized

※
public class Synchronized {
    public static void main(String[] args) {
        new Synchronized().xxx();
    }
    
    private void xxx() {
        TestSynchronized t = new TestSynchronized();// 1.7 要加 final
    
        new Thread(() -> {
            while (true) {
                try {
                    TimeUnit.MILLISECONDS.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                t.test5("0123456789");
            }
        }, "t1").start();
    
        new Thread(() -> {
            while (true) {
                try {
                    TimeUnit.MILLISECONDS.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                t.test5("abcdefghij");
            }
        }, "t2").start();
    }
}
    
    
    
class TestSynchronized {
    public void test1(String data) {
        try {
            TimeUnit.SECONDS.sleep(1);
            synchronized (this) {
                for (int i = 0; i < data.length(); i++) {
                    System.out.print(data.charAt(i));
                }
                System.out.println();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    
    public synchronized void test2(String data) {
        try {
            TimeUnit.SECONDS.sleep(1);
            for (int i = 0; i < data.length(); i++) {
                System.out.print(data.charAt(i));
            }
            System.out.println();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    
    public void test3(String data) {
        try {
            TimeUnit.SECONDS.sleep(1);
            synchronized (TestSynchronized.class) {
                for (int i = 0; i < data.length(); i++) {
                    System.out.print(data.charAt(i));
                }
                System.out.println();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    
    public static synchronized void test4(String data) {
        try {
            TimeUnit.SECONDS.sleep(1);
            for (int i = 0; i < data.length(); i++) {
                System.out.print(data.charAt(i));
            }
            System.out.println();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    
    public void test5(String data) {
        try {
            String x = "";
            TimeUnit.SECONDS.sleep(1);
            synchronized (x) {
                for (int i = 0; i < data.length(); i++) {
                    System.out.print(data.charAt(i));
                    x = "ooo" + i; // 修改不影響,因為每個執行緒進來的 x 是一樣的,原子性不用加鎖
                }
                System.out.println();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

※Thread 要使用區域變數在 1.8 可以不加 final,但如果改值還是會編譯錯誤

※synchronized(this) 和寫在普通方法上一樣,只差在鎖的範圍 (wait()只能使用在這兩種);
synchronized(類.class) 和寫在靜態方法上一樣,只差在鎖的範圍
P.S 將 wait 寫在 synchronized 靜態方法裡會編譯錯誤;但如果寫在 synchronized(類.class) 會是執行期錯誤

※兩個線程的鎖必需是相同的鎖,如 xxx 方法的 t.test5() 改成 new TestSynchronized().test5(),因為有 new 了,所以就不是一樣的鎖了
但 test4 是靜態方法,所以 new 幾次都一樣

※test5 的鎖是自己寫的,內容不能改,但因為有字串池的關係,所以改也 ok;如果將「//修改不影響」這行改成 new String 也沒關係(因為每個執行緒進來是一樣的);String x 改成用 new 的方式就不行
.如果改成用 data,那傳進去的字串內容一樣也是 ok 的,因為有字串池;又如果傳進去的值是用 new String("") ,就沒有字串池了,所以就不行了

.Byte、Short、Integer、Long、Character 有 XxxCache 做快取,會在 -128~127,所以這個範圍內的鎖也像字串池一樣;我試了給 127,然後 一直+1 (或者 new Integer()),雖然已超過這個範圍但也是 ok 的(因為每個執行緒進來是一樣的);又如果一開始就超過這個範圍就不行了

※synchronized(this) 如果是鎖整個方法,那就和 synchronized 方法一樣

※synchronized(類別名稱) 如果是鎖整個方法,那就和 synchronized 靜態方法一樣


※練習一

主線程執行 3 次、子線程執行 10 次、主線程再執行 3 次、子線程再執行 10 次,如此循環 5 次

※第一步

public class Outer5Main3Sub10 {
    private void xxx() {
        new Thread(() -> {
            try {
                TimeUnit.SECONDS.sleep(1);
                for (int i = 1; i <= 5; i++) {
                    synchronized (Outer5Main3Sub10.class) {
                        for (int j = 1; j <= 3; j++) {
                            System.out.println(Thread.currentThread().getName() + j + ",i=" + i);
                        }
                    }
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "子").start();
    
        new Thread(() -> {
            try {
                TimeUnit.SECONDS.sleep(1);
                for (int i = 1; i <= 5; i++) {
                    synchronized (Outer5Main3Sub10.class) {
                        for (int j = 1; j <= 10; j++) {
                            System.out.println(Thread.currentThread().getName() + j + ",i=" + i);
                        }
                    }
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "主").start();
    }
    
    public static void main(String[] args) {
        new Outer5Main3Sub10().xxx();
    }
}

※先寫個互斥


※第二步

public class Outer5Main3Sub10 {
    private MainSub ms = new MainSub();
    
    private void xxx() {
        new Thread(() -> {
            try {
                TimeUnit.SECONDS.sleep(1);
                for (int i = 1; i <= 5; i++) {
                    ms.sub(i);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "子").start();
    
        new Thread(() -> {
            try {
                TimeUnit.SECONDS.sleep(1);
                for (int i = 1; i <= 5; i++) {
                    ms.main(i);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "主").start();
    }
    
    public static void main(String[] args) {
        new Outer5Main3Sub10().xxx();
    }
}
    
class MainSub {
    public synchronized void sub(int i) {
        for (int j = 1; j <= 3; j++) {
            System.out.println(Thread.currentThread().getName() + j + ",i=" + i);
        }
    }
    
    public synchronized void main(int i) {
        for (int j = 1; j <= 10; j++) {
            System.out.println(Thread.currentThread().getName() + j + ",i=" + i);
        }
    }
}

※將同步鎖放到新的類別


※第三步

public class Outer5Main3Sub10 {
    private MainSub ms = new MainSub();
    
    private void xxx() {
        new Thread(() -> {
            try {
                TimeUnit.SECONDS.sleep(1);
                for (int i = 1; i <= 5; i++) {
                    ms.sub(i);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "子").start();
    
        new Thread(() -> {
            try {
                TimeUnit.SECONDS.sleep(1);
                for (int i = 1; i <= 5; i++) {
                    ms.main(i);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "主").start();
    }
    
    public static void main(String[] args) {
        new Outer5Main3Sub10().xxx();
    }
}
    
class MainSub {
    private boolean sub = true;
    
    public synchronized void sub(int i) {
        while(!sub) { // 注意 spurious wake up,所以不要用 if
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        for (int j = 1; j <= 3; j++) {
            System.out.println(Thread.currentThread().getName() + j + ",i=" + i);
        }
         sub = false;
         this.notify();
    }
    
    public synchronized void main(int i) {
        while(sub) { // 注意 spurious wake up,所以不要用 if
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        for (int j = 1; j <= 10; j++) {
            System.out.println(Thread.currentThread().getName() + j + ",i=" + i);
        }
        sub = true;
        this.notify();
    }
}

※使用喚醒

※API 的 Object 物件的 wait 方法有說明,有時候會有 spurious wake up (偽喚醒),所以使用 while 代替 if



如果第二步不寫類別

public class Outer5Main3Sub10 {
    private boolean sub = true;
    
    private void xxx() {
        Outer5Main3Sub10 sm = new Outer5Main3Sub10();
    
        new Thread(() -> {
            try {
                TimeUnit.SECONDS.sleep(1);
                for (int i = 1; i <= 5; i++) {
                    synchronized (sm) {
                        while (!sub) {
                            try {
                                sm.wait();
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                        for (int j = 1; j <= 3; j++) {
                            System.out.println(Thread.currentThread().getName() + j + ",i=" + i);
                        }
                        sub = false;
                        sm.notify();
                    }
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "子").start();
    
        new Thread(() -> {
            try {
                TimeUnit.SECONDS.sleep(1);
                for (int i = 1; i <= 5; i++) {
                    synchronized (sm) {
                        while (sub) {
                            try {
                                sm.wait();
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                        for (int j = 1; j <= 10; j++) {
                            System.out.println(Thread.currentThread().getName() + j + ",i=" + i);
                        }
                        sub = true;
                        sm.notify();
                    }
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "主").start();
    }
    
    public static void main(String[] args) {
        new Outer5Main3Sub10().xxx();
    }
}

※


※練習二

兩個執行緒,第一個印 1~52;第二個印 A~Z,結果為 12A34B56C...5152Z

private void xxx() {
    SynchronizedTest st = new SynchronizedTest();
    
    new Thread(() -> {
        for (var i = 1; i <= 26; i++) {
            synchronized (st) {
                while (!flag) {
                    try {
                        st.wait();
                    } catch (InterruptedException e) {
                        System.err.println("t1err=" + e.getMessage());
                    }
                }
                System.out.print(i * 2 - 1 + " ");
                System.out.println(i * 2);
                System.out.println("========================");
                flag = false;
                st.notify();
            }
        }
    }).start();
    
    new Thread(() -> {
        for (var i = 1; i <= 26; i++) {
            synchronized (st) {
                while (flag) {
                    try {
                        st.wait();
                    } catch (InterruptedException e) {
                        System.err.println("t2err=" + e.getMessage());
                    }
                }
                System.out.println((char) (i + ('A' - 1)));
                System.out.println("========================");
                flag = true;
                st.notify();
            }
        }
    }).start();
}



2018年6月6日 星期三

CountDownLatch、CyclicBarrier、Semaphore、Exchanger、Phaser

CountDownLatch

執行緒的倒數計時器,全部的計時器都到了,再做想做的事
如果想知道執行緒執行完需要多少時間可以使用這個工具

※
CountDownLatch cdt = new CountDownLatch(3);
Runnable run = () -> {
    for (int i = 0; i < 10000000; i++) {
    }
    System.out.println(Thread.currentThread().getName());
    cdt.countDown();
};
    
Thread t1 = new Thread(run, "t1");
Thread t2 = new Thread(run, "t2");
Thread t3 = new Thread(run, "t3");
    
long start = System.currentTimeMillis();
t1.start();
t2.start();
t3.start();
    
try {
    System.out.println("wait...");
    cdt.await();
    System.out.println(System.currentTimeMillis() - start);
} catch (InterruptedException e) {
    e.printStackTrace();
}

※countDown() 將倒數計時器減1,await() 是等待全部的執行緒(此例是3)都到時才能通過



CyclicBarrier

一個班級到達玩的地點,全部都上車才能去下一個地點

※
CyclicBarrier cb = new CyclicBarrier(3, () -> System.out.println("barrier被觸發時呼叫,也就是所有執行緒都到時呼叫"));
System.out.println("總共有" + cb.getParties() + "個執行緒\r\n");
Runnable run = () -> {
    try {
        cb.await();
        TimeUnit.SECONDS.sleep(1);
        System.out.println("執行緒" + Thread.currentThread().getName() + "已到達一號站");
    
        TimeUnit.SECONDS.sleep(1);
        cb.await();
        System.out.println("執行緒" + Thread.currentThread().getName() + "已到達二號站");
    
        TimeUnit.SECONDS.sleep(1);
        cb.await();
        System.out.println("執行緒" + Thread.currentThread().getName() + "已到達三號站");
    } catch (InterruptedException | BrokenBarrierException e) {
        e.printStackTrace();
    }
};
    
Thread t1 = new Thread(run, "t1");
Thread t2 = new Thread(run, "t2");
Thread t3 = new Thread(run, "t3");
    
t1.start();
t2.start();
t3.start();

※有點類似 CountDownLatch,但可以執行多次

※建構子的第二個參數可以不寫,是 overloading



Phaser

有 CountDownLatch 和 CyclicBarrier 的功能,但還可以動態增加 parties,可參考這裡

※
public class PhaserTest extends Phaser {
    // 每個階段執行完會自動調用
    @Override
    protected boolean onAdvance(int phase, int registeredParties) {
        if (phase == 0) return init();
        else if (phase == 1) return one();
        else if (phase == 2) return two();
        else if (phase == 3) return three();
        return true; // phase 應該結束就給 true
    }

    private boolean one() {
        System.out.println("共幾人=" + getRegisteredParties());
        return false;
    }

    private boolean two() {
        System.out.println("共幾人=" + getRegisteredParties());
        return false;
    }

    private boolean three() {
        System.out.println("共幾人=" + getRegisteredParties());
        return false;
    }

    private boolean init() {
        System.out.println("共幾人=" + getRegisteredParties());
        return false;
    }

    static class Runner implements Runnable {
        private Phaser phaser;

        public Runner(Phaser phaser) {
            this.phaser = phaser;
        }

        @Override
        public void run() {
            System.out.println("班級" + Thread.currentThread().getName() + ":已到出發站");
            phaser.arriveAndAwaitAdvance(); // 全到才會繼續往下走

            System.out.println("執行緒" + Thread.currentThread().getName() + "已到達1號站");
            phaser.arriveAndAwaitAdvance();

            System.out.println("執行緒" + Thread.currentThread().getName() + "已到達2號站");
            phaser.arriveAndAwaitAdvance();

            System.out.println("執行緒" + Thread.currentThread().getName() + "已到達3號站");
            phaser.arriveAndAwaitAdvance();

        }
    }

    public static void main(String[] args) {
        PhaserTest phaser = new PhaserTest();
        phaser.register(); // 每註冊一次,phaser 就會多維護一個執行緒
        for (int i = 0; i < 4; i++) {
            phaser.register();// 每註冊一次,phaser 就會多維護一個執行緒
            new Thread(new Runner(phaser)).start();
        }
        phaser.arriveAndDeregister();
    }
}

Semaphore

限制最多可以有幾個執行緒執行,以公共廁所為例

※
final int toilet = 3;// 設定3個廁所
final int user = 30;// 使用人數
ExecutorService es = Executors.newFixedThreadPool(user);
final Semaphore sp = new Semaphore(toilet, true);// 誰先等誰就先用(公平鎖)
    
for (int i = 0; i < user; i++) {
    try {
        TimeUnit.MILLISECONDS.sleep(100);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    
    Runnable runnable = () -> {
        try {
            System.out.println(Thread.currentThread().getName() + " 要求使用廁所");
            sp.acquire(); // 要求上廁所,如果還不滿3人才能使用
        } catch (InterruptedException e1) {
            e1.printStackTrace();
        }
        System.out.println(
                Thread.currentThread().getName() + " 拉屎中,目前有" + (toilet - sp.availablePermits()) + "個在使用");
    
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    
        System.out.println(Thread.currentThread().getName() + " 在沖馬桶了");
        sp.release(); // 歸還廁所
    
        System.out.println(
                Thread.currentThread().getName() + " 離開,目前有" + (toilet - sp.availablePermits()) + "個在使用\r\n");
    };
    es.execute(runnable);
}
es.shutdown();

※



Exchanger

執行緒相互交換資料

※
ExecutorService es = Executors.newFixedThreadPool(2);
Exchanger<String> exchanger = new Exchanger<>();
    
es.execute(() -> {
    try {
        String in = "xxx";
        System.out.println(Thread.currentThread().getName() + "將" + in + "送出去");
        Thread.sleep(1000);
        String out = exchanger.exchange(in);
        System.out.println(Thread.currentThread().getName() + "取得" + out);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
});
    
es.execute(() -> {
    try {
        String in = "ooo";
        System.out.println(Thread.currentThread().getName() + "將" + in + "送出去");
        Thread.sleep(1000);
        String out = exchanger.exchange(in);
        System.out.println(Thread.currentThread().getName() + "取得" + out);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
});
es.shutdown();

※可以用偶數個執行緒,但到底是哪兩個交換資料就不一定了

2018年2月3日 星期六

泛型的extends和super

※賦值

class Animal {}
class Bird extends Animal {}
class Chicken extends Bird {}


List<Animal> listA = new ArrayList<>();
List<Bird> listB = new ArrayList<>();
List<Chicken> listC = new ArrayList<>();

// ------------- 等號右邊沒有 super extends 賦值時 --------------
// 方法的參數有以下左邊的物件時,意思是一樣的
List<? super Animal> a1 = listA;
List<? super Bird> b1 = listA;
List<? super Chicken> c1 = listA;

List<? super Animal> a1s = listB; // x
List<? super Bird> b1s = listB;
List<? super Chicken> c1s = listB;

List<? super Animal> a1ss = listC; // x
List<? super Bird> b1ss = listC; // x
List<? super Chicken> c1ss = listC;
// -------------------------------
List<? extends Animal> a2 = listA;
List<? extends Bird> b2 = listA; // x
List<? extends Chicken> c2 = listA; // x

List<? extends Animal> a2e = listB;
List<? extends Bird> b2e = listB;
List<? extends Chicken> c2e = listB; // x

List<? extends Animal> a2ee = listC;
List<? extends Bird> b2ee = listC;
List<? extends Chicken> c2ee = listC;

// ------------- 等號右邊有 super extends 賦值時 --------------
// 不管是 super 或 extends,都把它當作右邊的物件來看
// super 只能賦值 super;extends 只能賦值 extends
// 被賦值的(等號右邊)沒有 super 或 extends 都會編譯錯誤,但相反可以,如上面的例子
List<? super Animal> qs1 = a1;
List<? super Animal> qs2 = b1; // x
List<? super Animal> qs3 = c1; // x
List<? super Bird> qs4 = a1;
List<? super Bird> qs5 = b1;
List<? super Bird> qs6 = c1; // x
List<? super Chicken> qs7 = a1;
List<? super Chicken> qs8 = b1;
List<? super Chicken> qs9 = c1;

// -------------------------------
List<? extends Animal> qe1 = a2;
List<? extends Animal> qe2 = b2;
List<? extends Animal> qe3 = c2;
List<? extends Bird> qe4 = a2; // x
List<? extends Bird> qe5 = b2;
List<? extends Bird> qe6 = c2;
List<? extends Chicken> qe7 = a2; // x
List<? extends Chicken> qe8 = b2; // x
List<? extends Chicken> qe9 = c2;

※增加元素

Animal a = new Animal();
Bird b = new Bird();
Chicken c = new Chicken();
    
List<? super Animal> listA = new ArrayList<>();
List<? super Bird> listB = new ArrayList<>();
List<? super Chicken> listC = new ArrayList<>();
listA.add(a);
listA.add(b);
listA.add(c);
    
// listB.add(a);
listB.add(b);
listB.add(c);
    
// listC.add(a);
// listC.add(b);
listC.add(c);

※super 可增加元素,取得元素要用 Object;extends 只能取得元素,不能增加元素

※增加元素時,不要看 super 和 extends 關鍵字,會搞混,那是給賦值的時候用的

※如果改用 ? 是無法增加元素的;? 等同 ? extends Object,因為有 extends 就不可以


※注意

1.super 只有傳參時,才表示此類和此類的父類;賦值時最多只表示此類
2.super 轉換後,可以存也可以取
3.extends 只能取得父類的方法,無法存


※super

Animal a = new Animal();
a.setEat("Animal吃");
a.setJump("Animal跳");
    
Bird b = new Bird();
b.setEat("Bird吃");
b.setJump("Bird跳");
b.setFly("Bird飛");
    
Chicken c = new Chicken();
c.setMorning("Chicken說早安");
    
List<? super Animal> list = new ArrayList<>();
list.add(a);
list.add(b);
list.add(c);
    
for (Object o : list) {
    if (o instanceof Chicken) {
        Chicken chicken = (Chicken) o;
        System.out.println(chicken.getMorning());
        continue;
    }
    
    if (o instanceof Bird) {
        Bird bird = (Bird) o;
        System.out.println(bird.getFly());
        continue;
    }
    
    if (o instanceof Animal) {
        Animal animal = (Animal) o;
        System.out.println(animal.getEat());
        continue;
    }
}

※注意順序和 continue,子類在上;父類在下


※extends

public class ExtendsAndSuper {
    public static void main(String[] args) {
        List al= new ArrayList<>();
        Animal a = new Animal();
        a.setEat("Animal吃");
        a.setJump("Animal跳");
        al.add(a);
    
        List bl= new ArrayList<>();
        Bird b = new Bird();
        b.setEat("Bird吃");
        b.setJump("Bird跳");
        b.setFly("Bird飛");
        bl.add(b);
    
        List cl= new ArrayList<>();
        Chicken c = new Chicken();
        c.setEat("Chicken吃");
        c.setMorning("Chicken說早安");
        cl.add(c);
    
        // new ExtendsAndSuper().xxx(al);
        new ExtendsAndSuper().xxx(bl);
        new ExtendsAndSuper().xxx(cl);
    }
    
    private void xxx(List<? extends Bird> list) {
        for (Animal a : list) {
            System.out.println(a.getEat());
            a.setEat("好吃");
            System.out.println(a.getEat());
        }
    }
}

※注意迴圈裡轉換的是 Bird 的父類,還是可以的;但如果迴圈用的是 Bird,還可以用 Bird 的方法

2017年11月4日 星期六

排序:冒泡、選擇、插入


※冒泡排序


元素會像泡泡一樣,往左或往右移動,取決於使用升序或降序,做法是判斷相鄰的元素

綠排為 index,藍排為值
以升序為例,先判斷 index 0和1,然後 index 1和2,一直到最後
每判斷一次,如果左值 > 右值就交換
內迴圈跑完就會產生最大的數在後面,所以內迴圈跑完就會少一次外迴圈


※
int count = 0;
int[] intArray = new int[] { 5, 4, 3, 2, 1 };
int len = intArray.length - 1;
    
for (int i = 0; i < len; i++) {
    boolean finish = true;
    for (int j = 0; j < len - i; j++) {
        count++;
        if (intArray[j] > intArray[j + 1]) {
            int temp = intArray[j];
            intArray[j] = intArray[j + 1];
            intArray[j + 1] = temp;
            finish = false;
        }
    }
    if (finish) break;
} System.out.println("迴圈跑幾次=" + count); System.out.println(Arrays.toString(intArray));

※外迴圈只有控制內迴圈跑完一次減少一次

※如果要排序的值有一些已經排好順序了,那 finish 變數就會看出效果


※選擇排序

選擇、插入 都是將資料分成已排序和未排序兩個部分
從未排序的元素選擇一個最大或最小值放入排序的元素裡,和插入排序相反

以最上面的圖為例
以升序為例,以 index 0為基底,判斷 index 1、index2…到最後
每判斷一次,如果左值 > 右值就交換
內迴圈跑完就會產生最小的數在前面,所以內迴圈跑完就會少一次外迴圈

※
int count = 0;
int[] intArray = new int[] { 5, 4, 3, 2, 1 };
int len = intArray.length - 1;
    
for (int i = 0; i < len; i++) {
    boolean finish = true;
    for (int j = 0; j < len - i; j++) {
        count++;
        if (intArray[i] > intArray[j + 1 + i]) {
            int temp = intArray[i];
            intArray[i] = intArray[j + 1 + i];
            intArray[j + 1 + i] = temp;
            finish = false;
        }
    }
    if (finish) break;
} System.out.println("迴圈跑幾次=" + count); System.out.println(Arrays.toString(intArray));

※和冒泡排序只差在 if 而已

※外迴圈是抓基底的來判斷


※插入排序

從未排序的元素取第一個插入到排序的元素裡,和選擇排序相反

以最上面的圖為例
以升序為例:
第一次判斷 index 0和1
第二次判斷 index 1和2,再判斷 index 0和1
第三次判斷 index 2和3,再判斷 index 1和2,再判斷 index 0和1
依此類推...
每判斷一次,如果左值 > 右值就交換


※
int count = 0;
int[] intArray = new int[] { 3, 9, 4, 6, 1, 7, 2, 5, 8 };
    
for (int i = 0; i < intArray.length - 1; i++) {
    for (int j = i; j >= 0; j--) {
        boolean finish = true;
        count++;
        if (intArray[j] > intArray[j + 1]) {             int temp = intArray[j];             intArray[j] = intArray[j + 1];             intArray[j + 1] = temp;             finish = false;         }
        if (finish) break;
    }
}
System.out.println("迴圈跑幾次=" + count);
System.out.println(Arrays.toString(intArray));

※外迴圈 i 值給內迴圈 j

2017年10月31日 星期二

volatile 和 原子性

※volatile


尤於 CPU 的速度比主存還快,所以每個 Thread 都會有一個專屬的 cache,將主存的資料從主存拷過去

volatile 能保證 1.內存可見性和 2.排序性
.volatile 只能宣告在全域變數
.內存可見性:volatile 有揮發性的意思,意思就是用完就丟,在 java 裡的意思是值有改變就去主存抓
以上面的圖來說,要是值改變了,就相當於沒有 CPU cache 了,所以會到主存去抓 (保下面的賣票例子不可用 volatile,因為沒有原子性)

.排序性:CPU 會將我們寫的程式碼重排,如寫在第一行,但不一定就是先執行,不過他保證結果是一樣的,但可惜只是單線程一樣,多線程有可能會不一樣,而加上這個關鍵字可以禁止 CPU 重排

.只能解決一寫多讀的情形

.++i 是原子性;i++ 不是原子性,可用 AtomicXXX 或 LongAdder 產生原子性,高併發時用 LongAdder 較快,否則 AtomicXXX 較快


※
public class App {
    private volatile boolean flag = false;
    
    public void xxx() {
        new Thread(() -> {
            try {
                Thread.sleep(200);
                flag = true;
                System.out.println("oooooooooooooo");
            } catch (InterruptedException e) {
            }
        }).start();
    
        new Thread(() -> {
            while (true) {
                if (flag) {
                    System.out.println("xxxxxxxxxxxxxx");
                    break;
                }
            }
        }).start();
    }
    
    public static void main(String... ss) {
        new App.xxx();
    }
}


※此例如果不加 volatile 有可能兩個 CPU 同時抓到,所以迴圈裡的 flag 永遠都是 false了

※此例也可用 synchronized 或 Lock,但效能比較差

※不代表 volatile 可取代 synchronized
volatile 並沒有互斥性,也不能保證原子性
互斥性:像 synchronized 和 Lock 就有,一次只能一個進入,但指的是相同的物件
例:HttpSession 可以鎖定成功;但 HttpServletRequest 就不可能鎖定成功


※賣票(必需要有互斥性)

public class AppTest {
    volatile AtomicInteger ticket = new AtomicInteger(10);
    // Integer ticket = 10;
    
    public static void main(String... s) {
        new AppTest().xxx();
    }
    
    public void xxx() {
        Runnable run = () -> {
            for (;;) {
                // if (ticket > 0) {
                System.out.println("票" + ticket.get());
                if (ticket.get() > 0) {
                    try {
                        TimeUnit.SECONDS.sleep(1);
                        // System.out.println(--ticket);
                        System.out.println(ticket.decrementAndGet());
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                } else {
                    break;
                }
            }
        };
    
        new Thread(run).start();
        new Thread(run).start();
        new Thread(run).start();
        new Thread(run).start();
    }
}


※還沒加原子性:雖然在減 1 時,其他線程有看見,但有可能還沒寫,其他線程就讀到舊的,因為 volatile 沒有原子性

※加原子性的 AtomicInteger 後,使用兩個線程看起來好像可以,實際上還是有問題,因為此例是偶數票,每次少兩張,最後是 0,但用更多的線程去跑或改成單數票又不行了

.因為雖然加了原子性,但在改之前,其他線程還是讀的到 (資料庫的不可重覆讀)
如第 1 次,四個線程都讀到 10,然後都減 1,但有原子性,所以最後是 6
而第 2 次,四個線程都讀到  6,然後都減 1,還是有原子性,所以最後是 2
而第 3 次,四個線程都讀到  2,然後都減 1,還是有原子性,所以最後是 -2
也就是說一次只能有一個線程讀取,所以必需要有互斥性

※如果沒有 if,只讓它一直加或一直減,最終會是正確的



※原子性

表示不能切割,如後置遞增/減
例:i++ 在底層運作是
int temp = i;
i = i+1;
return temp;
這三步是不能切割的,但 volatile 無法保證這一點


※
public class MyThread implements Runnable {
    private int increment;
    // private AtomicInteger increment = new AtomicInteger(0);
    
    @Override
    public void run() {
        try {
            Thread.sleep(200);
            System.out.println(increment++);
            // System.out.println(increment.getAndIncrement());
        } catch (InterruptedException e) {}
    }
    
    public static void main(String... ss) {
        MyThread my = new MyThread();
        for (int i = 0; i < 10; i++) {
            new Thread(my).start();
        }
    }
}

※increment 有可能會有同時進入 CPU 的情形,使用 volatile 也沒有用

※java 1.5 新增了 java.util.concurrent.atomic 套件,專門做原子的操作

※incrementAndGet方法就是前置遞增了,而遞減單字是 decrement

※原子性採用 CAS (compare and swap) 算法,屬於樂觀鎖

※CAS 有三個值v和a都是主存抓來的,b是替換值,只有 v 和 a 的值相等才會將 b 的值取代 v 的值(CPU2 的圖錯了,v=1, a=0 才對)

※以上圖為例,CPU1 從主存抓到 i 的值是 0,此時 CPU2也進來抓到0
這時又換 CPU1,比較過後是一樣的,所以更新為 1,i 更新成功為1
這時又換 CPU2,因為 i 是 volatile,所以 v=1, a=0, 什麼也不做
※如果是 CAS 自旋,那 CPU2 還會繼續,這是 v 和 a 都是 1,就看 b 是什麼就可以取代

※判斷和塞新值要看成一個做法,也就是說不會在判斷時,另一個 Thread 進來的情況

※此例當然也是可以用 synchronized,但 synchronized 效能比較差

※還可看高手寫的文章

2017年8月17日 星期四

正則表達式

可看 java api 的 java.util.regex 套件裡的 Pattern


※^

表示開頭的意思,但在方括號裡是相反的意思,如[^\d],表示非數字,相當於 [\D] 或 [^0-9]


※\b

System.out.println("hello".matches("he\\b.*")); // false
System.out.println("hello".matches(".*lo\\b.*")); // true
System.out.println("follow".matches(".*lo\\b.*")); // false
     
System.out.println("hellow".matches(".*lo\\b.*")); // false

※控制是不是整個單字

※重點是第二個和第四個
第二個雖然是.*結尾,但已經控制成任何字元的 lo 結尾,所以第四行只是多個 w,就過不了



※\Q~\E

final String pipe = "|";
System.out.println(pipe.matches("\\|"));
System.out.println(pipe.matches("\\Q|\\E"));
System.out.println(pipe.matches(Pattern.quote("|")));
    
final String slash = "\\";
System.out.println(slash.matches("\\\\"));
System.out.println(slash.matches("\\Q\\\\E"));
System.out.println(slash.matches(Pattern.quote("\\")));

※使用 \Q \E 包住的字,可使裡面的字變成普通的字串

※也可用 Pattern.quote



※\A \Z \z

String matcherStr = "This is Java\r\nThis is a Java\r\n";
Matcher m = Pattern.compile("Java\\Z", Pattern.MULTILINE).matcher(matcherStr);// //AThis、Java//Z
    
while (m.find()) {
    System.out.println(m.group());
    System.out.println(m.start());
    System.out.println(m.end());
    System.out.println();
}

※^$第一行的開頭結尾,如果是多行模式,就是每行的開頭結尾

※\A、\Z、\z 不會管多行模式,可以說是文章的開頭和結尾

※\Z結束是終止符號時(如\r\n…等),也會匹配,但\z不行

※Pattern.compile 的第二個參數不加又想啟用多行模式時,可用(?m) 寫在第一個參數最前面或最後面
而 (?i) ,可代替 Pattern.CASE_INSENSITIVE
兩個都用就用 (?im) 或 (?mi)



※\1~\9

System.out.println("1_aa11a".matches("(\\d)_(\\w)\\2\\1\\1\\2"));

※數字表示從左到右第幾個圓括號

※第1個圓括號是數字,假設輸入8,那後面的 \1 ,也就一定要 8 才會過

※括號是從左到右,所以不能下如:(\\d)\\2(\\w)\\1,此時\\2在第二個圓括號前面,但這時第二個圓括號還沒出生,所以並沒有成功



※中文

\u4e00-\u9fa5
可以在執行打 charmap,可以看到是從「一」到「龥」,跟字典一樣

※可以看出來,是從左到右,然後上到下開始編。但如上圖,還是可以看出來「一」前面的和「龥」後面的漢字並沒有包括進去,所以
System.out.println("龦".matches("[\\u4e00-\\u9fa5]")); 為 false
但這個字很奇怪,明明畫面是「火+常」,但複製後居然是「火+堂」,這是用windows 的 chrome 電腦看的,但用手機的又是「火+常」
這張圖選的是細明體,選別的不一定會有一樣的字,因為選其他的字體,有可能沒做某些字的字體,所以如果選西歐語系的,通常就不會做中文的

※以下列出 java 版本和 unicode 的對應,java api 裡的 Character有寫
java6 4.0.0
java7 6.0.0,左下有個 Related Links,子項的 Unicode Character Name Index 可點進去,第四版我沒找到
java8 6.2.0
java9 和10 8.0.0
java11 10.0.0
java12 11.0.0
java api 看 Character 裡面會寫 unicode 的版本

中文歸類在 CJK 裡,China、Japan、Korean 的意思
由於 Unicode 使用 ASCII 時都是 2B,這樣容量就多一倍了,所以才會有轉換格式,如 UTF-8 (Unicode Transfer Format 8bits),轉換轉的是 ASCII (2 的 7 次方),不是 extend ASCII (2 的 8 次成),ASCII 二進位都是 0 開頭,extend ASCII 容量在 UTF-8 都是 2B

UTF-16 又分成 UTF16BE、UTF16LE,Macintosh (Mac) 電腦和其他電腦對 UTF-16 是不同的解譯,所以才有了 BE 和 LE

※Unicode 轉 UTF-8

0000 0000-0000 007F | 0xxxxxxx
0000 0080-0000 07FF | 110xxxxx 10xxxxxx
0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx
0001 0000-001F FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
0020 0000-03FF FFFF | 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
0400 0000-7FFF FFFF | 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx

1.「|」的左邊為 Unicode (16進制);右邊為 UTF-8 (2進制),維基可找到這個表,但 java 不支援最後兩行,因為Character API 有說明,只有到 10FFFF,再加 1 直接報錯
2.0 開頭可以對應 ASCII,所以容量和 ISO-8859-1 沒區別
3.剩下的都是以 1 為開頭,以 1 為開頭的除了第一個 Byte 以外,其他都是 10 開頭
4.第 1 個 Byte 的 1 有幾個,就代表這個字有幾 Byte

以「搴」為例,在 charmap 找到的 unicode 為 6434
6434 轉 2 進制為:
0110 0100 0011 0100
以上面的面 x 有 16 個的,只有第 3 行,所以將這些值填進去後的結果為:
1110 0110 1001 0000 1011 0100,再轉成 16 進制變成:
E6 90 B4


※驗證:

方法一:
String s = Integer.toHexString("搴".charAt(0));
System.out.println(s); // 6434
System.out.println((char) Integer.parseInt(s, 16)); // 搴

強轉成 char,char 只有 2 的 16 次方,超過會自動 mod 65536 (也就是一直減 65536,減到在 65536 裡的範圍) 此種方法只有在 65536 裡的字有效,所以這種方法最多只有 3B 的字

方法二:
String x = "\uD840\uDC13"; //𠀓,貼到 String 裡,IDE 自動變成 \u 開頭了「※ \u 在註解裡執行時也會報錯」
int unicodeTen = x.codePointAt(0); // 131091
System.out.println(new StringBuilder().appendCodePoint(unicodeTen).toString());

.
System.out.println(new String(Character.toChars(unicodeTen)));
可用以上兩種方法轉出超過 65536 的字元

※131091 的公式是 (\uD840 << 10) + \uDC13 - 56613888‬
56688640 + 56339 - 56613888 = 131091
最多到 \uDBFF\uDFFF 就是 10FFFF 了

※Unicode 、 UTF-8、UTF-16 各佔幾個 Byte

System.out.println(Charset.defaultCharset()); // UTF-8
for (byte b : "搴".getBytes()) {
System.out.println(Integer.toHexString(b & 255));
}

System.out.println("---------------------");
for (byte b : "搴".getBytes("Unicode")) {
System.out.println(Integer.toHexString(b & 255));
}
for (byte b : "\uD840\uDC13".getBytes(StandardCharsets.UTF_16)) {
System.out.println(Integer.toHexString(b & 255));
}

Charset.defaultCharset 要看你的系統是什麼,如果是 UTF-8 ,使用 length 沒問題
使用 Unicode、UTF-16 長度必需減 2,因為最前面會有 FEFF 開頭,為 BOM



※超過 FFFF

int x = 0x10FFFF; // Character API 說最多到這裡
System.out.println(new String(Character.toChars(x)));

String y = new StringBuilder().appendCodePoint(0xffff).toString();
System.out.println(y);

沒辦法用 char 去接了,所以就用以上的兩種方法之一即可
要有安裝相應的字體才能顯示正確,我看字元對應表最多只能顯示 FFFF 的樣子
超過的就是上表的 4~6 行了,會佔 4~6 Byte
這個符號「𒈬」在 unicode 和 utf-8 分別就是 3、4 Byte了,可用上面的程式 FFFF 去跑迴圈並印出來就能看到了,有可能沒安裝字體看不到,可到 https://www.google.com/get/noto/ 下載,我只有安裝 (NotoSansCJKsc-Black.otf) Noto Sans CJK SC 黑色


𠀓 (引下面還有一條線)就超過了
如果看不到,可安裝完上面的字體後到 https://www.unicode.org/charts/PDF/U20000.pdf 隨便複製一個試試看就知道了,可配合我這篇驗證,維基也有將 Unicode 做分類,java 則在 Character 類別的 blockStarts[]

StringBuilder sb = new StringBuilder();
Stream.iterate(0, x -> x).limit(1000).reduce((i, j) -> {
sb.appendCodePoint(131100 + i);
return ++i;
});
System.out.print(sb);

列出一部分超過 3B 的中文,數字是 10 進制,也可以用 16 進制或其他的,如 0x2001C,最多只到 10FFFF,再加 1 就直接報錯了,並不會 mod

以下是結果:
𠀜𠀝𠀞𠀟𠀠𠀡𠀢𠀣𠀤𠀥𠀦𠀧𠀨𠀩𠀪𠀫𠀬𠀭𠀮𠀯𠀰𠀱𠀲𠀳𠀴𠀵𠀶𠀷𠀸𠀹𠀺𠀻𠀼𠀽𠀾𠀿𠁀𠁁𠁂𠁃𠁄𠁅𠁆𠁇𠁈𠁉𠁊𠁋𠁌𠁍𠁎𠁏𠁐𠁑𠁒𠁓𠁔𠁕𠁖𠁗𠁘𠁙𠁚𠁛𠁜𠁝𠁞𠁟𠁠𠁡𠁢𠁣𠁤𠁥𠁦𠁧𠁨𠁩𠁪𠁫𠁬𠁭𠁮𠁯𠁰𠁱𠁲𠁳𠁴𠁵𠁶𠁷𠁸𠁹𠁺𠁻𠁼𠁽𠁾𠁿𠂀𠂁𠂂𠂃𠂄𠂅𠂆𠂇𠂈𠂉𠂊𠂋𠂌𠂍𠂎𠂏𠂐𠂑𠂒𠂓𠂔𠂕𠂖𠂗𠂘𠂙𠂚𠂛𠂜𠂝𠂞𠂟𠂠𠂡𠂢𠂣𠂤𠂥𠂦𠂧𠂨𠂩𠂪𠂫𠂬𠂭𠂮𠂯𠂰𠂱𠂲𠂳𠂴𠂵𠂶𠂷𠂸𠂹𠂺𠂻𠂼𠂽𠂾𠂿𠃀𠃁𠃂𠃃𠃄𠃅𠃆𠃇𠃈𠃉𠃊𠃋𠃌𠃍𠃎𠃏𠃐𠃑𠃒𠃓𠃔𠃕𠃖𠃗𠃘𠃙𠃚𠃛𠃜𠃝𠃞𠃟𠃠𠃡𠃢𠃣𠃤𠃥𠃦𠃧𠃨𠃩𠃪𠃫𠃬𠃭𠃮𠃯𠃰𠃱𠃲𠃳𠃴𠃵𠃶𠃷𠃸𠃹𠃺𠃻𠃼𠃽𠃾𠃿𠄀𠄁𠄂𠄃𠄄𠄅𠄆𠄇𠄈𠄉𠄊𠄋𠄌𠄍𠄎𠄏𠄐𠄑𠄒𠄓𠄔𠄕𠄖𠄗𠄘𠄙𠄚𠄛𠄜𠄝𠄞𠄟𠄠𠄡𠄢𠄣𠄤𠄥𠄦𠄧𠄨𠄩𠄪𠄫𠄬𠄭𠄮𠄯𠄰𠄱𠄲𠄳𠄴𠄵𠄶𠄷𠄸𠄹𠄺𠄻𠄼𠄽𠄾𠄿𠅀𠅁𠅂𠅃𠅄𠅅𠅆𠅇𠅈𠅉𠅊𠅋𠅌𠅍𠅎𠅏𠅐𠅑𠅒𠅓𠅔𠅕𠅖𠅗𠅘𠅙𠅚𠅛𠅜𠅝𠅞𠅟𠅠𠅡𠅢𠅣𠅤𠅥𠅦𠅧𠅨𠅩𠅪𠅫𠅬𠅭𠅮𠅯𠅰𠅱𠅲𠅳𠅴𠅵𠅶𠅷𠅸𠅹𠅺𠅻𠅼𠅽𠅾𠅿𠆀𠆁𠆂𠆃𠆄𠆅𠆆𠆇𠆈𠆉𠆊𠆋𠆌𠆍𠆎𠆏𠆐𠆑𠆒𠆓𠆔𠆕𠆖𠆗𠆘𠆙𠆚𠆛𠆜𠆝𠆞𠆟𠆠𠆡𠆢𠆣𠆤𠆥𠆦𠆧𠆨𠆩𠆪𠆫𠆬𠆭𠆮𠆯𠆰𠆱𠆲𠆳𠆴𠆵𠆶𠆷𠆸𠆹𠆺𠆻𠆼𠆽𠆾𠆿𠇀𠇁𠇂𠇃𠇄𠇅𠇆𠇇𠇈𠇉𠇊𠇋𠇌𠇍𠇎𠇏𠇐𠇑𠇒𠇓𠇔𠇕𠇖𠇗𠇘𠇙𠇚𠇛𠇜𠇝𠇞𠇟𠇠𠇡𠇢𠇣𠇤𠇥𠇦𠇧𠇨𠇩𠇪𠇫𠇬𠇭𠇮𠇯𠇰𠇱𠇲𠇳𠇴𠇵𠇶𠇷𠇸𠇹𠇺𠇻𠇼𠇽𠇾𠇿𠈀𠈁𠈂𠈃𠈄𠈅𠈆𠈇𠈈𠈉𠈊𠈋𠈌𠈍𠈎𠈏𠈐𠈑𠈒𠈓𠈔𠈕𠈖𠈗𠈘𠈙𠈚𠈛𠈜𠈝𠈞𠈟𠈠𠈡𠈢𠈣𠈤𠈥𠈦𠈧𠈨𠈩𠈪𠈫𠈬𠈭𠈮𠈯𠈰𠈱𠈲𠈳𠈴𠈵𠈶𠈷𠈸𠈹𠈺𠈻𠈼𠈽𠈾𠈿𠉀𠉁𠉂𠉃𠉄𠉅𠉆𠉇𠉈𠉉𠉊𠉋𠉌𠉍𠉎𠉏𠉐𠉑𠉒𠉓𠉔𠉕𠉖𠉗𠉘𠉙𠉚𠉛𠉜𠉝𠉞𠉟𠉠𠉡𠉢𠉣𠉤𠉥𠉦𠉧𠉨𠉩𠉪𠉫𠉬𠉭𠉮𠉯𠉰𠉱𠉲𠉳𠉴𠉵𠉶𠉷𠉸𠉹𠉺𠉻𠉼𠉽𠉾𠉿𠊀𠊁𠊂𠊃𠊄𠊅𠊆𠊇𠊈𠊉𠊊𠊋𠊌𠊍𠊎𠊏𠊐𠊑𠊒𠊓𠊔𠊕𠊖𠊗𠊘𠊙𠊚𠊛𠊜𠊝𠊞𠊟𠊠𠊡𠊢𠊣𠊤𠊥𠊦𠊧𠊨𠊩𠊪𠊫𠊬𠊭𠊮𠊯𠊰𠊱𠊲𠊳𠊴𠊵𠊶𠊷𠊸𠊹𠊺𠊻𠊼𠊽𠊾𠊿𠋀𠋁𠋂𠋃𠋄𠋅𠋆𠋇𠋈𠋉𠋊𠋋𠋌𠋍𠋎𠋏𠋐𠋑𠋒𠋓𠋔𠋕𠋖𠋗𠋘𠋙𠋚𠋛𠋜𠋝𠋞𠋟𠋠𠋡𠋢𠋣𠋤𠋥𠋦𠋧𠋨𠋩𠋪𠋫𠋬𠋭𠋮𠋯𠋰𠋱𠋲𠋳𠋴𠋵𠋶𠋷𠋸𠋹𠋺𠋻𠋼𠋽𠋾𠋿𠌀𠌁𠌂𠌃𠌄𠌅𠌆𠌇𠌈𠌉𠌊𠌋𠌌𠌍𠌎𠌏𠌐𠌑𠌒𠌓𠌔𠌕𠌖𠌗𠌘𠌙𠌚𠌛𠌜𠌝𠌞𠌟𠌠𠌡𠌢𠌣𠌤𠌥𠌦𠌧𠌨𠌩𠌪𠌫𠌬𠌭𠌮𠌯𠌰𠌱𠌲𠌳𠌴𠌵𠌶𠌷𠌸𠌹𠌺𠌻𠌼𠌽𠌾𠌿𠍀𠍁𠍂𠍃𠍄𠍅𠍆𠍇𠍈𠍉𠍊𠍋𠍌𠍍𠍎𠍏𠍐𠍑𠍒𠍓𠍔𠍕𠍖𠍗𠍘𠍙𠍚𠍛𠍜𠍝𠍞𠍟𠍠𠍡𠍢𠍣𠍤𠍥𠍦𠍧𠍨𠍩𠍪𠍫𠍬𠍭𠍮𠍯𠍰𠍱𠍲𠍳𠍴𠍵𠍶𠍷𠍸𠍹𠍺𠍻𠍼𠍽𠍾𠍿𠎀𠎁𠎂𠎃𠎄𠎅𠎆𠎇𠎈𠎉𠎊𠎋𠎌𠎍𠎎𠎏𠎐𠎑𠎒𠎓𠎔𠎕𠎖𠎗𠎘𠎙𠎚𠎛𠎜𠎝𠎞𠎟𠎠𠎡𠎢𠎣𠎤𠎥𠎦𠎧𠎨𠎩𠎪𠎫𠎬𠎭𠎮𠎯𠎰𠎱𠎲𠎳𠎴𠎵𠎶𠎷𠎸𠎹𠎺𠎻𠎼𠎽𠎾𠎿𠏀𠏁𠏂𠏃𠏄𠏅𠏆𠏇𠏈𠏉𠏊𠏋𠏌𠏍𠏎𠏏𠏐𠏑𠏒𠏓𠏔𠏕𠏖𠏗𠏘𠏙𠏚𠏛𠏜𠏝𠏞𠏟𠏠𠏡𠏢𠏣𠏤𠏥𠏦𠏧𠏨𠏩𠏪𠏫𠏬𠏭𠏮𠏯𠏰𠏱𠏲𠏳𠏴𠏵𠏶𠏷𠏸𠏹𠏺𠏻𠏼𠏽𠏾𠏿𠐀𠐁𠐂

String s = "\uD840\uDCE6";
System.out.println(s.length()); // 2
System.out.println(s.codePointCount(0, s.length())); // 1
因為以上的原故,所以長度 length 就不一定是1了,要改用 codePointCount


※UTF-16

U+0000---U+FFFF xxxxxxxx xxxxxxxx yyyyyyyy yyyyyyyy 0-65535
U+10000---U+10FFFF 110110yyyyyyyyyy 110111xxxxxxxxxx 65536-1114111
第一條佔 2Byte,第二條佔 4Byte
java 的 Character 和 String 使用的是 UTF-16,當英文字多時,用 UTF-8 較節省,因為 UTF-8 的前三行在 UTF-16 都是 2B,空檔案也是 2B

𠀓
D840  DC13  16 進制直接轉 2 進制就好了
1101 1000 0100 0000
1101 1100 0001 0011

搴
6434
0110 0100 0011 0100


UTF-16 還有 BE 和 LE

每個文件開頭有個看不見的 BOM,FEFF,程式一讀到就知道是 UTF-16了
以 D840  DC13 來說,假設 BE 為 FEFF D840  DC13
那 LE 會以 16 進制來調換,變成 FFFE 40D8 13DC
big endian 小 -> 大,所以是 FEFF
little endian 大 -> 小,所以是 FFFE


UTF-32

全部都是 4B


javascript 

let unicodeTen = '\uDBFF\uDFFF'.codePointAt();
console.log(unicodeTen); // 1114111
console.log(unicodeTen.toString(16)); // 10ffff
console.log(String.fromCodePoint(0x10ffff));
js 和 java 完全一樣,內部也是用 UTF-16

※其他編碼細節,可看這篇文章



※判斷中日韓

private static boolean isCJK3Byte(char c) {
    Character.UnicodeBlock ub = Character.UnicodeBlock.of(c);
    List<Character.UnicodeBlock> chineseRange = List.of(
        Character.UnicodeBlock.CJK_RADICALS_SUPPLEMENT, // 2E80-2EFF 中日韓漢字部首補充
        Character.UnicodeBlock.CJK_SYMBOLS_AND_PUNCTUATION, // 3000-303F 中日韓符號和標點 (全形空格是 3000)
        Character.UnicodeBlock.CJK_STROKES, // 31C0-31EF 中日韓筆畫
        Character.UnicodeBlock.ENCLOSED_CJK_LETTERS_AND_MONTHS, // 3200-32FF 帶圈的中日韓字符及月份 (有中文的為 3220-3247、3280-32B0、32C1-32CB、32FF)
        Character.UnicodeBlock.CJK_COMPATIBILITY, // 3300-33FF 中日韓兼容字符 (有中文的為 3358-3370、337B-337F、33E0-33FE)
        Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS_EXTENSION_A, // 3400-4DBF 中日韓一表意文字擴展區A
        Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS, // 4E00-9FFF 中日韓統一表意文字
        Character.UnicodeBlock.CJK_COMPATIBILITY_IDEOGRAPHS, // F900-FAFF 中日韓兼容表意文字
        Character.UnicodeBlock.CJK_COMPATIBILITY_FORMS, // FE30-FE4F 中日韓兼容形式 (裡面是一些括號)
        Character.UnicodeBlock.HALFWIDTH_AND_FULLWIDTH_FORMS); // FF00-FFEF 半形及全形字符 (ASCII 從!到~符號的全形在 FF01-FF5E)
    
    return chineseRange.contains(ub);
}
    
private static boolean isCJK4Byte(String str) {
    if (str == null || str.strip().length() != 2) {
        return false;
    }
    String codePoint = String.valueOf(str.codePointAt(0));
    System.out.println("cp=" + codePoint);
    List<Pattern> listPattern = List.of(
            Pattern.compile("[\\u20000-\\u2A6DF]"), // 中日韓一表意文字擴展區B
            Pattern.compile("[\\u2A700-\\u2B73F]"), // 中日韓一表意文字擴展區C
            Pattern.compile("[\\u2B740-\\u2B81F]"), // 中日韓一表意文字擴展區D
            Pattern.compile("[\\u2B820-\\u2CEAF]"), // 中日韓一表意文字擴展區E
            Pattern.compile("[\\u2CEB0-\\u2EBEF]"), // 中日韓一表意文字擴展區F
            Pattern.compile("[\\u2F800-\\u2FA1F]")); // 中日韓兼容表意文字增補
    
    for (Pattern p : listPattern) {
        if (p.matcher(codePoint).find()) {
            return true;
        }
    }
    return false;
}


※因為 Character.UnicodeBlock.of 只接受 char,所以只能判斷 3Byte 的部分,4Byte 只好用正則了



※在 Windows 看 Big5 和 Unicode



切換到注音輸入法,然後出現「中」或「英」按右鍵有輸入法整合器
我的 Windows 8 要先開啟控制台,按右鍵才有反應


選左邊的「符」,然後上面的下拉可以選適合的,滑鼠移到符號上就有提示了
但下拉選項沒看到中文的,只好到全字庫查詢了


※小技巧:

切換到注音輸入法的中文模式,然後到可以輸入的地方
打上「`」 (鍵盤左上,上往下第二個按鍵), 再打上 [ubp] 的其中一個
u 表示 unicode
b 表示 big5
p 表示自己曾經自己定義過的符號
此例為 的 unicode 為 0x2199; big5 為 0xA1FA
打 `u2199 然後空格即可
打 `bA1FA 不用空格,自己會跳出符號
以上的按鍵都要在鍵盤的左邊打才行,不能使用鍵盤最右邊的數字鍵盤


※貪婪、勉強、完全匹配

※貪婪 (Greedy quantifiers):預設的模式,一次讀全部的字串,沒匹配就刪除一個字符再次匹配,如此循環,有匹配到就返回
X?
X*
X+
X{n}
X{n,}
X{n,m}

※勉強 (Reluctant quantifiers):在預設模式的最後加上「?」即可,一次讀一個字符,沒匹配到就增加一個字符,如此循環,有匹配到就返回
X??
X*?
X+?
X{n}?
X{n,}?
X{n,m}?

※完全匹配 (Possessive quantifiers):在預設模式的最後加上「+」即可,此模式只會跑一次,一次讀全部的字串,有匹配到就返回
X?+
X*+
X++
X{n}+
X{n,}+
X{n,m}+

※範例一

StringBuilder html = new StringBuilder();
html.append("<table>");
    
html.append("<tr>");
html.append("<td>aaa</td>");
html.append("<td>bbb</td>");
html.append("<td>ccc</td>");
html.append("</tr>");
    
html.append("<tr>");
html.append("<td>ddd</td>");
html.append("<td>eee</td>");
html.append("<td>fff</td>");
html.append("</tr>");
    
html.append("</table>");
    
Matcher m = Pattern.compile("<td>(.*)</td>").matcher(html);
while (m.find()) {
    System.out.println(m.group());
    System.out.println(m.start());
    System.out.println(m.end());
    System.out.println();
}

※貪婪
<td>aaa</td><td>bbb</td><td>ccc</td></tr><tr><td>ddd</td><td>eee</td><td>fff</td>
11
92

※非貪婪
<td>aaa</td>
11
23

<td>bbb</td>
23
35

<td>ccc</td>
35
47

<td>ddd</td>
56
68

<td>eee</td>
68
80

<td>fff</td>
80
92

※完全匹配
什麼都沒匹配到,因為「.*」會一直找到最後

※範例二

String html = "<ol><li>blog</li><li>book</li></ol>";
Matcher m = Pattern.compile("<li>(.{4,17})</li>").matcher(html);
while (m.find()) {
    System.out.println(m.group());
    System.out.println(m.start());
    System.out.println(m.end());
    System.out.println();
}

※貪婪
<li>Blog</li><li>book</li>
4
30

※非貪婪
<li>Blog</li>
4
17

<li>book</li>
17
30

※完全匹配
<li>Blog</li><li>book</li>
4
30


※捕獲組

final String html = "<ol><li title='a'>blog</li><li title='b'>book</li></ol>";
final String regexp = "(<li.*?>)(.*?)(</li>)";
Matcher m = Pattern.compile(regexp).matcher(html);
while (m.find()) {
    System.out.println(m.group(0)); // 和 m.group() 一樣
    System.out.println(m.group(1));
    System.out.println(m.group(2));
    System.out.println(m.group(3));
    System.out.println(m.start() + "," + m.end());
    System.out.println();
}
    
System.out.println(html.replaceFirst(regexp, "$2"));
System.out.println(html.replaceAll(regexp, "$2,"));

※就是補獲的字串是什麼,有幾個圓括號,group 就有幾個

※取代時,可用 $number 獲得補獲的內容

※結果:
<li title='a'>blog</li>
<li title='a'>
blog
</li>
4,27

<li title='b'>book</li>
<li title='b'>
book
</li>
27,50

<ol>blog<li title='b'>book</li></ol>
<ol>blog,book,</ol>



※非補獲組

非補獲的意思就是 matcher 的 group() 方法不會抓圓括號裡匹配到的值
也就是只分組而不捕獲

以「(?」開始,「)」結尾的為非捕獲組

※往前|後找(從這以下都很難懂,我這邊寫的也不一定是對的)

(?=X) X, via zero-width positive lookahead (往前找是 X 的)
(?!X) X, via zero-width negative lookahead (往後找不是 X 的)
(?<=X) X, via zero-width positive lookbehind (往後找是 X 的,也可想成左邊是 X 的)
(?<!X) X, via zero-width negative lookbehind (往前找不是 X 的,也可想成左邊不是 X 的)
指的是 (?=X) 的前後
<:往後看;沒有 < 就是往前看
!:不是
=:是

.零寬度:
例1:Pattern.compile("a(?=b)bc").matcher("abc")
1.先找到 b,然後看前面是不是 a
2.是 a,但因為是零寬度,所以只有 a
3.a 後面是 bc,所以以結果是 abc (0, 3)

例2:Pattern.compile("ab(?<=b)c").matcher("abc")
1.先找到左邊是 b 的,所以找到了 c
2.但因為是零寬度,所以只有 c
3.c 後面是 ab,所以以結果是 abc (0, 3)


.有 ! 的零寬度:! 為「不是」的意思
例1:Pattern.compile("a(?!b)").matcher("a")
1.找到 a 右邊的字是不是 b (此例只有一個 a,想像 a 右標的游標,所以不是 b),然後看前面是不是 a
2.是 a,但因為是零寬度,所以只有 a
3.結果為 a (0,1)

例2:Pattern.compile("a(?!b)c").matcher("ac")
1.找到 a 右邊的字是不是 b,看前面是不是 a
2.是 a,但因為是零寬度,所以只有 a
3..a 後面是 c,結果為 ac (0,2)


find(Pattern.compile("(?=yellow)").matcher("my dog is yellow and white")); // 10,10
System.out.println("######");
find(Pattern.compile("my dog is (?=yellow)").matcher("my dog is yellow and white")); // my dog is 0,10
    
System.out.println("######");
find(Pattern.compile("(?<=yellow)").matcher("my dog is yellow and white")); // 16,16
System.out.println("######");
find(Pattern.compile("(?<=yellow) and white").matcher("my dog is yellow and white")); //  and white 16,26
    
find(Pattern.compile("(?!yellow) and white").matcher("my dog is yellow and white")); // and white 16,26
find(Pattern.compile("my dog is (?<!yellow)").matcher("my dog is yellow and white")); //  my dog is 0,10
    
    
final String s = "0ab34ab789";
find(Pattern.compile("(?<!ab)").matcher(s)); // 0 1 2 4 5 6 8 9 10,左邊不是ab,注意有最後的 10,10
System.out.println("######");
find(Pattern.compile("(?!ab)").matcher(s)); // 0 2 3 4 6 7 8 9 10,不是ab,注意有最後的 10,10
    
    
private static void find(Matcher m) {
    while (m.find()) {
        System.out.println(m.group());
        System.out.println(m.start() + "," + m.end());
        System.out.println("----------");
    }
}

※數字代表 index,但要注意數字一樣的,如 10,10,也就是游標的位置也會算的



※名稱補獲

final String s = "0ab34ab789";
Matcher m = Pattern.compile("(?<xxx>.ab)").matcher(s);
while (m.find()) {
    System.out.println(m.group()); // 0ab、4ab
    System.out.println(m.group("xxx")); // 0ab、4ab
    System.out.println(m.start() + "," + m.end()); // 0,3、4,7
    System.out.println("----------");
}
//--------------------------------------------------------
Matcher m = Pattern.compile("(?<xxx>.ab)\\k<xxx>").matcher("0ab0ab789");
while (m.find()) {
    System.out.println(m.group()); // 0ab0ab
    System.out.println(m.group("xxx")); // 0ab
    System.out.println(m.start() + "," + m.end()); // 0,6
    System.out.println("----------");
}


※也就是在 ? 後面多個 <>,裡面取個名稱,在 java 可用 group 取抓到的值

※\k 專門用來配合名稱補獲的


※特殊符號補獲

find(Pattern.compile("(?i)aa(?-i)").matcher("aaAaAAaA")); // aa 0,2、Aa 2,4、AA 4,6、aA 6,8
System.out.println("######");
find(Pattern.compile("(?i:aa)cd").matcher("aaCDAaCdAAcDaAcd")); // aAcd 12,16
System.out.println("######");
final String s = "aaCDAaCdAAcDaAcd" +
    "aaCDAaCdAAcDaAcd" +
    "aaCDAaCdAAcDaAcd" +
    "aaCDAaCdAAcDaAcd";
    
find(Pattern.compile("(?im)aa(?-im)").matcher(s));
System.out.println("######");
find(Pattern.compile("(?im:aa)cd").matcher(s));
    
    
find(Pattern.compile("(?u)\u4e00(?-u)").matcher("一"));
System.out.println("######");
find(Pattern.compile("(?u:\u4e00)").matcher("一"));
System.out.println("######");
    
find(Pattern.compile("(?u)\u4e00(?-u)").matcher("\u4e00"));
System.out.println("######");
find(Pattern.compile("(?u:\u4e00)").matcher("\u4e00"));
System.out.println("######");
    
String s = "abc";
find(Pattern.compile("(?sx)a #b##.(?-sx)").matcher(s));
System.out.println("######");
find(Pattern.compile("(?sx:a b.)").matcher(s)); // 此種寫法,裡面不能寫 #,編譯會錯誤
System.out.println("######");
    
String s2 = "abc\ndef\nabc";
find(Pattern.compile("(?d)abc(?-d)").matcher(s2));
System.out.println("######");
find(Pattern.compile("(?d:abc)").matcher(s2));
System.out.println("######");
    
    
// find(Pattern.compile("\\p{Sc}").matcher("$\uFF04₿"));// FF04 是全型的 $
String s2 = "ab₿cde$f";
find(Pattern.compile("(?U)\\p{Sc}(?-U)").matcher(s2)); // ₿ 2,3、$ 6,7
System.out.println("######");
find(Pattern.compile("(?U:\\p{Sc})").matcher(s2)); // ₿ 2,3、$ 6,7


※
(?idmsuxU-idmsuxU) Nothing, but turns match flags i d m s u x U on - off
(?idmsux-idmsux:X)  X, as a non-capturing group with the given flags i d m s u x on - off

i 忽略 ASCII 的大小寫
d \n 會被當作一行的結束符號
m 多行模式
s dotall,就是「.」,可匹配任何字,包括結束符號
u 匹配 unicode
x 忽略表達式理的空格和 # 號
U Unicode character class,這個看不懂

※dmsuU,我試了沒加也可以,可能預設就有啟用了吧!

※unicode 超過 FFFF 會變成兩個字元,如「一」的結果為 0,1;但「𠀓」結果為 0,2,最多到 10FFFF

※\p 看 java API 裡的 Pattern 有說明,貨幣符號我只知道半全形的 $,和 Unicode 規定的,沒辦法,api 細節寫的都不清楚

※?:X、?>X

類似 ?= 但是沒有零寬度
想像成右邊是 X 的

find(Pattern.compile("(?=b)b").matcher("bb")); // b 0,1、b 1,2
System.out.println("######");
find(Pattern.compile("(?=b)b").matcher("bc")); // b 0,1
System.out.println("######");
find(Pattern.compile("(?:b)b").matcher("bb")); // bb 0,2
System.out.println("######");
find(Pattern.compile("(?:b)b").matcher("bc")); //

※以第二和第四個為例,找到 b 後,零寬度只能匹配到一個;非零寬度就能匹配到兩個



?:X、?>X 差異


find(Pattern.compile("a(?>bc|b)c").matcher("abc")); //
System.out.println("######");
find(Pattern.compile("a(?>bc|b)c").matcher("abcc")); // abcc 0,4
System.out.println("######");
find(Pattern.compile("a(?:bc|b)c").matcher("abc")); // abc 0,3
System.out.println("######");
find(Pattern.compile("a(?:bc|b)c").matcher("abcc")); // abcc 0,4


※?> 只會匹配一次,所以  bc|b 改成 b|bc 就能匹配上,類似上面的完全匹配