2007年11月26日 星期一

修改 Ubuntu runlevel 設定

今天想要把原本 run XWindow 的 Ubuntu 改為 run console 就好了,這樣系統資源的耗費可以省一點。

結果發現,在 Ubuntu 中竟然沒有 /etc/inittab 這個檔案........(忘記從哪一版開始沒有,不過我用 6.10 就已經沒有了!)

因此 google 的一下修改方式,找到以下這邊討論串:

由於解說蠻簡單的,也就不翻譯了,直接節錄修改方式:
1./etc/inittab.
You're right. It's gone. It left as of Edgy Eft, as Ubuntu now manages start-up services with "upstart."
The file does exist in pre-Edgy Ubuntu's, and other flavors of Linux, so you were not crazy to look there .

2. default runlevel in Ubuntu

In redhat/suse, the default run level is 5. I came from RH, and like you, expected the same in Ubuntu. After the "upstart" transition, the notion of runlevels is supposedly gone, but if forced to choose, the default run level is actually 2 in Ubuntu, not 5.

2. You don't want GNOME to start at boot, how?

So yes, in other flavors of Linux, you'd probably just change run levels, because on run level would have X, and another wouldn't. But in Ubuntu, *all* run levels start GNOME, so here's an easier way:

In /etc/rc2.d, you will see a file S13gdm. This starts the GNOME system.
Simply change that file to K13gdm.

# sudo mv S13gdm K13gdm

(It's possible the number between the "S" and the "gdm" will be slightly different than 13. That's OK.)

This leaves the package installed, but just not started at boot.

To start X later, run:

# sudo /etc/init.d/gdm start.
看了以上的修改方式,就是不讓 GDM 啟動就對了!

不過應該還有更好的修改方式,之後有看到再說吧!!

2007年11月25日 星期日

The C Programming Language 讀後整理(1) - A Tutorial Introduction

變數型態(type)介紹

在 C 中,所有的變數在使用前都必須宣告其型態(type),以下是 C 提供的基本型態
  1. char:character,為單一 byte
  2. short:short integer
  3. int:integer
  4. long:long integer
  5. float:floating point
  6. double:doule-precision floating point

而每種變數型態都有其大小的範圍:(以下用個簡單範例來說明)
#include <stdio.h>
#include <limits.h> //要取得變數型態的大小範圍值,必須 include 此 header file

main()
{
//short(short integer) 的範圍 => 16 bits
printf("%d\n", SHRT_MIN); //-32768
printf("%d\n", SHRT_MAX); // 32767

//int(interget) 的範圍
printf("%d\n", INT_MIN); //-2147483648
printf("%d\n", INT_MAX); // 2147483647
}
以上是在 Linux 的環境上的執行結果,每個 int 的長度為 32 bits;而各種變數型態在不同的 OS 平台上可能會有不同的長度,這可能需要注意一下。


使用不同型態的變數,撰寫方式也必須不同

以下用一個華氏(fahrenheit)轉攝氏(celsius)範例來說明:
#include <stdio.h>

main()
{
float fahr, celsius;
int lower, upper, step;

lower = 0;
upper = 300;
step = 20;

//以 while 的方式撰寫
fahr = lower;
while(fahr <= upper) {
//由於指定變數型態為 float,前面相除的兩個變數必須以 float 方式去撰寫
//若沒有,會被 compiler 視為 int 而遭截斷為 0
celsius = (5.0 / 9.0) * (fahr - 32.0);
//以比較漂亮的方式印出結果
printf("%3.0f\t%6.1f\n", fahr, celsius);
fahr = fahr + step;
} //end while
//以 while 的方式撰寫 =============== End

//以 for 的方式撰寫
for(fahr = 0 ; fahr <= upper ; fahr = fahr + step)
printf("%3.0f\t%6.1f\n", fahr, (5.0 / 9.0) * (fahr - 32.0));
//以 for 的方式撰寫 =============== End
}


Symbolic Constant 的使用

在程式中,如果直接使用某個數字,而未註明其用途,可能會讓其他看程式碼的人不知道原作者的用意為何。因此最好每個值都可以賦與其意義,讓使用上不會太過於突兀,其中一個方法就是使用「Symbolic Constant」。

而使用的語法如下:
#define [name] [replacement text]
以下用個簡單範例來說明:
#include <stdio.h>

//Symbolic Constant 的定義 (名稱通常以「大寫」表示)
#define LOWER 0
#define UPPER 300
#define STEP 20

main()
{
int fahr;
for(fahr = LOWER ; fahr <= UPPER ; fahr = fahr + STEP)
printf("%3d %6.1f\n", fahr, (5.0 / 9.0) * (fahr - 32));
}
如此一來,每個值都知道其代表意義為何了!!


Character Input and Output

在程式中,字元(字串)資料的處理是相當普遍的。

在 C 中,資料的 input 與 output 的功能是由 library 所提供,因此不太需要須考慮其實作的細節。

接著,文字資料的輸入或輸出方面,不論來源是鍵盤,或是檔案,都會被視為由許多字元所組合而成的 stream;而 text stream 則是一行一行分開,且有順序的字元,每一行都包含 0 或以上的字元,且在結尾帶有一個「n」(newline)字元。

File Copying

C standard library 中提供了許多存取單一字元的 function,其中最常用的就是 putchar() 以及 getchar() 兩個 function,以下有個簡單範例將來源資料一個一個字元取出並印出來:
#include <stdio.h>

main()
{
//為了確保變數可以存入所有字元,因此宣告型態為 int
int c;

//取得一個字元
while((c = getchar()) != EOF) { //未到檔案結尾(End of File),繼續執行
putchar(c); //輸出一個字元
printf("\n%s\n", "-----------------------");
} //end while
}
PS. 其實 EOF 的值為 -1

Character & Word & Line Counting

首先說明計算的規則:
  • Character:非 EOF 字元
  • Word:非空白、n、t .... 等字元
  • Line:n 字元

以下用個簡單範例說明:
#include <stdio.h>

#define IN 1 //在 word 內
#define OUT 0 //在 word 外

main()
{
//number of characters
//number of words
//number of lines
long nc, nw, nl;
nc = nw = nl = 0; //初始化變數

//保留輸入字元之用
int c;

//檢查是否在 word 中
int state;

while((c = getchar()) != EOF) {
++nc; //character count + 1

//檢查是否為 new line
if(c == '\n')
++nl;

//檢查字元是否為空白、new line 或是 tab
if(c == ' ' || c == '\n' || c == '\t')
state = OUT; //設定在 word 之外
else if(state == OUT) { //字元不是空白,也不是 new line,也不是 tab,但狀態正在 word 之外
//表示進入新的 word 中
state = IN; //設定為進入 word 中
++nw;
}
} //end while

printf("%ld %ld %ld\n", nc, nl, nw);
}
【註】輸入 Ctrl + D 即為丟出 EOF 訊息!


Arrays

看過上面範例後,如果要計算 0~9 各出現了幾次,要怎麼做呢? 有兩種作法:
  1. 宣告 10 個變數,分別儲存 0~9 所出現的次數
  2. 宣告 1 個長度為 10 的 array,每個 element 分別儲存 0~9 出現的次數
顯而易見的,使用 array 是比較好的選擇,以下用個範例程式來說明:
#include <stdio.h>

main()
{
int c, i, nwhite, nother;
int ndigit[10]; //宣告長度為 10 的 int array

nwhite = nother = 0;
for(i = 0 ; i < 10 ; i++)
ndigit[i] = 0;

while((c = getchar()) != EOF) {
if(c >= '0' && c <= '9') //檢查是否為數字
ndigit[c - '0']++; //由於 c 是 char 型態,因此透過此方式轉為 int
else if(c == ' ' || c == '\n' || c == '\t')
nwhite++;
else
nother++;
} //end while

printf("digits = ");
for(i = 0 ; i < 10 ; i++)
printf(" %d", ndigit[i]);
printf(", white space = %d, other = %d\n", nwhite, nother);
}


Function

function 的目的在於將程式封裝,讓使用 function 的人不需要知道 function 是如何達成其所宣告可達成的功能,只要拿來使用即可。

這個概念在 OO 設計中是相當重要的一部分,當然 C 也提供了這個功能,以下用個範例程式來說明:
#include <stdio.h>

//function 要在前面先進行宣告
//此處定義的參數名稱不會與以下的程式中的變數名稱有所衝突(僅宣告而已)
//甚至可以省略為 int power(int, int)
int power(int m, int n);

main()
{
int i;

for(i = 0 ; i < 10 ; i++)
printf("%6d %6d %6d\n", i, power(2, i), power(-3, i));
}


//回傳型態:int
//function 名稱:power
//參數 1:base
//參數 2:n
int power(int base, int n) //計算次方的 function
{
int i, p;

p = 1;
for(i = 1 ; i <=n ; i++)
p = p * base;
return p;
}


Arguments - Call by Value

在 C 中,所有 function 的參數都是 Call by Value,也就是呼叫 function 時,程式會將要傳入的變數複製一份再傳入,而不會用原本的變數,因此在 function 中對變數的任何操作,並不會影響到原本的變數。

對於 function 來說,參數值可以視為 funtion 開始就初始化並已經指定好值的變數。

PS. 其實參數名稱所代表的是 memory address,也就是之後會提到的 pointer


Character Array

在一般程式語言中很常用的 string 型態,在 C 中是沒有的;而在 C 中若是要表示 string,就必須要使用 char array。

除此之外,為了方便辨識 string 的結尾,C 會在 char array 最後方加入「n」與「0」 兩個字元,如下所示:

範例字串:this is a dog!
0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
t
h
i
s

i
s

a

d
o
g
!
\n
\0

表示上面那個 string,最少需要長度為 16 的 char 陣列,其中 index 為 14、15 個兩個字元,是 C 用來做 string 結尾表示之用,因此在處理 string 的時候,必須要注意這個地方。

以下用個範例來說明 char array 的使用:
#include <stdio.h>

#define MAXLINE 1000

//宣告 function
int getline(char[], int maxline);
void copy(char[], char[]);

main()
{
int len; //目前資料行的長度
int max; //目前最大的資料行長度
char line[MAXLINE]; //目前讀取的資料行
char longest[MAXLINE]; //最長資料行的儲存陣列

max = 0;
while((len = getline(line, MAXLINE)) > 0) { //取得每一行資料,並檢查長度是否大於0
if(len > max) { //資料長度比目前最長的還要更長
max = len; //修改最長資料長度變數
copy(longest, line); //最長資料陣列的資料也隨著更換
}

//印出最長的資料行
if(max > 0)
printf("%s", longest);
} //end while
}

//讀取每行的資料進入 s 陣列,並回傳陣列長度
int getline(char s[], int lim)
{
int c, i;

//資料行檢查條件:
//(1)尚未到達資料行最大行數(通常這不會到達)
//(2)未遇到 EOF 字元 (按下 Enter 或是 Ctrl+D 會送出 EOF 字元)
//(3)未遇到 new line 字元
for(i = 0 ; (i < lim - 1 && (c = getchar()) != EOF && c != '\n') ; i++)
s[i] = c;
if(c == '\n') {
s[i] = c;
++i;
}
s[i] = '\0'; //「n」與「0」為 string 結束的最後兩個字元
return i;
}

//將 from 陣列的內容複製到 to 陣列 (假設陣列夠大)
void copy(char to[], char from[])
{
int i;

i = 0;
while((to[i] = from[i]) != '\0') //檢查是否到了 string 結束字元
i++;
}
由上面的程式可知道,要印出 string 型態的變數,必須使用「%s」這個符號,而 C 也會自動將表示 string 結尾的兩個字元給忽略不印。


External Variables and Scope

這個部份要談到外部變數(external variables)以及其有效範圍。

在一般的 function 中,變數的有效範圍僅限於該 function 內,每次呼叫相同的 function ,裡面的變數值也不一定會相同;且 function 之間的變數是毫無相關的。

但若是有共同變數的需求呢? 此時就要借用 external variable 的方式來達成了!

其中,最重要的關鍵字即為「extern」的使用,以下用個範例來說明:
#include <stdio.h>

#define MAXLINE 1000

int max;
char line[MAXLINE];
char longest[MAXLINE];

//修改為使用 external variable 的版本,因此不需要參數了
int getline(void); //void 表示沒有參數(可省略)
int copy(void);

main() {
int len;
//extern int max; //使用 extern 關鍵字,就知道使用的是 external variable
//extern char longest[];

max = 0;
while((len = getline()) > 0) {
if(len > max) {
max = len;
copy();
}
} //end while

if(max > 0)
printf("%s\n", longest);
} //end main


//取得一行資料的內容
int getline(void) {
int c, i;
extern char line[]; //使用 extern 關鍵字,就知道使用的是 external variable

for(i = 0 ; i < MAXLINE - 1 && (c = getchar()) != EOF && c != '\n' ; i++)
line[i] = c;
if(c = '\n') {
line[i] = c;
i++;
}
line[i] = '\0';
return i;
} //end getline


//複製陣列
int copy(void) {
int i;
extern char line[], longest[]; //使用 extern 關鍵字,就知道使用的是 external variable

i = 0;
while((longest[i] = line[i]) != '\0')
i++;
} //end copy
外部變數的使用,有時候固然方便,不過也會因為不小心誤改了變數值而造成其他 function 執行上的錯誤。

就軟體工程的觀念來說,此種方式會讓 function 不夠模組化,因為必須依賴 external variable 才能正常運作,對於 reuse 來說並不會有什麼幫助,因此使用上還是要注意一下。

PS. 其實不使用 extern 關鍵字也是可以通過編譯的! (extern 整行拿掉)

一般來說,若有一群 external variable 需要定義被使用,通常會使用額外的檔案來進行變數的定義,然後在程式中把該檔案引用進來;而通常這一類的檔案都稱為 header,副檔名為「.h」,在檔案一開始就會被 include 進來。

就像每個程式一開始的「#include <stdio.h>」就是標準的用法,將常用的 I/O function 給引用進來。


參考資料
  1. The GNU C Library - A.5.2 Range of an Integer Type


2007年11月17日 星期六

Head First Java 讀後整理(15) - Distributed Computing

1、一般來說, object method 的呼叫都是在相同的 JVM 上進行的(亦即相同 heap 上的兩個 object 之間)

2、RMI(Remote Method Invoction) 技術可以使用 Socket 與 I/O 讓呼叫 remote object method 就像 local object method 一樣

3、RMI 是由 4 個部分所建構出來的:Client object、Client helper(stub) <=> Server helper(skeleton)、Server obejct

4、在 RMI 中 client helper 稱為「stub」;而 server helper 稱為「skeleton」

5、helper 的作用在於處理所有 client 與 server 間低階網路輸出入細節的部分,讓使用者感覺 client 與 server 都在 local 端

6、RMI 的執行流程:
(1) client object 所呼叫的為 stub 的 method,因為其偽裝為提供服務的 object,扮演著 proxy 的角色
(2) stub 收到請求後,會去連絡 skeleton,將資訊傳送過去
(3) 而 skeleton 透過 socket 收到 stub 的請求後,解析資訊並呼叫真正的服務(server object method)
(4) skeleton 取得回傳值後,就會把它包裝後送回給 stub
(5) stub 收到回傳資訊後,會將其解開並傳回給 client object

7、client object 看似在呼叫遠端的 method,但實際只是呼叫在 local 處理的 Socket 與 Stream 細節的 proxy

8、由於 RMI 是透過網路進行 method 的呼叫,因此都會拋出例外,因此必須處理

9、建構 RMI 的 5 個步驟:
//引用 RMI package
import java.rmi.*;

//制定繼承 Remote interface 的 interface
//用以宣告此 interface 與 RMI 有關
public interface MyRemote extends Remote {
//每個遠端呼叫都會被認為有風險,因此必須宣告會拋出例外
public String sayHello() throws RemoteException;
} //close MyRemote
  • 實作 Remote
//引用 RMI package
import java.rmi.*;
import java.rmi.server.*;

//1、必須繼承 UnitcastRemoteObject,才能成為遠端服務的務建
//2、必須實作 Remote interface
public class MyRemoteImplement extends UnicastRemoteObject implements MyRemote {

//3、撰寫無參數的 constructor,並宣告會拋出 RemoteException 例外
public MyRemoteImplement() throws RemoteException { } //close constructor

public static void main(String[] args) {
try {
//4、向 RMI registry 登記服務
MyRemote service = new MyRemoteImplement();
//幫服務命名(client 會靠名字查詢 registry),並向 RMI registry 登記
//RMI 會將 stub 做交換並擺 stub 加入 registry
Naming.rebind("RemoteHello", service);
} catch(Exception ex) { ex.printStackTrace(); }
} //close main

public String sayHello() {
return "Server says, 'Hey!'";
} //close sayHello
} //close MyRemoteImplement
  • 產生 stub 與 skeleton
# 書上說會產生 stub 與 skeleton 檔
# 但實際測試後,發現只有 stub 檔
# 只有產生出 MyRemoteImplement_Stub.class,但並不影響後面程式的執行
shell> rimc MyRemoteImplement
  • 執行 rmiregistry
shell> rmiregistry
  • 啟動服務(main() 會將物件初始化並把它登記給 RMI registry)
shell> java MyRemoteImplement

10、client 取得 stub object 的方式
(1) 用戶端查詢 RMI registry
(2) RMI registry 傳回 stub object
(3) client 就像取用真正服務一樣,呼叫 stub object 上的 method
import java.rmi.*;

public class MyRemoteClient {
public static void main(String[] args) {
new MyRemoteClient().go();
} //close main

public void go() {
try {
//(1)用戶端查詢 RMI registry
//(2)RMI registry 傳回 stub object
MyRemote service = (MyRemote)Naming.lookup("rmi://127.0.0.1/RemoteHello");
//(3)client 就像取用真正服務一樣,呼叫 stub object 上的 method
String s = service.sayHello();
System.out.println(s);
} catch(Exception ex) { ex.printStackTrace(); }
} //close go
} //close MyRemoteClient

11、RMI 中 client 與 server 的架構與所需的檔案 (來源:Head First Java)


12、servlet 是完全在 HTTP server 上跑的 Java 程式,是一種用來處理與 user 互動的網頁程式

13、要 compiler servlet 需要 servlets.jar 檔案,但是它不是標準函式庫的一部分,需要從其他處下載

14、JSP 與 servlet 的差異
JSP 為 Java Server Pages。實際上 web server 會將 JSP 轉換為 servlet。
而兩者的差別在於 servlet 是讓使用者寫出帶有 HTML 輸出的 class;而 JSP 剛好相反,讓使用者寫出帶有 Java 程式的網頁。

15、JSP 的優點在於更容易撰寫 HTML 的部分,而不會像 servlet 一樣需要用一些難以閱讀的 print 指令來產生 HTML

16、EJB server 具有一些 RMI 不會有的功能,例如:交易管理、安全性、資料庫、網路....等等

Head First Java 讀後整理(14) - Release Your Code

1、部署的選擇:
(1) 本機:整支程式都在使用者的電腦上以獨立、可攜的方式執行,並以可執行的 Jar 來部署
(2) 遠端:整個應用程式都在 server 端執行,client 可能透過 browser 等非 Java 型式來存取
(3) 兩者之間組合:應用程式被分散程在用戶本地系統跑的 client 端,連接到執行應用程式服務的 server 端

2、一般專案中會有 source(存放原始碼) 目錄以及 classes(存放 compiler 產生的 .class 檔) 目錄

3、將原始碼與 class 檔案分離,可簡化管理工作
#進入原始碼目錄
shell> cd MyProject/source

#使用「-d」參數指定 .class 檔案要存放的地方
shell> javac -d ../classes MyApp.java

# 編譯目錄中所有的 .java 檔
shell> javac -d ../classes *.java

#執行程式
shell> cd ../classes
shell> java MyApp

4、JAR 即為 Java Archive,是個 pkzip 格式的檔案,會把一堆 class 檔案包裝起來,只要用一個檔案就可以交差了

5、產生 JAR 的過程,需要建構 manifest 檔案,其帶有 JAR 的資訊,告知 JVM main() method 的所在位置

6、建構與執行 executable JAR 的步驟:
#確定所有的 class 檔案都在同一個目錄下
shell> cd MyProject

#建立 manifest 檔案來描述 main() method 的位置
# 內容如下:
# Main-Class: MyApp
shell> vi manifest.info

#執行 jar 工具來建構帶有所有 class 以及 manifest 資訊的 JAR 檔
shell> jar -cvmf manifest.info app1.jar *.class

# 執行 JAR
shell> java -jar app1.jar

7、JVM 必須要能找到 JAR,所以必須放在 classpath 下,或是目前工作所在目錄下

8、大部分完全在本機的 Java 應用程式都是以 executable JAR 來部署

9、package 可以預防名稱衝突,最好的方式是在前面加上反過來的 domain name
package net.twcic.myPackage;	//若有 import 則必須放在 import 之前

public class Hello {
public static void main(String[] args) {
System.out.println("Hello World");
} //close main
} //close Hello

10、使用 compiler 中的「-d」參數,cpmpiler 就會在 classes 目錄中產生相對應於 package 名稱的目錄結構

shell> javac -d ../classes Hello.java

11、一旦 class 被包進 package 中,就不能使用"簡寫"的名稱來呼叫它,必須指定 main() class 的完整名稱
shell> java net.twcic.myPackage.Hello

12、以 package 建構與執行 executable JAR 的步驟:
# 確定所有的 class 檔都放在 classes 目錄下正確對應的 package 結構中
shell> cd MyProject/classes

#建立 manifest 檔案來描述 main() method 的位置
# 內容如下:
# Main-Class: net.twcic.MyPackage.Hello
shell> vi manifest.info

#執行 jar 工具來建構帶有所有 class 以及 manifest 資訊的 JAR 檔
#最後只要指定 package 結構的最上層目錄即可
shell> jar -cvmf manifest.info packEx.jar net

13、使用 jar 中的「-tf」參數檢視 jar 檔案中的內容
#檢視 jar 檔案中的內容
shell> jar -tf packEx.jar
#以下為產生出來的內容
META-INF/ #META-INF 代表 META Information,jar 會自動建立此檔案與上面的目錄
META-INF/MANIFEST.MF # jar 不會直接將原本的 manifest 檔案加入,僅會使用其內容資訊
net/
net/twcic/
net/twcic/myPackage/
net/twcic/myPackage/Hello.class

14、若要解開 jar 檔案中的內容,可使用 jar 中的「-xf」參數

15、使用者能夠過點選某個連結來啟動 Java Web Start 的應用程式,一旦程式下載後,就可獨立於 browser 之外來執行(就跟網路下載的 executable JAR 一樣)

16、Java Web Start 的運作方式
(1) client 端點選某個網頁上得 JWS 應用程式的 link( .jnlp 檔)
(2) web server 收到 request 送出 .jnlp 檔案(not JAR)給 client 端的 browser
(3) browser 啟動 Java Web Start,接著 JWS 的 helper app 讀取 .jnlp 檔案,再向 server 要求 MyApp.jar
(4) web server 送出 .jar 檔
(5) JWS 取得 JAR 並呼叫指定的 main() 來起凍應用程式

17、jnlp 是個 XML 文件,裡面包含了許多應用程式相關的使用資訊
<?xml version="1.0" encoding="utf-8"?>
<!-- codebase 用來指定相關檔案的起始目錄 -->
<jnlp spec="0.2.1.0"
codebase="http://127.0.0.1/~test"
href="MyApp.jnlp">
<!-- 相對於 codebase 的路徑 -->

<information>
<!-- 以下這些 tag 都必須加入,不然程式可能會有問題 -->
<title>Test App</title>
<vender>godleon</vender>
<homepage href="index.html" />
<description>Head First Website demo</description>
<icon href="test.gif" />
<offline-allowed /> <!--設定成離線也可以執行-->
</information>

<resource>
<j2se version="1.3+" /> <!-- 指定需要 1.3 或之後版本的 Java -->
<jar href="MyApp.jar" /> <!-- executable JAR 名稱 -->
</resource>
</jnlp>

18、建構與部屬 Java Web Start 的步驟:
(1) 將程式製作成 executable JAR
(2) 撰寫 .jnlp 檔案
(3) 將 .jnlp 與 JAR 檔案放到網站上,並設定 MIME type 為「application/x-java-jnlp-file」
(4) 設定網頁連結到 .jnlp 檔案

19、applet 是網頁的一部分,不是單獨下載的;而 JWS 從網站下載後就可以不用 browser 離線執行

2007年11月15日 星期四

Head First Java 讀後整理(13) - Data Structures

1、ArrayList:在 Java 中最常被使用到的 collection structure,但是沒有排序的功能
但是還是可以使用 Collectins.sort() 來進行資料的排序

2、TreeSet:以排序過後的狀態保存,並可防止重複

3、HashMap:可用成對 name/value 來儲存或取出資料

4、LinkedList:針對經常 insert / delete 中間元素的行為,處理效率較好

5、HashSet:防止重複的集合,並可快速的尋找相符的元素

6、LinkedHashMap:類似 HashMap,但可記住元素的 insert 順序,也可以設定成依照原入上次的存取先後進行排序

7、Generic 代表更好的型別安全性
幾乎以 generic 寫的程式都跟處理 collection 有關,透過 generic 可以讓 compiler 在編譯期間發現型別的錯誤,而不需等到 Run-Time 的時候才會發現。
舉例來說,在 JDK 5.0 之前,collection 都是用 Object 在處理的! 因此把綿羊 object 送到老虎 collection 中是不會有問題的。

8、使用 generic 關鍵的兩個部分:
(1) class 的宣告
(2) 新增元素的 method 的宣告

//E 部分會用所宣告與建構的真正型別來取代
//ArrayList 為 AbstractList 的 subclass,所以指定給 ArrayList 的型別會自動用在 AbstractList 上
public class ArrayList<E> extends AbstractList<E> implements List<E> {
//E 用來指示可以加入 ArrayList 的元素型別
public boolean add(E 0) {
//......
} //close add
} //close ArrayList

9、<T exntends Animal> 與 <Animal> 是不一樣的
首先說明,以下兩個語法都是合法的,但是意義不同!
//可使用任何一種 Animal 的 ArrayList
public <T extends Animal> void takeThing(ArrayList<T> list)
//只能使用 Animal 的 ArrayList
public void takeThing(ArrayList<Animal> list)

10、以 generic 的觀念來說,「extends」代表 extend 或 implement
//Comparable 是個 interface
//因此這可以讀作 => T 必須是個有 implements 過 Comparable interface 的型別
public static <T extends Comparable<? super T>> void sort(List<T> list)

11、List 排序的方式:
(1) 呼叫單一參數的 sort(List o),代表由 List 元素上的 compareTo() 來決定順序。

(2) 呼叫 sort(List o, Comparator c) 代表不會呼叫 List 元素的 compareTo(),而會使用 Comparator 的 compare(),代表 List 元素不需要實作 Comparable interface。

12、判斷 object 是否相同
(1) 參考相等性:heap 上同一個 object 的兩個參考
(2) 物件相等性:heap 上兩個不同 object,但在意義上是相同的

13、HashSet 檢查重複方式:檢查 hashcode() 的值是否相同,再使用 equal() 檢查是否相同
因為 hashcode() 使用的 Hash Function,有可能會讓多個 object 傳回相同的 hash code,這是 Hash Function 的問題。

14、如果兩個 object 有相同的 hashcode,不一定是相同;但若兩個 object 相同,則 hashcode 一定是相同的

15、若 equal() 被 override 過,則 hashcode() 也必須被 override

16、equal() 的預設行為是執行 == 的比較,也就是測試兩個參考是否指向 heap 上相同的 object

17、TreeSet 的元素必須是 Comparable,而要使用 TreeSet必須滿足下面其中一個條件:
(1) collection 中的元素必須有實作 Comparable interface
(2) 使用有實作 Comparator interface 的 class 所產生的 object,作為 TreeSet constructor 的參數

18、如果需要用名稱來取得值,選用 Map collection 是比較推薦的作法

19、array 的型別在 Run Time 檢查,但 collection 的型別檢查只會發生在編譯期間
所以 Dog 與 Cat 不能當作參數加入 ArrayList<Animal> 中。

除非 collection 換成 ArrayList<? extends Animal>;但若此方式用在 method 的參數時,只能讀取 collection 中的元素而不能新增,因為 compiler 會避免此種行為造成程式在 Run Time 時發生錯誤。

2007年11月13日 星期二

Head First Java 讀後整理(12) - Make a Connection

1、要讓 client 與 server 可以互相連結運作,流程如下
  • client 藉由建立 Socket 來連接 server
  • 一旦建立連線,client 可以從 socket 取得低階 output stream
  • 透過 InputStreamReader 與 Socket 的 input stream 來讀取 server 的文字資料,並建構 BufferedReader之鏈結
Socket s = new Socket("127.0.0.1", 4242);
//使用 InputStreamReader 與來自 Socket 的 input stream 結合以取得 server 資料
//再使用 BufferedReader 與之鏈結,進行文字資料擷取
BufferedReader reader = new BufferedReader(new InputStreamReader(s.getInputStream()));
//讀取來自 server 的文字資料
String advice = reader.readLine();

2、Socket 是個代表兩台機器之間網路連線的 object

3、要建構 Socket 連線,必須知道 IP address 與 port number

4、InputStreamReader 用來將 byte 轉換成字元,主要作為 BufferedReader 與 Socket input stream 的橋樑

5、從 Socket 上讀取資料,使用 BufferedReader;寫資料到 Socket 上,使用 PrintWriter
6、Server 的運作方式
  • server 應用程式對特定 port 建構出 ServerSocket
  • client 對 server 應用程式建構 Socket 連線
  • serevr 建構出與 client 通訊的新 Socket (新 Socket 與 ServerSocket 所用的 port number 並不相同,因此 ServerSocket 可以空出來等待其他 client)

7、建構 PrintWriter 與 socket output stream 進行鏈結,便可用 print() 與 println() 來送 String 給 server

8、server 使用 ServerSocket 來等待 client 對特定 port 的要求

9、當 ServerSocket 接到要求時,會產生一個新的 socket 連線來接受 client 的要求

10、在 Java 中,每個 thread 都有獨立的執行空間,每個 thread 都需要任務,而任務是實作 Runnable interface 的實體

11、Thread 初始化之後,在呼叫 start() 之前都處於新建構的狀態

12、呼叫 Thread object 的 start() 之後,會建構出新的執行空間,並處於可執行狀態等待 scheduler 安排執行

13、當 scheduler 選擇某個 thread 之後,它就處於執行中的狀態(單處理器的機器只能有一個執行中的 thread)

14、有時候 thread 會因為某些原因被 block 住

15、scheduler 不能保證任何執行時的時間與順序,因此不要想要透過 sleep 的方式調整 thread 執行順序

16、用 setName() 幫 thread 命名通常是用在 debug 之用

17、兩個或以上的 thread 存取 heap 上相同的物件,可能會發生 concurrency 的問題

18、使用「synchronized」關鍵字修飾 method,可以防止多個 thread 同時進入同一個 object 來執行相同 method

19、object 就算有多個 synchronized method,也還是只有一個 key
一旦某個 thread 進入該 object 的 synchronized method,其他 thread 就無法進入該 object 上的任何 synchronized method

2007年11月12日 星期一

Head First Java 讀後整理(11) - Saving Objects

1、儲存狀態的選擇,大致有兩個大方向
  • 只有自己寫的 Java 程式會用到這些資料 => 加以序列化(serialization)
  • 資料需要被其他程式引用 => 存於固定格式的資料媒體中(例如:試算表、資料庫 ..... etc)

2、serialize 後的檔案室很難一般人閱讀的,但比純文字更容易讓程式恢復原本的狀態,也比較安全

3、Serialization 的步驟

4、stream 要兩兩連接才能做出有意義的事情 - 其中一個表示連結,另一個則是要被呼叫
以上面 object serialization 為例,由於 FileOutputStream 是很低階的(連結 stream 通常都很低階),可以直接寫入 byte 至檔案中。

但一般的作法不會直接寫入 byte;此時為了保持良好的 OO 設計,需要以物件層次的觀點來寫入,因此需要高階的 stream,即為上面的 ObjectOutputStream,由 ObjectOutputStream 將 object 轉換為 byte,再由 FileOutputStream 將 byte 寫入至檔案。

5、當 object serialization 進行時,除了該 object 的 instance variable 會進行 serialize,所有被參考的 object 也會進行 serialize,而且是全自動化

6、如果要讓 class 能夠被 serialize,就必須實作 Serializable interface(裡面沒有 method 需要實作,僅為標示之用)
import java.io.*;
//沒有 method 需要實作,只是告訴 JVM 它可以被 serialize
public class Box implements Serializable {
//serialize 後,這兩個值會被保留起來
private int width;
private int height;

public void setWidth(int w) {
width = w;
} //close setWidth
public void setHeight(int h) {
height = h;
} //close setHeight

public static void main(String[] args) {
Box myBox = new Box();
myBox.setWidth(50);
myBox.setHeight(20);

try {
FileOutputStream fs = new FileOutputStream("foo.ser");
ObjectOutputStream os = new ObjectOutputStream(fs); //設定連結 stream
os.writeObject(myBox);
os.close();
} catch(Exception ex) {
ex.printStackTrace();
}
} //close main
} //close Box

7、Serialize 是全有或全無的
若物件參考兼有任何一個 class 沒有實作 Serializable interface,就無法進行 serialize,因為 JVM 必須確定 object 狀態能夠完整被保留

8、如果某個 instance variable 不能或不應該 serialize,就將其標示為 transient
transient 的 instance variable,不論儲存當時的值為何,回復之後都會變成 null(object reference) 或是預設值(primitive type)。

但這樣可能會有問題,因此解決方式可以有兩種:
(1) 當 object 被帶回來時,重新初始化 instance variable
(2) 若 transient instance variable 的值很重要,就需要將它的值保存下來,將 object 帶回來的時後才有辦法復原

import java.net.*;
class Chat implements Serializable {
transient String currentID; //此 instance variable 就不會被 serialize 了!
String userName; //這個 instance variable 會被 serialize
//........
//........
}


9、Deserialization 的步驟
  • 建構 FileInputStream
  • 建構 ObjectInputStream
  • 讀取 object (每次讀出一個 object,讀取順序與當初寫入順序是相同的,次數超過會拋出例外)
  • 轉換 object type (回傳的 type 為 Object,因此必須轉換)
  • 關閉 ObjectInputStream

10、object deserialization 的過程
  • object 從 stream 中讀取出來
  • JVM 透過儲存的資訊判斷 object 的 class type
  • JVM 會嘗試尋找並載入 object 所屬的 class,若找不到或無法載入,則會拋出例外
  • 新的 object 會被配置在 heap 上,但 constructor 不會被執行(執行就會初始化了)
  • 若繼承樹中有包含不可 serialize 的 superclass,則從第一個不可 serialize 的 superclass 開始,全部的 constructor 都會執行(r即為初始化)
  • object 的 instance variable 會被還原為 serialize 時的狀態,transient instance variable 會被指派為 null、0、false .... etc

11、正常情況下,不會將 class 也進行 serialize,而會使用 RMI(Remote Method Invocation) 來解決

12、static variable 不會被 serialize,因為所有 object 都是共用同一份 static variable 的值

13、File 物件可以作的事情:(不僅是單純的 file 而已)

14、buffer 的奧妙在於使用了會提升程式運作的效率
藉由 BufferedWriterFileWriter 的連結,BufferedWriter 可以暫存一堆資料,當 buffer 滿的時後再寫入磁碟,這樣可以減少磁碟操作的次數,將強效率

15、使用「serialVersionUID」可避免因為修改 class 而造成 deserialize 時發生無法正確還原的錯誤 (亦即版本控制)
若要查詢 serialVersionUID,可以使用 JDK 提供的「serialver」程式來查詢

2007年11月11日 星期日

Head First Java 讀後整理(10) - Work On Your Swing

1、幾乎所有 GUI 的 component 都來自(extend)於 javax.swing.JComponent

2、建構 GUI 的四個步驟
  • 建構 windows(JFrame)
  • 建構 component
  • 將 component 加到 frame 上
  • 顯示出來

3、Layout Manager 是個與特定 component 相關連的 Java 物件
Layout Manager 用來控制所關連 component 上所攜帶的其他 component。

假設某個 frame 帶有 panel,而 panel 中帶有三個 button,則 panel 的 Layout Manager 就控制著 button 的大小與位置;而 frame 的 Layout Manager 則控制著 panel 的大小與位置;最後,button 因為沒有攜帶其他 component,因此不需要 Layout Manager。

4、不同的 layout manager 對 component 都會有不同的安置策略,但還是保有其設計的彈性
5、世界三大首席管理員:BorderLayout、FlowLayout、BoxLayout
  • BorderLayout:此 manager 會將背景 component 分割成五個區域(EAST、WEST、NORTH、SOUTH、CENTER),每個被管理的的區域只能放一個 component,為 frame 預設的 layout manager。
  • FlowLayout:此 manager 行為跟文書處理程式的版面配置方式差不多,每個 component 都會依照理想的大小呈現,由左到右依照順序排列(有可能會換行),為 panel 預設的 layout manager。
  • BoxLayout:此 manager 讓每個 component 跟 FlowLayout 的方式排列,不過是以垂直的方式。

6、可以藉由呼叫 setLayout() 來改變 panel 的 layout manager

2007年11月10日 星期六

Head First Java 讀後整理(9) - A Very Graphic Story

1、JFrame 是個代表畫面上的 window 物件,一旦建構出 JFrame 後,就可以把 widget 加到上面
import javax.swing.*;   //這個 package 是一定要 import 的!
public class SimpleGui1 {
public static void main(String[] args) {
//這就是 JFrame 啦!!
JFrame frame = new JFrame();
//這就是其中一個 widget
//還有其他像是 JRadioButton、JCheckBox、JLabel、JList .... etc
JButton button = new JButton("Click Me");
//設定關閉視窗後的動作
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//將 widget 加入 JFrame 中
frame.getContentPane().add(button);
//設定視窗大小並顯示
frame.setSize(300, 300);
frame.setVisible(true);
}
}

2、要讓 Button 在使用者按下後執行特定工作,需要:
  • 被按下時要執行的 method
  • 能夠偵測到按鈕被按下的方法(也就是按鈕的感應裝置)

3、取得與處理使用者動作事件的過程,稱為 Event-Handling (事件處理)

4、Listener interface 是介於 listener(程式設計師) 與事件來源(例如:按鈕)間的橋樑
換句話說,如果想要知道按鈕的事件,就必須實作 listen 該事件的 interface。例如:想要接收 MouseEvent 就必須實作 MouseListener interface;要接收 WindowEvent 就要實作 WindowListener interface。

5、取得按鈕的 ActionEvent
  • 實作 ActionListener interface
  • 向按鈕登記 (告訴按鈕你要 listen 事件)
  • 定義事件處理的 method (其實就是實作 interface 上的 method)
import javax.swing.*;   //繪製圖形用的 package
import java.awt.event.*; //處理事件用的 package
//實作 ActionLister interface
//表示 SimpleBui1B 是個 ActionListener(事件只會通知有實作 ActionListener 的 class)
public class SimpleGui1B implements ActionListener {
JButton button;

public static void main(String[] args) {
SimpleGui1B gui = new SimpleGui1B();
gui.go();
}
public void go() {
JFrame frame = new JFrame();
button = new JButton("Click Me");
button.addActionListener(this); //向按鈕登記
frame.getContentPane().add(button);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(300, 300);
frame.setVisible(true);
}

//實作 ActionListener 中定義的 method,用來處理事件用
//按鈕會以 ActionEvent object 作為 argument 來呼叫此 method
public void actionPerformed(ActionEvent e) {
button.setText("I've been clicked!!");
}
}

6、Listener、來源、與事件
  • 程式設計師的工作是要實作出 Listener interface 並向按鈕登記
  • 身為事件來源的按鈕,必須接受登記、取得使用者的事件,並在使用者採取動作時呼叫 Listener 的事件處理 method
  • 當呼叫 Listener interface 上的 method 時,帶有事件資訊的 Event object 會被當作 argument 傳入

7、在 GUI 上加東西的方法有三種
  • 在 frame 上放置 widget(在 javax.swing package 中有很多 widget 可用)
  • 在 widget 上繪製 2D 圖形(使用 graphic 物件來繪製圖形)
  • 在 widget 上繪製 JPEG 圖

8、paintComponent(Graphics g) {} 中的 g,其實是個 Graphics2D object
由於 g 是個 Graphics2D 的物件,因此可以將其轉換回來,以使用更多 Graphics2D class 所提供的功能

9、當有多個 widget 會觸發事件,就必須要有多個 Listener 進行處理,此時使用 Inner class 是很好的解決之道
因為 inner class 可以自由存取 outer class 的內容,即使定義為 private 也是沒有問題

10、inner class 的時體一定會綁在 outer class 的實體上
任何 inner class 都僅能存取它所屬的 outer class,無法對其它的 class 進行存取

11、Inner class 提供了在一個 class 中實作「同一個 interface」多次的機會