Google Code Prettify

2015年1月17日 星期六

EBCDIC <=> ASCII 間的轉換

即使常聽到許多公司"計畫"將 IBM 的 z/OS 和 AS400 替換成 open 系統,但是聽了20年後,還是有非常多的系統使用 z/OS 或 AS400 … ╮(╯▽╰)╭ 
這兩個系統的內碼採用 EBCDIC 和一般開放系統的 ASCII 不一樣,所以系統整合時,無可避免的要在兩種編碼間轉換,UNIX 有提供一個指令 - dd,可以完成這件事,如下: 
假設現在有一個 ascii 編碼的檔案,檔名為 sales.csv。
  • ASCII => EBCDIC
dd conv=ebcdic if=sales.csv of=sales.ebcdic
  • EBCDIC => ASCII
dd conv=ascii if=sales.ebcdic of=sales.ascii
dd 這個指令很簡單,用 conv 選項設定要轉成什麼編碼,用 if 指定輸入檔檔名,of 指定輸出檔檔名。

2015年1月16日 星期五

使用 sort、join 處理 csv 檔案

雖然理想上,使用者要的資料最好都是由系統產生報表,使用者直接瀏覽或下載,但是,使用者常臨時要一些業績或管理性報表,IT 人員也只能從資料庫中下 sql statement 後匯出給使用者,有時,IT 人員更可能拿到別家公司匯出的數個 csv 檔,使用者卻希望將它們合併成一個檔案,當然,一般情況 IT 人員應該會找到一台資料庫,把檔案匯入處理後,再匯出給使用者,不過,IT 人員的環境裡並不總是有資料庫可以使用 (可能是控管嚴格沒有權限) ,這時候可以使用 Unix 中的一些指令來處理,Unix 通常鼓勵處理資料時,用文字檔的方式處理,所以,提供了非常豐富的處理文字檔指令,這裡要介紹的是 sort 和 join,透過這兩個指令可以將兩個 csv 檔像在資料庫中下 sql statement 將兩個 table 的資料 join 在一起一樣。
假設有兩個 csv 檔如下:
  • 業務員業績配額 (quotas.csv)
chris,95
michael,200
marry,200
herman,80
jane,75
joe,50
  • 業務員業績達成值 (sales.csv)
桃園分行,chris,300
高雄分行,herman,150
高雄分行,jane,200
台南分行,joe,100
宜蘭分行,michael,180
台北分行,marry,210
這兩個檔案是 key 都是人名,所以,join 時就以人名為 key,指令如下
sort -t ',' -k 1 quotas.csv > quotas.sorted
sort -t ',' -k 2 sales.csv > sales.sorted
先對兩個檔案排序,這是要使用 join 前的規定,一定要先排序! 指令中的 -t ',' 表示,檔案的分隔符號是逗號,-k 1 或 -k 2 表示該檔案的 key 是第幾個欄位。
join -t ',' -1 2 sales.sorted -2 1 quotas.sorted > result.csv
這樣就產生結果到 result.csv 中,-t 的選項如 sort 也是指分隔符號,-1 2 是指第 1 個檔案是以第 2 個欄位為 key,-2 1 則是指第 2 個檔案是以第 1 個欄位為 key,產生的結果如下:
chris,桃園分行,300,95
herman,高雄分行,150,80
jane,高雄分行,200,75
joe,台南分行,100,50
marry,台北分行,210,200
michael,宜蘭分行,180,200
第一筆資料指出,chris 在桃園分行,業績值 300,業績配額是 95,看來業績相當好,其餘資料的解讀就不綴述。

2015年1月10日 星期六

ByteBuffer 指標說明

當使用 java NIO 來讀寫檔案或 socket 時,一定會用到 ByteBuffer,大部份的人一開始會被它提供的許多 method 搞得很混亂,像是 flip、compact、rewind,甚至會質疑為什麼是提供這些 method ? 還有,ByteBuffer 中的三個指標 position、limit、capacity 會怎麼移動? 這裡做點簡單的說明。
  • allocate
  使用 ByteBuffer 前,一定要先為它向系統要一塊記憶體,如下:
ByteBuffer buffer = ByteBuffer.allocate(1024);
  這時候指標的位置會如下:
  position = 0
  limit = 1024
  capacity = 1024
  毫無疑問的,capacity 是指這個緩衝區的總量,這個數字在後續的各項操作都不會改變。
  position 是指,資料可以從這裡開始"處理",一直"處理"到 limit 所指的位置,這些所謂的"處理"有時是讀有時是寫,視情況而定。
  • 寫入 ByteBuffer
  當我們用 socketChannel 讀取一段資料到緩衝區後會怎麼變化? 程式如下:
socketChannel.read(buffer); //從 socket 讀出、寫入 buffer
  假設寫入的資料有 153 個 bytes,那麼,指標會如下變化:
  position = 153
  limit = 1024
  capacity = 1024
  position 指向下一個可將資料寫入緩衝區的位置,limit 仍是指可被寫入資料的限制,不可超過這個位置。
  • flip
  通常,程式寫入一段資料後,當然會想要處理這段資料,處理前,會先呼叫 flip():
  limit 會被設定為原本 position 的值,即 153,position =0,這表示,之後的程式可透過 get() 來讀取 position 到 limit 這段區間的資料。position 的角色在 flip 前後產生了變化,在呼叫 flip 之前是指向可被寫入的位置,呼叫 flip 後變成了正要被讀取的位置。
  • get
byte b = buffer.get();
  每呼叫一次 get(),即會傳回 position 所指位置的資料,並將 position 加1,position 最多可以加到 limit,如果 position 已經等於 limit,再呼叫 get(),會拋出 BufferUnderflowException 例外。
  有另外一個 method - get(i),可以指定要讀取特定位置的資料,這個 method 呼叫後除了傳回值外,不會移動 position。
  • wrap
  如果是要寫資料到 socket 呢? 這時候可能就會用到 wrap 這個 method,將一段原本放於 byte[] 陣列中的資料,放入 ByteBuffer 裡。
byte[] b = new byte[100];
// put something to b
ByteBuffer buffer = ByteBuffer.wrap(b);
  如下的程式,會產生一個長為 100 bytes 的 ByteBuffer,並放入 b 的資料,所以,各指標如下:
  position = 0
  limit = 100
  capacity = 100
  • 從 ByteBuffer 讀出
  在 buffer 中有了資料後,要寫入 socket 連線裡,程式如下:
socketChannel.write(buffer); //由 buffer 讀出、寫入 socket
if (buffer.hasRemaining()) {
        buffer.compact();
} else {
        buffer.clear();
}
  在第一行 write 之後,不一定就會全部 100 個 bytes 都寫進 socket 裡,有可能只寫了 90 個bytes,所以,程式會判斷是否還有剩下,有剩下就可能要再 write 一次,判斷有沒有剩下的方法是用 hasRemaining(),它其實是判斷 position 和 limit 間是否還有資料罷了。假設,只從 buffer 讀出 90 bytes,指標會如何移動? 如下:
  position = 90
  limit = 100
  capacity = 100
  • compact
  承上,還有 10 bytes 未處理,假設我們希望再放入一些資料到 ByteBuffer 裡,那麼要怎麼做?
buffer.compact();
  沒錯,就是執行  compact(),這時未寫出的 10 bytes 資料會被移到最前面 index 0 ~ 9 的位置,指標會如下:
  position = 10
  limit = 100
  capacity = 100
  position 的角色在 compact 前後也產生了變化,呼叫 compact 前是指向將被讀取的位置,呼叫 compact 後指向下一個可以放新資料的位置。
  • put
  上面介紹的 wrap 是一次放入一個 byte array 到 ByteBuffer 裡,另一個方法是 put,它可以一次只放一個 byte,也可以放一個 byte array,假設程式如下:
byte b = new byte(0x55);
buffer.put(b);
  因為僅放入一個 byte,position 會加 1,即變成 11,其餘不變。如果是要 put 一個 byte array ? 程式如下:
byte[] c = new byte[] { 'A', 'B', 'C', 'D', 'E', 'F', 'G' };
buffer.put(c);
  因為又放入了 7 個 bytes,因此 position = 18,其餘不變。wrap 和 put 的關鍵差別是,wrap 是產生一個新的 ByteBuffer,而 put 是將資料放入既有的 ByteBuffer 裡。
  • rewind
這個 method 是把 position 移到 0,而 limit 不變。為什麼要這麼做? 有一種狀況是 ByteBuffer 已存在的資料要被讀取兩次,或是處理到一個段落後,想要提取出 ByteBuffer 中 0 ~ limit 的資料。看一下下面的例子:
byte[] array = new byte[8];
ByteBuffer buffer = ByteBuffer.wrap("0123456789".getBytes());
buffer.position(2);
buffer.get(array, 0, 8);
System.out.println(new String(array)); //23456789
buffer.rewind();
buffer.get(array);
System.out.println(new String(array)); //01234567
從上面的例子,可以觀察在呼叫 rewind 前後的變化,在呼叫 rewind 前,position = 2,表示要讀取資料的話,會由第 2 個元素開始,rewind 後 position = 0,所以,讀取資料即由第 0 個元素開始。
  • clear
  當在 ByteBuffer 裡的資料都處理完了,要將緩衝區裡的資料清除,並將指標回復到原始狀態,就呼叫 clear(),全部的內容會清為 0x00,指標如下:
      position =0
      limit = capacity
  • 轉換成 byte[]
  雖然 NIO 的類別幾乎都是操作 ByteBuffer,但是,有時還是會有需要將 ByteBuffer 的內容轉為 byte[],這時有兩個 method 可以用,如下:
       1. array: 傳回 0 ~ capacity 間的所有內容,包括 limit ~ capacity 間的內容,position、limit 都不會改變,說實在的,這個 method 好像不太實用? (也可能是我不知道要用在什麼場合)
       2. get(array, offset, length): 傳回 position ~ limit 間的內容,要注意 offset 是指 array 不是 ByteBuffer,實際傳回的內容由 length 指定,也就是 position ~ position + length 的內容,position + length 不能超過 limit,執行後 position 會加 length。

2014年8月9日 星期六

剖析固定長度欄位的訊息字串

在進行系統整合時,系統間不管是透過網路、檔案傳送訊息資料,常會遇到訊息字串的各個欄位是固定長度的,這種情況下程式怎麼寫會比較簡潔易懂? 這裡是我提供的一個方法,使用 annotation 將訊息字串欄位的設定值直接寫在程式上,而不是寫在設定檔裡,這樣應該會比較直覺。
假設有個程式,要顯示中華民國總統的任期和歷史評價,其訊息格式如下:




首先,我們定義一個 annotation,可以設定欄位的開始位置和長度,如下:
 1 package idv.steven.annotation;;
 2 
 3 import java.lang.annotation.ElementType;
 4 import java.lang.annotation.Retention;
 5 import java.lang.annotation.RetentionPolicy;
 6 import java.lang.annotation.Target;
 7 
 8 @Retention(RetentionPolicy.RUNTIME)
 9 @Target(ElementType.FIELD)
10 public @interface MsgField {
11     public int start();
12     public int length();
13 }
接著定義一個介面,所有訊息的 POJO 都要實作這個介面,這個介面的作用只是為了統一各個訊息的類型。
1 package idv.steven.msgspec;
2 
3 public interface FixedFieldMsg {
4 
5 }
接著依訊息的需要定義 POJO,這裡當然就是依上方的訊息規格來定義,並且於類別裡的各個欄位上設定開始位置、長度,因為訊息有四個欄位,當然這個類別也有相對應的四個欄位。
 1 package idv.steven.pojo;
 2 
 3 import idv.steven.MsgField;
 4 
 5 public class President implements FixedFieldMsg {
 6     @MsgField(start=0,length=4)
 7     private String startYear;
 8     @MsgField(start=4,length=4)
 9     private String endYear;
10     @MsgField(start=8,length=10)
11     private String name;
12     @MsgField(start=18,length=30)
13     private String evaluate;
14     
15     /**
16      * @return 任期開始的年份
17      */
18     public String getStartYear() {
19         return startYear;
20     }
21     public void setStartYear(String startYear) {
22         this.startYear = startYear;
23     }
24     
25     /**
26      * @return 任期結束的年份
27      */
28     public String getEndYear() {
29         return endYear;
30     }
31     public void setEndYear(String endYear) {
32         this.endYear = endYear;
33     }
34     
35     /**
36      * @return 姓名
37      */
38     public String getName() {
39         return name;
40     }
41     public void setName(String name) {
42         this.name = name;
43     }
44     
45     /**
46      * @return 評價
47      */
48     public String getEvaluate() {
49         return evaluate;
50     }
51     public void setEvaluate(String evaluate) {
52         this.evaluate = evaluate;
53     }
54 }
下面就是剖析訊息的程式了…
 1 package idv.steven;
 2 
 3 import java.lang.reflect.Field;
 4 import java.lang.reflect.InvocationTargetException;
 5 import java.lang.reflect.Method;
 6 import java.util.Arrays;
 7 
 8 import idv.steven.annotation.MsgField;
 9 import idv.steven.msgspec.FixedFieldMsg;
10 import idv.steven.msgspec.President;
11 
12 /**
13  * 剖析固定欄位長度的訊息字串成為一個物件
14  * @author Steven
15  */
16 public class Parser {
17     /**
18      * 將訊息字串轉換為訊息物件
19      * @param msgText 訊息字串
20      * @param msgObject 訊息物件
21      * @return 訊息物件
22      * @throws IllegalArgumentException
23      * @throws IllegalAccessException
24      * @throws NoSuchMethodException
25      * @throws SecurityException
26      * @throws InvocationTargetException
27      * @throws ClassNotFoundException
28      */
29     public FixedFieldMsg parsing(byte[] msgText, FixedFieldMsg msgObject) 
30             throws IllegalArgumentException, IllegalAccessException, 
31             NoSuchMethodException, SecurityException, 
32             InvocationTargetException, ClassNotFoundException {
33         
34         Class<?> aClass = Class.forName(msgObject.getClass().getName());
35         Field[] fields = aClass.getDeclaredFields();
36         for(Field field:fields) {
37             MsgField msgField = field.getAnnotation(MsgField.class);
38             if (msgField != null) {
39                 byte[] subMsg = Arrays.copyOfRange(msgText, msgField.start(), msgField.start() + msgField.length());
40                 
41                 Method method = aClass.getMethod(getSetterName(field.getName()), String.class);
42                 method.invoke(msgObject, new String(subMsg));
43                 //field.set(message, new String(subMsg));
44             }
45         }
46         
47         return msgObject;
48     }
49     
50     private String getSetterName(String fieldName) {
51         return String.format("set%s%s", fieldName.substring(0, 1).toUpperCase(), fieldName.substring(1));
52     }
53 
54     public static void main(String[] args) {
55         String msgText = "20082016馬英九    一個自私的笨蛋、禍國殃民!     ";
56         
57         try {
58             Parser parser = new Parser();
59             President president = (President) parser.parsing(msgText.getBytes(), new President());
60             
61             System.out.println("任期: " + president.getStartYear() + " ~ " + president.getEndYear());
62             System.out.println("總統姓名: " + president.getName());
63             System.out.println("歷史評價: " + president.getEvaluate());
64         } catch (Exception e) {
65             e.printStackTrace();
66         }    
67     }
68 }
這個程式如果要實際應用,當然還要加上一些錯誤處理,以避免傳入的字串不合規定,在這裡為使程式邏輯更清楚,就先省略了,只談我們真正在意的部份。程式說明如下:
  1. parsing method 無疑是程式的重點,這裡將利用 Java 的反射 (reflect) 機制,簡潔的將訊息剖析及訊息轉換成物件合在一起,注意一下 Line 59,呼叫 parsing 時,第二個參數就傳入實際要轉換成的物件。
  2. Line 34: 透過這個方法,可以取得類別的相關訊息,要特別注意的是,這裡取得的不是物件 (object) 而是類別 (class)。
  3. Line 35: 取得類別中所有的欄位,不管它是 public、protected、private 或無修飾子。
  4. Line 37: 取得欄位上的 annotation 相關的訊息。
  5. Line 39: 以 annotation 設定的開始位置、長度,從訊息字串中取出子字串。
  6. Line 41: 依 Java 的習慣,POJO 的欄位會設定成 private,再透過 getter、setter method 來存取,我們依這樣的習慣寫這個程式,所以要取得 setter method 才能給欄位設定字串中取得的值。getMethod 一子第一個參數是 setter method 的名稱,第二個參數是傳給 setter method 的參數的類別種類。
  7. Line 42: 呼叫 setter method 將值設定給欄位。
  8. Line 43: 如果 Message 類別的欄位修飾子改成 public,我們也可以用這個方式給各個欄位值,這只是給各位參考,但不建議這麼做。
執行結果如下:
任期: 2008 ~ 2016
總統姓名: 馬英九    
歷史評價: 一個自私的笨蛋、禍國殃民!    

see also: Spring Batch: FixedLengthTokenizer

【日劇 - 倒數第二次戀愛】
以中年人為主角的日劇好像還不少,除了這一部戲外,天海祐希主演的「偽裝夫妻」,篠原涼子主演的「熟女正青春」也都蠻好看的。但是,要說選角嘛~ 「倒數第二次戀愛」選的小泉今日子 (飾演 吉野千明)、中井貴一 (飾演 長倉和平) 真的最讓人覺得完全沒得挑剔,劇中兩人的鬥嘴真的就是活生生的中年人,非常合拍。
除了主角生動的演出外,配角也相當不錯,三井 (久保田磨希 飾) 是吉野千明的得力助手,出現的時間固然不多,表現似乎也沒有什麼特別,但是她的存在總能突顯吉野的一些人格特質,是不可或缺的人物。
女主角吉野的兩個閏密長得如三井一樣不亮眼,可是這兩個閏密的存在很能顯示出吉野中年生活的空虛,而且那看似平淡無奇的演出,就是生活中三個歐巴桑聚在一起的樣子。
這部戲沒有什麼特別的高潮,從頭到尾卻完全不會讓人覺得無聊,蠻神奇的編劇、蠻神奇的演出,要說有什麼讓人不滿意的,雙胞胎兄妹長倉萬理子 (內田有紀 飾)、長倉真平 (坂口憲二 飾) 的角色設定比較讓人覺得怪,一方面是兩個角色怪異的個性和這平淡的劇情有點格格不入,另一方面是造型看起來有那麼一點不舒服 ...