2014年4月10日 星期四

XP 的 Telnet 服務

今天花了很多時間測試 plink, 不知哪裡出問題, 還是無進展. 於是找出湯秉翰的 Java 網路程式設計這本書, 其中 8,9,10 三章談如何設計 Telnet, 打算從 protocol 本身了解如何實作 Telnet 功能. But ... 打開 XP 的命令提示字元, 敲入 :

>telnet localhost

出現下列錯誤 :



馬上想到可能服務沒開啟, 果然, 到控制台/系統管理工具/服務, 就發現 Telnet 已被停用 :



要打開 Telnet 服務, 只要在該列點兩下,



把啟動類型改為手動, 按套用, 服務狀態中的啟動鈕就會變成可按, 按啟動即可. 為了安全起見, 使用完還是關閉為宜.

接著切到 "登入" 頁籤設定 telnet 帳號與密碼 :






在這個帳戶指定登入名稱與密碼, 當然必須先在 "控制台/使用者" 新增此使用者才行. 確定後會提示必須先停止此服務再次啟動, 這樣就可以用 Telnet 遠端登入了.



# 遠端啟動 Telnet Server for Windows 


如何於 Java 程式中對 Putty 下指令

終於要進入這次複習 Java 的最後階段了, 就是用 Java 程式啟動 Putty, 利用 Putty 向目標系統下達命令, 例如登入系統, 傳送指令等等. 要達成此功能需準備 putty.exe 與 plink.exe 這兩個檔案, 可由下列網站下載 :

# http://www.chiark.greenend.org.uk/~sgtatham/putty/download.html 


Java API :

# http://docs.oracle.com/javase/7/docs/api/


~未完待續~

參考資料 :
  1. Java and putty - send commands
  2. Define IP to run PuTTY.exe from Java 
  3. When Runtime.exec() won't 
  4. Using Runtime.exec to open a putty session 
  5. Java Telnet Library
  6. Example of use of Telnet Client 
  7. Telnet in Java
  8. Apache : TelnetClient.java Telnet.java
  9. Java – Writing An Automated Telnet Client
  10. Java 網路程式設計 -- Telnet

2014年4月9日 星期三

完成 XP 更新

由於 XP 在 4/8 後不再更新, 因此從上週起陸續將家中總共 6 台 XP 電腦做完更新 (很恐怖, 竟然有 6 台電腦), 並產生 GHOST 檔, 因為這些電腦都還會用很久.

XP 真是微軟最長壽 (也成功) 的作業系統, 使用方式最直觀, 雖然縫縫補補的更新檔很龐大, 但總比 WIN8 都不知道要如何關機好吧! 其缺點就是用久了會變慢, 需要 GHOST 時光回溯一番.

公司的 WIN7 用一段時間後也漸熟悉了, 其實跟 XP 相差不大, 還可以接受, WIN8 的那些彩色磚塊看了就討厭, 事實上我都沒用, 直接切到檔案總管畫面.


一點都不稀奇

# 1300名醫護連署︰追究國家暴力

"行政院現場員警突然強制驅離臨時救護站,甚至以「統統上手銬」等不當執法方式,威脅在場中立之醫護人員撤離;在醫護人員含淚離開現場時,警方甚至響起歡呼掌聲。凡此種種,都令醫護人員難以置信,並感到無比沉痛。"

當開過的政治支票都可以說是 "開玩笑", 副總統也姓白, 這一切, 一點都不稀奇了. 看看過去七年來說過的每一句謊話, 誰還會再相信放羊的孩子呢?

# 20120507一個便當吃不飽怎不吃第二個原文+滿意度剩15%
# 你記得嗎?馬英九事件簿
# 090815 馬英久語錄 網友看了瞠目結舌
# 20121018 馬英九 最怕大家看到的一則新聞影音
# 親愛的馬英九 希望你可以好好聽一下你說過的話 自己下台吧
# 我把你當人看!我教育你!你的基因沒問題! (在結尾處)
# 馬英九治國週記 全集 一次看個夠

更多歷史還原, 請上 Youtube 找馬英九.


2014年4月7日 星期一

Java 資料庫存取 : 使用 ACCESS (續)

今天回過頭來再整理 ACCESS 資料庫存取的函式庫, 為了簡短起見, 常用函式庫也改名為 JT.java (Java Tools 之意), 最近函式庫收尾後, 過兩週授課完畢, 就要準備改寫公司的資料擷取程式. 上一篇 ACCESS 筆記參見 :

# http://yhhuang1966.blogspot.tw/2014/03/java-access_14.html 
# http://docs.oracle.com/javase/7/docs/api/

首先改寫了連線函式 :

import java.io.*;
import java.sql.*;
import java.util.*;
import java.util.Date;
import java.text.*;
import java.util.regex.*;
public class JT {
  static Connection conn;
  static Statement stat;
  public static void connectDB(String db) {
    connectDB(db, "", "");
    }
  public static void connectDB(String db, String user, String pwd){
    try {
      Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
      dsn="jdbc:odbc:Driver={Microsoft Access Driver (*.mdb)};DBQ=" +
              db + ";DriverID=22;READONLY=false}";
      conn=DriverManager.getConnection(dsn, user, pwd);
      stat=conn.createStatement();
      }
    catch (Exception e) {System.out.println(e);}
    }
  public static void closeDB() {
    try {conn.close();}
    catch (Exception e) {System.out.println(e);}
    conn=null;
    stat=null;
    }
  public static ResultSet runSQL(String SQL) {
    ResultSet rs=null;
    try {
      if (SQL.indexOf("SELECT")==-1) {stat.executeUpdate(SQL);}
      else {rs=stat.executeQuery(SQL);}
      }
    catch (Exception e) {System.out.println(e);}
    return rs;
    }
  public static boolean createTable(String table) {
    try {stat.executeUpdate("CREATE TABLE " + table);}
    catch (Exception e) {System.out.println(e);return false;}
    return true;
    }
  public static boolean addField(String table, String field, String type) {
    String SQL="ALTER TABLE " + table + " ADD " + field + " " + type;
    try {stat.executeUpdate(SQL);}
    catch (Exception e) {System.out.println(e);return false;}
    return true;
    }
 }

在 connectDB() 方法裡加入了資料庫帳號密碼參數, 利用方法多載, 設定預設值為空字串 (即資料庫無密碼). 事實上只要前三個方法就可以搞定一切 ACCESS 資料庫的存取操作, 只要寫好 SQL 指令丟給 runSQL() 執行即可. 但為了方便, 還是多寫一些函式來用, 這樣就可以不需要使用 SQL 指令了.

首先準備一個空白資料庫 test.mdb, 先來新增資料表與資料欄位, 這裡用 createTable() 與 addField() 這兩個方法來包裝了 SQL 指令 :

import java.util.*;
public class mytest {
  public static void main(String[] args) {
    JT.connectDB("test.mdb");
    System.out.println(JT.createTable("users"));  //輸出 true
    System.out.println(JT.addField("users","name","varchar(20)"));  //輸出 true
    System.out.println(JT.addField("users","id","AUTOINCREMENT PRIMARY KEY"));  //輸出 true
    System.out.println(JT.addField("users","gender","VARCHAR(5)"));  //輸出 true
    JT.closeDB();
    }
  }

createTable() 與 addField() 方法執行 SQL 成功時傳回 true, 可見已在 test.mdb 中建立了三個欄位 name, id, 與 gender.


其次要來抓出資料庫中的全部資料表名稱, 我們要給 JT.java 添加下列方法 getTables() :

  public static String[] getTables() {
    ArrayList list=new ArrayList();
    try {
      DatabaseMetaData md=conn.getMetaData();
      String[] types={"TABLE"};
      ResultSet rs=md.getTables(null, null, "%", types);    
      while (rs.next()) {list.add(rs.getString("TABLE_NAME"));}
      }
    catch(Exception e) {System.out.println(e);}
    list.trimToSize();
    String[] array=list.toArray(new String[list.size()]);
    return array;          
    }

這裡用 ArrayList 來儲存資料表名稱, 呼叫 Connection 物件的 getMetaData() 方法即可取得資料庫資訊 DatabaseMetaData 物件, 呼叫此物件的 getTables() 可取得全部資料表名稱, 用迴圈將資料表名稱存入 ArrayList 後再轉成陣列傳回. 參見 :

# How to get all table names from database?
# How to get all table names from a database?

範例如下 :

import java.util.*;
public class mytest {
  public static void main(String[] args) {
    JT.connectDB("test.mdb");
    System.out.println(Arrays.deepToString(JT.getTables()));  //輸出 [users]
    System.out.println(JT.createTable("books"));                       //輸出 true
    System.out.println(Arrays.deepToString(JT.getTables()));  //輸出 [books,users]
    JT.closeDB();
    }
  }

這裡用 java.util.Arrays 的 deepToString() 來顯示物件陣列的內容, 可見資料表名稱會照字母順序排列. 既然已可取得資料表名稱, 那麼就可以搜尋資料庫裏面是否已存在指定之資料表, 這可以利用 java.util.Arrays 的 binaraySearch() 方法, 我們為 JT.java 添加 findTable() 方法如下 :

  public static boolean findTable(String table) {
    if (Arrays.binarySearch(getTables(), table) < 0) {return false;}
    else {return true;}  
    }

我們把 JT.getTables() 傳回的資料表名稱陣列作為 binarySearch() 之第一參數, 去搜尋是否已存在指定之 table 陣列, 若傳回值為負表示沒找到, 就傳回 false, 非負值表示有找到, 傳回 true. 範例如下 :

import java.util.*;
public class mytest {
  public static void main(String[] args) {
    JT.connectDB("test.mdb");
    System.out.println(JT.findTable("users"));    //輸出 true
    System.out.println(JT.findTable("user"));     //輸出 false
    System.out.println(JT.findTable("books"));  //輸出 true
    System.out.println(JT.findTable("book"));   //輸出 false
    JT.closeDB();
    }
  }

資料庫 test.mdb 目前只有 users 與 books 兩個資料表, 沒有 user 與 book, 因此傳回 false.

欄位名稱與型態也是可以比照取得資料表名稱的方法寫成函式, 但資料表名稱是利用 DatabaseMetaData 物件取得, 而資料表欄位資訊則是利用 ResultSetMetaData 物件, 分別呼叫其 getColumnLabel() 與 getColumnTypeName() 方法即可取得欄位名稱與型態. 因此我們又在 JT.java 添加了四個方法 :

  public static String[] getFieldNames(String table) {
    ArrayList list=new ArrayList();
    try {
      ResultSet rs=runSQL("SELECT * FROM " + table);
      ResultSetMetaData md=rs.getMetaData();
      for (int i=1; i<=md.getColumnCount(); i++) {
        list.add(md.getColumnLabel(i));
        }
      }
    catch(Exception e) {System.out.println(e);}
    list.trimToSize();
    String[] array=list.toArray(new String[list.size()]);
    return array;           
    }

  public static String[] getFieldTypes(String table) {
    ArrayList list=new ArrayList();
    try {
      ResultSet rs=runSQL("SELECT * FROM " + table);
      ResultSetMetaData md=rs.getMetaData();
      for (int i=1; i<=md.getColumnCount(); i++) {
        list.add(md.getColumnTypeName(i));
        }
      }
    catch(Exception e) {System.out.println(e);}
    list.trimToSize();
    String[] array=list.toArray(new String[list.size()]);
    return array;           
    }

  public static Integer[] getFieldSizes(String table) {
    ArrayList list=new ArrayList();
    try {
      ResultSet rs=runSQL("SELECT * FROM " + table);
      ResultSetMetaData md=rs.getMetaData();
      for (int i=1; i<=md.getColumnCount(); i++) {
        list.add(md.getColumnDisplaySize(i));
        }
      }
    catch(Exception e) {System.out.println(e);}
    list.trimToSize();
    Integer[] array=(Integer[])list.toArray(new Integer[list.size()]);
    return array;
    }
  public static String getFieldType(String table, String field) {
      String[] names=getFieldNames(table);
      String[] types=getFieldTypes(table);
      String type="";
      for (int i=0; i<names.length; i++) {
        if (names[i].equals(field)) {type=types[i];break;}
        }
    return type;
    }

  public static int getFieldSize(String table, String field) {
    String[] names=getFieldNames(table);
    Integer[] sizes=getFieldSizes(table);
    int size=0;
    for (int i=0; i<names.length; i++) {
      if (names[i].equals(field)) {size=sizes[i];break;}
      }
    return size;
    }
  public static boolean findField(String table, String field) {
    if (Arrays.binarySearch(getFieldNames(table), field) < 0) {return false;}
    else {return true;}    
    }

getFieldSizes() 部分使用 Integer[] 包裹類別陣列來將 toArray() 傳回值強制轉型, 使用上與 int[] 一樣, 參考 :

# How to convert an ArrayList containing Integers to primitive int array?

範例如下 :

import java.util.*;
public class mytest {
  public static void main(String[] args) {
    JT.connectDB("test.mdb");
    System.out.println(JT.createTable("test")); 
    System.out.println(JT.addField("test","fid","AUTOINCREMENT PRIMARY KEY")); 
    System.out.println(JT.addField("test","fvarchar","varchar(9)")); 
    System.out.println(JT.addField("test","fchar","char(9)")); 
    System.out.println(JT.addField("test","fsmallint","smallint")); 
    System.out.println(JT.addField("test","fint","int")); 
    System.out.println(JT.addField("test","ffloat","float")); 
    System.out.println(JT.addField("test","fmemo","memo")); 
    System.out.println(JT.addField("test","ftext","text")); 
    System.out.println(JT.addField("test","fdate","date")); 
    System.out.println(JT.addField("test","fyesno","yesno"));
    System.out.println(Arrays.deepToString(JT.getFieldNames("test")));  
    System.out.println(Arrays.deepToString(JT.getFieldTypes("test")));  
    System.out.println(Arrays.deepToString(JT.getFieldSizes("test")));
    System.out.println(JT.findField("test","id"));   //輸出 flase
    System.out.println(JT.findField("test","fid"));  //輸出 true
    System.out.println(JT.getFieldType("test", "fid"));  //輸出 COUNTER
    System.out.println(JT.getFieldSize("test", "fmemo"));  //輸出 1073741823
    JT.closeDB();
    }
  }

[fid, fvarchar, fchar, fsmallint, fint, ffloat, fmemo, ftext, fdate, fyesno]
[COUNTER, VARCHAR, CHAR, SMALLINT, INTEGER, DOUBLE, LONGCHAR, VARCHAR, DATETIME, BIT]

[11, 9, 9, 6, 11, 22, 1073741823, 255, 19, 1]
false
true
COUNTER
1073741823

由此可知 ACCESS 的每一種資料類型代號, 比較特別的是自動增量欄位傳回值是 COUNTER, 而非 INTEGER; 而 MEMO 為 LONGCHAR, 其容量比 TEXT 的 VARCHAR 還要大; 適合儲存 remark 之類的長文句. YESNO 欄位類型為 BIT. 欄位型態與傳回值對照如下 :

欄位型態傳回值
 AUTOINCREMENT COUNTER
 VARCHAR VARCHAR
 CHAR CHAR
 SMALLINT SMALLINT
 INT INTEGER
 FLOAT DOUBLE
 MEMO LONGCHAR
 TEXT VARCHAR
 DATE DATETIME
 YESNO BIT

這些欄位中, 有六個屬於文字類型, 亦即在 INSERT 時其值要用單引號刮起來, 為了方便分辨是否為文字類型欄位, 有必要在 JT.java 添加一個函式 isTextField() 來處理 :

  public static boolean isTextField(String table, String field) {
    String type=getFieldType(table, field);
    boolean isText=type.equals("CHAR")||type.equals("VARCHAR")||
                   type.equals("LONGCHAR")||type.equals("BIT")||
                   type.equals("DATETIME");
    if (isText) {return true;}
    else {return false;}  
    }

範例如下 :

import java.util.*;
public class mytest {
  public static void main(String[] args) {
    JT.connectDB("test.mdb");
    System.out.println(JT.isTextField("test","fid"));        //輸出 false
    System.out.println(JT.isTextField("test","fmemo"));  //輸出 true
    JT.closeDB();
    }
  }

接下來要在 JT.java 添加一個新增紀錄的 insert() 函式, 這裡我用 HashMap 來儲存要寫入的資料, 其 key 為欄位名稱, value 則為欄位值, 不管欄位型態, 一律以字串輸入以配合 HashMap 須指定 K,V 型態之要求 :


  public static boolean insert(String table, HashMap records) {
    String[] fields=new String[records.size()];
    String[] values=new String[records.size()];
    int i=0;
    for (Object key:records.keySet()) {
      fields[i]=key.toString();  //儲存欄位名稱
      values[i]="'" + records.get(key) + "'";  //儲存值
      ++i;
      }
    String SQL="INSERT INTO " + table + "(" + join(fields) +
               ") VALUES(" + join(values) + ")";
    try {stat.executeUpdate(SQL);return true;}
    catch (Exception e) {System.out.println(e);return false;}
    }

這裡呼叫 HashMap 的 keySet() 方法會將 HashMap 的鍵以 Set 集合方式傳回, 然後用 for each 迴圈將 key 與 value 存入字串陣列, 最後呼叫 join() 來組成 SQL 指令. 關於 join() 參見下列實作 :

  public static String join(String[] arr) {
    return join(arr, ",");
    }
  public static String join(String[] arr, String g) {
    if (arr.length==0) {return "";}
    else {
      StringBuilder sb=new StringBuilder();
      sb.append(arr[0]);
      for (int i=1; i<arr.length; i++) {
        sb.append(g).append(arr[i]);
        }
      return sb.toString();
      }
    }

以下範例為在 users 資料表裡插入一筆紀錄 :

import java.util.*;
public class mytest {
  public static void main(String[] args) {
    JT.connectDB("test.mdb");
    JT.addField("users","age","int");  //添加 age 欄位
    HashMap records=new HashMap();
    records.put("name","Tony");
    records.put("gender","male");
    records.put("age","15");  //即使為 int 欄位亦以字串輸入
    System.out.println(JT.insert("users",records)); //輸出 true, 插入成功
    JT.closeDB();
    }
  }

這裡 HashMap 的每一個欄位與其值是用 put() 方法儲存的, 感覺好像不如直接用 runSQL() 方法來得乾脆, 就當作是 HashMap 用法的練習好了. 注意, HashMap 須指定 K-V 型態, 不能用如下宣告 :

HashMap() records=new HashMap();

否則會出現如下錯誤 :

Note: mytest.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.

參考 :
# Java 中 map 集合的用法    
# Java中Set的使用
# Java HashMap question?

本來還想再從 Javascript 函式庫移植 search, update, delete 過來的, 但覺得不僅實作麻煩, 而且不如直接使用 SQL 簡潔快速, 還可以保持對 SQL 語法的熟悉, 因此就此打住, 以後很閒時再說. 

還有兩個函式需要移植到 JT.java, 就是刪除欄位與資料表的 dropField() 與 dropTable() :

  public static boolean dropField(String table, String field) {
    String SQL="ALTER TABLE " + table + " DROP " + field;
    try {stat.executeUpdate(SQL);}
    catch (Exception e) {System.out.println(e);return false;}
    return true;
    }
  public static boolean dropTable(String table) {
    try {stat.executeUpdate("DROP TABLE " + table);}
    catch (Exception e) {System.out.println(e);return false;}
    return true;
    }

我們就以刪除上例中 users 資料表新增的 age 欄位與刪除 books 資料表為例, 範例如下 :

import java.util.*;
public class mytest {
  public static void main(String[] args) {
    JT.connectDB("test.mdb");
    System.out.println(Arrays.deepToString(JT.getFieldNames("users")));
    System.out.println(JT.dropField("users","age"));
    System.out.println(Arrays.deepToString(JT.getFieldNames("users")));
    System.out.println(Arrays.deepToString(JT.getTables()));
    System.out.println(JT.dropTable("users"));
    System.out.println(Arrays.deepToString(JT.getTables()));
    JT.closeDB();
    }
  }

執行結果如下 :

[name, id, gender, age]  (刪除 age 欄位前)
true
[name, id, gender]  (欄位 age 被刪除了)
[books, test, users, zins]  (刪除資料表 users 之前)
true
[books, test, zins]  (資料表 users 被刪除了)

OK, 常用的 ACCESS 函式庫到此大功告成, 明天要向 putty 進軍了.

# 
How to find whether a ResultSet is empty or not in Java?


2014年4月5日 星期六

Java 複習筆記 : 字串 (續)

Java 常用函式庫改寫得差不多了, 找了一下 PHP 與 Python 的書, 看看還有哪些函式是值得移植到 Java 的. 以下是字串處理時可能會用到的幾個好用的函式 :

# Java 複習筆記 : 字串 (前一篇)

1. nl2br() :

此函式來自 PHP, 用來將 text 檔的換行符號 (Windows 是 "\r\n" 或 "%0D%0A") 改為 HTML 的換行標籤 <br>, 這在多行純文字內容改成網頁格式上挺常用的, 特別是在寫 JSP 或 Servlet 時常用. 參見 :

# PHP 字串函式庫
# http://php5.idv.tw/documents/mou/string/nl2br.html

依據 PHP 的文件說明, 此函式會在字串中所有的 newline 字元前面插入 <BR> 後傳回字串. 這裡為求簡化, 直接把換行字元替換掉. 但換行字元因作業系統而異, Windows 是 "\r\n", 而 Linux 是 "/n", 若寫死的話就有跨平台問題, 必須利用 System.getProperty("line.separator") 來取得系統之分行字元為妥, 範例如下 :

public class mytest {
  public static void main(String[] args) {
    System.out.println(nl2br("0\n1\n2\n3\n4"));  
    System.out.println(nl2br("0\r\n1\r\n2\r\n3\r\n4"));
    }
  public static String nl2br(String str) {
    String nl=System.getProperty("line.separator");
    return str.replaceAll(nl,"<br>");
    }
  }

執行結果 :

0
1
2
3
4
0<br>1<br>2<br>3<br>4

這是在 Windows 上的輸出, 第一次呼叫 nl2br() 的字串是以 "\n", 在 Windows 上執行 getProperty("line.separator") 傳回的是 "\r\n", 因此都不匹配, 沒有轉換成功; 第二次呼叫用 "\r\n" 分行就匹配了.


# 換行的另類寫法

2. quotemeta() :

此函式來自 PHP, 功能是將下列特殊字元 (即正規表示法之特殊字元) 加上反斜線 (用來脫逸) :

. \\ + * ? [ ^ ] ( $ )

範例如下 :

public class mytest {
  public static void main(String[] args) {
    System.out.println(quotemeta("1(2)3.+4*"));  //輸出 1\(2\)3\.\+4\*
    }
  public static String quotemeta(String str) {
    String nl=System.getProperty("line.separator");
    return str.replaceAll("([^a-zA-z0-9])","\\\\$1");
    }
  }

3. chr() 與 ord() :

此兩函式來自 PHP, chr() 是傳入一個整數, 傳回其 ASCII 字元; 而 ord() 則是相反, 傳入字元, 傳回其 ASCII 碼 (整數). 這在 Java 只要用型態轉型即可 :

public class mytest {
  public static void main(String[] args) {
    System.out.println(ord('A'));  //輸出 65
    System.out.println(chr(65));   //輸出 'A'
    }
  public static int ord(char ch) {
    return (int)ch;
    }
  public static char chr(int i) {
    return (char)i;
    }
  }

4. ljust() 與 rjust() :

這兩個好用的方法來自 Python, API 詳見 :

# Python String rjust() Method
# Python String ljust() Method

ljust() 是用來在字串前面填充指定字元, 使其總字串長度正規化為指定長度, 而 rjust() 則是在字串後面填充, 此二方法在調整字串輸出為向左或向右對齊時很有用. 例如一個二維陣列 :

    String[][] users={{"Id","Name","Gender","Age","Email"},
                               {"1","Amy","Female","12","amy@gmail.com"},
                               {"2","Peter","Male","14","peter@gmail.com"},
                               {"3","Kelly","Female","16","kelly@gmail.com"}};

如果直接列印的話, 用 tab 隔開就會很整齊, 例如下面的範例  :

public class mytest {
  public static void main(String[] args) {
    String[][] users={{"Id","Name","Gender","Age","Email"},
                              {"1","Amy","Female","12","amy@gmail.com"},
                              {"2","Peter","Male","14","peter@gmail.com"},
                              {"3","Kelly","Female","16","kelly@gmail.com"}};
    for (int i=0; i<users.length; i++){
      for (int j=0; j<users[i].length; j++){
        System.out.print(users[i][j] + "\t");
        }
      System.out.println("");
      }
    }
  }

執行結果 :


似乎 \t 就搞定了, 但是那是因為我們把最長的字串 Email 放在最後面的緣故, 如果 Email 後面還有欄位, 那麼就會無法對齊了, 例如在 Email 後面再加上 Tel 欄位 :

public class mytest {
  public static void main(String[] args) {
    String[][] users={{"Id","Name","Gender","Age","Email","Tel"},
                               {"1","Amy","Female","12","amy@gmail.com","123"},
                               {"2","Peter","Male","14","peter@gmail.com","456"},
                               {"3","Kelly","Female","16","kelly@gmail.com","789"}};
    for (int i=0; i<users.length; i++){
      for (int j=0; j<users[i].length; j++){
        System.out.print(users[i][j] + "\t");
        }
      System.out.println("");
      }
    }
  }



可見 Tel 標題與其內容沒有對齊, 這是因為 \t 長度是 8 個空格的緣故, 當內容大於 8 個字元時, 它會空一格往後推. 為了解決這種對齊問題, 雖然可以使用兩個 tab (\t\t) 保留 16 個字元空間, 但是 \t 均為向左對齊, 無法向右對齊, 而且一個 tab 單位為 8 格, 缺乏彈性. 解決之道是用 ljust() 或 rjust() 調整每個欄位寬度 :

public class mytest {
  public static void main(String[] args) {
    String[][] users={{"Id","Name","Gender","Age","Email","Tel"},
                               {"1","Amy","Female","12","amy@gmail.com","123"},
                               {"2","Peter","Male","14","peter@gmail.com","456789"},
                               {"3","Kelly","Female","16","kelly@gmail.com","123456789"}};
    for (int i=0; i<users.length; i++){
      for (int j=0; j<users[i].length; j++){
        if (j==4) {System.out.print(ljust(users[i][j],20));}
        else if (j==5) {System.out.print(ljust(users[i][j],10));}
        else {System.out.print(rjust(users[i][j],6));}
        }
      System.out.println("");
      }
    }
  public static String ljust(String str, int width) {
    return ljust(str, width, ' ');
    }
  public static String ljust(String str, int width, char fill) {
    int len=str.length();
    if (width <= len || width < 0) {return str;}
    else {
      StringBuilder sb=new StringBuilder();
      for (int i=0; i<width-len; i++) {sb.append(fill);}
      sb.append(str);
      return sb.toString();
      }
    }
  public static String rjust(String str, int width) {
    return rjust(str, width, ' ');
    }
  public static String rjust(String str, int width, char fill) {
    int len=str.length();
    if (width <= len || width < 0) {return str;}
    else {
      StringBuilder sb=new StringBuilder(str);
      for (int i=0; i<width-len; i++) {sb.append(fill);}
      return sb.toString();
      }
    }
  }

可見 Email 與 Tel 欄位利用 ljust() 在左方填充空格達成向右對齊目的, 並分別固定占用 20 格與 10 格; 其餘欄位用 rjust() 在右方填充空格達成向左對齊目的.

5. bin2hex() 與 hex2bin() :

此兩函式來自 PHP, bin2hex() 用來將二進位整數轉成 16 進位數, 而 hex2bin() 則反過來將 16 進位轉成二進位.

# http://www.360doc.com/content/12/0913/14/203871_235898869.shtml



~未完待續~

參考 :

# http://stackoverflow.com/questions/6318710/javascript-equivalent-of-perls-q-e-or-quotemeta
# How to escape text for regular expression in Java
# Perl to Java regular expressions tutorial
# is Java 10x more verbose than Python (LOC)?

Facebook 帳號取回

今日收到 FB 通知, 說帳號已幫我取回, 要我重設密碼, 順便設定帳號保全, 跟 Gmail 一樣兩重保護, 只要有人從我沒用過的電腦登入 FB 都會以簡訊傳送臨時密碼確認.




經過重設密碼後, 就按右上角的倒三角, 選設定, 然後左左上角選帳號保安, 設定登入通知與登入許可 (輸入手機號碼) :



雖然不常使用 FB, 但是身為程式員, 竟然疏忽個人資安, 實在說不過去. 基本上必須做到 :
  1. Google 找不到我的個人資料
  2. Google 找不到我的照片
  3. 所有帳號需兩重保全
  4. 所有密碼使用 function(方言+英語) 轉換加密
優秀情報員要永遠保持浮潛姿勢, 而且要經常對自己實施反偵蒐, 呵呵.

2014年4月3日 星期四

台灣大預付卡 vs 中華電如意卡

我家申請的行動電話預付卡原本全都是中華的如意卡, 前年在上海工作的同學返台過年前, 囑我先幫他申請一張預付卡, 我就幫他申請了台灣大的, 後來卻說不用了, 只好給菁菁用.

菁菁有手機後常打給同學, 結果很快地數百元儲值就用完了. 我檢查手機發現台灣大的預付卡沒有簡訊回報, 所以菁菁對每通電話花了多少元沒概念, 當然就不會節制了. 姐姐的中華如意卡, 每打一通電話都會收到簡訊回報通話費多少, 餘額剩多少, 姐姐很省, 她都長話短說, 我看她的回報簡訊很多都是通話不足 2 元, 但中華電信卻花 2 元簡訊回報, 可見中華電信還是比較貼心, 民營業者將本求利, 根本捨不得花這些成本 (還是系統比較爛做不到呢?).

另外一點還可以看出對用戶貼不貼心, 台灣大預付卡儲值一律 300 元起跳, 中華電在 7-11 儲值也是 300 元, 但若到服務中心櫃台儲值, 則最低只需 100 元. 像姐姐通話少就很適合, 因為每半年儲值一次, 她都用不到 100 元, 如果每次儲 300 元就太多了, 姐姐目前儲值就累積了 1200 元 (那是之前不知道, 都在 7-11 儲值的結果). 台灣大就算你去它的直營店, 一律 300 元.

其他就不用說了, 登山的朋友最清楚.

我打算明天去中華看看有沒有好記的號碼, 乾脆換過新號碼, 因為預付卡若攜碼到中華, 還要付 240 元携碼費給台灣大, 不划算.



2014年4月2日 星期三

Java 複習筆記 : 陣列 (續)

類別庫 java.util 有許多好用工具, 其中 java.util.Arrays 是處理陣列的好幫手. 以下繼續整理常用的陣列方法. 參考 :

# http://docs.oracle.com/javase/7/docs/api/java/util/Arrays.html 

1. 清除陣列中重複的元素 :

在我的常用 Javascript 函式庫中有一個改寫自 JSLIB 網站的陣列函式 unique_array() , 可以將陣列中的重複元素清除掉. 參考 :

# http://www.jslab.dk/library/Array
# Get unique values from a list in python

範例如下 :

import java.util.*;
import java.text.*;
public class mytest {
  public static void main(String[] args) {
    String[] arr={"楊過","小龍女","公孫止","楊過"};
    arr=unique(arr);
    System.out.println(Arrays.toString(arr));  //輸出 [楊過, 小龍女, 公孫止]
    System.out.println(Arrays.asList(arr));     //輸出 [楊過, 小龍女, 公孫止]
    System.out.println(Arrays.deepToString(arr));  //輸出 [楊過, 小龍女, 公孫止]
    for (String el:arr) {System.out.print(el + " ");}  //輸出 楊過, 小龍女, 公孫止
    }
  public static String[] unique(String[] arr) {
    Set temp=new LinkedHashSet(Arrays.asList(arr));
    return temp.toArray(new String[temp.size()]);
    }
  }

在此例的 unique() 方法中, 利用 Arrays.asList() 把陣列轉成 List, 再轉成 LiskedHashSet 物件, 從而消除了重複的元素, 因為集合之元素不允許重複, 然後再轉回陣列.

同時, 我們也展示了顯示陣列內容的四種方式 : 即呼叫 toString(), asList(), deepToString() 三種方法, 以及傳統的 for each 迴圈. 其中 toString() 可用於任何資料型態陣列, 而 deepToString() 則只能用於物件陣列.

如果將 toString() 用在物件陣列, 將輸出各物件的參考記憶體代號, 而不是各物件的內容. 例如下列範例所示 :

import java.util.*;
public class mytest {
  public static void main(String[] args) {
    String[] arr1={"楊過","小龍女","公孫止","楊過"};
    int[] arr2={97,85,78,100,98};
    Object[] arr={arr1,arr2};
    System.out.println(Arrays.toString(arr));
    System.out.println(Arrays.deepToString(arr));
    }
  }

輸出結果為 :

[[Ljava.lang.String;@290fbc, [I@1c80b01]
[[楊過, 小龍女, 公孫止, 楊過], [97, 85, 78, 100, 98]]

可見物件陣列必須用 deepToString() 才會顯示陣列內各物件之內容.

2. 複製陣列 :

類別 java.util.Arrays 有一個 copyOf() 方法可以複製陣列實體, 此方法對 8 種原始資料型別都實作了方法多載, 其 API 如下 :

T copyOf(T arr, int newLength)

其第二個參數為新陣列的長度, 若比原陣列長, 則多出來的元素在整數與實數會填 0, 布林會填 false, 字元填空字元, 字串或物件則填 null. 若比原陣列短, 則多出來的部分會被截掉, 如下列範例所示 :

import java.util.*;
public class mytest {
  public static void main(String[] args) {
    int[] i={0,1,2,3,4,5,6,7,8,9};
    char[] c={'0','1','2','3','4','5','6','7','8','9'};
    String[] s={"0","1","2","3","4","5","6","7","8","9"};
    boolean[] b={true,true,true,true,true,true,true,true,true,true};
    int[] i1=Arrays.copyOf(i,11);
    int[] i2=Arrays.copyOf(i,9);
    char[] c1=Arrays.copyOf(c,11);
    String[] s1=Arrays.copyOf(s,11);
    boolean[] b1=Arrays.copyOf(b,11);
    System.out.println(Arrays.toString(i1));
    System.out.println(Arrays.toString(i2));
    System.out.println(Arrays.toString(c1));
    System.out.println(Arrays.toString(s1));
    System.out.println(Arrays.toString(b1));
    }
  }

執行結果 :

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0]
[0, 1, 2, 3, 4, 5, 6, 7, 8]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9,  ]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, null]
[true, true, true, true, true, true, true, true, true, true, false]

可見陣列複製到較長之新陣列會自動補足 (padding), 較短則直接截掉 (truncating). 截掉的情形其實就是取得子陣列, 如同 Javascript 的 slice() 方法.

另外一個複製陣列的方法是 copyOfRange, 用來複製陣列中一段連續元素成為新陣列, 這也是取得子陣列的方法, 其 API :

T copyOfRange(T arr, int from, int to)

其中 from 與 to 為要複製的元素索引範圍, 但注意不包含 to 索引元素, 如下列範例所示 :

import java.util.*;
public class mytest {
  public static void main(String[] args) {
    int[] i={0,1,2,3,4,5,6,7,8,9};
    int[] i1=Arrays.copyOfRange(i,3,7);
    System.out.println(Arrays.toString(i));    //輸出 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
    System.out.println(Arrays.toString(i1));  //輸出 [3, 4, 5, 6]
    }
  }

可見雖指定複製範圍為索引 3~7, 但實際上是只到 6, 也就是 7 的前面. Java 中只要用到索引範圍, 皆是如此, 即不包含終點.

事實上, 陣列本身有一個繼承自 Object 類別的 clone() 方法可用來複製整個陣列, 但是它不能像 copyOfRange() 那樣只複製局部元素, 也就是它無法取得子陣列, 範例如下 :

import java.util.*;
public class mytest {
  public static void main(String[] args) {
    int[] i={0,1,2,3,4,5,6,7,8,9};
    int[] i1=i.clone();  //複製陣列
    System.out.println(Arrays.toString(i));    //輸出 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
    System.out.println(Arrays.toString(i1));  //輸出 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
    }
  }

這個 clone() 用來複製整個多維陣列或是多維陣列裡面的某個列陣列都很好用, 範例如下 :

import java.util.*;
public class mytest {
  public static void main(String[] args) {
    int[][] i={{1,2,3},{4,5,6},{7,8,9}};
    int[][] i1=i.clone();    //複製二維陣列
    int[] i2=i[2].clone();  //複製列陣列
    System.out.println(Arrays.deepToString(i));    //輸出 [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
    System.out.println(Arrays.deepToString(i1));  //輸出 [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
    System.out.println(Arrays.toString(i2));           //輸出 [7, 8, 9]
    }
  }

注意, 二維陣列其實是物件陣列 (列陣列之陣列), 因此必須用 deepToString() 才能顯示其元素內容.

還有一個複製陣列的方法, 是利用 System.arraycopy() 方法, 其 API 參見 :

# http://docs.oracle.com/javase/7/docs/api/java/lang/System.html

arraycopy(Object src, int srcPos, Object dest, int destPos, int length)

參數 src 為來源陣列, dest 為目標陣列, srcPos 為欲複製之來源陣列起始索引, destPos 為目標陣列複製之起始索引, length 為複製長度. 可見起始索引可以不同, 很有彈性. 但注意, length 不可超過範圍, 否則執行時期將會出現 java.lang.ArrayIndexOutOfBoundsException 錯誤. 範例如下 :

import java.util.*;
public class mytest {
  public static void main(String[] args) {
    int[] i={0,1,2,3,4,5,6,7,8,9};
    int[] j={10,11,12,13,14,15,16,17,18,19};
    System.out.println(Arrays.toString(i));  //輸出 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
    System.out.println(Arrays.toString(j));  //輸出 [10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
    System.arraycopy(i,2,j,0,5);
    System.out.println(Arrays.toString(j));  //輸出 [2, 3, 4, 5, 6, 15, 16, 17, 18, 19]
    }
  }

可見目標陣列有 5 個元素被取代了.

3. 元素填滿  :

Arrays 還有一個 fill() 方法可以將整個陣列以指定元素填滿, 範例如下 :

import java.util.*;
public class mytest {
  public static void main(String[] args) {
    int[] i={0,1,2,3,4,5,6,7,8,9};
    System.out.println(Arrays.toString(i));  //輸出 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
    Arrays.fill(i,0);
    System.out.println(Arrays.toString(i));  //輸出 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
    }
  }

的確會取代原先之元素內容, 但我還想不到這方法何時會用到.

4. 陣列反轉  :

所謂反轉即前軍作後軍, 後軍作前軍也, 索引 0 元素改排在最後面, 最後一個元素變索引 0.

這可以利用 for 迴圈來達成, 範例如下 :

import java.util.*;
public class mytest {
  public static void main(String[] args) {
    int[] a={0,1,2,3,4,5,6,7,8,9};
    reverse(a);
    System.out.println(Arrays.toString(a));  //輸出 [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
    }
  public static void reverse(int[] a) {
    for (int i=0; i<a.length; i++) {
      int tmp=a[i];
      a[i]=a[a.length-i-1];
      a[a.length-i-1]=tmp;
      }
    }
  }

此演算法原理就是以陣列中心為軸, 從頭依序處理各索引, 先把該元素放在暫存變數, 然後將對應元素搬過來, 再把暫存變數中的此元素搬到對應元素位置, 完成交換動作. 如果要處理字串陣列, 那就必須方法多載.

import java.util.*;
public class mytest {
  public static void main(String[] args) {
    String[] a={"楊過","小龍女","瑛姑"};
    reverse(a);
    System.out.println(Arrays.toString(a));  //輸出 [瑛姑, 小龍女, 楊過]
    }
  public static void reverse(String[] a) {
    for (int i=0; i<a.length; i++) {
      String tmp=a[i];
      a[i]=a[a.length-i-1];
      a[a.length-i-1]=tmp;
      }
    }
  }

5. 陣列串接 (concate) : 

將兩陣列串接後產生新陣列, 可以用 ArrayList 集合來做, 用 Collections 類別的 addAll() 來串接, 如下所示 :

import java.util.*;
public class mytest {
  public static void main(String[] args) {
    String[] a={"楊過","小龍女","瑛姑"};
    String[] b={"郭靖","黃蓉"};
    String[] c=concate(a,b);
    System.out.println(Arrays.toString(c));  //輸出 [楊過, 小龍女, 瑛姑, 郭靖, 黃蓉]
    }
  public static String[] concate(String[] a,String[] b) {
    ArrayList c=new ArrayList(a.length + b.length);
    Collections.addAll(c, a);
    Collections.addAll(c, b);
    return c.toArray(new String[c.size()]);
    }
  }


參考 :

# How do I reverse an int array in Java?
# How to concatenate two arrays in Java?

Java 複習筆記 : 日期與時間 (續)

昨晚整理完 Java 的常用日期時間相關類別之應用, 發現還有幾個常用的 javascript 版本的函式, 改寫如下 :

1. 判斷閏年 :

閏年的判斷演算法有一個公式, 在 Javascript 是這樣 :

return !(year % 4) && (year % 100) || !(year % 400) ? true : false;

即年份是 4 與 100 的公倍數, 或者是 400 的倍數. Java 處理閏年有現成的 GregorianCalendar 類別可用, 只要先建立 GregorianCalendar 物件, 再呼叫其 isLeapYear() 即可 :

GregorianCalendar g=new GregorianCalendar();

API 參見 :

# http://docs.oracle.com/javase/7/docs/api/java/util/GregorianCalendar.html

如下列範例所示 :

import java.util.*;
import java.text.*;
public class mytest {
  public static void main(String[] args) {
    System.out.println(isLeapYear());          //輸出 false (今年 2014 非閏年)
    System.out.println(isLeapYear(2012));  //輸出 true
    }
  public static int getYear() {
    SimpleDateFormat sdf=new SimpleDateFormat("yyyy");
    return Integer.parseInt(sdf.format(new Date()));
    }
  public static boolean isLeapYear() {
    return isLeapYear(getYear());
    }
  public static boolean isLeapYear(int year) {
    GregorianCalendar g=new GregorianCalendar();
    return g.isLeapYear(year);
    }
  }

此例中我使用了方法多載實現參數預設值功能, 無參數時預設為今年; 同時也用了 SimpleDateFormat 類別來求取今年年份.

2. 一年中的第幾天  :

一年 365/366 天, 若指定日期, 如何得知此為一年中的第幾天 ? 這可以利用 String 類別的 format() 方法, 將 Date 物件傳進去當地二參數, 利用第一參數 "%tj" 這個格式化字串傳回該日期是一年中的第幾天字串, 再轉成整數即可, 詳參 String 與 Formatter 兩類別 :

# http://docs.oracle.com/javase/7/docs/api/java/lang/String.html
# http://docs.oracle.com/javase/7/docs/api/java/util/Formatter.html

範例如下 :

import java.util.*;
import java.text.*;
public class mytest {
  public static void main(String[] args) {
    System.out.println(getDayOfYear());  //輸出 92 (今天是 2014-04-02)
    System.out.println(getDayOfYear("2012-01-01")); //輸出 1
    System.out.println(getDayOfYear("2012-12-31")); //輸出 366
    }
  public static String getDate() {
    SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd");
    return sdf.format(new Date());  //傳回今日的日期字串
    }
  public static int getDayOfYear() {
    return getDayOfYear(getDate());
    }
  public static int getDayOfYear(String date) {
    Date d=new Date();
    SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd");
    try {d=sdf.parse(date);}  //將傳入日期剖析為 Date 物件
    catch (ParseException e) {e.printStackTrace();}
    return Integer.parseInt(String.format("%tj",d));  //轉成一年中的第幾天
    }
  }

此例中同樣使用方法多載處理預設值, 無參數傳入時表示求今日為一年中第幾日.

3. 一年中的第幾周 : 

在 Calendar 類別中有一個 Calendar.WEEK_OF_YEAR 可以求得指定日期是一年中的第幾周. 先將指定日期字串分解為整數陣列後, 傳入 Calendar.set() 方法中將日曆設為指定日期, 即可由 Calendar.WEEK_OF_YEAR 取得答案.

# http://docs.oracle.com/javase/7/docs/api/java/util/Calendar.html

範例如下 :

import java.util.*;
import java.text.*;
public class mytest {
  public static void main(String[] args) {
    System.out.println(getWeekOfYear()); //輸出 14 (今天是 2014-04-02)
    System.out.println(getWeekOfYear("2012-01-01"));  //輸出 1
    System.out.println(getWeekOfYear("2012-12-28"));  //輸出 52
    System.out.println(getWeekOfYear("2012-07-01"));  //輸出 27
    System.out.println(getWeekOfYear("2014-01-01"));  //輸出 1
    System.out.println(getWeekOfYear("2014-12-28"));  //輸出 1
    System.out.println(getWeekOfYear("2014-07-01"));  //輸出 27
    }
  public static String getDate() {  //傳回今日日期字串
    SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd");
    return sdf.format(new Date());
    }
  public static int[] parseDate(String date) { //將日期字串轉成陣列
    String[] d=date.split("-");
    int[] dt=new int[3];
    dt[0]=Integer.parseInt(d[0]);
    dt[1]=Integer.parseInt(d[1]);
    dt[2]=Integer.parseInt(d[2]);
    return dt;
    }
  public static int getWeekOfYear() {
    return getWeekOfYear(getDate());  //傳入今日日期字串
    }
  public static int getWeekOfYear(String date) {
    int[] d=parseDate(date);  //將日期字串轉成陣列
    Calendar c=Calendar.getInstance();
    c.set(d[0],d[1]-1,d[2]);   //設定日曆物件之日期 (注意月份要減 1)
    return c.get(Calendar.WEEK_OF_YEAR);
    }
  }

此例中, 2012-12-28 為 2012 年的第 52 周; 但 2014-12-28 卻是第 1 周, 這是因為年尾的幾天有可能已被歸入下年度的第一週.

4. 一個月中的第幾周 :

與上面一年中的第幾周類似, 也是利用 Calendar 類別的 Calendar.WEEK_OF_MONTH 欄位來求解, 範例如下, 僅最後一行改變而已 :

import java.util.*;
import java.text.*;
public class mytest {
  public static void main(String[] args) {
    System.out.println(getWeekOfMonth());
    System.out.println(getWeekOfMonth("2014-04-01"));
    System.out.println(getWeekOfMonth("2014-04-30"));
    }
  public static String getDate() {
    SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd");
    return sdf.format(new Date());
    }
  public static int[] parseDate(String date) {
    String[] d=date.split("-");
    int[] dt=new int[3];
    dt[0]=Integer.parseInt(d[0]);
    dt[1]=Integer.parseInt(d[1]);
    dt[2]=Integer.parseInt(d[2]);
    return dt;
    }
  public static int getWeekOfMonth() {
    return getWeekOfMonth(getDate());
    }
  public static int getWeekOfMonth(String date) {
    int[] d=parseDate(date);
    Calendar c=Calendar.getInstance();
    c.set(d[0],d[1]-1,d[2]);
    return c.get(Calendar.WEEK_OF_MONTH);
    }
  }

5. 取得星期名稱 : 

有時在記錄到 log 或資料庫時, 會順便紀錄星期幾, 在上一篇曾用 switch 來處理, 現在改用陣列來做, 同時改良功能, 加入語言參數, 可以選擇傳回繁體中文的 "星期日" 或英文的 "Sunday". 範例如下 :

import java.util.*;
import java.text.*;
public class mytest {
  public static void main(String[] args) {
    System.out.println(getDayOfWeek());  //輸出 星期三 (今天 2014-04-02)
    System.out.println(getDayOfWeek("2014-04-01"));  //輸出 星期二
    System.out.println(getDayOfWeek("2014-04-01","en"));  //輸出 Tuesday
    }
  public static String getDate() {
    SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd");
    return sdf.format(new Date());
    }
  public static int[] parseDate(String date) {
    String[] d=date.split("-");
    int[] dt=new int[3];
    dt[0]=Integer.parseInt(d[0]);
    dt[1]=Integer.parseInt(d[1]);
    dt[2]=Integer.parseInt(d[2]);
    return dt;
    }
  public static String getDayOfWeek() {
    return getDayOfWeek(getDate(),"zh");
    }
  public static String getDayOfWeek(String date) {
    return getDayOfWeek(date,"zh");
    }
  public static String getDayOfWeek(String date, String language) {
    int[] d=parseDate(date);
    Calendar c=Calendar.getInstance();
    c.set(d[0],d[1]-1,d[2]);
    String[] zh=
      {"星期日","星期一","星期二","星期三","星期四","星期五","星期六"};
    String[] en=
      {"Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"};
    String w=null;
    if (language.equals("zh")) {w=zh[c.get(Calendar.DAY_OF_WEEK)-1];}
    else {w=en[c.get(Calendar.DAY_OF_WEEK)-1];}
    return w;
    }
  }

這裡要注意, Calendar.DAY_OF_WEEK 欄位之值為 1 (Sunday)~7 (Saturday), 而陣列索引為 0~6, 因此存取時要減 1. 第二參數 language 預設是 "zh" (中文), 若指定為 "en" 則傳回英文.

6. 取得月份名稱 :

如果給予日期, 要如何取得月份的中英文名稱? 跟上面取得星期名稱類似, 只要利用 Calendar.MONTH 欄位來判斷即可, 範例如下 :

import java.util.*;
import java.text.*;
public class mytest {
  public static void main(String[] args) {
    System.out.println(getMonthName());  //輸出 四月 (今天 2012-04-02)
    System.out.println(getMonthName("2014-12-01"));  //輸出 十二月
    System.out.println(getMonthName("2014-12-01","en"));  //輸出 December
    }
  public static String getDate() {
    SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd");
    return sdf.format(new Date());
    }
  public static int[] parseDate(String date) {
    String[] d=date.split("-");
    int[] dt=new int[3];
    dt[0]=Integer.parseInt(d[0]);
    dt[1]=Integer.parseInt(d[1]);
    dt[2]=Integer.parseInt(d[2]);
    return dt;
    }
  public static String getMonthName() {
    return getMonthName(getDate(),"zh");
    }
  public static String getMonthName(String date) {
    return getMonthName(date,"zh");
    }
  public static String getMonthName(String date, String language) {
    int[] d=parseDate(date);
    Calendar c=Calendar.getInstance();
    c.set(d[0],d[1]-1,d[2]);
    String[] zh={"一月","二月","三月","四月","五月","六月","七月","八月",
                 "九月","十月","十一月","十二月"};
    String[] en={"January","February","March","April","May","June","July",
                 "August","September","October","November","December"};
    String m=null;
    if (language.equals("zh")) {m=zh[c.get(Calendar.MONTH)];}
    else {m=en[c.get(Calendar.MONTH)];}
    return m;
    }
  }

參考 :

# JAVA String.format 方法使用介紹

6. 計算時間差 : 

這在比較兩個演算法的運算效能時會用到, System 套件有一個 currentTimeMillis() 方法可以用來計算時間差, 它會傳回當時的時間戳 (毫秒, 長整數), 因此只要在演算法開始前後呼叫此方法, 再將兩值相減, 就可以得到所經過時間的毫秒值.

long start=System.currentTimeMillis();
......
long end=System.currentTimeMillis();
System.out.println(Arrays.toString(a));

# http://docs.oracle.com/javase/7/docs/api/java/lang/System.html

下列範例要利用此方法配合 Runtime 類別的 freeMemory() 方法來比較 String 與 StringBuffer 在執行速度與記憶體佔用的情形. Runtime 類別參見 :

# http://docs.oracle.com/javase/7/docs/api/java/lang/Runtime.html

import java.util.*;
public class mytest {
  public static void main(String[] args) {
    String str="Java";
    long start=System.currentTimeMillis();
    long mem1=Runtime.getRuntime().freeMemory();
    for (int i=0; i<1000; i++) {str += "!";}
    long end=System.currentTimeMillis();
    long mem2=Runtime.getRuntime().freeMemory();
    System.out.println("Elapsed : " + (end-start) + " ms");
    System.out.println("Used : " + (mem1-mem2) + " bytes");
    }
  }

在我的電腦執行結果 :

Elapsed : 16 ms
Used : 39000 bytes

注意, 這裡迴圈若太大會導致執行時期錯誤 :  java.lang.OutOfMemoryError: Java heap space.

# Java 内存溢出(java.lang.OutOfMemoryError)的常见情况


另外, 常用到的時間差計算是, 當使用者登入後顯示 "您上次登入是 3 天 14 時 23 分 45 秒前", 這通常是比對兩組時間字串, 例如 "2014-04-02 13:01:23" 與 "2013-02-09 21:34:00" 的時間差.

import java.util.*;
public class mytest {
  public static void main(String[] args) {
    String dt1="2012-01-01 00:00:00";
    String dt2="2013-01-01 00:00:00";
    System.out.println(timeDiff(dt1,dt2)); //輸出 31622400000
    System.out.println(getDuration(dt1,dt2)); //輸出 366 日 0 時 0 分 0 秒
    System.out.println(getDuration(dt1,dt2,"en")); //輸出 366 days 0 hours 0 minutes 0 seconds
    }
  public static int[] parseDateTime(String datetime) {
    String[] a=datetime.split(" ");
    String[] d=a[0].split("-");
    String[] t=a[1].split(":");
    int[] dt=new int[6];
    dt[0]=Integer.parseInt(d[0]);
    dt[1]=Integer.parseInt(d[1]);
    dt[2]=Integer.parseInt(d[2]);
    dt[3]=Integer.parseInt(t[0]);
    dt[4]=Integer.parseInt(t[1]);
    dt[5]=Integer.parseInt(t[2]);
    return dt;
    }
  public static long timeDiff(String datetime1, String datetime2) {
    int[] d=parseDateTime(datetime1);
    Calendar c1=Calendar.getInstance();
    c1.set(d[0],d[1]-1,d[2],d[3],d[4],d[5]);
    d=parseDateTime(datetime2);
    Calendar c2=Calendar.getInstance();
    c2.set(d[0],d[1]-1,d[2],d[3],d[4],d[5]);
    return c2.getTimeInMillis()-c1.getTimeInMillis();
    }
  public static String getDuration(String dt1, String dt2) {
    return getDuration(dt1, dt2, "zh");
    }
  public static String getDuration(String dt1, String dt2, String language) {
    long ms=Math.abs(timeDiff(dt1, dt2));
    long d=ms/(1000 * 60 * 60 * 24); 
    long h=(ms%(1000 * 60 * 60 * 24))/(1000 * 60 * 60); 
    long m=(ms%(1000 * 60 * 60))/(1000 * 60); 
    long s=(ms%(1000 * 60))/1000;
    String str=null;
    if (language.equals("en")) {
      str=d + " days " + h + " hours " + m + " minutes " + s + " seconds";
      }
    else {str=d + " 日 " + h + " 時 " + m + " 分 " + s + " 秒";}
    return str;
    }
  }

執行結果 :

31622400000
366 日 0 時 0 分 0 秒
366 days 0 hours 0 minutes 0 seconds

此例中, timeDiff() 方法會傳回 dt2-dt1 毫秒差值, 故有正有負, 但在轉換為日時分秒前會先取絕對值再計算.

參考 :

# Java中将毫秒数转为*天*小时*分*秒 
#  請問如何算得兩個時間的時間差