JDBC初級應用實例(三)
發表人:wzh2352802 | 發表時間: 2007年一月23日, 13:53
再談JDBC連結
為什么要反復談JDBC連結,因為所以JDBC的性能,最主要的就是JDBC連結,而SQL語句的
優化,和JAVA編程無關,你的一個查詢語句的效率,是你對于SQL語法的使用技巧,這一方
面你就可請教DBA,而不是來看我這種程序設計者的文章.
我們已經知道,取得數據庫連結,有幾種層次的實現方法,一是直接得到物理連結,而是通
過傳統的連結池(沒有多大區別),三是通過java的擴展包javax.sql.DataSource得到連結
句柄,對于上面兩種,沒有什么可以多說的,對于DataSource,我們再深入一些.
一般來說,DataSource是容器本身作為一個JDNI查找的對象返回出來的,也就是說要依賴
容器進行配置,而如果一個100%的應用程序(Application),比如基于swing的App,我根本
不需要運行容器,那我如何取得DataSource對象?這時可能要你自己寫基于DataSource的
連結池了(是不是有些太深入了?要做就做高手,SUN能做我們就能做).
如果自己要實現DataSource,要清楚幾個關系,即DataSource中返回的Connection是一個連
結的句柄,它要和實際的物理連結關連,這些實際的物理連結就是PooledConnection,我們
叫它池中的連結,可以通過實現ConnectionPoolDataSource,從中得到PooledConnection,
這部分本來是廠商實現的,但這部份實現和傳統的連結池沒有什么大的區別,主要是從
ConnectionPoolDataSource中得到PooledConnection的物理連結,但如何從PooledConnection
中getConnection(),返回給用戶.這部分實現就是DataSource實現的性能高低的關鍵,一般
來說,我們可以先把一個物理連結PooledConnection和多個客戶連結相關連來增加性能,也
就是一個PooledConnection本身再作為一個工場的種子,通過一個PooledConnection再返回
多個Connection,說白了就是多個Connection的請求通過一個PooledConnection傳遞給數據庫.
只要用戶調用Connection的close()方法,就打斷這個Connetion與PooledConnection的關聯而
讓PooledConnection可以和新的Connection進行關聯.
JDBC事務
JDBC1開始,就支持本地事務,所謂要地事務,就是在一個連結中的多個操作可以作為一個事務
過程來提交.注意,只要你使用conn.setAutoCommit(false);方法就隱式地打開了一個事務.當
事務被commit或abort時,隱含的是打開了一個新的事務.
另外,當一次事務被commit或abort,PreparedSattement和CallableStatement綁定的結果集全
部被關閉,而普通的Statement綁一的結果集將被維持.
在處理多個操作時:
conn.setAutoCommit(false);
Statement st1 = conn.createSatatement(sql1);
Statement st2 = conn.createSatatement(sql2);
Statement st3 = conn.createSatatement(sql3);
Statement st4 = conn.createSatatement(sql4);
st1.executeXXXXX();
st2.executeXXXXX();
st3.executeXXXXX();
st4.executeXXXXX();
在這里,我們要么把四個操作一起回滾,或一起提交,但如果我們認為,1-3的操作是要求一致完
成,而4的操作在一至三完成時再完成.在經前的版本中我們要把它們分開在兩次事務中,但在
JDBC3.0以后,我們可以利用conn.setSavepoint() 來得到一個SavePoint來對同一事務中不同階
段進行斷點保存.然后可以在任何斷點上進行提交和回滾.同時我們還可以利用
conn.setTransactionIsolation()來設置隔離級別.
要注意的是,對事務的支持要看數據庫的具體實現.如果數據庫本身不支持事務,那么以上的操作
都是無效的,可以從 DatabaseMetaData中查詢數據庫對事務的支持情況.
畢竟,本地事務功能并不是很強,而如果不是編程人員對SQL語句傳入錯誤,那么在一次連結中
多個操作只完成部份的機率并不容易發生(當然有時還會發生的,要不本地事務就不會產生了).
其實,JDBC事務最重要的是分布式事務,即同時操作不同的連結,可能是同物理庫的不同空間,也
可能是同一主機的不同數據庫或不同主機的多個數據庫.這就很難保證每個操作都是成功的,發
生操作不一致的機會太多了,可以說如果不在事務中測試你就無法相信操作的一致性.所以分布
式事務是JDBC的重要技術.
在下一節我們重點介紹JDBC分布式事務.
JDBC初級應用實例(二)
發表人:wzh2352802 | 發表時間: 2007年一月23日, 13:53
本來想繼續談JDBC的高級連結方式,事務模式.但發現關于大對象存儲有很多人在問,所以
先來插入一節關于大對象存儲的內容,然后再接著原來的思路寫下去.
JDBC的大對象存儲聽起來復雜,其實如果你明白了原理以后,就非常簡單,網上有關這方面的
教材很少,而SUN的文檔中,我從1.2開始看到一在仍然是錯誤的,不知道寫文檔的人長腦子沒
有,就那幾行代碼你試試不就知道了,這么多次重抄下來還是錯誤的.
大對象分類:一般來說,大對象分為:大的文本對象,比如一個很長的文本(請你要注意什么是
文本文件,什么是二進制文件)文件,或者是你定義的一個長字符串,比如你定義了:
String s = "我們要去吃飯了......................然后睡覺!";
從吃飯到睡覺中間省略了實際的10000000000000字,雖然你不會真的定義這么稱的String,但
有時會從什么地方得到這樣的String,要寫到數據庫中.
另一種就是大的二進制對象,象執行文件,圖象文件等,注意,word,excel,ppt這些"帶格式"的文
檔都應該以二進制對象存儲.
一般來說,數據庫如果支持大對象存儲,會有這幾種類型的SQL數據類型:
BLOB,CLOCB,NLOB,也有的數據數只有一種BLOB,基本上是這樣的:BLOB用來存放二進制文件,而
CLOB用來存放文本文件,NLOB是對多字節文本文件支持.假如你的文本文件是純英文的,放在
BLOB中當然可以,也就是說它是以byte格式存儲的,而多字節是以CHAR格式存儲的.
同樣對于這幾種類型的文檔,有幾種相對應的存取方式:
setter:
利用PreparedStatement的setXXX方法,
setAsciiStream()方法用于寫入一般的文本流.setBinaryStream()方法用于寫入二進制流
而setUnicodeStream()用于寫好UNICODE編碼的文本,與此相對應的ResultSet中三個getter方法
用于取回:getAsciiStream(),getBinaryStream(),getBinaryStream().
對于文件本身,要把它作為一個流,只要new InputStream(new FileInputStream("文件路徑"))
就可以了,但對于大的String對象,你不會寫入文件再轉換成輸入流吧?
new StringBufferInputStream(String s),記住了.
JDBC2以后提供了java.sql.BLOB對象,我不建議大家使用它,一是很麻類,二是容易出錯,要先插
入一個空的BLOB對象,然后再填充它,實在沒有必要,直接setXXX就行了,我試過,至少mysql,
oracle,sql server是可以直接set的.
好了,我們先看一個例子如何寫入文件到數據庫:
數據結構:
create table test(
name varchar(200),
content BLOB
);
File f = new File("a.exe");//先生成File對象是為了取得流的長度.FileInputStram可以直接
//傳入文件路徑
InputStream in = new InputStream(new FileInputStream(f));
PreparedStatement ps = conn.prepareStatement("insert into test (?,?)");
ps.setString(1,"a.exe");
ps.setBinaryStream(2,in,(int)f.length());
ps.executeUpdate();
f的長度一定要做從long到int的轉換,SUN的文檔中好幾版都沒有改過來.就這么簡單,當然,不同的
數據庫存本身要設置它允許的最大長度,MYSQL默認只能傳1M的文件,要修改參數原能存更大的文件.
如果要從數庫中取得文件:
PreparedStatement ps = conn.prepareStatement("select * from test where name=?");
ps.setString(1,"a.exe");
ResultSet rs = ps.executeQuery();
if(rs.next()){
InputStream in = rs.getBinaryStream("content");
}
得到in對象后,你可以進行任何處理,寫向文件和寫向頁面只是out對象不同而已:
寫向文件:
DateOutputStream out = new DateOutputStream(new FileOutputStream("b.exe"));
寫向頁面:
response.reset();
response.setContType("類型");
ServletOutputSreamt out = response.getOutputSream();
得到out對象后,就可以輸出了:
byte[] buf = new byte[1024];
int len = 0;
while((len = in.read(buf)) >0)
out.write(buf,0,len);
in.close();
out.close();
對于向頁面輸入,要設置什么樣的ContType,要看你想如何輸出,如果你想讓對方下載,就設為
"application/octet-stream",這樣即使是文本,圖象都會下載而不會在瀏覽器中打開.如果你要想
在瀏覽器中打開,就要設置相應的類型,還要在容器的配置文件中設置支持這種文檔類型的輸出,但
對于很多格式的文件,到底要輸出什么類型,其實就是HTTP的MIME集,比如圖片:image/gif,當然你如
果你的文件擴展名(ext)不確定,你也不要用if(ext.equals("gif"))......這樣來判斷,我教你一個
技巧,我之所以說是技巧,是我沒有在別的地方發現有人用這種方法,對我來說我是絕對不會把別人的
方法拿來說是我的技巧的:
構造一個file類型的URL,我們知道URL目前JAVA可以支持HTTP,FTP,MAILTO,FILE,LDAP等,從FILE類型
的URL就可以得到它的MIME:
URL u = new URL("file://a.exe");
String mime = u.openConnection().getContentType();
這樣你就可以直接response.setContType(mime);而不用一個一個類型判斷了.
好了,大對象存儲就說到這兒,不同的數據仍然和些特殊的規定,不在此一一列舉了.
JDBC初級應用實例(一)
發表人:wzh2352802 | 發表時間: 2007年一月23日, 13:53
在了解JDBC基礎知識以后,我們先來寫一個數據庫操作的類(Bean)以后我們會
在這個類的基礎上,隨著介紹的深入不斷提供優化的方案.
要把一個數據庫操作獨立到一個類(Bean)中,至少要考慮以下幾個方面:
1.對于不同層次的應用,應該有不同的得到連結的方法,如果得到連結的方法要隨
著應用層次的不同而改變,我們就應該把他獨立成一個專門的類中,而把在任何應用層次
中都通用的處理方法封裝到一個(類)Bean中.
2.既然考慮到既作為javaBean使用又可以用為一個普通類調用,要考慮到javaBean
的規范和普通類的靈活性.
3.對于特定的數據庫操作不應封裝到共性的(類)Bean中,而應該成為它的擴展類.
以上幾點是充分考慮JAVA的面象對象的思想,經過深入的抽象形成的層次,下面我
們就按這個思想來設計:
一:定義一個用于連結的Bean,以后如果要在不同的應用中,如可以在J2EE中從
DataSource中得到連結,或從普通的連結池中得到連結,以及直接從DriverManager中得到
連結,只需修改本類中的得到連結的實現方法.
package com.imnamg.axman.beans;
import java.sql.*;
import ..................
public class ConnectionFactory{
protected Connection conn;
ConnectionFactory() throws SQLException
{ //構造方法中生成連結
//無論是從DataSource還是直接從DriverManager中取得連結.
//先初始化環境,然后取得連結,本例作為初級應用,從
//DriverManager中取得連結,因為是封裝類,所以要把異常拋
//給調用它的程序處理而不要用try{}catch(){}塊自選處理了.
//因為要給業務方法的類繼承,而又不能給調用都訪問,所以
//conn聲明為protected
conn = DriverManager.getConnection(url,user,passwd);
}
/**
在多線程編程中,很多時候有可能在多個線程體中得到同一連
結的引用,但如果在一個線程中關閉了連結,則另一個得到相同
引用的線程就無法操作了,所以我們應該加一個重新建立連結
的輔助方法,有人問為什么既然有這個輔助方法不直接調用這個
輔助而要在構造方法中生成連結?因為這樣可以增加效率,如果
在構造時不能生成連結則就不能生成這個對象了,沒有必要在
對象生成后再測試能不能生成連結.
*/
public void makeConnection(){
//此處的代碼同構造方法,無論以后如果實現連結,都將構造方
//法的代碼復制到此處.
conn = DriverManager.getConnection(url,user,passwd);
}
}
這個類就封裝到這里,當然你可以在這兒增加業務方法,但如果要修改連結的實現,
整個類都要重新編譯,因為業務方法和應用層次無關,代碼一經生成不易變動,所以獨立封裝.
以下我們實現業務方法:
package com.imnamg.axman.beans;
import java.sql.*;
import ..................
public class DBOperater extends ConnectionFactory{
//private Statement stmt;
//private ResultSet rs;
//為什么要注釋成員變量stmt和rs,基礎部分已經說過,如果聲明為成員變量,
//在關閉conn時可以顯示地先關閉rs和stmt,別的沒有任何好處,而顯示關
//閉只是說明你編程風格好,但綜合考慮,我們要生成多個stmt或不是類型的
//stmt就不能聲明為成員方法,否則引用同一對象,所以我們要業務方法中生
//成stmt對象.不僅可以同時處理多個結果集,還可以提高性能和靈活性.
public ResultSet executeQuery(String sql) throws SQLException{
if(conn==null || conn.isClosed())
makeConnection();
Statement stmt = con.createStatement(
ResultSet.TYPE_SCROLL_INSENSITIVE,
ResultSet.CONCUR_READ_ONLY);
//對于一般的查詢操作,我們只要生成一個可流動的結果集就行了.
//而對于在查詢時要更新記錄,我們用另一個業務方法來處理,這樣,
//這樣可以在普通查詢時節省回滾空間.
ResultSet rs = stmt.executeQuery(sql);
return rs;
}
public ResultSet executeUpdatabledQuery(String sql) throws SQLException{
if (con == null || con.isClosed())
makeConnection();
Statement stmt = con.createStatement(
ResultSet.TYPE_SCROLL_INSENSITIVE,
ResultSet.CONCUR_UPDATABLED);
//可更新的結果結要更大的回滾空間,普通查詢時不要調用這個方法
ResultSet rs = stmt.executeQuery(sql);
return rs;
}
/**
基于同上的原因,在執行更新操作是我們根本不要任何回滾空間,所以建立
一個基本類型的stmt,實現如下
*/
public int executeUpdate(String sql) throws SQLException{
if (con == null || con.isClosed())
makeConnection();
Statement stmt = con.createStatement();
//這個stmt在執行更新操作時更加節省內存,永遠記住,能節省的時候要節省
//每一個字節的內存,雖然硬件設備可能會有很大的物理內存,但內存是給用
//戶用的而不是給程序員用的(!!!!!!!!!!!!!!!!!!)
int s = stmt.executeUpdate(sql);
return s;
}
//以上實現了常用功能,還有兩個通用的功能也是"共性"的,我們一起在這個封裝類
//中實現:
public PreparedStatement getPreparedStmt(String sql) throws SQLException{
if (con == null || con.isClosed())
makeConnection();
PreparedStatement ps = con.prepareStatement(sql);
return ps;
}
public CallableStatement getCallableStmt(String sql) throws SQLException{
if (con == null || con.isClosed())
makeConnection();
PreparedStatement ps = con.prepareCall(sql);
return ps;
}
//記住:對于封裝類而言預編譯語句和存儲過程調用應該從連結中返PreparedStatement
//和CallableStatement供調用者處理而不是返回它們的處理結果.也就是說封裝類只封
//裝了它們的連結過程.最后再次聲明,一定要有一個close()方法供調用者調用,而且告
//訴調用者無論如果要調用這個方法:
public void close() throws SQLException{
if(conn != null && !conn.isClosed())
conn.close();
}
//這個方法最好放在ConnectionFactory中,這樣可以直接調用來只測試連結.而不用再
調用子類來關閉
}
OK,我們已經實現了數據庫常用操作的封裝,注意這些業務方法都是把異常拋給調用者而沒有用
try...catch來處理,你如果在這里處理了那么調用者則無法調試了.對于特定的數據庫的特殊操作,不要封
裝到此類中,可以再從這個類繼承,或直接從ConnectionFactory類繼承,當然最好是從這個業務類中繼承,
這樣不僅可以調用特殊方法也可以調用共性的業務方法,興一個例子,我在應用Oracle時要把XML文件直接
存到數據數和把數據直接讀取為XML文件,那么這兩個方法只對Oracle才用到,所以:
package com.inmsg.axman.beans;
import java.sql.*;
import oracle.xml.sql.query.OracleXMLQuery;
import oracle.xml.sql.dml.OracleXMLSave;
public class OracleDBOperater extends DBOperater{
public OracleXMLQuery getOXQuery(String sql,String table) throws Exception
{
OracleXMLQuery qry = new OracleXMLQuery(con,sql);
qry.setRowsetTag(table);
qry.setRowTag("RECORD");
return qry;
}
public int insertXML(String path,String table) throws Exception
{
OracleXMLSave sav = new OracleXMLSave(con,table);
URL url = sav.createURL(path);
sav.setRowTag("RECORD");
int x = sav.insertXML(url);
sav.close();
return x;
}
}
現在,有了這樣的幾個"東西"在手里,你還有什么覺得不方便的呢?
雖然本處作為初級應用,但設計思想已經是JAVA高手的套路了,是不是有些自吹自擂了啊?
好的,休息一下吧.
Java調用存儲過程
發表人:wzh2352802 | 發表時間: 2007年一月19日, 22:24
摘要:
本文闡述了怎么使用DBMS存儲過程。我闡述了使用存儲過程的基本的和高級特性,比如返回ResultSet。本文假設你對DBMS和JDBC已經非常熟悉,也假設你能夠毫無障礙地閱讀其它語言寫成的代碼(即不是Java的語言),但是,并不要求你有任何存儲過程的編程經歷。
本文闡述了怎么使用DBMS存儲過程。我闡述了使用存儲過程的基本的和高級特性,比如返回ResultSet。本文假設你對DBMS和JDBC已經非常熟悉,也假設你能夠毫無障礙地閱讀其它語言寫成的代碼(即不是Java的語言),但是,并不要求你有任何存儲過程的編程經歷。
存儲過程是指保存在數據庫并在數據庫端執行的程序。你可以使用特殊的語法在Java類中調用存儲過程。在調用時,存儲過程的名稱及指定的參數通過JDBC連接發送給DBMS,執行存儲過程并通過連接(如果有)返回結果。
使用存儲過程擁有和使用基于EJB或CORBA這樣的應用服務器一樣的好處。區別是存儲過程可以從很多流行的DBMS中免費使用,而應用服務器大都非常昂貴。這并不只是許可證費用的問題。使用應用服務器所需要花費的管理、編寫代碼的費用,以及客戶程序所增加的復雜性,都可以通過DBMS中的存儲過程所整個地替代。
你可以使用Java,Python,Perl或C編寫存儲過程,但是通常使用你的DBMS所指定的特定語言。Oracle使用PL/SQL,PostgreSQL使用pl/pgsql,DB2使用Procedural SQL。這些語言都非常相似。在它們之間移植存儲過程并不比在Sun的EJB規范不同實現版本之間移植Session Bean困難。并且,存儲過程是為嵌入SQL所設計,這使得它們比Java或C等語言更加友好地方式表達數據庫的機制。
因為存儲過程運行在DBMS自身,這可以幫助減少應用程序中的等待時間。不是在Java代碼中執行4個或5個SQL語句,而只需要在服務器端執行1個存儲過程。網絡上的數據往返次數的減少可以戲劇性地優化性能。
使用存儲過程簡單的老的JDBC通過CallableStatement類支持存儲過程的調用。該類實際上是PreparedStatement的一個子類。假設我們有一個poets數據庫。數據庫中有一個設置詩人逝世年齡的存儲過程。下面是對老酒鬼Dylan Thomas(old soak Dylan Thomas,不指定是否有關典故、文化,請批評指正。譯注)進行調用的詳細代碼:
try{
int age = 39;
String poetName = "dylan thomas";
CallableStatement proc = connection.prepareCall("{ call set_death_age(?, ?) }");
proc.setString(1, poetName);
proc.setInt(2, age);
cs.execute();
}catch (SQLException e){ // ....}
傳給prepareCall方法的字串是存儲過程調用的書寫規范。它指定了存儲過程的名稱,?代表了你需要指定的參數。
和JDBC集成是存儲過程的一個很大的便利:為了從應用中調用存儲過程,不需要存根(stub)類或者配置文件,除了你的DBMS的JDBC驅動程序外什么也不需要。
當這段代碼執行時,數據庫的存儲過程就被調用。我們沒有去獲取結果,因為該存儲過程并不返回結果。執行成功或失敗將通過例外得知。失敗可能意味著調用存儲過程時的失敗(比如提供的一個參數的類型不正確),或者一個應用程序的失敗(比如拋出一個例外指示在poets數據庫中并不存在“Dylan Thomas”)
結合SQL操作與存儲過程映射Java對象到SQL表中的行相當簡單,但是通常需要執行幾個SQL語句;可能是一個SELECT查找ID,然后一個INSERT插入指定ID的數據。在高度規格化(符合更高的范式,譯注)的數據庫模式中,可能需要多個表的更新,因此需要更多的語句。Java代碼會很快地膨脹,每一個語句的網絡開銷也迅速增加。
將這些SQL語句轉移到一個存儲過程中將大大簡化代碼,僅涉及一次網絡調用。所有關聯的SQL操作都可以在數據庫內部發生。并且,存儲過程語言,例如PL/SQL,允許使用SQL語法,這比Java代碼更加自然。下面是我們早期的存儲過程,使用Oracle的PL/SQL語言編寫:
create procedure set_death_age(poet VARCHAR2, poet_age NUMBER)
poet_id NUMBER;
begin SELECT id INTO poet_id FROM poets WHERE name = poet;
INSERT INTO deaths (mort_id, age) VALUES (poet_id, poet_age);
end set_death_age;
很獨特?不。我打賭你一定期待看到一個poets表上的UPDATE。這也暗示了使用存儲過程實現是多么容易的一件事情。set_death_age幾乎可以肯定是一個很爛的實現。我們應該在poets表中添加一列來存儲逝世年齡。Java代碼中并不關心數據庫模式是怎么實現的,因為它僅調用存儲過程。我們以后可以改變數據庫模式以提高性能,但是我們不必修改我們代碼。
下面是調用上面存儲過程的Java代碼:
public static void setDeathAge(Poet dyingBard, int age) throws SQLException{
Connection con = null;
CallableStatement proc = null;
try {
con = connectionPool.getConnection();
proc = con.prepareCall("{ call set_death_age(?, ?) }");
proc.setString(1, dyingBard.getName());
proc.setInt(2, age);
proc.execute();
}
finally {
try { proc.close(); }
catch (SQLException e) {}
con.close();
}
}
為了確保可維護性,建議使用像這兒這樣的static方法。這也使得調用存儲過程的代碼集中在一個簡單的模版代碼中。如果你用到許多存儲過程,就會發現僅需要拷貝、粘貼就可以創建新的方法。因為代碼的模版化,甚至也可以通過腳本自動生產調用存儲過程的代碼。
Functions存儲過程可以有返回值,所以CallableStatement類有類似getResultSet這樣的方法來獲取返回值。當存儲過程返回一個值時,你必須使用registerOutParameter方法告訴JDBC驅動器該值的SQL類型是什么。你也必須調整存儲過程調用來指示該過程返回一個值。
下面接著上面的例子。這次我們查詢Dylan Thomas逝世時的年齡。這次的存儲過程使用PostgreSQL的pl/pgsql:
create function snuffed_it_when (VARCHAR) returns integer ''declare
poet_id NUMBER;
poet_age NUMBER;
begin
--first get the id associated with the poet.
SELECT id INTO poet_id FROM poets WHERE name = $1;
--get and return the age.
SELECT age INTO poet_age FROM deaths WHERE mort_id = poet_id;
return age;
end;'' language ''pl/pgsql'';
另外,注意pl/pgsql參數名通過Unix和DOS腳本的$n語法引用。同時,也注意嵌入的注釋,這是和Java代碼相比的另一個優越性。在Java中寫這樣的注釋當然是可以的,但是看起來很凌亂,并且和SQL語句脫節,必須嵌入到Java String中。
下面是調用這個存儲過程的Java代碼:
connection.setAutoCommit(false);
CallableStatement proc = connection.prepareCall("{ ? = call snuffed_it_when(?) }");
proc.registerOutParameter(1, Types.INTEGER);
proc.setString(2, poetName);
cs.execute();
int age = proc.getInt(2);
如果指定了錯誤的返回值類型會怎樣?那么,當調用存儲過程時將拋出一個RuntimeException,正如你在ResultSet操作中使用了一個錯誤的類型所碰到的一樣。
復雜的返回值關于存儲過程的知識,很多人好像就熟悉我們所討論的這些。如果這是存儲過程的全部功能,那么存儲過程就不是其它遠程執行機制的替換方案了。存儲過程的功能比這強大得多。
當你執行一個SQL查詢時,DBMS創建一個叫做cursor(游標)的數據庫對象,用于在返回結果中迭代每一行。ResultSet是當前時間點的游標的一個表示。這就是為什么沒有緩存或者特定數據庫的支持,你只能在ResultSet中向前移動。
某些DBMS允許從存儲過程中返回游標的一個引用。JDBC并不支持這個功能,但是Oracle、PostgreSQL和DB2的JDBC驅動器都支持在ResultSet上打開到游標的指針(pointer)。
設想列出所有沒有活到退休年齡的詩人,下面是完成這個功能的存儲過程,返回一個打開的游標,同樣也使用PostgreSQL的pl/pgsql語言:
create procedure list_early_deaths () return refcursor as ''declare
toesup refcursor;
begin
open toesup for SELECT poets.name, deaths.age FROM poets, deaths -- all entries in deaths are for poets. -- but the table might become generic.
WHERE poets.id = deaths.mort_id AND deaths.age < 60;
return toesup;
end;'' language ''plpgsql'';
下面是調用該存儲過程的Java方法,將結果輸出到PrintWriter:
PrintWriter:
static void sendEarlyDeaths(PrintWriter out){
Connection con = null;
CallableStatement toesUp = null;
try {
con = ConnectionPool.getConnection();
// PostgreSQL needs a transaction to do this... con.
setAutoCommit(false); // Setup the call.
CallableStatement toesUp = connection.prepareCall("{ ? = call list_early_deaths () }");
toesUp.registerOutParameter(1, Types.OTHER);
toesUp.execute();
ResultSet rs = (ResultSet) toesUp.getObject(1);
while (rs.next()) {
String name = rs.getString(1);
int age = rs.getInt(2);
out.println(name + " was " + age + " years old.");
}
rs.close();
}
catch (SQLException e) { // We should protect these calls. toesUp.close(); con.close();
}
}
因為JDBC并不直接支持從存儲過程中返回游標,我們使用Types.OTHER來指示存儲過程的返回類型,然后調用getObject()方法并對返回值進行強制類型轉換。
這個調用存儲過程的Java方法是mapping的一個好例子。Mapping是對一個集上的操作進行抽象的方法。不是在這個過程上返回一個集,我們可以把操作傳送進去執行。本例中,操作就是把ResultSet打印到一個輸出流。這是一個值得舉例的很常用的例子,下面是調用同一個存儲過程的另外一個方法實現:
public class ProcessPoetDeaths{
public abstract void sendDeath(String name, int age);
}
static void mapEarlyDeaths(ProcessPoetDeaths mapper){
Connection con = null;
CallableStatement toesUp = null;
try {
con = ConnectionPool.getConnection();
con.setAutoCommit(false);
CallableStatement toesUp = connection.prepareCall("{ ? = call list_early_deaths () }");
toesUp.registerOutParameter(1, Types.OTHER);
toesUp.execute();
ResultSet rs = (ResultSet) toesUp.getObject(1);
while (rs.next()) {
String name = rs.getString(1);
int age = rs.getInt(2);
mapper.sendDeath(name, age);
}
rs.close();
} catch (SQLException e) { // We should protect these calls. toesUp.close();
con.close();
}
}
這允許在ResultSet數據上執行任意的處理,而不需要改變或者復制獲取ResultSet的方法:
static void sendEarlyDeaths(final PrintWriter out){
ProcessPoetDeaths myMapper = new ProcessPoetDeaths() {
public void sendDeath(String name, int age) {
out.println(name + " was " + age + " years old.");
}
};
mapEarlyDeaths(myMapper);
}
這個方法使用ProcessPoetDeaths的一個匿名實例調用mapEarlyDeaths。該實例擁有sendDeath方法的一個實現,和我們上面的例子一樣的方式把結果寫入到輸出流。當然,這個技巧并不是存儲過程特有的,但是和存儲過程中返回的ResultSet結合使用,是一個非常強大的工具。
結論存儲過程可以幫助你在代碼中分離邏輯,這基本上總是有益的。這個分離的好處有:
? 快速創建應用,使用和應用一起改變和改善的數據庫模式。
? 數據庫模式可以在以后改變而不影響Java對象,當我們完成應用后,可以重新設計更好的模式。
? 存儲過程通過更好的SQL嵌入使得復雜的SQL更容易理解。
? 編寫存儲過程比在Java中編寫嵌入的SQL擁有更好的工具--大部分編輯器都提供語法高亮!
? 存儲過程可以在任何SQL命令行中測試,這使得調試更加容易。
并不是所有的數據庫都支持存儲過程,但是存在許多很棒的實現,包括免費/開源的和非免費的,所以移植并不是一個問題。Oracle、PostgreSQL和DB2都有類似的存儲過程語言,并且有在線的社區很好地支持。
存儲過程工具很多,有像TOAD或TORA這樣的編輯器、調試器和IDE,提供了編寫、維護PL/SQL或pl/pgsql的強大的環境。
存儲過程確實增加了你的代碼的開銷,但是它們和大多數的應用服務器相比,開銷小得多。
Java/J2EE中文問題終極解決之道
發表人:wzh2352802 | 發表時間: 2007年一月19日, 21:57
Java/J2EE中文問題終極解決之道
|
Java中文問題一直困擾著很多初學者,如果了解了Java系統的中文問題原理,我們就可以對中文問題能夠采取根本的解決之道。
最古老的解決方案是使用String的字節碼轉換,這種方案問題是不方便,我們需要破壞對象封裝性,進行字節碼轉換。
還有一種方式是對J2EE容器進行編碼設置,如果J2EE應用系統脫離該容器,則會發生亂碼,而且指定容器配置不符合J2EE應用和容器分離的原則。
在Java內部運算中,涉及到的所有字符串都會被轉化為UTF-8編碼來進行運算。那么,在被Java轉化之前,字符串是什么樣的字符集? Java總是根據操作系統的默認編碼字符集來決定字符串的初始編碼,而且Java系統的輸入和輸出的都是采取操作系統的默認編碼。
因此,如果能統一Java系統的輸入、輸出和操作系統3者的編碼字符集合,將能夠使Java系統正確處理和顯示漢字。這是處理Java系統漢字的一個原則,但是在實際項目中,能夠正確抓住和控制住Java系統的輸入和輸出部分是比較難的。J2EE中,由于涉及到外部瀏覽器和數據庫等,所以中文問題亂碼顯得非常突出。
J2EE應用程序是運行在J2EE容器中。在這個系統中,輸入途徑有很多種:一種是通過頁面表單打包成請求(request)發往服務器的;第二種是通過數據庫讀入;還有第3種輸入比較復雜,JSP在第一次運行時總是被編譯成Servlet,JSP中常常包含中文字符,那么編譯使用javac時,Java將根據默認的操作系統編碼作為初始編碼。除非特別指定,如在Jbuilder/eclipse中可以指定默認的字符集。
輸出途徑也有幾種:第一種是JSP頁面的輸出。由于JSP頁面已經被編譯成Servlet,那么在輸出時,也將根據操作系統的默認編碼來選擇輸出編碼,除非指定輸出編碼方式;還有輸出途徑是數據庫,將字符串輸出到數據庫。
由此看來,一個J2EE系統的輸入輸出是非常復雜,而且是動態變化的,而Java是跨平臺運行的,在實際編譯和運行中,都可能涉及到不同的操作系統,如果任由Java自由根據操作系統來決定輸入輸出的編碼字符集,這將不可控制地出現亂碼。
正是由于Java的跨平臺特性,使得字符集問題必須由具體系統來統一解決,所以在一個Java應用系統中,解決中文亂碼的根本辦法是明確指定整個應用系統統一字符集。
指定統一字符集時,到底是指定ISO8859_1 、GBK還是UTF-8呢?
(1)如統一指定為ISO8859_1,因為目前大多數軟件都是西方人編制的,他們默認的字符集就是ISO8859_1,包括操作系統Linux和數據庫MySQL等。這樣,如果指定Jive統一編碼為ISO8859_1,那么就有下面3個環節必須把握:
開發和編譯代碼時指定字符集為ISO8859_1。
運行操作系統的默認編碼必須是ISO8859_1,如Linux。
在JSP頭部聲明:。
(2)如果統一指定為GBK中文字符集,上述3個環節同樣需要做到,不同的是只能運行在默認編碼為GBK的操作系統,如中文Windows。
統一編碼為ISO8859_1和GBK雖然帶來編制代碼的方便,但是各自只能在相應的操作系統上運行。但是也破壞了Java跨平臺運行的優越性,只在一定范圍內行得通。例如,為了使得GBK編碼在linux上運行,設置Linux編碼為GBK。
那么有沒有一種除了應用系統以外不需要進行任何附加設置的中文編碼根本解決方案呢?
將Java/J2EE系統的統一編碼定義為UTF-8。UTF-8編碼是一種兼容所有語言的編碼方式,惟一比較麻煩的就是要找到應用系統的所有出入口,然后使用UTF-8去“結扎”它。
一個J2EE應用系統需要做下列幾步工作:
開發和編譯代碼時指定字符集為UTF-8。JBuilder和Eclipse都可以在項目屬性中設置。 使用過濾器,如果所有請求都經過一個Servlet控制分配器,那么使用Servlet的filter執行語句,將所有來自瀏覽器的請求(request)轉換為UTF-8,因為瀏覽器發過來的請求包根據瀏覽器所在的操作系統編碼,可能是各種形式編碼。關鍵一句: request.setCharacterEncoding("UTF-8")。 網上有此filter的源碼,Jdon框架源碼中com.jdon.util.SetCharacterEncodingFilter 需要配置web.xml 激活該Filter。 在JSP頭部聲明:。 在Jsp的html代碼中,聲明UTF-8:
設定數據庫連接方式是UTF-8。例如連接MYSQL時配置URL如下: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8 注意,上述寫法是JBoss的mysql-ds.xml寫法,多虧網友提示,在tomcat中&要寫成&即可。一般其他數據庫都可以通過管理設置設定UTF-8 其他和外界交互時能夠設定編碼時就設定UTF-8,例如讀取文件,操作XML等。 筆者以前在Jsp/Servlet時就采取這個原則,后來使用Struts、Tapestry、EJB、Hibernate、Jdon等框架時,從未被亂碼困擾過,可以說適合各種架構。希望本方案供更多初學者分享,減少Java/J2EE的第一個攔路虎,也避免因為采取一些臨時解決方案,導致中文問題一直出現在新的技術架構中。 //http://www.leftworld.net [2006-12-08]
|
|
|
J2EE Tutorial
J2EE到底是什么?
發表人:wzh2352802 | 發表時間: 2007年一月19日, 21:12
J2EE到底是什么?
目前所有的B/S系統應用可以分為:有狀態(statefull)和無狀態(stateless)兩大類別。 有狀態是指在整個系統的處理過程中要保留記住一些信息,而無狀態則相反,每次request都是獨立的連接,不需要在每個request之間共享數據等等。
對于這兩種應用,通常第一考慮是性能要最優,性能是我們選擇IT技術的主要依據之一。
為達到最大化的性能,對于Java系統,以前通常的作法是使用對象池,這樣節約對象生成時的性能開銷,也就是說系統啟動時,預先生成一定數目的對象實例在內存中,需要使用時,從對象池中取出實例,用完,歸還對象池,對于有狀態的應用,可以使用相關持久化(persistence)策略來保存狀態。
下一步,如何并行訪問對象池將是非常重要,java的多線程技術為我們提供了實現可能,線程的創建銷毀也是可能非常耗時的,那么,無疑象使用對象池一樣,我們必須使用線程池來實現多線程并行計算的最優化。
使用線程池和對象池,每次客戶端請求發生一次就從線程池中借用一個線程,處理完這個請求就將線程返回線程池,同樣,使用線程快速的訪問對象,對象也是從對象池中借用,用完就還回對象池。 整個這樣的架構設計在性能上是最優的。
有了性能保證,安全機制、事務機制、集群(cluster)技術也將是選擇IT技術的主要依據。
J2EE就是這樣一個實現上述多種考量的綜合標準框架系統,在具體使用中,也許我們對所有這些考量的要求并不都一樣重視,比如:如果純粹追求性能是第一,可以忽視事務機制,那么,完整的J2EE技術也許就并不適合你。
那么我們先看看J2EE是如何從性能上保證我們的應用系統以最快速度運行的,也就是說J2EE中必然應該有上述線程池和對象池的實現技術,servlet實際是基于線程池的更好的線程容器;EJB是基于對象池的更好的對象容器。
看看Servler的架構圖:
當client1發生請求時servlet容器會從線程池中分配一個線程給這個request.
再看看EJB的架構圖:
instance Pool作為一個對象實例池,維持著EJB實例,當然這個對象池是用生命周期的,簡單的說 EJB=對象池+遠程對象池
但是,EJB還整合了相當的其它增強功能,如安全 事務機制等,這些對于一般應用都是必需的,當然你還必須根據你的需要來選擇是否使用J2EE,如果你的應用對安全 事務機制沒有要求,直接使用線程池和對象池技術肯定獲得最好的性能。
所以,根據Servler和EJB的原理,我們已經可以規劃我們的應用,什么可以放在servlet,或什么需要放在EJB中實現:
線程的本質決定了servlet只適合一些輕量的應用,如分析簡單XML文檔, 通過JDBC訪問數據源,使用JMS或JavaMail處理簡單的信息Message,或使用JTS/JTA處理簡單的事務機制,注意這些用詞都是"簡單"的,一旦復雜了,就要使用EJB了。
下面從客戶端和服務器端兩個方面來具體考量這兩個技術的使用,這里的客戶端不一定是指最終客戶端,因為J2EE是多層結構,中間層可能在多個服務器上實現,如果一個服務器上的服務是供另外一個服務器上的應用訪問的,那么后者我們也稱為客戶端。
根據應用的復雜程度和要求不同,分下列情況:
1.在WEB層可以實現的一些應用
如果你的系統沒有很復雜的事務處理,或訪問很多企業原有的資源,那么可以借助javabean這樣的一些Help性質的類來實現你的應用,但是,這樣的方案不是最干凈clean, 最有效efficient, 或最有擴展性的scalable。
否則,將所有核心計算放置入EJB中。
2.所有的復雜商務計算核心都在EJB中完成
如果你的客戶端和服務器端之間有防火墻,那么目前能夠無障礙通過防火墻的協議只有Http了(Web Service也是基于http就是這個道理),既然使用http了,而Servlet是基于Http協議的,那么就需要通過servlet來訪問EJB,這是我們最普遍的應用情況。
但是,如果你的客戶端和服務器端可以放置在一個網絡內,之間沒有防火墻,那么就不必使用Servlet,直接使用Java調用RMI來訪問EJB,這樣性能是最好的,這時的Servlet大概只有用于控制Jsp的頁面的輸出了(MVC模式中的控制作用)。
如果是非java客戶端,可以通過CORBA組件來訪問EJB。
3.如果你的應用對速度要求很高,要求非常快,對于事務處理等方面幾乎無要求
直接使用J2SE,加上線程池和對象池技術,將會使你的java系統性能發揮極致。Jakarta.Apache.org有這兩種技術的源碼,線程池可以從Servlet容器Tomcat的源碼中發現。
多線程
發表人:wzh2352802 | 發表時間: 2007年一月19日, 21:10
多線程
1
.多線程中有主內存和工作內存之分, 在JVM中,有一個主內存,專門負責所有線程共享數據;而每個線程都有他自己私有的工作內存, 主內存和工作內存分貝在JVM的stack區和heap區。
2.
線程的狀態有'Ready', 'Running', 'Sleeping', 'Blocked', 和 'Waiting'幾個狀態,
'Ready' 表示線程正在等待CPU分配允許運行的時間。
3.
線程運行次序并不是按照我們創建他們時的順序來運行的,CPU處理線程的順序是不確定的,如果需要確定,那么必須手工介入,使用setPriority()方法設置優先級。
4.
我們無從知道一個線程什么時候運行,兩個或多個線程在訪問同一個資源時,需要synchronized
5.
每個線程會注冊自己,實際某處存在著對它的引用,因此,垃圾回收機制對它就“束手無策”了。
6.
Daemon線程區別一般線程之處是:主程序一旦結束,Daemon線程就會結束。
7.
一個對象中的所有synchronized方法都共享一把鎖,這把鎖能夠防止多個方法對通用內存同時進行的寫操作。synchronized static方法可在一個類范圍內被相互間鎖定起來。
8.
對于訪問某個關鍵共享資源的所有方法,都必須把它們設為synchronized,否則就不能正常工作。
9.
假設已知一個方法不會造成沖突,最明智的方法是不要使用synchronized,能提高些性能。
10
. 如果一個"同步"方法修改了一個變量,而我們的方法要用到這個變量(可能是只讀),最好將自己的這個方法也設為 synchronized。
11.
synchronized不能繼承, 父類的方法是synchronized,那么其子類重載方法中就不會繼承“同步”。
12.
線程堵塞Blocked有幾個原因造成:
(1)線程在等候一些IO操作
(2)線程試圖調用另外一個對象的“同步”方法,但那個對象處于鎖定狀態,暫時無法使用。
13.
原子型操作(atomic), 對原始型變量(primitive)的操作是原子型的atomic. 意味著這些操作是線程安全的, 但是大部分情況下,我們并不能正確使用,來看看 i = i + 1 , i是int型,屬于原始型變量:
(1)從主內存中讀取i值到本地內存.
(2)將值從本地內存裝載到線程工作拷貝中.
(3)裝載變量1.
(4)將i 加 1.
(5)將結果給變量i.
(6)將i保存到線程本地工作拷貝中.
(7)寫回主內存.
注意原子型操作只限于第1步到第2步的讀取以及第6到第7步的寫, i的值還是可能被同時執行i=i+1的多線程中斷打擾(在第4步)。
double 和long 變量是非原子型的(non-atomic)。數組是object 非原子型。
14.
由于13條的原因,我們解決辦法是:
class xxx extends Thread{
//i會被經常修改
private int i;
public synchronized int read(){ return i;}
public synchronized void update(){ i = i + 1;}
..........
}
15.
Volatile變量, volatile變量表示保證它必須是與主內存保持一致,它實際是"變量的同步", 也就是說對于volatile變量的操作是原子型的,如用在long 或 double變量前。
16.
使用yield()會自動放棄CPU,有時比sleep更能提升性能。
17.
sleep()和wait()的區別是:wait()方法被調用時會解除鎖定,但是我們能使用它的地方只是在一個同步的方法或代碼塊內。
18.
通過制造縮小同步范圍,盡可能的實現代碼塊同步,wait(毫秒數)可在指定的毫秒數可退出wait;對于wait()需要被notisfy()或notifyAll()踢醒。
19.
構造兩個線程之間實時通信的方法分幾步:
(1). 創建一個PipedWriter和一個PipedReader和它們之間的管道;
PipedReader in = new PipedReader(new PipedWriter())
(2). 在需要發送信息的線程開始之前,將外部的PipedWriter導向給其內部的Writer實例out
(3). 在需要接受信息的線程開始之前,將外部的PipedReader導向給其內部的Reader實例in
(4). 這樣放入out的所有東西度可從in中提取出來。
20.
synchronized帶來的問題除性能有所下降外,最大的缺點是會帶來死鎖DeadLock,只有通過謹慎設計來防止死鎖,其他毫無辦法,這也是線程難以馴服的一個原因。不要再使用stop() suspend() resume()和destory()方法
21.
在大量線程被堵塞時,最高優先級的線程先運行。但是不表示低級別線程不會運行,運行概率小而已。
22.
線程組的主要優點是:使用單個命令可完成對整個線程組的操作。很少需要用到線程組。
23.
從以下幾個方面提升多線程的性能:
檢查所有可能Block的地方,盡可能的多的使用sleep或yield()以及wait();
盡可能延長sleep(毫秒數)的時間;
運行的線程不用超過100個,不能太多;
不同平臺linux或windows以及不同JVM運行性能差別很大。
24. 推薦幾篇相關英文文章:
Use Threading Tricks to Improve Programs
Java 專業人士必備的書籍和網站列表
發表人:wzh2352802 | 發表時間: 2007年一月17日, 20:47
對于 Java? 語言開發人員來說,信息過量是一個真正的問題。每個新入行的程序員都要面臨一個令人畏縮的挑戰:要進入的行業是一個具有海量知識的行業。要了解的東西簡直太多了。對于有經驗的老手來說,情況只有些微好轉。知識量總在增大,僅僅跟上進度就是一個挑戰。如果有一份專業人士必備的書籍和網站列表該有多好!本文就是這個列表。它包含了每個專業的 Java 語言程序員在書架或瀏覽器書簽中必備的最重要的書籍和網站。
這些都是您書架上必備的書和應該經常使用的 Web 鏈接。時間是一項重要的資源,本文幫您回避那些分心的事情,把時間專注于最有益于您作為Java 語言程序員職業生涯的信息源。盡管有多少程序員就有多少他們最喜歡的參考資料,但本文收集的這些都是優中選優,來源于我書架上的私家珍藏和許多 Java 專家的推薦。
我考慮了兩種組織這份參考資料列表的方法。我本可以通過主題領域來組織,這也許很有幫助,但主題列表很快就會變得不實用。相反,我選擇了另一種方法:通過類型來組織,即書籍和 Web 站點。
總的來講,有經驗的老手們用 Web 站點來跟蹤行業的走勢。書籍、文章和論文有助于跟上潮流,但它們總體上更適合于基礎學習。極富創造性的書籍偶爾會撼動一兩個基礎性的東西。這樣的書也在本列表之列。
需要提出的一點警告是,專注于 Java 語言的書籍和 Web 站點數量巨大。您鐘愛的未必在這份列表里。那并不意味著它們不好。它們只是不在這份列表里而已。可能是因為我還不知道它們。也可能是因為我不認為它們能夠算得上是重要資源。不包含一些參考資料是一個評判問題,但如果不這樣的話,您也許就要花幾小時來拖動滾動條,還要花上成千上萬美元來買書。如果您作為一個專業的 Java 程序員,有一些常用的優秀參考資料,一定要讓我知道這些資料。這份列表一直都在更新中,您提出的那些也許就會被收錄進去。
書籍
每個程序員都會有一些由于經常被當作專業資料參閱而磨壞的書。下列書籍應該是 Java 語言程序員的書架上必備的。書很貴,所以我有意將這份列表弄得很短,僅限于重要書籍。
Thinking in Java (Bruce Eckel)
Thinking in Java, 3rd edition
(Bruce Eckel; Prentice Hall PTR,2002 年)
Java 編程思想:第3版 (陳昊鵬 等譯; 機械工業出版社,2005 年)
Eckel 的書對于學習如何在 Java 語言環境中使用好面向對象技術極其實用。書中大量的代碼樣例解釋了他所介紹的概念。文字出自一個并不認為 Java 技術總是正確答案的人,所以相當地實用。Eckel 具有多種語言的大量經驗,還有用面向對象方式進行思考的扎實技能。本書將這些技能放到實用的 Java 語言環境中。他還在寫一本新書,名為 Thinking in Enterprise Java。
Effective Java (Joshua Bloch)
Effective Java: Programming Language Guide
(Joshua Bloch; Addison-Wesley,2001 年)
Effective Java 中文版 (潘愛民 譯; 機械工業出版社,2003 年)
本書是理解優秀 Java 程序設計原則的最佳書籍。大多數材料從其他的 “學習 Java ” 的書中根本找不到。例如,Bloch 書中關于覆蓋 equals()
這一章是我讀過的最好的參考資料之一。他也在書中包括了很實用的建議:用接口替代抽象類和靈活使用異常。Bloch 是 Sun 公司 Java 平臺庫的架構師,所以他透徹地了解這門語言。事實上,他編寫了該語言中大量有用的庫。本書必讀!
The Java Programming Language (Ken Arnold, James Gosling, David Holmes)
The Java Programming Language
(Ken Arnold,James Gosling,David Holmes; Addison-Wesley,2000 年)
Java 編程語言(第 3 版) (虞萬榮 等譯,中國電力出版社,2003 年)
這也許是能弄到的最好的 Java 入門讀物。它并不是一個標準規范,而是一本介紹每門語言特性的可讀書籍。這本書在嚴謹性和教育性方面權衡得很好,能夠讓懂編程的人迅速被 Java 語言(和其豐富的類庫)所吸引。
Concurrent Programming in Java: Design Principles and Patterns (Doug Lea)
Concurrent Programming in Java: Design Principles and Patterns, 2nd edition
(Doug Lea; Addison-Wesley,1999 年)
Java 并發編程—設計原則與模式(第二版) (趙涌 等譯,中國電力出版社,2004 年)
不是每個開發人員都需要如此細致地了解并發性,也不是每個工程師都能達到本書的水準,但卻沒有比本書更好的關于并發性編程的概述了。如果您對此感興趣,請從這里開始。Lea 是 SUNY 的一名專業程序員,他的和并發性有關的作品和想法都包含在了 JDK 5.0 規范(引自 JSR166)中,所以您大可放心,他所說的關于有效使用 Java 語言的建議是值得一聽的。他是一個很善于溝通的人。
Expert One-On-One J2EE Design and Development (Rod Johnson)
Expert One-On-One J2EE Design and Development
(Rod Johnson)
WROX: J2EE 設計開發編程指南 (魏海萍 譯,電子工業出版社,2003 年)
對于剛接觸 J2EE 的人來說,這是唯一的一本如實反映這項技術的書。本書收錄了多年的成功經驗和失敗經驗,不同于其他許多作者,Johnson 樂于將失敗的經驗公諸于眾。J2EE 常常都被過度使用。Johnson 的書能幫您避免這一點。
Refactoring (Martin Fowler, Kent Beck, John Brant, William Opdyke, Don Roberts)
Refactoring: Improving the Design of Existing Code
(Martin Fowler,Kent Beck,John Brant,William Opdyke,Don Roberts; Addison-Wesley,1999 年)
重構:改善既有代碼的設計(中文版) (侯捷 等譯,中國電力出版社 ,2003 年)
Fowler 寫了幾本現已出版的最流行的編程書,包括 Analysis Patterns。他的關于重構 的書是這一主題的基本書籍。重構代碼是被程序員忽略的訓練,但卻是程序員最直觀的想法。重構是在不改變代碼結果的前提下改進現有代碼的設計。這是保持代碼整潔的最佳方式,用這種方法設計的代碼總是很容易修改。什么時候進行重構呢?當代碼“散發出味道”時。Fowler 的書里滿是 Java 語言代碼的例子。許多 Java 語言集成開發環境(IDE)(包括了 IBM 的 Eclipse)都將 Fowler 的重構包含了進去,每一個都使用他的重構名命名,所以熟悉如extract method 等重構方法還是很值得的。
Design Patterns (Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides)
Design Patterns: Elements of Reusable Object Oriented Software
(Erich Gamma,Richard Helm,Ralph Johnson,John Vlissides; Addison-Wesley,1997 年)
設計模式:可復用面向對象軟件的基礎 (李英軍 等譯,機械工業出版社 ,2005 年)
這是一本在專業程序員圈子里更為有名的書,基于作者共同的綽號,這本書被認為是 “四人幫(GOF)之書”。模式是思考和解決普通編程問題時可以重用的方式。學習模式是一門學科。使用好模式(或知道什么時候不 使用模式)是一項技能。忽略模式則是錯誤的。書中所有的例子都以 C++ 表示,但 Java 語言是從那里誕生的,讓 Java 語言程序員由此聯系到如何在 Java 語言中實現這些模式相對簡單一些。熟悉模式并了解如何使用好模式使編程更加簡單。這使得和其他程序員交流也更簡單,因為在針對通用問題的通用解決方案中,模式是描述解決方案中彼此協作的大量相關編程概念的快捷方式。一些更為通用的方式,如工廠方法 則是普便存在的,甚至存在于 Java 語言本身。關于明智使用模式的這個主題,也可以閱讀 Joshua Kerievsky 的 Refactoring to Patterns,該書稱可以讓代碼來告訴您何時實現模式。
Patterns of Enterprise Application Architecture (Martin Fowler)
Patterns of Enterprise Application Architecture
(Martin Fowler; Addison-Wesley,2002 年)
企業應用架構模式 (王懷民 等譯,機械工業出版社 ,2004 年)
比起小型、一次性項目來說,企業開發當然代表了更大的挑戰。那并不意味著企業開發帶來的所有挑戰都是新挑戰。事實上有些時候,這項開發已經 是以前完成過的了。Fowler 做了很多個這樣的項目。他的書提到了一些通用解決方案,并提供了關于使用、折中和可選方案的指導。Fowler 在書中包含了一些熟悉的模式,如模型視圖控制器(MVC),他也提供了一些您也許不了解的模式,如處理 Web 站點上特定頁面請求或行為請求的 Page Controller 模式。正如您對待大多數模式一樣,一旦您讀過許多模式,您就會認為 “我已經知道那個模式了” 。也許是這樣,但有一個用來引用模式的通用表達方式還是很有幫助的。在有多個組件(由不同人開發)的大型項目中,該類引用是一項很好的幫助。
UML Distilled (Martin Fowler)
UML Distilled: A Brief Guide to the Standard Object Modeling Language
(Martin Fowler; Addison-Wesley 2003 年)
UML精粹:標準對象語言簡明指南(第3版) (徐家福 譯,清華大學出版社 ,2005 年)
對于專業的程序員來說,UML 是一門很重要的通用可視化溝通語言,但是它被過度使用和草率地濫用了。您無需對使用 UML 溝通了解太多。Martin 對 UML 的提煉為您提供了最核心的東西。事實上,前后的封頁提供了常規基礎上可能使用到的所有東西。該書中 UML 例子的代碼都是 Java 代碼。
Test-Driven Development: By Example (Kent Beck)
Test-Driven Development: By Example
(Kent Beck; Addison-Wesley 2002 年)
測試驅動開發(中文版) (崔凱 譯,中國電力出版社 ,2004 年)
測試優先編程將使編程發生革命性變化,能助您成為更好的程序員。在寫代碼之前編寫測試開始很難,但卻是一項威力強大的技能。通過優先編寫測試,可使代碼更加簡單,并確保從一開始它就能工作(Beck 實踐著他提倡的測試優先,與人合寫了 JUnit,這是 Java 語言最流行的測試框架)。Beck 的書是權威的參考資料,擴展了的 Money 例子也用 Java 語言寫成。Beck 詳述了如何用測試優先進行 思考(這也許是許多程序員首先遇到的障礙)。
The Pragmatic Programmer: From Journeyman to Master (Andy Hunt and Dave Thomas)
The Pragmatic Programmer: From Journeyman to Master
(Andrew Hunt 和 David Thomas; Addison-Wesley 1999 年)
程序員修煉之道——從小工到專家 (馬維達 譯,電子工業出版社 ,2004 年)
做一個純粹的面向對象開發人員有其優勢所在。在當今復雜的社會中,作為 Java 語言開發人員,為完成任務常要妥協。Hunt 和 Thomas 探討了如何不將真正重要的東西妥協掉而完成任務。這不是一本關于 Java 語言的書,而是 Java 語言開發人員重要的思想讀物。例如,我認為沒從“要解決問題,而不是推卸責任”這句忠言中受益的程序員,不能像個自豪的藝術家一樣在他的杰作上簽上大名。
Peopleware: Productive Projects and Teams (Tom DeMarco and Timothy Lister)
Peopleware: Productive Projects and Teams
(Tom DeMarco,Timothy Lister; Dorset House,1999 年)
人件(第2版) (UMLChina 翻譯組 譯,清華大學出版社 ,2003 年)
這份列表中的其他所有書籍都至少和技術有些相關。這本書卻不是。在所有技術行話和首字母縮略詞的海洋中,有時軟件開發人員和經理們會忘記:是人 制造了軟件。DeMarco 和 Lister 向我們提醒了這一事實,也向我們提醒了形成這一區別的原因。這不是一本關于一門特定編程語言的書籍,但卻是每個 Java 語言程序員都應該讀的書。關于 “累死程序員如何讓經理們適得其反” 還有許多其他的好書,但這是最好的一本。
Web 站點
Web 站點的數目浩如煙海,如果您想要消化其中的內容,窮畢生之力也難以全部訪問。包含 Java 語言某方面內容的詳盡的網站列表會大得離譜。下列站點都是可靠、真實的。
Sun 的 Java 技術站點
Sun 的 Java 語言站點
這是 Sun 的 Java 語言主站。作為 Java 語言開發人員,您會發現自己頻繁地訪問此站點。下列鏈接特別重要,特別是對新入行的 Java 語言開發人員:
-
New to Java Center
New to Java Center
New to Java Center 存放了許多循序漸進的 Java 技術資源鏈接。如果您剛接觸這門語言,這是一個好的起點。
-
教程和代碼庫
Java Tutorial
這里有大名鼎鼎的 Java Tutorial,以及關于 Java 語言各個方面(例如 Collection)的其他教程。
IBM developerWorks
IBM 的 developerWorks
推銷自己也許有些厚臉皮,但 developerWorks 是一項巨大的資源,收錄了大量 Java 語言工具和技術的教程和文章。其內容從初學者指南到學習這門語言到高級并發性技術。可以根據主題搜索內容,然后根據類型瀏覽。
Apache Software Foundation
Apache Software Foundation
Apache 站點是許多可重用庫(通用領域)和工具的主頁,這些庫和工具幫助 Java 開發人員進行開發。這里的內容全都是開放源碼,所以盡管下載想要的吧!許多極其流行的 Java 語言庫和工具(如 Struts、Ant 和 Tomcat)都始于 Apache 項目。Jakarta 專區匯聚了大多數新興的 Java 語言材料。
Eclipse.org
Eclipse
有幾個好的 Java 語言集成開發環境(IDE)。Eclipse(來自 IBM)是最新的 IDE 之一,它很快成為 Java 語言開發的首要 IDE。它完全是開源的,這意味著它是免費的。該站包含了學習如何有效使用 Eclipse 的各種參考資料。這里還有關于 Standard Widget Toolkit(SWT)的信息,SWT 是相對于 Swing 來說更加輕量級的選擇。
Eclipse 插件中心和 Eclipse 插件
Eclipse 插件中心
和 Eclipse 插件
Eclipse 基于插件架構。事實上,插件是 Eclipse 的 Java 語言開發組件。但有差不多上千個插件,從 Web 開發的插件到在 Eclipse 環境中玩游戲的插件。這兩個站點分類列出了大多數插件,可以進行搜索。它們是很棒的資源。如果您想在 Eclipse 開發環境中弄點新東西,幸運的話有某個插件可能已經實現,從這兩個站點能找到想要的插件。這兩個站點都允許評論插件,這樣您就可以知道哪些插件好,哪些值得一試。
JUnit.org
JUnit.org
Junit 是 Java 語言中一個基本的單元測試框架。該站點包含了 Junit 最新最棒的版本,外加大量有關測試(Java 語言或者其他語言的)各個層面上(針對桌面應用程序、Web 應用程序、J2EE 應用程序等)的其他資源。如果您想找測試資源,這里就是最佳起點。
TheServerSide.com
TheServerSide.com
如果您要(或將要)從事服務器端 Java 語言的開發,此站點是一處舉足輕重的資源。您可以到這里找到有關 JBoss、J2EE、LDAP、Struts 和大量其他主題的文章,并且都是完全可檢索的。這些文章不僅僅是簡單描述 Java 語言的特征或者支持的庫。它們更進一步地描述了庫的新奇用法(如使用 Jakarta Velocity 作為規則引擎,而不是模板引擎)。它們也提供了有關 Java 語言現狀的連續評論(當前的一篇文章是由 Tim Bray 所寫的 Java is boring )。該站點更好的通用功能之一是對 Java 語言工具和產品(應用服務器等)的矩陣式比較。
Bruce Eckel's MindView, Inc.
Bruce Eckel's MindView, Inc.
Eckel 寫了幾本 “用 …… 進行思考” 的書,內容關于 Java 語言、Python 和 C++ ,當我學習 Java 語言時,他的 Thinking in Java 對我尤其有幫助。它很實用并切中要害,在“在 Java 語言環境中如何面向對象思考”方面具有卓識。您可以從此站點免費下載他所有書籍的電子版。他也寫了許多好文章,并且他把這些文章的鏈接都放到了這里(包括關于 Jython、Java 和 .NET 比較等內容的文章)。
ONJava.com
ONJava.com
O'Reilley 歷年來出版了一些有關編程語言和工具的優秀書籍。他們的專注于 Java 語言的網站也不錯。它有些有關各種 Java 語言工具(如 JDOM 和 Hibernate)、Java 平臺(如 J2SE 和 J2EE)不同領域不同部分的文章。全部都可以被檢索到。他們有優秀的文章和教程。該站點按主題排列。例如有 Java 和 XML、Java Security、Wireless Java 和 Java SysAdmin。該站點也有到 O'Reilley Learning Lab 的鏈接,在那里您能獲得在線參考資料(Java 語言相關和其他的)。那些不是免費的,但是許多都面向大學認證。因此您可以以一種很方便的方式來學習技能,并得到一些認證。
java.net
java.net 社區
java.net 社區有多個“社區”,有特定于主題的論壇和文章。例如 Java Desktop 社區有各類與 Java 語言桌面開發相關的資料。Java Patterns 社區作為一個門戶,也許對提供 Java 語言的模式資源相當感興趣。還有一個 Java User Groups (JUG) 社區,在那里能找到有關創建、加入和管理一個 JUG 的信息。
結束語
任何 “好的”、“關鍵性的” 或者 “重要的” 參考資料列表都注定是不完整的,本文的列表也未能例外。 Java 語言的書籍數目眾多,當然,萬維網也很龐大。除本文所列的參考資料之外,還有很多用于學習 Java 語言的參考資料。但如果您擁有了這里所提到的所有書籍、網站、文章或者教程,您應當已經擁有了一個使您良好開端并助您登堂入室的實用寶庫。
最后,要成為一個能力日增和高效的 Java 語言開發人員,方法就是用它工作,動手來嘗試。如果有一個教程詳細介紹了所需創建的軟件的每一部分,您很可能并沒得到多少好處。有時,您可能得走自己的路。在成功地嘗試了一些新的東西之后,您可能想要寫一篇文章、教程或者一本書來分享您所學到的。
參考資料
關于作者
|
|
|
Roy Miller 是一名獨立軟件開發培訓師、程序員兼作家,他在充滿挑戰、快節奏的咨詢公司里從事了十多年軟件開發和項目管理工作。他最初在 Andersen Consulting(現在是 Accenture)公司工作,在那里,他管理團隊實現了許多系統,從主機記帳系統到星形模式數據集市。最近三年來,他在北卡羅來納州 Holly Springs 的 RoleModel Software, Inc. 公司工作,在那里他專業地運用著 Java 技術,并擔任開發人員兼 Extreme Programming (XP) 培訓師。他與人合著了 Addison-Wesley XP 系列的 Extreme Programming Applied: Playing to Win 一書,最近他寫了 Managing Software for Growth: Without Fear, Control and the Manufacturing Mindset 一書,來幫助經理和管理層理解:像 XP 這樣的敏捷構建方法為什么比傳統的方法更有效。2003 年,他創辦了自己的公司:The Other Road,該公司幫助其他公司了解如何向 XP 和被他稱為 Extreme Business (XB) 的方法轉換。
|
Jbuider生成EXE文件
發表人:wzh2352802 | 發表時間: 2007年一月17日, 19:51
倘若說看到標題后,以為jb真的提供了一種把java應用程序打包成exe文件的主流方法的話,你會失望的,下面的一個小技巧只是一個技巧而已。
這個是borland不公開的使用技巧,能夠通過jbuilder來制作exe文件來啟動java文件。jbuilder并不支持本地編譯機制。但是有一個隱藏的技巧可以讓你從可執行文件來啟動java程序,可以出現或者不出現console窗口。想做到這些,需要jbuilder的bin目錄下的這些文件:
jbuilder.exe
jbuilderw.exe (可選)
jbuilder.config
jdk.config
javalauncher.dll
“jbuilder.exe”是一個通用的可執行外殼文件,用以啟動java程序,”jbuilderw.exe“好像是javaw.exe一樣,它把”jbuilder.exe”包裝起來,但是運行時候不顯示那個console的窗口。使用這些文件的關鍵是文件名。“jbuilder.exe”查找一個文件叫”jbuilder.config”的配置文件,里面包含了運行java程序的必須信息。同樣的”jbuilderw.exe”查找”jbuilder.exe”來啟動不帶console窗口的java程序。如果把jbuilder.exe重命名為”foo.exe”,那”foo.exe”將去尋找”foo.config”配置文件,同樣”jbuilderw.exe”被重命名為”foow.exe”,它會去尋找”foo.exe”文件。
說到這里,聰明的讀者應該猜到怎樣利用jbuilder.exe來啟動應用程序了。只要把jbuilder.exe,jbuilerw.exe,jbuilder.config改名成相應的文件名,在jbuilder.config里面指定主類和類路徑,就能夠通過執行jbuilder.exe(或者被改名后的exe文件)來啟動java應用程序了。下面是用本機為例。
borland jbuilder 5被安裝在e:jbuilder5目錄下,在e:jbuilder5in下建立一個temp目錄,然后把jbuilder.exe,jbuilder.config,javalauncher.dll,jdk.config四個文件拷貝到:jbuilder5in emp目錄下,然后在這個目錄下建立一個hello目錄,在這個目錄下生成一個hello.java文件,即e:jbuilder5in emphellohello.java文件,file://hello.java/package
hello;
public class hello{
public static void main(string s[]){
system.out.println("hello, exe file!");
}
}
然后打開jbuilder.config文件,作相應的修改:
在jbuilder.config里面找到下面兩行
# start jbuilder using the its main class
mainclass com.borland.jbuilder.jbuilder
修改為
# start jbuilder using the its main class
mainclass hello.hello
addpath e:/jbuilder5/bin/temp/
addpath命令是把目錄加入類路徑中,這個命令和其它config里面可以識別的命令可以在jbuilder/bin目錄下的config_readme.txt里面找到詳細說明。
然后將jdk.config里面的javapath修改成相對的路徑,例如原來是
javapath ../jdk1.3/bin/java
修改成
javapath ../../jdk1.3/bin/java
最后
將jbuilder.exe,jbuilder.config修改成所需要的文件名,例如foo.exe和foo.config文件 。
現在執行foo.exe文件
至此,通過修改jbuilder來使用exe文件啟動自己的java應用程序已經完成了。
但是好玩的地方并不在這個地方,下面的小技巧可能更有趣,將jar文件打包進入exe文件!
假設利用上面的文件,生成hello.jar包,執行過程.
jar cvf hello.jar hello*.class
將類文件打包成exe文件
然后將jar包附加到jbuilder.exe后面去.
copy /b ..jbuilder.exe+hello.jar foo.exe
將jar文件轉化成exe文件
在foo.config(jbuilder.config)文件里面把前面加入的類路徑去掉,并加入下面的路徑:
addpath e:/jbuilder5/bin/temp/foo.exe
然后執行.
看到了么?一個含jar包的exe文件被執行了!
這個過程的大致原理是:exe文件的重要信息都在文件頭部,所以把亂七八糟的東西放exe文件尾部是不要緊的;而jar/zip文件的重要信息是在文件尾部的,這樣它們兩不相干,能夠容易的被執行。
詳細介紹什么是Java虛擬機
發表人:wzh2352802 | 發表時間: 2007年一月17日, 19:40
一、什么是Java虛擬機
當你談到Java虛擬機時,你可能是指:
1、抽象的Java虛擬機規范
2、一個具體的Java虛擬機實現
3、一個運行的Java虛擬機實例
二、Java虛擬機的生命周期
一個運行中的Java虛擬機有著一個清晰的任務:執行Java程序。程序開始執行時他才運行,程序結束時他就停止。你在同一臺機器上運行三個程序,就會有三個運行中的Java虛擬機。
Java虛擬機總是開始于一個main()方法,這個方法必須是公有、返回void、直接受一個字符串數組。在程序執行時,你必須給Java虛擬機指明這個包換main()方法的類名。
Main()方法是程序的起點,他被執行的線程初始化為程序的初始線程。程序中其他的線程都由他來啟動。Java中的線程分為兩種:守護線程 (daemon)和普通線程(non-daemon)。守護線程是Java虛擬機自己使用的線程,比如負責垃圾收集的線程就是一個守護線程。當然,你也可 以把自己的程序設置為守護線程。包含Main()方法的初始線程不是守護線程。
只要Java虛擬機中還有普通的線程在執行,Java虛擬機就不會停止。如果有足夠的權限,你可以調用exit()方法終止程序。
三、Java虛擬機的體系結構
在Java虛擬機的規范中定義了一系列的子系統、內存區域、數據類型和使用指南。這些組件構成了Java虛擬機的內部結構,他們不僅僅為Java虛擬機的實現提供了清晰的內部結構,更是嚴格規定了Java虛擬機實現的外部行為。
每一個Java虛擬機都由一個類加載器子系統(class loader subsystem),負責加載程序中的類型(類和接口),并賦予唯一的名字。每一個Java虛擬機都有一個執行引擎(execution engine)負責執行被加載類中包含的指令。
程序的執行需要一定的內存空間,如字節碼、被加載類的其他額外信息、程序中的對象、方法的參數、返回值、本地變量、處理的中間變量等等。Java虛擬機將 這些信息統統保存在數據區(data areas)中。雖然每個Java虛擬機的實現中都包含數據區,但是Java虛擬機規范對數據區的規定卻非常的抽象。許多結構上的細節部分都留給了 Java虛擬機實現者自己發揮。不同Java虛擬機實現上的內存結構千差萬別。一部分實現可能占用很多內存,而其他以下可能只占用很少的內存;一些實現可 能會使用虛擬內存,而其他的則不使用。這種比較精煉的Java虛擬機內存規約,可以使得Java虛擬機可以在廣泛的平臺上被實現。
數據區中的一部分是整個程序共有,其他部分被單獨的線程控制。每一個Java虛擬機都包含方法區(method area)和堆(heap),他們都被整個程序共享。Java虛擬機加載并解析一個類以后,將從類文件中解析出來的信息保存與方法區中。程序執行時創建的 對象都保存在堆中。
當一個線程被創建時,會被分配只屬于他自己的PC寄存器“pc register”(程序計數器)和Java堆棧(Java stack)。當線程不掉用本地方法時,PC寄存器中保存線程執行的下一條指令。Java堆棧保存了一個線程調用方法時的狀態,包括本地變量、調用方法的 參數、返回值、處理的中間變量。調用本地方法時的狀態保存在本地方法堆棧中(native method stacks),可能再寄存器或者其他非平臺獨立的內存中。
Java堆棧有堆棧塊(stack frames (or frames))組成。堆棧塊包含Java方法調用的狀態。當一個線程調用一個方法時,Java虛擬機會將一個新的塊壓到Java堆棧中,當這個方法運行結束時,Java虛擬機會將對應的塊彈出并拋棄。
Java虛擬機不使用寄存器保存計算的中間結果,而是用Java堆棧在存放中間結果。這是的Java虛擬機的指令更緊湊,也更容易在一個沒有寄存器的設備上實現Java虛擬機。
圖中的Java堆棧中向下增長的,PC寄存器中線程三為灰色,是因為它正在執行本地方法,他的下一條執行指令不保存在PC寄存器中。
四、數據類型(Data Types)
所有Java虛擬機中使用的數據都有確定的數據類型,數據類型和操作都在Java虛擬機規范中嚴格定義。Java中的數據類型分為原始數據類型 (primitive types)和引用數據類型(reference type)。引用類型依賴于實際的對象,但不是對象本身。原始數據類型不依賴于任何東西,他們就是本身表示的數據。
所有Java程序語言中的原始 數據類型,都是Java虛擬機的原始數據類型,除了布爾型(boolean)。當編譯器將Java源代碼編譯為自己碼時,使用整型(int)或者字節型 (byte)去表示布爾型。在Java虛擬機中使用整數0表示布爾型的false,使用非零整數表示布爾型的true,布爾數組被表示為字節數組,雖然他 們可能會以字節數組或者字節塊(bit fields)保存在堆中。
除了布爾型,其他Java語言中的原始類型都是Java虛擬機中的數據類型。在Java中數據類型被分為:整形的byte,short,int,long;char和浮點型的float,double。Java語言中的數據類型在任何主機上都有同樣的范圍。
在Java虛擬機中還存在一個Java語言中不能使用的原始數據類型返回值類型(returnValue)。這種類型被用來實現Java程序中的“finally clauses”,具體的參見18章的“Finally Clauses”。
引用類型可能被創建為:類類型(class type),接口類型(interface type),數組類型(array type)。他們都引用被動態創建的對象。當引用類型引用null時,說明沒有引用任何對象。
Java虛擬機規范只定義了每一種數據類型表示的范圍,沒有定義在存儲時每種類型占用的空間。他們如何存儲由Java虛擬機的實現者自己決定。關于浮點型更多信息參見14章“Floating Point Arithmetic”。
TypeRange
byte8-bit signed two's complement integer (-27 to 27 - 1, inclusive)
short16-bit signed two's complement integer (-215 to 215 - 1, inclusive)
int32-bit signed two's complement integer (-231 to 231 - 1, inclusive)
long64-bit signed two's complement integer (-263 to 263 - 1, inclusive)
char16-bit unsigned Unicode character (0 to 216 - 1, inclusive)
float32-bit IEEE 754 single-precision float
double64-bit IEEE 754 double-precision float
returnValueaddress of an opcode within the same method
referencereference to an object on the heap, or null
五、字節長度
Java虛擬機中最小的數據單元式字(word),其大小由Java虛擬機的實現者定義。但是一個字的大小必須足夠容納byte,short,int, char,float,returnValue,reference;兩個字必須足夠容納long,double。所以虛擬機的實現者至少提供的字不能小 于31bits的字,但是最好選擇特定平臺上最有效率的字長。
在運行時,Java程序不能決定所運行機器的字長。字長也不會影響程序的行為,他只是在Java虛擬機中的一種表現方式。
六、類加載器子系統
Java虛擬機中的類加載器分為兩種:原始類加載器(primordial class loader)和類加載器對象(class loader objects)。原始類加載器是Java虛擬機實現的一部分,類加載器對象是運行中的程序的一部分。不同類加載器加載的類被不同的命名空間所分割。
類加載器調用了許多Java虛擬機中其他的部分和java.lang包中的很多類。比如,類加載對象就是java.lang.ClassLoader子類 的實例,ClassLoader類中的方法可以訪問虛擬機中的類加載機制;每一個被Java虛擬機加載的類都會被表示為一個 java.lang.Class類的實例。像其他對象一樣,類加載器對象和Class對象都保存在堆中,被加載的信息被保存在方法區中。
1、加載、連接、初始化(Loading, Linking and Initialization)
類加載子系統不僅僅負責定位并加載類文件,他按照以下嚴格的步驟作了很多其他的事情:(具體的信息參見第七章的“類的生命周期”)
1)、加載:尋找并導入指定類型(類和接口)的二進制信息
2)、連接:進行驗證、準備和解析
①驗證:確保導入類型的正確性
②準備:為類型分配內存并初始化為默認值
③解析:將字符引用解析為直接飲用
3)、初始化:調用Java代碼,初始化類變量為合適的值
2、原始類加載器(The Primordial Class Loader)
每個Java虛擬機都必須實現一個原始類加載器,他能夠加載那些遵守類文件格式并且被信任的類。但是,Java虛擬機的規范并沒有定義如何加載類,這由 Java虛擬機實現者自己決定。對于給定類型名的類型,原始萊加載器必須找到那個類型名加“.class”的文件并加載入虛擬機中。
3、類加載器對象
雖然類加載器對象是Java程序的一部分,但是ClassLoader類中的三個方法可以訪問Java虛擬機中的類加載子系統。
1)、protected final Class defineClass(…):使用這個方法可以出入一個字節數組,定義一個新的類型。
2)、protected Class findSystemClass(String name):加載指定的類,如果已經加載,就直接返回。
3)、protected final void resolveClass(Class c):defineClass()方法只是加載一個類,這個方法負責后續的動態連接和初始化。
具體的信息,參見第八章“連接模型”( The Linking Model)。
4、命名空間
當多個類加載器加載了同一個類時,為了保證他們名字的唯一性,需要在類名前加上加載該類的類加載器的標識。具體的信息,參見第八章“連接模型”( The Linking Model)。
七、方法區(The Method Area)
在Java虛擬機中,被加載類型的信息都保存在方法區中。這寫信息在內存中的組織形式由虛擬機的實現者定義,比如,虛擬機工作在一個“little- endian”的處理器上,他就可以將信息保存為“little-endian”格式的,雖然在Java類文件中他們是以“big-endian”格式保 存的。設計者可以用最適合并地機器的表示格式來存儲數據,以保證程序能夠以最快的速度執行。但是,在一個只有很小內存的設備上,虛擬機的實現者就不會占用 很大的內存。
程序中的所有線程共享一個方法區,所以訪問方法區信息的方法必須是線程安全的。如果你有兩個線程都去加載一個叫Lava的類,那只能由一個線程被容許去加載這個類,另一個必須等待。
在程序運行時,方法區的大小是可變的,程序在運行時可以擴展。有些Java虛擬機的實現也可以通過參數也訂制方法區的初始大小,最小值和最大值。
方法區也可以被垃圾收集。因為程序中的內由類加載器動態加載,所有類可能變成沒有被引用(unreferenced)的狀態。當類變成這種狀態時,他就可 能被垃圾收集掉。沒有加載的類包括兩種狀態,一種是真正的沒有加載,另一個種是“unreferenced”的狀態。詳細信息參見第七章的類的生命周期 (The Lifetime of a Class)。
1、類型信息(Type Information)
每一個被加載的類型,在Java虛擬機中都會在方法區中保存如下信息:
1)、類型的全名(The fully qualified name of the type)
2)、類型的父類型的全名(除非沒有父類型,或者弗雷形式java.lang.Object)(The fully qualified name of the typeís direct superclass)
3)、給類型是一個類還是接口(class or an interface)(Whether or not the type is a class )
4)、類型的修飾符(public,private,protected,static,final,volatile,transient等)(The typeís modifiers)
5)、所有父接口全名的列表(An ordered list of the fully qualified names of any direct superinterfaces)
類型全名保存的數據結構由虛擬機實現者定義。除此之外,Java虛擬機還要為每個類型保存如下信息:
1)、類型的常量池(The constant pool for the type)
2)、類型字段的信息(Field information)
3)、類型方法的信息(Method information)
4)、所有的靜態類變量(非常量)信息(All class (static) variables declared in the type, except constants)
5)、一個指向類加載器的引用(A reference to class ClassLoader)
6)、一個指向Class類的引用(A reference to class Class)
1)、類型的常量池(The constant pool for the type)
常量池中保存中所有類型是用的有序的常量集合,包含直接常量(literals)如字符串、整數、浮點數的常量,和對類型、字段、方法的符號引用。常量池 中每一個保存的常量都有一個索引,就像數組中的字段一樣。因為常量池中保存中所有類型使用到的類型、字段、方法的字符引用,所以它也是動態連接的主要對 象。詳細信息參見第六章“The Java Class File”。
2)、類型字段的信息(Field information)
字段名、字段類型、字段的修飾符(public,private,protected,static,final,volatile,transient等)、字段在類中定義的順序。
3)、類型方法的信息(Method information)
方法名、方法的返回值類型(或者是void)、方法參數的個數、類型和他們的順序、字段的修飾符(public,private,protected,static,final,volatile,transient等)、方法在類中定義的順序
如果不是抽象和本地本法還需要保存
方法的字節碼、方法的操作數堆棧的大小和本地變量區的大小(稍候有詳細信息)、異常列表(詳細信息參見第十七章“Exceptions”。)
4)、類(靜態)變量(Class Variables)
類變量被所有類的實例共享,即使不通過類的實例也可以訪問。這些變量綁定在類上(而不是類的實例上),所以他們是類的邏輯數據的一部分。在Java虛擬機使用這個類之前就需要為類變量(non-final)分配內存
常量(final)的處理方式于這種類變量(non-final)不一樣。每一個類型在用到一個常量的時候,都會復制一份到自己的常量池中。常量也像類變 量一樣保存在方法區中,只不過他保存在常量池中。(可能是,類變量被所有實例共享,而常量池是每個實例獨有的)。Non-final類變量保存為定義他的 類型數據(data for the type that declares them)的一部分,而final常量保存為使用他的類型數據(data for any type that uses them)的一部分。詳情參見第六章“The Java Class FileThe Java Class File”
5)、指向類加載器的引用(A reference to class ClassLoader)
每一個被Java虛擬機加載的類型,虛擬機必須保存這個類型是否由原始類加載器或者類加載器加載。那些被類加載器加載的類型必須保存一個指向類加載器的引 用。當類加載器動態連接時,會使用這條信息。當一個類引用另一個類時,虛擬機必須保存那個被引用的類型是被同一個類加載器加載的,這也是虛擬機維護不同命 名空間的過程。詳情參見第八章“The Linking Model”
6)、指向Class類的引用(A reference to class Class)
Java虛擬機為每一個加載的類型創建一個java.lang.Class類的實例。你也可以通過Class類的方法:
public static Class forName(String className)來查找或者加載一個類,并取得相應的Class類的實例。通過這個Class類的實例,我們可以訪問Java虛擬機方法區中的信息。具體參照Class類的JavaDoc。
2、方法列表(Method Tables)
為了更有效的訪問所有保存在方法區中的數據,這些數據的存儲結構必須經過仔細的設計。所有方法區中,除了保存了上邊的那些原始信息外,還有一個為了加快存 取速度而設計的數據結構,比如方法列表。每一個被加載的非抽象類,Java虛擬機都會為他們產生一個方法列表,這個列表中保存了這個類可能調用的所有實例 方法的引用,報錯那些父類中調用的方法。詳情參見第八章“The Linking Model”八、堆
當Java程序創建一個類的實例或者數組時,都在堆中為新的對象分配內存。虛擬機中只有一個堆,所有的線程都共享他。
1、垃圾收集(Garbage Collection)
垃圾收集是釋放沒有被引用的對象的主要方法。它也可能會為了減少堆的碎片,而移動對象。在Java虛擬機的規范中沒有嚴格定義垃圾收集,只是定義一個Java虛擬機的實現必須通過某種方式管理自己的堆。詳情參見第九章“Garbage Collection”。
2、對象存儲結構(Object Representation)
Java虛擬機的規范中沒有定義對象怎樣在堆中存儲。每一個對象主要存儲的是他的類和父類中定義的對象變量。對于給定的對象的引用,虛擬機必須嫩耨很快的 定位到這個對象的數據。另為,必須提供一種通過對象的引用方法對象數據的方法,比如方法區中的對象的引用,所以一個對象保存的數據中往往含有一個某種形式 指向方法區的指針。
一個可能的堆的設計是將堆分為兩個部分:引用池和對象池。一個對象的引用就是指向引用池的本地指針。每一個引用池中的條目都包含兩個部分:指向對象池中對 象數據的指針和方法區中對象類數據的指針。這種設計能夠方便Java虛擬機堆碎片的整理。當虛擬機在對象池中移動一個對象的時候,只需要修改對應引用池中 的指針地址。但是每次訪問對象的數據都需要處理兩次指針。下圖演示了這種堆的設計。在第九章的“垃圾收集”中的HeapOfFish Applet演示了這種設計。
另一種堆的設計是:一個對象的引用就是一個指向一堆數據和指向相應對象的偏移指針。這種設計方便了對象的訪問,可是對象的移動要變的異常復雜。下圖演示了這種設計
當程序試圖將一個對象轉換為另一種類型時,虛擬機需要判斷這種轉換是否是這個對象的類型,或者是他的父類型。當程序適用instanceof語句的時候也 會做類似的事情。當程序調用一個對象的方法時,虛擬機需要進行動態綁定,他必須判斷調用哪一個類型的方法。這也需要做上面的判斷。
無論虛擬機實現者使用哪一種設計,他都可能為每一個對象保存一個類似方法列表的信息。因為他可以提升對象方法調用的速度,對提升虛擬機的性能非常重要,但 是虛擬機的規范中比沒有要求必須實現類似的數據結構。下圖描述了這種結構。圖中顯示了一個對象引用相關聯的所有的數據結構,包括:
1)、一個指向類型數據的指針
2)、一個對象的方法列表。方法列表是一個指向所有可能被調用對象方法的指針數組。方法數據包括三個部分:操作碼堆棧的大小和方法堆棧的本地變量區;方法的字節碼;異常列表。
每一個Java虛擬機中的對象必須關聯一個用于同步多線程的lock(mutex)。同一時刻,只能有一個對象擁有這個對象的鎖。當一個擁有這個這個對象 的鎖,他就可以多次申請這個鎖,但是也必須釋放相應次數的鎖才能真正釋放這個對象鎖。很多對象在整個生命周期中都不會被鎖,所以這個信息只有在需要時才需 要添加。很多Java虛擬機的實現都沒有在對象的數據中包含“鎖定數據”,只是在需要時才生成相應的數據。除了實現對象的鎖定,每一個對象還邏輯關聯到一 個“wait set”的實現。鎖定幫組線程獨立處理共享的數據,不需要妨礙其他的線程。“wait set”幫組線程協作完成同一個目標。“wait set”往往通過Object類的wait()和notify()方法來實現。
垃圾收集也需要堆中的對象是否被關聯的信息。Java虛擬機規范中指出垃圾收集一個運行一個對象的finalizer方法一次,但是容許 finalizer方法重新引用這個對象,當這個對象再次不被引用時,就不需要再次調用finalize方法。所以虛擬機也需要保存finalize方法 是否運行過的信息。更多信息參見第九章的“垃圾收集”
3、數組的保存(Array Representation)
在Java 中,數組是一種完全意義上的對象,他和對象一樣保存在堆中、有一個指向Class類實例的引用。所有同一維度和類型的數組擁有同樣的Class,數組的長 度不做考慮。對應Class的名字表示為維度和類型。比如一個整型數據的Class為“[I”,字節型三維數組Class名為“[[[B”,兩維對象數據 Class名為“[[Ljava.lang.Object”。
多維數組被表示為數組的數組,如下圖:
數組必須在堆中保存數組的長度,數組的數據和一些對象數組類型數據的引用。通過一個數組引用的,虛擬機應該能夠取得一個數組的長度,通過索引能夠訪問特定 的數據,能夠調用Object定義的方法。Object是所有數據類的直接父類。更多信息參見第六章“類文件”。
九、PC寄存器(程序計數器)(The Program Counter)
每一個線程開始執行時都會被創建一個程序計數器。程序計數器只有一個字長(word),所以它能夠保存一個本地指針和returnValue。當線程執行 時,程序計數器中存放了正在執行指令的地址,這個地址可以使一個本地指針,也可以使一個從方法字節碼開始的偏移指針。如果執行本地方法,程序計數器的值沒 有被定義。
十、Java堆棧(The Java Stack)
當一個線程啟動時,Java虛擬機會為他創建一個Java堆棧。Java堆棧用一些離散的frame類紀錄線程的狀態。Java虛擬機堆Java堆棧的操作只有兩種:壓入和彈出frames。
線程中正在執行的方法被稱為當前方法(current method),當前方法所對應的frame被稱為當前幀(current frame)。定義當前方法的類被稱為當前類(current class),當前類的常量池被稱為當前常量池(current constant pool.)。當線程執行時,Java虛擬機會跟蹤當前類和當前常量池。但線程操作保存在幀中的數據時,他只操作當前幀的數據。
當線程調用一個方法時,虛擬機會生成一個新的幀,并壓入線程的Java堆棧。這個新的幀變成當前幀。當方法執行時,他使用當前幀保存方法的參數、本地變 量、中間結構和其他數據。方法有兩種退出方式:正常退出和異常推出。無論方法以哪一種方式推出,Java虛擬機都會彈出并丟棄方法的幀,上一個方法的幀變 為當前幀。
所有保存在幀中的數據都只能被擁有它的線程訪問,線程不能訪問其他線程的堆棧中的數據。所以,訪問方法的本地變量時,不需要考慮多線程同步。
和方法區、堆一樣,Java堆棧不需要連續的內存空間,它可以被保存在一個分散的內存空間或者堆上。堆棧具體的數據和長度都有Java虛擬機的實現者自己定義。一些實現可能提供了執行堆棧最大值和最小值的方法。
十一、堆棧幀(The Stack Frame)
堆棧幀包含三部分:本地變量、操作數堆棧和幀數據。本地變量和操作數堆棧的大小都是一字(word)為單位的,他們在編譯就已經確定。幀數據的大小取決于 不同的實現。當程序調用一個方法時,虛擬機從類數據中取得本地變量和操作數堆棧的大小,創建一個合適大小和幀,然后壓入Java堆棧中。
1、本地變量(Local Variables)
本地變量在Java堆棧幀中被組織為一個從0計數的數組,指令通過提供他們的索引從本地變量區中取得相應的值。Int,float,reference, returnValue占一個字,byte,short,char被轉換成int然后存儲,long和doubel占兩個字。
指令通過提供兩個字索引中的前一個來取得long,doubel的值。比如一個long的值存儲在索引3,4上,指令就可以通過3來取得這個long類型的值。
本地變量區中包含了方法的參數和本地變量。編譯器將方法的參數以他們申明的順序放在數組的前面。但是編譯器卻可以將本地變量任意排列在本地變量數組中,甚至兩個本地變量可以公用一個地址,比如,當兩個本地變量在兩個不交疊的區域內,就像循環變量i,j。
虛擬機的實現者可以使用任何結構來描述本地變量區中的數據,虛擬機規范中沒有定義如何存儲long和doubel。
2、操作數堆棧(Operand Stack)
向本地變量一樣,操作數堆棧也被組織為一個以字為單位的數組。但是不像本地變量那樣通過索引訪問,而是通過push和pop值來實現訪問的。如果一個指令push一個值到堆棧中,那么下一個指令就可以pop并且使用這個值。
操作數堆棧不像程序計數器那樣不可以被指令直接訪問,指令可以直接訪問操作數堆棧。Java虛擬機是一個以堆棧為基礎,而不是以寄存器為基礎的,因為它的 指令從堆棧中取得操作數,而不是同寄存器中。當然,指令也可以從其他地方去的操作數,比如指令后面的操作碼,或者常量池。但是Java虛擬機指令主要是從 操作數堆棧中取得他們需要的操作數。
Java虛擬機將操作數堆棧視為工作區,很多指令通過先從操作數堆棧中pop值,在處理完以后再將結果push回操作數堆棧。一個add的指令執行過程如 下圖所示:先執行iload_0和iload_1兩條指令將需要相加的兩個數,從本地方法區中取出,并push到操作數堆棧中;然后執行iadd指令,現 pop出兩個值,相加,并將結果pusp進操作數堆棧中;最后執行istore_2指令,pop出結果,賦值到本地方法區中。
3、幀數據(Frame Data)
處理本地變量和操作數堆棧以外,java堆棧幀還包括了為了支持常量池,方法返回值和異常分發需要的數據,他們被保存在幀數據中。
當虛擬機遇到使用指向常量池引用的指令時,就會通過幀數據中指向常量區的指針來訪問所需要的信息。前面提到過,常量區中的引用在最開始時都是符號引用。即使當虛擬機檢查這些引用時,他們也是字符引用。所以虛擬機需要在這時轉換這個引用。
當一個方法正常返回時,虛擬機需要重建那個調用這個方法的方法的堆棧幀。如果執行完的方法有返回值,虛擬機就需要將這個值push進調用方法的哪個操作數堆棧中。
幀數據中也包含虛擬機用來處理異常的異常表的引用。異常表定義了一個被catch語句保護的一段字節碼。每一個異常表中的個體又包含了需要保護的字節瑪的 范圍,和異常被捕捉到時需要執行的字節碼的位置。當一個方法拋出一個異常時,Java虛擬機就是用異常表去判斷如何處理這個異常。如果虛擬機找到了一個匹 配的catch,他就會將控制權交給catch語句。如果沒有找到匹配的catch,方法就會異常返回,然后再調用的方法中繼續這個過程。
除了以上的三個用途外,幀數據還可能包含一些依賴于實現的數據,比如調試的信息。
十二、本地方法堆棧
本地方法區依賴于虛擬機的不同實現。虛擬機的實現者可以自己決定使用哪一種機制去執行本地方法。
任何本地方法接口(Native Method Interface)都使用某種形式的本地方法堆棧。
十三、執行引擎
一個java虛擬機實現的核心就是執行引擎。在Java虛擬機規范,執行引擎被描述為一系列的指令。對于每一個指令,規范都描述了他們應該做什么,但是沒有說要如何去做。
1、指令集
在Java虛擬機中一個方法的字節碼流就是一個指令的序列。每一個指令由一個字節的操作碼(Opcode)和可能存在的操作數(Operands)。操作 碼指示去做什么,操作數提供一些執行這個操作碼可能需要的額外的信息。一個抽象的執行引擎每次執行一個指令。這個過程發生在每一個執行的線程中。
有時,執行引擎可能會遇到一個需要調用本地方法的指令,在這種情況下,執行引擎會去試圖調用本地方法,但本地方法返回時,執行引擎會繼續執行字節碼流中的下一個指令。本地方法也可以看成對Java虛擬機中的指令集的一種擴充。
決定下一步執行那一條指令也是執行引擎工作的一部分。執行引擎有三種方法去取得下一條指令。多數指令會執行跟在他會面的指令;一些像goto, return的指令,會在他們執行的時候決定他們的下一條指令;當一個指令拋出異常時,執行引擎通過匹配catch語句來決定下一條應該執行的指令。
平臺獨立性、網絡移動性、安全性左右了Java虛擬機指令集的設計。平臺獨立性是指令集設計的主要影響因素之一。基于堆棧的結構使得Java虛擬機可以在 更多的平臺上實現。更小的操作碼,緊湊的結構使得字節碼可以更有效的利用網絡帶寬。一次性的字節碼驗證,使得字節碼更安全,而不影響太多的性能。
2、執行技術
許多種執行技術可以用在Java虛擬機的實現中:解釋執行,及時編譯(just-in-time compiling),hot-spot compiling,native execution in silicon。
3、線程
Java虛擬機規范定義了一種為了在更多平臺上實現的線程模型。Java線程模型的一個目標時可以利用本地線程。利用本地線程可以讓Java程序中的線程能過在多處理器機器上真正的同時執行。
Java線程模型的一個代價就是線程優先級,一個Java線程可以在1-10的優先級上運行。1最低,10最高。如果設計者使用了本地線程,他們可能將這 10個優先級映射到本地優先級上。Java虛擬機規范只定義了,高一點優先級的線程可以卻一些cpu時間,低優先級的線程在所有高優先級線程都堵塞時,也 可以獲取一些cpu時間,但是這沒有保證:低優先級的線程在高優先級線程沒有堵塞時不可以獲得一定的cpu時間。因此,如果需要在不同的線程間協作,你必 須使用的“同步(synchronizatoin)”。
同步意味著兩個部分:對象鎖(object locking)和線程等待、激活(thread wait and notify)。對象鎖幫助線程可以不受其他線程的干擾。線程等待、激活可以讓不同的線程進行協作。
在Java虛擬機的規范中,Java線程被描述為變量、主內存、工作內存。每一個Java虛擬機的實例都有一個主內存,他包含了所有程序的變量:對象、數組合類變量。每一個線程都有自己的工作內存,他保存了哪些他可能用到的變量的拷貝。規則:
1)、從主內存拷貝變量的值到工作內存中
2)、將工作內存中的值寫會主內存中
如果一個變量沒有被同步化,線程可能以任何順序更新主內存中的變量。為了保證多線程程序的正確的執行,必須使用同步機制。
十四、本地方法接口(Native Method Interface)
Java虛擬機的實現并不是必須實現本地方法接口。一些實現可能根本不支持本地方法接口。Sun的本地方法接口是JNI(Java Native Interface)。
十五、現實中的機器(The Real Machine)
十六、數學方法:仿真(Eternal Math : A Simulation)