Java 入門指南 - 編碼

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




上圖是用了如下的表格
cArray = {'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 編碼 97 到 122 之間,我們先將該字元轉換為整數,然後減掉 97 就會是表格中對應字元索引值。


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


因此,我們對編碼方法 (method) 設計如下
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] = getArray()[m];
        }
    }
    
    // 將字元陣列儲存為字串   
    for (i = 0; i < cs.length; i++) {
        cc = new Character(cs[i]);
        rs = rs.concat(cc.toString());
    }
        
    return rs;
}


toEncode() 接收一個字串當參數,也回傳一個新字串。我們在 toEncode() 建立一個新字元陣列 cs ,這是因為字串被設計成不可變的 (immutable) ,也就是說,字串一旦被建立後,就不能再被更改內容,所以利用字串的 toCharArray() 方法,講字串轉換成字元陣列,然後指派給 cs ,這樣一來,我們便可以計算出結果後,直接將得到的結果設定給原來的陣列中相同的元素 (element) 。


進行編碼轉換的迴圈
// 進行編碼轉換    
for (i = 0; i < cs.length; i++) {
    if (cs[i] >= 97 && cs[i] <= 122) {
        ci = cs[i];
        m = ci - 97;
        cs[i] = getArray()[m];
    }
}


字元陣列的屬性 (field) length 為陣列中元素的個數,迴圈的控制變數 i 最大不超過 cs.length ,因為陣列索引從 0 開始,最後一個元素的索引為 length - 1 。留意這一行
cs[i] = getArray()[m];


getArray() 回傳我們的轉換表格 cArray ,因此這一行等同於
cs[i] = cArray[m];



最後我們還需要一個迴圈,用來把字元陣列逐一附加到字串內
// 將字元陣列儲存為字串   
for (i = 0; i < cs.length; i++) {
    cc = new Character(cs[i]);
    rs = rs.concat(cc.toString());
}


同樣的,這個迴圈重複進行 cs.length 次。迴圈內的工作先以 Character() 將字元陣列中的字元 cs[i] 包裝成字元物件,這是因為基本資料型態 (primitive data type) 的字元與字元物件是不一樣的,基本資料型態直接是存在變數記憶體中的數值 (value) ,物件的變數記憶體儲存的是對物件的參考 (reference) 。


其實 Character() 就是字元物件的建構子 (constructor) ,這裡轉換為字元物件後,才可以利用字元物件的 toString() 方法,將字元物件轉換為字串。我們使用字串的 concat() 方法將參數附接到呼叫此方法的字串末端,而 concat() 需要以字串當參數,所以我們需要進行以上的型態轉換工作。


咦,不是說字串是不可變的嗎?這裡我們最初建立空字串變數 rs ,最後得到的結果同樣為變數 rs ,也回傳 rs 。的確,字串是不可變的,但是我們可以把字串的參考變數重新指向自己,也就是
rs = rs.concat(cc.toString());


道理很簡單,因為 rs.concat(cc.toString()) 會傳一個新字串,我們將 rs 重新當成這個新字串的參考,因此 rs 最初是空字串,進入這個迴圈後,字元陣列 cs 中的字元會逐一轉換為字串加進 rs 之中。


事實上,這過程會產生 cs.length 個字串,每個字串物件失去參考變數後,會被 Java 虛擬機器的資源回收者(garbage collector) 回收處理,然後釋放出原先分配的記憶體空間,所以程式可以有效執行,不會拖泥帶水造成系統的累贅。


我們只增加一個 toEncode() 方法,完整的 Encrypt 類別可以參考 Encrypt.java


EncryptDemo 需要修改,然後才可對 toEncode() 進行測試
// 測試 Encrypt 的類別
public class EncryptDemo {
    public static void main(String[] args) {
        System.out.println();
        String s, t;
        s = "There is no spoon.";
        t = ""; 
        for (int i = 0; i < 16; i++) {
            Encrypt e = new Encrypt();
            t = e.toEncode(s);
            System.out.println(t);
        }
        System.out.println();
    }
}

/* 《程式語言教學誌》的範例程式
    http://pydoing.blogspot.com/
    檔名:EncryptDemo.java
    功能:示範 Java 程式 
    作者:張凱慶
    時間:西元 2011 年 4 月 */


我們只對 "There is no spoon." 進行編碼,總共 16 次,每一次採用不同的轉換表格。記得, Encrypt 與EncryptDemo 兩個類別都修改過,因此需要重新編譯,才可以執行出所要測試的程式碼喔!



接下來,我們繼續加入解碼的功能吧!


中英文術語對照
編碼encoding
陣列array
字串string
迴圈loop
字元character
方法method
不可變的immutable
元素element
屬性field
基本資料型態primitive data type
數值value
參考reference
建構子constructor
資源回收者garbage collector


您可以繼續參考
軟體開發


相關目錄
回 Java 入門指南
回 Java 教材目錄
回首頁


參考資料
The JavaTM Tutorials: Getting Started
The JavaTM Tutorials: Learning the Java Language
The JavaTM Tutorials: Essential Classes
The Java Language Specification, Third Edition


本文於 2013 年 1 月訂正
本文於 2013 年 2 月修正

4 則留言:

symis 提到...

然後到第 1 個字元 'h' ,這是英文小寫字母編碼為 84
→ 應該是104吧?

Kaiching Chang 提到...

這部份打錯了,已修改,感謝指正 :)

symis 提到...

以下純屬實驗性質:
將2個for迴圈合併:
for(i=0; i< cs.length; i++){
if (cs[i]>=97 && cs[i]<=122){
m= cs[i]-97;
cs[i]=getArray()[m];
}
cc= new Character(cs[i]);
rs= rs.concat(cc.toString());
}
這樣是OK的,但若改成如下:
for(i=0; i< cs.length; i++){
if (cs[i]>=97 && cs[i]<=122){
m= cs[i]-97;
cs[i]=getArray()[m];
}
rs= rs.concat(cs[i]);
}
則會出現error:
The method concat(String) in the type String is not applicable for the arguments (char)
但我在for迴圈中用下式去查,二者是一樣的:
System.out.println(cs[i]);
System.out.println(cc);
請問,問題出在哪?
for迴圈合併,不能再精簡了嗎?
感謝指導!

Kaiching Chang 提到...

cs[i] 的是基本資料型態的字元, cc 則是字元物件, concat() 的參數必須是字串物件,這裡是把字元先轉換成字元物件,然後再用 toString() 轉換成字串物件。如果要精簡的話,不妨可以改成直接用字串處理 :)