#
Java把內存劃分成兩種:一種是棧內存,一種是堆內存。
在函數中定義的一些基本類型的變量和對象的引用變量都在函數的棧內存中分配。
當在一段代碼塊定義一個變量時,Java就在棧中為這個變量分配內存空間,當超過變量的作用域后,Java會自動釋放掉為該變量所分配的內存空間,該內存空間可以立即被另作他用。
堆內存用來存放由new創建的對象和數組。
在堆中分配的內存,由Java虛擬機的自動垃圾回收器來管理。
在堆中產生了一個數組或對象后,還可以在棧中定義一個特殊的變量,讓棧中這個變量的取值等于數組或對象在堆內存中的首地址,棧中的這個變量就成了數組或對象的引用變量。
引用變量就相當于是為數組或對象起的一個名稱,以后就可以在程序中使用棧中的引用變量來訪問堆中的數組或對象。
具體的說:
棧與堆都是Java用來在Ram中存放數據的地方。與C++不同,Java自動管理棧和堆,程序員不能直接地設置棧或堆。
Java的堆是一個運行時數據區,類的(對象從中分配空間。這些對象通過new、newarray、anewarray和multianewarray等 指令建立,它們不需要程序代碼來顯式的釋放。堆是由垃圾回收來負責的,堆的優勢是可以動態地分配內存大小,生存期也不必事先告訴編譯器,因為它是在運行時 動態分配內存的,Java的垃圾收集器會自動收走這些不再使用的數據。但缺點是,由于要在運行時動態分配內存,存取速度較慢。
棧的優勢是,存取速度比堆要快,僅次于寄存器,棧數據可以共享。但缺點是,存在棧中的數據大小與生存期必須是確定的,缺乏靈活性。棧中主要存放一些基本 類型的變量(,int, short, long, byte, float, double, boolean, char)和對象句柄。
棧有一個很重要的特殊性,就是存在棧中的數據可以共享。假設我們同時定義:
int a = 3;
int b = 3;
編譯器先處理int a = 3;首先它會在棧中創建一個變量為a的引用,然后查找棧中是否有3這個值,如果沒找到,就將3存放進來,然后將a指向3。接著處理int b = 3;在創建完b的引用變量后,因為在棧中已經有3這個值,便將b直接指向3。這樣,就出現了a與b同時均指向3的情況。這時,如果再令a=4;那么編譯器 會重新搜索棧中是否有4值,如果沒有,則將4存放進來,并令a指向4;如果已經有了,則直接將a指向這個地址。因此a值的改變不會影響到b的值。要注意這 種數據的共享與兩個對象的引用同時指向一個對象的這種共享是不同的,因為這種情況a的修改并不會影響到b, 它是由編譯器完成的,它有利于節省空間。而一個對象引用變量修改了這個對象的內部狀態,會影響到另一個對象引用變量。
String是一個特殊的包裝類數據。可以用:
String str = new String("abc");
String str = "abc";
兩種的形式來創建,第一種是用new()來新建對象的,它會在存放于堆中。每調用一次就會創建一個新的對象。
而第二種是先在棧中創建一個對String類的對象引用變量str,然后查找棧中有沒有存放"abc",如果沒有,則將"abc"存放進棧,并令str指向”abc”,如果已經有”abc” 則直接令str指向“abc”。
比較類里面的數值是否相等時,用equals()方法;當測試兩個包裝類的引用是否指向同一個對象時,用==,下面用例子說明上面的理論。
String str1 = "abc";
String str2 = "abc";
System.out.println(str1==str2); //true
可以看出str1和str2是指向同一個對象的。
String str1 =new String ("abc");
String str2 =new String ("abc");
System.out.println(str1==str2); // false
用new的方式是生成不同的對象。每一次生成一個。
因此用第二種方式創建多個”abc”字符串,在內存中其實只存在一個對象而已. 這種寫法有利與節省內存空間. 同時它可以在一定程度上提高程序的運行速度,因為JVM會自動根據棧中數據的實際情況來決定是否有必要創建新對象。而對于String str = new String("abc");的代碼,則一概在堆中創建新對象,而不管其字符串值是否相等,是否有必要創建新對象,從而加重了程序的負擔。
另一方面, 要注意: 我們在使用諸如String str = "abc";的格式定義類時,總是想當然地認為,創建了String類的對象str。擔心陷阱!對象可能并沒有被創建!而可能只是指向一個先前已經創建的 對象。只有通過new()方法才能保證每次都創建一個新的對象。 由于String類的immutable性質,當String變量需要經常變換其值時,應該考慮使用StringBuffer類,以提高程序效率。
java中內存分配策略及堆和棧的比較
2.1 內存分配策略
按照編譯原理的觀點,程序運行時的內存分配有三種策略,分別是靜態的,棧式的,和堆式的.
靜態存儲分配是指在編譯時就能確定每個數據目標在運行時刻的存儲空間需求,因而在編譯時就可以給他們分配固定的內存空間.這種分配策略要求程序代碼中不允 許有可變數據結構(比如可變數組)的存在,也不允許有嵌套或者遞歸的結構出現,因為它們都會導致編譯程序無法計算準確的存儲空間需求.
棧式存儲分配也可稱為動態存儲分配,是由一個類似于堆棧的運行棧來實現的.和靜態存儲分配相反,在棧式存儲方案中,程序對數據區的需求在編譯時是完全未知 的,只有到運行的時候才能夠知道,但是規定在運行中進入一個程序模塊時,必須知道該程序模塊所需的數據區大小才能夠為其分配內存.和我們在數據結構所熟知 的棧一樣,棧式存儲分配按照先進后出的原則進行分配。
靜態存儲分配要求在編譯時能知道所有變量的存儲要求,棧式存儲分配要求在過程的入口處必須知道所有的存儲要求,而堆式存儲分配則專門負責在編譯時或運行時 模塊入口處都無法確定存儲要求的數據結構的內存分配,比如可變長度串和對象實例.堆由大片的可利用塊或空閑塊組成,堆中的內存可以按照任意順序分配和釋 放.
2.2 堆和棧的比較
上面的定義從編譯原理的教材中總結而來,除靜態存儲分配之外,都顯得很呆板和難以理解,下面撇開靜態存儲分配,集中比較堆和棧:
從堆和棧的功能和作用來通俗的比較,堆主要用來存放對象的,棧主要是用來執行程序的.而這種不同又主要是由于堆和棧的特點決定的:
在編程中,例如C/C++中,所有的方法調用都是通過棧來進行的,所有的局部變量,形式參數都是從棧中分配內存空間的。實際上也不是什么分配,只是從棧頂 向上用就行,就好像工廠中的傳送帶(conveyor belt)一樣,Stack Pointer會自動指引你到放東西的位置,你所要做的只是把東西放下來就行.退出函數的時候,修改棧指針就可以把棧中的內容銷毀.這樣的模式速度最快, 當然要用來運行程序了.需要注意的是,在分配的時候,比如為一個即將要調用的程序模塊分配數據區時,應事先知道這個數據區的大小,也就說是雖然分配是在程 序運行時進行的,但是分配的大小多少是確定的,不變的,而這個"大小多少"是在編譯時確定的,不是在運行時.
堆是應用程序在運行的時候請求操作系統分配給自己內存,由于從操作系統管理的內存分配,所以在分配和銷毀時都要占用時間,因此用堆的效率非常低.但是堆的 優點在于,編譯器不必知道要從堆里分配多少存儲空間,也不必知道存儲的數據要在堆里停留多長的時間,因此,用堆保存數據時會得到更大的靈活性。事實上,面 向對象的多態性,堆內存分配是必不可少的,因為多態變量所需的存儲空間只有在運行時創建了對象之后才能確定.在C++中,要求創建一個對象時,只需用 new命令編制相關的代碼即可。執行這些代碼時,會在堆里自動進行數據的保存.當然,為達到這種靈活性,必然會付出一定的代價:在堆里分配存儲空間時會花 掉更長的時間!這也正是導致我們剛才所說的效率低的原因,看來列寧同志說的好,人的優點往往也是人的缺點,人的缺點往往也是人的優點(暈~).
2.3 JVM中的堆和棧
JVM是基于堆棧的虛擬機.JVM為每個新創建的線程都分配一個堆棧.也就是說,對于一個Java程序來說,它的運行就是通過對堆棧的操作來完成的。堆棧以幀為單位保存線程的狀態。JVM對堆棧只進行兩種操作:以幀為單位的壓棧和出棧操作。
我們知道,某個線程正在執行的方法稱為此線程的當前方法.我們可能不知道,當前方法使用的幀稱為當前幀。當線程激活一個Java方法,JVM就會在線程的 Java堆棧里新壓入一個幀。這個幀自然成為了當前幀.在此方法執行期間,這個幀將用來保存參數,局部變量,中間計算過程和其他數據.這個幀在這里和編譯 原理中的活動紀錄的概念是差不多的.
從Java的這種分配機制來看,堆棧又可以這樣理解:堆棧(Stack)是操作系統在建立某個進程時或者線程(在支持多線程的操作系統中是線程)為這個線程建立的存儲區域,該區域具有先進后出的特性。
每一個Java應用都唯一對應一個JVM實例,每一個實例唯一對應一個堆。應用程序在運行中所創建的所有類實例或數組都放在這個堆中,并由應用所有的線程 共享.跟C/C++不同,Java中分配堆內存是自動初始化的。Java中所有對象的存儲空間都是在堆中分配的,但是這個對象的引用卻是在堆棧中分配,也 就是說在建立一個對象時從兩個地方都分配內存,在堆中分配的內存實際建立這個對象,而在堆棧中分配的內存只是一個指向這個堆對象的指針(引用)而已。
1.去
http://barcode4j.sourceforge.net/下載文件,(源代碼和生成好的都要
下載)
2.解壓barcode4j-2.0alpha2-bin.zip這個包,在build目錄下有barcode4j.jar,在lib目錄下有avalon-framework-4.2.0.jar, 將barcode4j.jar和avalon-framework-4.2.0.jar添加到項目的lib中,eclipse中只要復制到web-inf下面的lib里面就OK了.
3.解壓將barcode4j-2.0alpha2-src.zip,將srcjavaorgkrysalisbarcode4jservlet目錄下的BarcodeServlet.java類的代碼拷出來,修改默認的圖片顯示方式,找到 if (format == null) format = MimeTypes.MIME_JPEG;這一行,表示默認的格式為JPEG文件
4.將以下這段servlet配置在web.xml中
<servlet>
<servlet-name>BarcodeServlet</servlet-name>
<servlet-class>com.yourname.BarcodeServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>BarcodeServlet</servlet-name>
<url-pattern>/barcode</url-pattern>
</servlet-mapping>
|
5.在頁面中添加<img src="<%=request.getContextPath() %>/barcode?msg=12345678" height="50px" width=130px/>
type是生成條形碼的類型:
看例子就明白了
<table border="1">
<tr>
<td>
<h1>code39</h1>
<img src="<%=request.getContextPath()%>/barcode?msg=0123456789&type=code39" height="100px" width=300px/>
</td>
<td>
<h1>code128</h1>
<img src="<%=request.getContextPath()%>/barcode?msg=0123456789&type=code128" height="100px" width=300px/>
</td>
<td>
<h1>Codabar</h1>
<img src="<%=request.getContextPath()%>/barcode?msg=0123456789&type=codabar" height="100px" width=300px/>
</td>
</tr>
<tr>
<td>
<h1>intl2of5</h1>
<img src="<%=request.getContextPath()%>/barcode?msg=01234567890540&type=intl2of5" height="100px" width=300px/>
</td>
<td>
<h1>upc-a</h1>
<img src="<%=request.getContextPath()%>/barcode?msg=012345678912&type=upc-a" height="100px" width=300px/>
</td>
<td>
<h1>ean-13</h1>
<img src="<%=request.getContextPath()%>/barcode?msg=200123457893&type=ean-13" height="100px" width=300px/>
</td>
<td>
<h1>ean-8</h1>
<img src="<%=request.getContextPath()%>/barcode?msg=20123451&type=ean-8" height="100px" width=300px/>
</td>
</tr>
<tr>
<td>
<h1>postnet</h1>
<img src="<%=request.getContextPath()%>/barcode?msg=01234567890540&type=postnet" height="100px" width=300px/>
</td>
<td>
<h1>royal-mail-cbc</h1>
<img src="<%=request.getContextPath()%>/barcode?msg=012345AS678912&type=royal-mail-cbc" height="100px" width=300px/>
</td>
<td>
<h1>pdf417</h1>
<img src="<%=request.getContextPath()%>/barcode?msg=200123457893&type=pdf417" height="100px" width=300px/>
</td>
<td>
<h1>datamatrix</h1>
<img src="<%=request.getContextPath()%>/barcode?msg=20123451&type=datamatrix" height="100px" width=300px/>
</td>
</tr>
</table>
|
運行時類型識別(run-time type identification ,RTTI)的概念上看非常簡單:
當只有一個指向對象基類的引用時RTTI機制可以讓你找到這個對象的確切概念。
1。Class對象是RTTI的核心,Class的類的類,每個類都有一個class對象。每當編寫并且編譯一個新類,就會產生一個Class對象(被保存在同名的.class文件當中)
2。Class.forName("classname"),如果對象沒有加載就加載對象(這將會觸發類的靜態初始化)
Class.newInstance()用來產生一個對象。如
Class m = Class.forName("classname");//1
Object o = m.newInstance();//2
java也提供"
類字面常量"的機制生成對象的引用。像這樣:
A.class
對于基本類型,boolean.class === Boolean.TYPE , char.class ===Character.TYP
void.class ===Void.TYPE,等等。。。。
那么也可以用Class m = char.class; //或者 Class m = <aclass>
.class
Object o = m.newInstance();
((Char)o).××
3。instanceof 關鍵字用于檢查對象是不是某個特定類型的實例。這用于類型轉換前做檢測。如:
if ( x instanceof Dog )
((Dog)x).bark();
除了 instanceof 關鍵字以外,還可以使用 Class.isInstance() 方法,兩者功能相同。
4。instanceof的替代方案是: x.getClass == Y.class 或者x.getClass.equals( Y.class)
5。Class對象的getInterfaces()獲得接口,getSurperClass 或者獲得超類。
6。反射是運行時的類信息。java附帶的庫java.lang.reflect含有Field,Method,Constructor類(每個類都實現了Memeber接口)。這些類型的對象是有JVM在運行時創建的,用以表示未知類里對象的成員,然后用Constructor創建新的對象,用get ()和set()方法讀取和修改Field對象關聯的字段,用invoke()方法調用于Method對象關聯的方 法,還可以用getFields(),getMethods(),getConstructors()等等方法。
一:無返回值的存儲過程
存儲過程為:
CREATE OR REPLACE PROCEDURE TESTA(PARA1 IN VARCHAR2,PARA2 IN VARCHAR2) AS
BEGIN
INSERT INTO HYQ.B_ID (I_ID,I_NAME) VALUES (PARA1, PARA2);
END TESTA;
|
然后呢,在java里調用時就用下面的代碼:
package com.hyq.src;
import java.sql.*;
import java.sql.ResultSet;
public class TestProcedureOne {
public TestProcedureOne() {
}
public static void main(String[] args ){
String driver = "oracle.jdbc.driver.OracleDriver";
String strUrl = "jdbc:oracle:thin:@127.0.0.1:1521: hyq ";
Statement stmt = null;
ResultSet rs = null;
Connection conn = null;
CallableStatement cstmt = null;
try {
Class.forName(driver);
conn = DriverManager.getConnection(strUrl, " hyq ", " hyq ");
CallableStatement proc = null;
proc = conn.prepareCall("{ call HYQ.TESTA(?,?) }");
proc.setString(1, "100");
proc.setString(2, "TestOne");
proc.execute();
}
catch (SQLException ex2) {
ex2.printStackTrace();
}
catch (Exception ex2) {
ex2.printStackTrace();
}
finally{
try {
if(rs != null){
rs.close();
if(stmt!=null){
stmt.close();
}
if(conn!=null){
conn.close();
}
}
}
catch (SQLException ex1) {
}
}
}
}
|
當然了,這就先要求要建張表TESTTB,里面兩個字段(I_ID,I_NAME)。
二:有返回值的存儲過程(非列表)
存儲過程為:
CREATE OR REPLACE PROCEDURE TESTB(PARA1 IN VARCHAR2,PARA2 OUT VARCHAR2) AS
BEGIN
SELECT INTO PARA2 FROM TESTTB WHERE I_ID= PARA1;
END TESTB;
|
在java里調用時就用下面的代碼:
package com.hyq.src;
public class TestProcedureTWO {
public TestProcedureTWO() {
}
public static void main(String[] args ){
String driver = "oracle.jdbc.driver.OracleDriver";
String strUrl = "jdbc:oracle:thin:@127.0.0.1:1521:hyq";
Statement stmt = null;
ResultSet rs = null;
Connection conn = null;
try {
Class.forName(driver);
conn = DriverManager.getConnection(strUrl, " hyq ", " hyq ");
CallableStatement proc = null;
proc = conn.prepareCall("{ call HYQ.TESTB(?,?) }");
proc.setString(1, "100");
proc.registerOutParameter(2, Types.VARCHAR);
proc.execute();
String testPrint = proc.getString(2);
System.out.println("=testPrint=is="+testPrint);
}
catch (SQLException ex2) {
ex2.printStackTrace();
}
catch (Exception ex2) {
ex2.printStackTrace();
}
finally{
try {
if(rs != null){
rs.close();
if(stmt!=null){
stmt.close();
}
if(conn!=null){
conn.close();
}
}
}
catch (SQLException ex1) {
}
}
}
}
}
|
注意,這里的proc.getString(2)中的數值2并非任意的,而是和存儲過程中的out列對應的,如果out是在第一個位置,那就是proc.getString(1),如果是第三個位置,就是proc.getString(3),當然也可以同時有多個返回值,那就是再多加幾個out參數了。
三:返回列表
由于oracle存儲過程沒有返回值,它的所有返回值都是通過out參數來替代的,列表同樣也不例外,但由于是集合,所以不能用一般的參數,必須要用pagkage了。所以要分兩部分,
1, 建一個程序包。如下:
CREATE OR REPLACE PACKAGE TESTPACKAGE AS
TYPE Test_CURSOR IS REF CURSOR;
end TESTPACKAGE;
|
2,建立存儲過程,存儲過程為:
CREATE OR REPLACE PROCEDURE TESTC(p_CURSOR out TESTPACKAGE.Test_CURSOR) IS
BEGIN
OPEN p_CURSOR FOR SELECT * FROM HYQ.TESTTB;
END TESTC;
|
可以看到,它是把游標(可以理解為一個指針),作為一個out 參數來返回值的。
在java里調用時就用下面的代碼:
package com.hyq.src;
import java.sql.*;
import java.io.OutputStream;
import java.io.Writer;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import oracle.jdbc.driver.*;
public class TestProcedureTHREE {
public TestProcedureTHREE() {
}
public static void main(String[] args ){
String driver = "oracle.jdbc.driver.OracleDriver";
String strUrl = "jdbc:oracle:thin:@127.0.0.1:1521:hyq";
Statement stmt = null;
ResultSet rs = null;
Connection conn = null;
try {
Class.forName(driver);
conn = DriverManager.getConnection(strUrl, "hyq", "hyq");
CallableStatement proc = null;
proc = conn.prepareCall("{ call hyq.testc(?) }");
proc.registerOutParameter(1,oracle.jdbc.OracleTypes.CURSOR);
proc.execute();
rs = (ResultSet)proc.getObject(1);
while(rs.next())
{
System.out.println("<tr><td>" + rs.getString(1) + "</td><td>"+rs.getString(2)+"</td></tr>");
}
}
catch (SQLException ex2) {
ex2.printStackTrace();
}
catch (Exception ex2) {
ex2.printStackTrace();
}
finally{
try {
if(rs != null){
rs.close();
if(stmt!=null){
stmt.close();
}
if(conn!=null){
conn.close();
}
}
}
catch (SQLException ex1) {
}
}
}
}
|
1、取得存儲過程返回的值
CallableStatement cs = conn.prepareCall("{call proc_fbquery(?,?,?)}"); //調用存儲過程cs.setString(1,mem);cs.setInt(2,n);cs.registerOutParameter(3,oracle.jdbc.OracleTypes.CURSOR);cs.execute();rs=(ResultSet)cs.getObject(3);
|
2、對存儲過程賦值時:
CallableStatement cs= conn.prepareCall("{call proc_fbquery(?)}"); //調用存儲過程cs.registerOutParameter(1,oracle.jdbc.OracleTypes.CURSOR);cs.setCursorName(cusorName); //提供result的名稱cs.setString(1,rs);rs=cs.executeQuery();rs =(ResultSet)cs.getObject(1);
|
云計算是一種全新的商業模式,其核心部分依然是數據中心,它使用的硬件設備主要是成千上萬的工業標準服務器,它們由英特爾或AMD生產的處理器以及其他硬件廠商的產品組成。企業和個人用戶通過高速互聯網得到計算能力,從而避免了大量的硬件投資。
簡而言之,云計算將使未來的互聯網變成超級計算的樂土。“云計算的基本原理是,通過使計算分布在大量的分布式計算機上,而非本地計算機或遠程服務器中,企業數據中心的運行將更與互聯網相似。這使得企業能夠將資源切換到需要的應用上,根據需求訪問計算機和存儲系統。”
import java.security.*;
import java.security.spec.*;
class MD5_Test{
public final static String MD5(String s){
char hexDigits[] = {
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd',
'e', 'f'};
try {
byte[] strTemp = s.getBytes();
MessageDigest mdTemp = MessageDigest.getInstance("MD5");
mdTemp.update(strTemp);
byte[] md = mdTemp.digest();
int j = md.length;
char str[] = new char[j * 2];
int k = 0;
for (int i = 0; i < j; i++) {
byte byte0 = md[i];
str[k++] = hexDigits[byte0 >>> 4 & 0xf];
str[k++] = hexDigits[byte0 & 0xf];
}
return new String(str);
}
catch (Exception e){
return null;
}
}
public static void main(String[] args){
//MD5_Test aa = new MD5_Test();
System.out.print(MD5_Test.MD5("XX"));
}
用三個方法設置
Oracle數據庫穿越
防火墻:
方法一:在系統注冊表中
hkey_local_machinesoftwareoraclehome0下加入字符串值:
方法二:
1、首先,我們需要將數據庫實例改為SHARED SERVER模式
2、以SYSDBA登錄SQLPLUS,通過SQLPLUS生成系統當前的參數設置文件pfile:
create pfile='d:init.ora' from spfile;
3、修改d:init.ora文件,在其中增加(用editplus編輯):
*.service_names='your service
name'和*.dispatchers='(address=(protocol=tcp)
(host=localhost)(port=1521) (dispatchers=1)' |
4、生成新的SPFILE:create spfile from pfile='d:init.ora';
5、重啟動數據庫。
6、在防火墻中開放1521端口。
方法三:
在數據庫端(可以是另外的機器,但cman的機器必須和數據庫都在防火墻的后面)安裝cman的前提下,啟動cman,然后開放防火墻端的1630端口(需要查看cman開的是什么端口),最后在客戶端的tnsnames.ora文件中添加以下內容:
cmantest = (description = (address_list = (address =
<- first address is to CMAN (protocol=tcp)
(host=hostname or ip of cman) (port=1610) )
(address= <- second address is to Listener
(protocol=tcp) (host=hostname or ip of listener) (port=1521) ) )
(connect_data = (sid = sidname)) (source_route = yes) |
現象:使用plsql/developer工具導出數據時出現錯誤,具體示例如下:
EXP-00056: 遇到 ORACLE 錯誤 6550
ORA-06550: line 1, column 41:
PLS-00302: component 'SET_NO_OUTLINES' must be declared
ORA-06550: line 1, column 15:
PL/SQL: Statement ignored
EXP-00000: 導出終止失敗
解決方法如下:
exp.exe 改成使用 expdp.exe
類似導入時使用impdp.exe命令
在plsql/dev中方法改成E:\oracle\product\10.2.0\client_2\bin\expdp.exe就可以了。
我想任何一本介紹模式的書在講到Decorator模式的時候不能不提到它的實際應用——在
Java/IO庫里面的應用,<<
Java與模式>>這本書也不例外,有點不一樣的是,這本書在介紹的時候有個專題,是從兩個模式來看Java/IO庫,完這個專題后,個人感覺對Java/IO庫有了全新的認識同時也加深了Decorator模式跟Adapter適配器模式的理解,現和大家分享下這個在我看來很偉大的成果,同時說明下,以下大部分文字跟圖片是來自<<Java與模式>>這本書。
一。引子(概括地介紹Java的IO)
無論是哪種編程語言,輸入跟輸出都是重要的一部分,Java也不例外,而且Java將輸入/輸出的功能和使用范疇做了很大的擴充。它采用了流的機制來實現輸入/輸出,所謂流,就是數據的有序排列,而流可以是從某個源(稱為流源或Source of Stream)出來,到某個目的地(稱為流匯或Sink of Stream)去的。由流的方向,可以分成輸入流和輸出流,一個程序從輸入流讀取數據向輸出流寫數據。
如,一個程序可以用FileInputStream類從一個磁盤文件讀取數據,如下圖所示:
像FileInputStream這樣的處理器叫做流處理器,它就像流的管道一樣,從一個流源吸入某種類型的數據,并輸出某種類型的數據。上面這種示意圖叫做流的管道圖。
同樣道理,也可以用FileOutputStream類向一個磁盤文件寫數據,如下圖所示:
在實際應用這種機制并不沒有太大的用處,程序需要寫出地通常是非常結構化的信息,因此這些byte類型的數據實際上是一些數值,文字,源代碼等。Java的I/O庫提供了一個稱做鏈接(Chaining)的機制,可以將一個流處理器跟另一個流處理器首尾相接,以其中之一的輸出為輸入,形成一個流管道的鏈接。
例如,DataInputStream流處理器可以把FileInputStream流對象的輸出當作輸入,將Byte類型的數據轉換成Java的原始類型和String類型的數據。如下圖所示:
類似地,向一個文件寫入Byte類型的數據不是一個簡單的過程。一個程序需要向一個文件里寫入的數據往往都是結構化的,而Byte類型則是原始類型。因此在寫的時候必須經過轉換。DataOutputStream流處理器提供了接收了原始數據類型和String數據類型,而這個流處理器的輸出數據則是Byte類型。也就是說DataOutputStream可以將源數據轉換成Byte類型的數據,再輸出來。
這樣一來,就可以將DataOutputStream與FileOutputStream鏈接起來,這樣程序就可以將原始數據類型和String類型的源數據寫入這個鏈接好的雙重管道里面,達到將結構化數據寫到磁盤文件里面的目的,如下圖所示:
這又是鏈接的所發揮的大作用。
流處理器所處理的流必定都有流源,而如果將流類所處理的流源分類的話,基本可以分成兩大類:
第一 數組,String,File等,這一種叫原始流源。
第二 同樣類型的流用做鏈接流類的流源,叫鏈接流源。
二 Java I/O庫的設計原則
Java語言的I/O庫是對各種常見的流源,流匯以及處理過程的抽象化。客戶端的Java程序不必知道最終的流源,流匯是磁盤上的文件還是數組等;也不必關心數據是否經過緩沖的,可否按照行號讀取等處理的細節。
書中提到了,對于第一次見到Java/IO庫的人,無不因為這個庫的龐雜而感到困惑;而對于熟悉這個庫的人,而又常常為這個庫的設計是否得當而爭論不體。書的作者提出自己的意見,要理解Java I/O這個龐大而復雜的庫,關鍵是要掌握兩個對稱性跟兩個設計模式模式。
Java I/O庫具有兩個對稱性,它們分別是:
1 輸入-輸出對稱性,比如InputStream和OutputStream各自占據Byte流的輸入與輸出的兩個平行的等級結構的根部。而Reader和Writer各自占據Char流的輸入與輸出的兩個平行的等級結構的根部。
2 byte-char對稱,InputStream和Reader的子類分別負責Byte和Char流的輸入;OutputStream和Writer的子類分別負責Byte和Char流的輸出,它們分別形成平行的等級結構。
Java I/O庫的兩個設計模式:
Java的I/O庫總體設計是符合裝飾者模式(Decorator)跟適配器模式(Adapter)的。如前所述,這個庫中處理流的類叫做流類。引子里所談到的FileInputStream,FileOutputStream,DataInputStream及DataOutputStream都是流處理器的例子。
1 裝飾者模式:在由InputStream,OutputStream,Reader和Writer代表的等級結構內部,有一些流處理器可以對另一些流處理器起到裝飾作用,形成新的,具有改善了的功能的流處理器。裝飾者模式是Java I/O庫的整體設計模式。這樣的一個原則是符合裝飾者模式的,如下圖所示:
2 適配器模式:在由InputStream,OutputStream,Reader和Writer代表的等級結構內部,有一些流處理器是對其它類型的流源的適配。這就是適配器模式的應用,如下圖所示。
適配器模式應用到了原始流處理器的設計上面,構成了I/O庫所有流處理器的起點。
JDK為程序員提供了大量的類庫,而為了保持類庫的可重用性,可擴展性和靈活性,其中使用到了大量的設計模式,本文將介紹JDK的I/O包中使用到的Decorator模式,并運用此模式,實現一個新的輸出流類。
Decorator模式簡介
Decorator模式又名包裝器(Wrapper),它的主要用途在于給一個對象動態的添加一些額外的職責。與生成子類相比,它更具有靈活性。
有時候,我們需要為一個對象而不是整個類添加一些新的功能,比如,給一個文本區添加一個滾動條的功能。我們可以使用繼承機制來實現這一功能,但是這種方法不夠靈活,我們無法控制文本區加滾動條的方式和時機。而且當文本區需要添加更多的功能時,比如邊框等,需要創建新的類,而當需要組合使用這些功能時無疑將會引起類的爆炸。
我們可以使用一種更為靈活的方法,就是把文本區嵌入到滾動條中。而這個滾動條的類就相當于對文本區的一個裝飾。這個裝飾(滾動條)必須與被裝飾的組件(文本區)繼承自同一個接口,這樣,用戶就不必關心裝飾的實現,因為這對他們來說是透明的。裝飾會將用戶的請求轉發給相應的組件(即調用相關的方法),并可能在轉發的前后做一些額外的動作(如添加滾動條)。通過這種方法,我們可以根據組合對文本區嵌套不同的裝飾,從而添加任意多的功能。這種動態的對對象添加功能的方法不會引起類的爆炸,也具有了更多的靈活性。
以上的方法就是Decorator模式,它通過給對象添加裝飾來動態的添加新的功能。如下是Decorator模式的UML圖:
Component為組件和裝飾的公共父類,它定義了子類必須實現的方法。
ConcreteComponent是一個具體的組件類,可以通過給它添加裝飾來增加新的功能。
Decorator是所有裝飾的公共父類,它定義了所有裝飾必須實現的方法,同時,它還保存了一個對于Component的引用,以便將用戶的請求轉發給Component,并可能在轉發請求前后執行一些附加的動作。
ConcreteDecoratorA和ConcreteDecoratorB是具體的裝飾,可以使用它們來裝飾具體的Component.
JAVA IO包中的Decorator模式
JDK提供的java.io包中使用了Decorator模式來實現對各種輸入輸出流的封裝。以下將以java.io.OutputStream及其子類為例,討論一下Decorator模式在IO中的使用。
首先來看一段用來創建IO流的代碼:
以下是代碼片段:
try {
OutputStream out = new DataOutputStream(new FileOutputStream("test.txt"));
} catch (FileNotFoundException e) {
e.printStackTrace();
}
這段代碼對于使用過JAVA輸入輸出流的人來說再熟悉不過了,我們使用DataOutputStream封裝了一個FileOutputStream.這是一個典型的Decorator模式的使用,FileOutputStream相當于Component,DataOutputStream就是一個Decorator.將代碼改成如下,將會更容易理解:
以下是代碼片段:
try {
OutputStream out = new FileOutputStream("test.txt");
out = new DataOutputStream(out);
} catch(FileNotFoundException e) {
e.printStatckTrace();
}
由于FileOutputStream和DataOutputStream有公共的父類OutputStream,因此對對象的裝飾對于用戶來說幾乎是透明的。下面就來看看OutputStream及其子類是如何構成Decorator模式的:
OutputStream是一個抽象類,它是所有輸出流的公共父類,其源代碼如下:
以下是代碼片段:
public abstract class OutputStream implements Closeable, Flushable {
public abstract void write(int b) throws IOException;
……
}
它定義了write(int b)的抽象方法。這相當于Decorator模式中的Component類。
ByteArrayOutputStream,FileOutputStream 和 PipedOutputStream 三個類都直接從OutputStream繼承,以ByteArrayOutputStream為例:
以下是代碼片段:
public class ByteArrayOutputStream extends OutputStream {
protected byte buf[];
protected int count;
public ByteArrayOutputStream() {
this(32);
}
public ByteArrayOutputStream(int size) {
if (size 〈 0) {
throw new IllegalArgumentException("Negative initial size: "
+ size);
}
buf = new byte[size];
}
public synchronized void write(int b) {
int newcount = count + 1;
if (newcount 〉 buf.length) {
byte newbuf[] = new byte[Math.max(buf.length 〈〈 1, newcount)];
System.arraycopy(buf, 0, newbuf, 0, count);
buf = newbuf;
}
buf[count] = (byte)b;
count = newcount;
}
……
}
它實現了OutputStream中的write(int b)方法,因此我們可以用來創建輸出流的對象,并完成特定格式的輸出。它相當于Decorator模式中的ConcreteComponent類。
接著來看一下FilterOutputStream,代碼如下:
以下是代碼片段:
public class FilterOutputStream extends OutputStream {
protected OutputStream out;
public FilterOutputStream(OutputStream out) {
this.out = out;
}
public void write(int b) throws IOException {
out.write(b);
}
……
}
同樣,它也是從OutputStream繼承。但是,它的構造函數很特別,需要傳遞一個OutputStream的引用給它,并且它將保存對此對象的引用。而如果沒有具體的OutputStream對象存在,我們將無法創建FilterOutputStream.由于out既可以是指向FilterOutputStream類型的引用,也可以是指向ByteArrayOutputStream等具體輸出流類的引用,因此使用多層嵌套的方式,我們可以為ByteArrayOutputStream添加多種裝飾。這個FilterOutputStream類相當于Decorator模式中的Decorator類,它的write(int b)方法只是簡單的調用了傳入的流的write(int b)方法,而沒有做更多的處理,因此它本質上沒有對流進行裝飾,所以繼承它的子類必須覆蓋此方法,以達到裝飾的目的。
BufferedOutputStream 和 DataOutputStream是FilterOutputStream的兩個子類,它們相當于Decorator模式中的ConcreteDecorator,并對傳入的輸出流做了不同的裝飾。以BufferedOutputStream類為例:
以下是代碼片段:
public class BufferedOutputStream extends FilterOutputStream {
……
private void flushBuffer() throws IOException {
if (count 〉 0) {
out.write(buf, 0, count);
count = 0;
}
}
public synchronized void write(int b) throws IOException {
if (count 〉= buf.length) {
flushBuffer();
}
buf[count++] = (byte)b;
}
……
}
這個類提供了一個緩存機制,等到緩存的容量達到一定的字節數時才寫入輸出流。首先它繼承了FilterOutputStream,并且覆蓋了父類的write(int b)方法,在調用輸出流寫出數據前都會檢查緩存是否已滿,如果未滿,則不寫。這樣就實現了對輸出流對象動態的添加新功能的目的。
下面,將使用Decorator模式,為IO寫一個新的輸出流。
自己寫一個新的輸出流
了解了OutputStream及其子類的結構原理后,我們可以寫一個新的輸出流,來添加新的功能。這部分中將給出一個新的輸出流的例子,它將過濾待輸出語句中的空格符號。比如需要輸出"java io OutputStream",則過濾后的輸出為"javaioOutputStream".以下為SkipSpaceOutputStream類的代碼:
以下是代碼片段:
import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.OutputStream;
/**
* A new output stream, which will check the space character
* and won‘t write it to the output stream.
* @author Magic
*
*/
public class SkipSpaceOutputStream extends FilterOutputStream {
public SkipSpaceOutputStream(OutputStream out) {
super(out);
}
/**
* Rewrite the method in the parent class, and
* skip the space character.
*/
public void write(int b) throws IOException{
if(b!=‘ ’){
super.write(b);
}
}
}
它從FilterOutputStream繼承,并且重寫了它的write(int b)方法。在write(int b)方法中首先對輸入字符進行了檢查,如果不是空格,則輸出。
以下是一個測試程序:
以下是代碼片段:
import java.io.BufferedInputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
/**
* Test the SkipSpaceOutputStream.
* @author Magic
*
*/
public class Test {
public static void main(String[] args){
byte[] buffer = new byte[1024];
/**
* Create input stream from the standard input.
*/
InputStream in = new BufferedInputStream(new DataInputStream(System.in));
/**
* write to the standard output.
*/
OutputStream out = new SkipSpaceOutputStream(new DataOutputStream(System.out));
try {
System.out.println("Please input your words: ");
int n = in.read(buffer,0,buffer.length);
for(int i=0;i〈n;i++){
out.write(buffer[i]);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
執行以上測試程序,將要求用戶在console窗口中輸入信息,程序將過濾掉信息中的空格,并將最后的結果輸出到console窗口。比如:
以下是引用片段:
Please input your words:
a b c d e f
abcdef
總 結
在java.io包中,不僅OutputStream用到了Decorator設計模式,InputStream,Reader,Writer等都用到了此模式。而作為一個靈活的,可擴展的類庫,JDK中使用了大量的設計模式,比如在Swing包中的MVC模式,RMI中的Proxy模式等等。對于JDK中模式的研究不僅能加深對于模式的理解,而且還有利于更透徹的了解類庫的結構和組成。
我們都知道,當想要保存一組基本類型數據時,數組是最有效的保存方式,也是推薦使用這種方式的。但是數組是固有大小的,當運行時才知道大小的程序,這種方式使用就受限制了,這就是
Java容器類產生的原因。
Java集合類有幾個特點:首先,這種容器是高性能的,對基本數據集合(動態數組、鏈接表、樹和散列表)的實現是高效率的。第二,容器類允許不同類型的類集合以相同的方式和高度互操作方式工作。第三,容器類是容易擴展或修改的。容器類的常用的基本類型有List、Set和Map,這些對象類型也稱為集合類,但是在Java中使用了Collection這個名字來指代該類庫的一個特殊子集,所以業界使用了范圍更廣泛的“容器”來稱呼。
Collection:是一個接口,它位于集合框架層次結構的頂層,繼承自Iterable接口,說明是可以用Iterator迭代器來訪問該集合中的元素的。又有List、Set和Queue接口繼承Collection接口,直接實現該接口的是一個叫AbstractCollection的抽象類,該抽象類以最大限度地減少了實現此接口所需的工作。
List:繼承自Collection接口,表示有序的、可包括重復元素的列表。同時擁有Collection內的方法外,還添加了大量的方法,使得可以在List的中間插入和刪除元素。實現該接口的基本類有ArrayList和LinkedList. ArrayList:擅長于對元素的隨機訪問,但是在插入和刪除元素時效率較慢。其實,看看ArrayList類實現的源代碼就知道,ArrayList是以線性表的數據結構形式存取數據的,初始化的表大小為10,下面就有幾個經常用到的核心方法:add(E e):在當前表的末尾插入元素,如果在前面表不滿的情況下,也是很高效的,直接插入到末尾,但是如果在當前表已經滿的情況下,就要重新生成一個比當前表大小更大的新表,新表的大小是當前表大小的1.5倍加1,比如當前表長度為20的,新表的大小就為31,還需要把當前表元素復制到新表中去,然后把當前表引用指向新表,最后把數值插入到表末尾,所以這種操作是非常低效的。
add(int index,E element):在指定索引位置插入元素,檢查表大小和重新追加表大小和上面的add(E e)方式是一樣的。最后是要把index以后的元素都是要依次往后移一個大小,然后把元素插入到index位置上去。涉及到表的復制和表內元素的移動,所以效率也是比add(E e)方法還要低。
remove(int index):在指定索引位置刪除元素,就是把index位置后的所有元素都往前移一個大小,也是涉及到表內元素的移動,效率也是很低的。
remove(Object o):刪除指定的元素,也就需要查找出該元素在表中出現第一次的位置,查找是用到順序一個一個進行匹配的方法,找出后就把該元素后面的所有元素往前移一個大小。該方法涉及到順序查找和表內元素移動,比remove(int index)方法更低效。
set(int index,E element):替換表中索引為index的元素值,返回被替換的值,直接用下標索引訪問元素,所以效率非常高。
get(int index):獲取索引為index的元素,直接用下標索引訪問,所以效率也是非常高。
indexOf(Object o):獲取元素的索引號,也就是需要查找,雖然用到了順序查找法,但效率還是比較高的。
LinkedList:擅長于對元素的插入和刪除操作,但對于隨機訪問元素比較慢。該類的實現是以雙向鏈表的數據結構為基礎的,所以是比較消耗內存的,但它的特定集比ArrayList更大。雙向鏈表,每個節點都有三個域,兩個域是存放前后節點的內存地址引用的,一個域是存放數據元素的。在LinkedList類中,有一個叫Entry的內部類,是private的,里面三個屬性,分別是element、next和previous,分別對應了雙向鏈表中的三個域,在ArrayList類中每實例化一個Entry就生成一個節點。下面看看它的核心方法:add(E e):把元素插入到鏈表末尾,首先要實例化一個節點,新節點previous域存放鏈表中最后一個節點地址,next域存放鏈表中第一個節點地址,element域存放元素值,鏈表中最后一個節點的next域存放新節點的地址,第一個元素的previous域存放新節點的地址,這樣這個元素就插入到該鏈表中去了,沒有涉及到復雜的操作,所以是非常高效的。
add(int index,E element):在index位置插入元素,這就需要先查找到該位置。查到后,這里就把查到的節點的前一個節點叫為A,實例化新的節點為B,查到index的節點為C.B的next域等于A的next值(也就是C的內存地址),B的previous域等于C的previous值(也就是A的內存地址),B的element域存放元素值,然后把A的next域和C的previous域都等于B的內存地址。這樣也就把元素插入到鏈表的index位置中去了,但涉及到了查詢,所以效率雖然高,但也沒有add(E e)那么高。
remove(int index):刪除在index位置的元素,首先也是要找到該位置的節點。然后把該節點的下一個節點(也就是該節點next域的內存地址那個節點)的previous值等于該節點的previous值,該節點的上一個節點(也就是該節點previous域的內存地址那個節點)的next值等于該節點的next值。這樣就把該節點從這條鏈表刪除了,過程中雖然涉及到了查找,但沒有涉及到像ArrayList類中的remove方法要移動表中元素,所以該方法的效率還是很高的。
remove(Object o):刪除在鏈表中第一個元素為o的節點,也是需要查找到該節點,然后就跟remove(int index)思路一樣把元素刪除,所以效率也是很高的。
set(int index,E element):把在鏈表中第index個元素值改為element,這也需要找到該節點來修改元素值,但涉及到了查找節點,ArrayList中的set方法就不用查找就可以修改,所以相對于ArrayList中的set方法,LinkedList方法set方法效率就沒那么高了。
get(int index):獲取第index位置的元素值,也是要找到該節點,所以就也沒ArrayList中的get方法那么高效率了,因為該方法需要查找鏈表。
indexOf(Object o):獲取該鏈表中第一o元素的位置,也是要查找鏈表,但ArrayList中的indexOf方法也是需要查找的,所以這兩個類的indexOf的效率都差不多。
所以,在編程中,如果要進行大量的隨機訪問,就使用ArrayList;如果要經常從表中插入或刪除元素的就應該使用LinkedList.
Set:繼承自Collection接口,表示無序的,無重復元素的集合。Set中最常使用的是測試歸屬性,可以很容易測試某個對象是否在某個Set中。所以,查找就成為了Set中最重要的操作,因此通常會選擇一個HashSet的實現查找,因為有比較復雜的哈希表支持,它專門對快速查找進行了優化。
迭代器:迭代器是一種設計模式,在這里是一個對象,它的作用就是遍歷并選擇列表和操作列表中的對象。迭代器的創佳的代價小,所以通常被稱為輕量級對象。迭代器統一了對各種容器的訪問方式,很方便。Java中的迭代器有兩種,一種是Iterator,另一種是繼承了Iterator只能用于各種List訪問的ListIterator. Iterator:只能用于單向移動,方法有:iterator()要求容器返回一個Iterator,Iterator將準備好返回序列的第一元素。next()獲得列表中的下一個元素。hasNext()檢查列表中是否還有元素。remove()將迭代器新近返回的元素刪除。
ListIterator:只能用于各種的List類的訪問,但能用于雙向的移動,有一個hasPrevious()檢查時候有前一個元素的,這種操作很像數據庫的游標。
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
public class ListTest ...{
public static void main(String [] args)
...{
Collection<Integer> col = new ArrayList<Integer>(Arrays.asList(10,20,30));
List<Integer> list = new LinkedList<Integer>();
list.addAll(col);
list.add(40);
list.add(50);
list.add(60);
displayIterator(list);
list.remove(3);
displayListIterator(list);
}
public static void displayIterator(Collection<Integer> list)
...{
Iterator<Integer> it = list.iterator();
Integer i;
while(it.hasNext())
...{
i = it.next();
System.out.print(i + " ");
if(i==50)
...{
it.remove();
}
}
System.out.println();
}
public static void displayListIterator(List<Integer> list)
...{
ListIterator<Integer> li = list.listIterator();
/** *//**以下注釋代碼為死循環,永遠輸入表中的第一個數據*/
/** *//**while(li.hasNext())
{
System.out.println(li.next());
System.out.println(li.previous());
}*/
while(li.hasNext())
...{
System.out.print(li.next() + " ");
}
System.out.println();
while(li.hasPrevious())
...{
System.out.print(li.previous() + " ");
}
}
}
|
Map:也是一個映射存儲鍵/值對的接口,但跟Collection沒有任何關系的,也沒有繼承任何接口,所以不能用Iterator迭代器來訪問該集合中的元素。給定一個關鍵字和一個值,可以存儲這個值到一個Map對象中,存儲以后,就可以使用它的關鍵字來檢索它。映射經常使用到的兩個基本操作:get()和put()。使用put()方法可以將一個指定了關鍵字和值的項加入映射。為了得到值,可以通過將關鍵字作為參數來調用get()方法。
import java.util.HashMap;
import java.util.Map;
public class TestMap ...{
public static void main(String [] args)
...{
Map<String,Integer> hm = new HashMap<String,Integer>();
hm.put("a1", 1);
hm.put("b2", 2);
hm.put("c3", 3);
hm.put("d4", 4);
hm.put("e5", 5);
display(hm);
System.out.println(hm.containsKey("c3"));
hm.remove("c3");
System.out.println(hm.containsValue(3));
System.out.println(hm.size());
}
public static void display(Map<String,Integer> m)
...{
for(String s : m.keySet())
...{
System.out.println(s + " : " + m.get(s));
}
}
}
|