Java 入門指南 V2.00 - 單元 17 - 編碼與解碼




編碼 (encoding) 需要用到轉換表格,我們利用陣列 (array) 儲存這個表格,簡單說,就是利用 Unicode 排列順序,相對到表格的對應關係



上圖是用了如下的表格


   code = {'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'}

我們先來想一想程式如何完成編碼工作,假設是對以下的字串 (string) 進行編碼


   "There is no spoon."

首先, 'T' 不是英文小寫字母,因此跳過,然後 'h''e''r''e' 都是英文小寫字母,對照表格,需要轉換為 'b''a''n''a' ,接下來遇到一個空格字元 ' ' ,也跳過,然後 'i''s' 也都是英文小寫字母,需要轉換為 'k''w' ,餘下類推。


所以需要利用一個迴圈 (loop) 進行上述編碼工作,逐一檢查字串中的每一個字元 (character) ,若是屬於英文小寫字母的編碼範圍就是 Unicode 編碼 97122 之間,我們先將該字元轉換為整數,然後減掉 97 就會是表格字串中對應字元索引值。


這是說,第 0 個字元(索引值為 0'T' 不在英文小寫字母編碼的範圍,因此程式不會處理,然後到第 1 個字元 'h' ,這是英文小寫字母編碼為 104 ,減去 97 之後為 7 ,對應到上面的表格會是 'b' ,因此得到的新字串第 1 個新字元就是 'b' ,餘下會一直進行重複的工作到字串結束為止。


因此,我們對 toEncode() 的設計如下


036// 編碼的方法
037public String toEncode(String s) {
038   char[] cs = s.toCharArray();
039   int i, ci, d, m;
040   char r;
041   String rs = "";
042   Character cc;
043      
044   for (i = 0; i < cs.length; i++) {
045      if (cs[i] >= 97 && cs[i] <= 122) {
046         ci = cs[i];
047         m = ci - 97;
048         cs[i] = code[m];
049      }
050   }
051
052   for (i = 0; i < cs.length; i++) {
053      cc = new Character(cs[i]);
054      rs = rs.concat(cc.toString());
055   }
056      
057   return rs;
058}

toEncode() 接收一個字串 s 當參數 (parameter) ,也回傳一個新字串 rss 就是要編碼的字串,而 rs 則是編碼過的字串。由於字串被設計成不可變的 (immutable) ,因此無法直接修改參數 s ,而要用一個新的字串變數。


由於接收的是字串,因此要先把字串轉換成字元陣列


038char[] cs = s.toCharArray();

進行編碼轉換的迴圈


044for (i = 0; i < cs.length; i++) {
045   if (cs[i] >= 97 && cs[i] <= 122) {
046      ci = cs[i];
047      m = ci - 97;
048      cs[i] = code[m];
049   }
050}

逐一判斷字元陣列中的字元,若該字元屬於英文小寫字母就進行編碼轉換。字元陣列的屬性 (field) length() 記錄陣列中的元素總數,迴圈的控制變數 i 最大不超過 length ,因為索引從 0 開始,最後一個元素的索引為 length


找到密碼表中要轉換的字元後,這裡直接將字元指派到本來的字元陣列中


048cs[i] = code[m];

最後再把字元陣列中的所有元素合併成字串


052for (i = 0; i < cs.length; i++) {
053   cc = new Character(cs[i]);
054   rs = rs.concat(cc.toString());
055}

完整的實作檔案可以參考「範例程式碼」的 Encrypt.java

解碼 (decoding) 需要用到與編碼相同的轉換表格,也就是要將編碼過的小寫英文字母回覆成原來的英文小寫字母,我們儲存密碼表所用的陣列,恰巧依索引值 (index) 可推回原來的英文小寫字母,這是說,索引值 0'q' ,所以是將原本的 'a' 變成 'q' ,因此,解碼就是依索引值重新加上 diff 即可。


實際上我們需要用到巢狀迴圈 (nested loop) ,也就是在迴圈中有其他的迴圈,對單一英文句子而言,我們需要一個迴圈判斷每個字元是否為英文小寫字母,若是英文小寫字母,我們就需要另一個迴圈找出對應的索引值。由這樣的概念設計的解碼方法 toDecode() ,如下


060// 解碼的方法
061public String toDecode(String s) {
062   char[] cs = s.toCharArray();
063   int i, j;
064   char r;
065   String rs = "";
066   Character cc;
067      
068   for (i = 0; i < cs.length; i++) {
069      if (cs[i] >= 97 && cs[i] <= 122) {
070         for (j = 0; j <= code.length; j++) {
071            if (cs[i] == code[j]) {
072               cs[i] = (char) (j + 97);
073                break;
074            }
075         }
076      }
077   }
078
079   for (i = 0; i < cs.length; i++) {
080      cc = new Character(cs[i]);
081      rs = rs.concat(cc.toString());
082   }
083
084   return rs;
085}

巢狀迴圈是迴圈中包含另一個迴圈,由於我們利用縮排的方式編輯程式碼,看起來內層迴圈像是凹陷進去的巢,故稱之為巢狀迴圈。

toDecode()toEncode() 相似,同樣需要一個字串當參數,結果也回傳一個字串。


解碼轉換由巢狀迴圈的部份來進行


068for (i = 0; i < cs.length; i++) {
069   if (cs[i] >= 97 && cs[i] <= 122) {
070      for (j = 0; j <= code.length; j++) {
071         if (cs[i] == code[j]) {
072            cs[i] = (char) (j + 97);
073            break;
074         }
075      }
076   }
077}

第一層迴圈,也就是外層迴圈會依序取得英文句子的每個字元,然後判斷是否為英文小寫字母,如果該字元是英文小寫字母,就會啟動另一個迴圈找出表格中對應的索引值出來,相反地,如果該字元不是英文小寫字母,控制變數 i 就會自動遞增,然後判斷下一個字元。


注意,這個巢狀迴圈用了兩個 for 、兩個 if ,依順序 forifforif ,這是我們打算讓程式執行的順序,若是沒有依照這樣的順序,程式可能會跑出無法預期的結果。



編譯後執行,結果如下



編碼與解碼的結果都正確無誤, Encrypt 類別的發展已經大體完備,正式進入 GUI 開發之前,我們先來認識一下 Java API 囉!


中英文術語對照


編碼encoding
陣列array
字串string
迴圈loop
字元character
參數parameter
不可變的immutable
屬性field
解碼decoding
索引值index
巢狀迴圈nested loop

重點整理


  1. Unicode 的英文字母是按照字母順序編排的。
  2. 利用字元陣列處理英文字串,要先把字串轉換成字元陣列。
  3. 編碼工作針對英文小寫字母,因此要先判斷該字元是否為英文小寫字母,若是英文小寫字母才進行編碼。
  4. 利用字串的 concat() 方法,可將另一個字串連結到原字串的最後面。
  5. 字串雖是不可變的資料型態,變數卻可以改變所指向的參考。
  6. 解碼工作也就是把編碼的過程顛倒過來,另外用內層迴圈找出表格中對應的索引值。
  7. 巢狀迴圈是迴圈中包含另一個迴圈,通常利用程式碼縮排來表示。

問題與討論


  1. 編碼可以直接用字串處理嗎?如果直接用字串處理的話,那該如何設計呢?
  2. 我們連接連結字串的方法,是用不斷的將新字串重新指派到原來的變數,這樣會不會很浪費記憶體,有其他方式可以改進嗎?
  3. 解碼工作一定得用巢狀迴圈嗎?有沒有簡單點的寫法?
  4. 可以直接對字串解碼嗎?如果直接對字串解碼,那該如何設計呢?

the end

沒有留言: