Java 入門指南
單元 17 - 編碼與解碼
編碼 (encoding) 需要用到轉換表格,我們利用陣列 (array) 儲存這個表格,簡單說,就是利用 Unicode 排列順序,相對到表格的對應關係
×
⇓
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 |
q | z | i | r | a | j | s | b | k | t | c | l | u | d | m | v | e | n | w | f | o | x | g | p | y | h |
上面是用了如下的表格
code_array = "qzirajsbktcludmvenwfoxgpyh"
我們先來想一想程式如何完成編碼工作,假設是對以下的字串 (string) 進行編碼
"There is no spoon."
首先, 'T' 不是英文小寫字母,因此跳過,然後 'h' 、 'e' 、 'r' 、 'e' 都是英文小寫字母,對照表格,需要轉換為 'b' 、 'a' 、 'n' 、 'a' ,接下來遇到一個空格字元 ' ' ,也跳過,然後 'i' 、 's' 也都是英文小寫字母,需要轉換為 'k' 、 'w' ,餘下類推。
所以需要利用一個迴圈 (loop) 進行上述編碼工作,逐一檢查字串中的每一個字元 (character) ,若是屬於英文小寫字母的編碼範圍就是 Unicode 編碼 97 到 122 之間,我們先將該字元轉換為整數,然後減掉 97 就會是表格字串中對應字元索引值。
這是說,第 0 個字元(索引值為 0 ) 'T' 不在英文小寫字母編碼的範圍,因此程式不會處理,然後到第 1 個字元 'h' ,這是英文小寫字母編碼為 104 ,減去 97 之後為 7 ,對應到上面的表格會是 'b' ,因此得到的新字串第 1 個新字元就是 'b' ,餘下會一直進行重複的工作到字串結束為止。
因此,我們對 toEncode() 的設計如下
// 編碼的方法
public String toEncode(String s) {
char[] cs = s.toCharArray();
int i, ci, d, m;
char r;
String rs = "";
Character cc;
for (i = 0; i < cs.length; i++) {
if (cs[i] >= 97 && cs[i] <= 122) {
ci = cs[i];
m = ci - 97;
cs[i] = code[m];
}
}
for (i = 0; i < cs.length; i++) {
cc = new Character(cs[i]);
rs = rs.concat(cc.toString());
}
return rs;
}
toEncode() 接收一個字串 s 當參數 (parameter) ,也回傳一個新字串 rs , s 就是要編碼的字串,而 rs 則是編碼過的字串。由於字串被設計成不可變的 (immutable) ,因此無法直接修改參數 s ,而要用一個新的字串變數。
由於接收的是字串,因此要先把字串轉換成字元陣列
char[] cs = s.toCharArray();
進行編碼轉換的迴圈
for (i = 0; i < cs.length; i++) {
if (cs[i] >= 97 && cs[i] <= 122) {
ci = cs[i];
m = ci - 97;
cs[i] = code[m];
}
}
逐一判斷字元陣列中的字元,若該字元屬於英文小寫字母就進行編碼轉換。字元陣列的屬性 (field) length() 記錄陣列中的元素總數,迴圈的控制變數 i 最大不超過 length ,因為索引從 0 開始,最後一個元素的索引為 length 。
找到密碼表中要轉換的字元後,這裡直接將字元指派到本來的字元陣列中
cs[i] = code[m];
最後再把字元陣列中的所有元素合併成字串
for (i = 0; i < cs.length; i++) {
cc = new Character(cs[i]);
rs = rs.concat(cc.toString());
}
完整的實作檔案可以參考「範例程式碼」的 Encrypt06.java 。
解碼 (decoding) 需要用到與編碼相同的轉換表格,也就是要將編碼過的小寫英文字母回覆成原來的英文小寫字母,我們儲存密碼表所用的陣列,恰巧依索引值 (index) 可推回原來的英文小寫字母,這是說,索引值 0 為 'q' ,所以是將原本的 'a' 變成 'q' ,因此,解碼就是依索引值重新加上 diff 即可。
實際上我們需要用到巢狀迴圈 (nested loop) ,也就是在迴圈中有其他的迴圈,對單一英文句子而言,我們需要一個迴圈判斷每個字元是否為英文小寫字母,若是英文小寫字母,我們就需要另一個迴圈找出對應的索引值。由這樣的概念設計的解碼方法 toDecode() ,如下
// 解碼的方法
public String toDecode(String s) {
char[] cs = s.toCharArray();
int i, j;
char r;
String rs = "";
Character cc;
for (i = 0; i < cs.length; i++) {
if (cs[i] >= 97 && cs[i] <= 122) {
for (j = 0; j <= code.length; j++) {
if (cs[i] == code[j]) {
cs[i] = (char) (j + 97);
break;
}
}
}
}
for (i = 0; i < cs.length; i++) {
cc = new Character(cs[i]);
rs = rs.concat(cc.toString());
}
return rs;
}
巢狀迴圈是迴圈中包含另一個迴圈,由於我們利用縮排的方式編輯程式碼,看起來內層迴圈像是凹陷進去的巢,故稱之為巢狀迴圈。
toDecode() 與 toEncode() 相似,同樣需要一個字串當參數,結果也回傳一個字串。
解碼轉換由巢狀迴圈的部份來進行
for (i = 0; i < cs.length; i++) {
if (cs[i] >= 97 && cs[i] <= 122) {
for (j = 0; j <= code.length; j++) {
if (cs[i] == code[j]) {
cs[i] = (char) (j + 97);
break;
}
}
}
}
第一層迴圈,也就是外層迴圈會依序取得英文句子的每個字元,然後判斷是否為英文小寫字母,如果該字元是英文小寫字母,就會啟動另一個迴圈找出表格中對應的索引值出來,相反地,如果該字元不是英文小寫字母,控制變數 i 就會自動遞增,然後判斷下一個字元。
注意,這個巢狀迴圈用了兩個 for 、兩個 if ,依順序 for 、 if 、 for 、 if ,這是我們打算讓程式執行的順序,若是沒有依照這樣的順序,程式可能會跑出無法預期的結果。
編譯後執行,結果如下
zejotydinsxchmrwbglqvafkpu |
There is no spoon. |
Titgt nl mr lwrrm. |
There is no spoon. |
編碼與解碼的結果都正確無誤, Encrypt 類別的發展已經大體完備,正式進入 GUI 開發之前,我們需要重新檢視 Encrypt 一下,看是否需要重構 (refactoring) 。
相關教學影片
中英文術語對照 | |
---|---|
array | 陣列 |
character | 字元 |
decoding | 解碼 |
encoding | 編碼 |
field | 屬性 |
immutable | 不可變的 |
index | 索引值 |
loop | 迴圈 |
nested loop | 巢狀迴圈 |
parameter | 參數 |
refactoring | 重構 |
string | 字串 |
重點整理 |
---|
1. 轉換表格利用字元陣列來儲存,字元陣列可用索引值存取元素,也就是字元。 |
2. 編碼時利用迴圈依序取得要編碼字串,先將編碼字串轉換成字元陣列,然後先判斷是否為英文小寫字母,如果是英文小寫字母就進行編碼。 |
3. 解碼用到巢狀迴圈,巢狀迴圈是指迴圈中還有另一個迴圈,第一層迴圈依序取得每一個解碼字元,第二層迴圈則是找出解碼字元在密碼表中的索引值,這個索引值也就是該字元在字母表中的索引值。 |
問題與討論 |
---|
1. 要怎麼判斷一個字元是英文小寫字母或大寫字母? |
2. 可以不用巢狀迴圈來解碼嗎? |
3. 巢狀迴圈可以用一樣名稱的控制變數嗎? |
練習 |
---|
1. 承接上一個單元的猜數字遊戲,將新專案寫在 Exercise1701 中,在遊戲迴圈中檢查使用者輸入的長度,若是長度不等於 4 就印出提示訊息進行下一輪。 |
2. 承上題,將新專案寫在 Exercise1702 中,把答案 answer 設定為 "0123456789" ,然後加入以下程式碼
這是利用 Collections 的 shuffle() 方法攪亂十個阿拉伯數字的順序,再截取前四個數字當答案。 |