ArrayList是List接口的一個可變長數組實現。實現了所有List接口的操作,并允許存儲null值。除了沒有進行同步,ArrayList基本等同于Vector。在Vector中幾乎對所有的方法都進行了同步,但ArrayList僅對writeObject和readObject進行了同步,其它比如add(Object)、remove(int)等都沒有同步。
1.存儲
ArrayList使用一個Object的數組存儲元素。
private?transient?Object?elementData[];
ArrayList實現了java.io.Serializable接口,這兒的transient標示這個屬性不需要自動序列化。下面會在writeObject()方法中詳細講解為什么要這樣作。
2.add和remove
????public?boolean?add(Object?o)?{?
????ensureCapacity(size?+?1);??//?Increments?modCount!!?
????elementData[size++]?=?o;?
????return?true;?
????}?
注意這兒的ensureCapacity()方法,它的作用是保證elementData數組的長度可以容納一個新元素。在“自動變長機制”中將詳細講解。
????public?Object?remove(int?index)?{?
????RangeCheck(index);?
????modCount++;?
????Object?oldValue?=?elementData[index];?
????int?numMoved?=?size?-?index?-?1;?
????if?(numMoved?>?0)?
????????System.arraycopy(elementData,?index+1,?elementData,?index,?
?????????????????numMoved);?
????elementData[--size]?=?null;?//?Let?gc?do?its?work?
????return?oldValue;?
????}?
RangeCheck()的作用是進行邊界檢查。由于ArrayList采用一個對象數組存儲元素,所以在刪除一個元素時需要把后面的元素前移。刪除一個元素時只是把該元素在elementData數組中的引用置為null,具體的對象的銷毀由垃圾收集器負責。
modCount的作用將在下面的“iterator()中的同步”中說明。
注:在前移時使用了System提供的一個實用方法:arraycopy(),在本例中可以看出System.arraycopy()方法可以對同一個數組進行操作,這個方法是一個native方法,如果對同一個數組進行操作時,會首先把從源部分拷貝到一個臨時數組,在把臨時數組的元素拷貝到目標位置。
3.自動變長機制
在實例化一個ArrayList時,你可以指定一個初始容量。這個容量就是elementData數組的初始長度。如果你使用:
????ArrayList?list?=?new?ArrayList();?
則使用缺省的容量:10。
????public?ArrayList()?{?
????this(10);?
????}?
ArrayList提供了四種add()方法,
public?boolean?add(Object?o)
public?void?add(int?index,?Object?element)
public?boolean?addAll(Collection?c)
public?boolean?addAll(int?index,?Collection?c)
在每一種add()方法中,都首先調用了一個ensureCapacity(int?miniCapacity)方法,這個方法保證elementData數組的長度不小于miniCapacity。ArrayList的自動變長機制就是在這個方法中實現的。
????public?void?ensureCapacity(int?minCapacity)?{?
????modCount++;?
????int?oldCapacity?=?elementData.length;?
????if?(minCapacity?>?oldCapacity)?{?
????????Object?oldData[]?=?elementData;?
????????int?newCapacity?=?(oldCapacity?*?3)/2?+?1;?
????????????if?(newCapacity?<?minCapacity)?
????????newCapacity?=?minCapacity;?
????????elementData?=?new?Object[newCapacity];?
????????System.arraycopy(oldData,?0,?elementData,?0,?size);?
????}?
????}?
從這個方法實現中可以看出ArrayList每次擴容,都擴大到原來大小的1.5倍。
每種add()方法的實現都大同小異,下面給出add(Object)方法的實現:
????public?boolean?add(Object?o)?{?
????ensureCapacity(size?+?1);??//?Increments?modCount!!?
????elementData[size++]?=?o;?
????return?true;?
????}?
4.iterator()中的同步
在父類AbstractList中定義了一個int型的屬性:modCount,記錄了ArrayList結構性變化的次數。
????protected?transient?int?modCount?=?0;?
在ArrayList的所有涉及結構變化的方法中都增加modCount的值,包括:add()、remove()、addAll()、removeRange()及clear()方法。這些方法每調用一次,modCount的值就加1。
注:add()及addAll()方法的modCount的值是在其中調用的ensureCapacity()方法中增加的。
AbstractList中的iterator()方法(ArrayList直接繼承了這個方法)使用了一個私有內部成員類Itr,生成一個Itr對象(Iterator接口)返回:
????public?Iterator?iterator()?{?
????return?new?Itr();?
????}?
Itr實現了Iterator()接口,其中也定義了一個int型的屬性:expectedModCount,這個屬性在Itr類初始化時被賦予ArrayList對象的modCount屬性的值。
????int?expectedModCount?=?modCount;?
注:內部成員類Itr也是ArrayList類的一個成員,它可以訪問所有的AbstractList的屬性和方法。理解了這一點,Itr類的實現就容易理解了。
在Itr.hasNext()方法中:
????public?boolean?hasNext()?{?
????????return?cursor?!=?size();?
????}?
調用了AbstractList的size()方法,比較當前光標位置是否越界。
在Itr.next()方法中,Itr也調用了定義在AbstractList中的get(int)方法,返回當前光標處的元素:
????public?Object?next()?{?
????????try?{?
????????Object?next?=?get(cursor);?
????????checkForComodification();?
????????lastRet?=?cursor++;?
????????return?next;?
????????}?catch(IndexOutOfBoundsException?e)?{?
????????checkForComodification();?
????????throw?new?NoSuchElementException();?
????????}?
????}?
注意,在next()方法中調用了checkForComodification()方法,進行對修改的同步檢查:
????final?void?checkForComodification()?{?
????????if?(modCount?!=?expectedModCount)?
????????throw?new?ConcurrentModificationException();?
????}?
現在對modCount和expectedModCount的作用應該非常清楚了。在對一個集合對象進行跌代操作的同時,并不限制對集合對象的元素進行操作,這些操作包括一些可能引起跌代錯誤的add()或remove()等危險操作。在AbstractList中,使用了一個簡單的機制來規避這些風險。這就是modCount和expectedModCount的作用所在。
5.序列化支持
ArrayList實現了java.io.Serializable接口,所以ArrayList對象可以序列化到持久存儲介質中。ArrayList的主要屬性定義如下:
private?static?final?long?serialVersionUID?=?8683452581122892189L;
private?transient?Object?elementData[];
private?int?size;
可以看出serialVersionUID和size都將自動序列化到介質中,但elementData數組對象卻定義為transient了。也就是說ArrayList中的所有這些元素都不會自動系列化到介質中。為什么要這樣實現?因為elementData數組中存儲的“元素”其實僅是對這些元素的一個引用,并不是真正的對象,序列化一個對象的引用是毫無意義的,因為序列化是為了反序列化,當你反序列化時,這些對象的引用已經不可能指向原來的對象了。所以在這兒需要手工的對ArrayList的元素進行序列化操作。這就是writeObject()的作用。
????private?synchronized?void?writeObject(java.io.ObjectOutputStream?s)?
????????throws?java.io.IOException{?
????//?Write?out?element?count,?and?any?hidden?stuff?
????s.defaultWriteObject();?
???//?Write?out?array?length?
????s.writeInt(elementData.length);?
????//?Write?out?all?elements?in?the?proper?order.?
????for?(int?i=0;?i<size;?i++)?
????????????s.writeObject(elementData[i]);?
????}?
這樣元素數組elementData中的所以元素對象就可以正確地序列化到存儲介質了。
對應的readObject()也按照writeObject()方法的順序從輸入流中讀取:
????private?synchronized?void?readObject(java.io.ObjectInputStream?s)?
????????throws?java.io.IOException,?ClassNotFoundException?{?
????//?Read?in?size,?and?any?hidden?stuff?
????s.defaultReadObject();?
????//?Read?in?array?length?and?allocate?array?
????int?arrayLength?=?s.readInt();?
????elementData?=?new?Object[arrayLength];?
????//?Read?in?all?elements?in?the?proper?order.?
????for?(int?i=0;?i<size;?i++)?
????????????elementData[i]?=?s.readObject();?
????}?
本章介紹Java的實用工具類庫java.util包。在這個包中,Java提供了一些實用的方法和數據結構。例如,Java提供日期(Data)類、日歷(Calendar)類來產生和獲取日期及時間,提供隨機數(Random)類產生各種類型的隨機數,還提供了堆棧(Stack)、向量(Vector)?、位集合(Bitset)以及哈希表(Hashtable)等類來表示相應的數據結構。
圖1.1給出了java.util包的基本層次結構圖。下面我們將具體介紹其中幾個重要的類。
┌java.util.BitSet
│java.util.Calendar
│ └java.util.GregorianCalendar
│java.util.Date
│java.util.Dictionary
│ └java.util.Hashtable
│ └java.util.Properties
│java.util.EventObject
│java.util.ResourceBundle
┌普通類┤ ├java.util.ListResourceBundle
│ │ └java.util.PropertyResourceBundle
│ │java.util.Local
│ │java.util.Observable
│ │java.util.Random
│ │java.util.StringTokenizer
│ │java.util.Vector
│ │ └java.util.Stack
Java.util┤ └java.util.TimeZone
│ └java.util.SimpleTimeZone
│ ┌java.util.Enumeration
├接 口┤java.util.EventListener
│ └java.util.Observer
│ ┌java.util.EmptyStackException
└異常類┤java.util.MissingResourceException
│java.util.NoSuchElementException
└java.util.TooManyListenersException
圖1.1?java.util包的基本層次結構
1.2?日期類Date
Java在日期類中封裝了有關日期和時間的信息,用戶可以通過調用相應的方法來獲取系統時間或設置日期和時間。Date類中有很多方法在JDK1.0公布后已經過時了,在8.3中我們將介紹JDK1.0中新加的用于替代Date的功能的其它類。
在日期類中共定義了六種構造函數。
(1)public?Date()
創建的日期類對象的日期時間被設置成創建時刻相對應的日期時間。
例?Date?today=new?Date();//today被設置成創建時刻相對應的日期時間。
(2)public?Date?(long?date)
long?型的參數date可以通過調用Date類中的static方法parse(String?s)來獲得。
例?long?l=Date.parse("Mon?6?Jan?1997?13:3:00");
Date?day=new?Date(l);
//day中時間為1997年?1月6號星期一,13:3:00。
(3)public?Date(String?s)
按字符串s產生一日期對象。s的格式與方法parse中字符串參數的模式相同。
例?Date?day=new?Date("Mon?6?Jan?1997?13:3:00");
//day?中時間為1997年1月6號星期一,13:3:00.
(4)public?Date(int?year,int?month,int?date)
(5)public?Date(int?year,int?month,int?date,int?hrs,int?min)
(6)public?Date(int?year,int?month,int?date,int?hrs,int?min,int?sec)
按給定的參數創建一日期對象。
參數說明:
year的值為:需設定的年份-1900。例如需設定的年份是1997則year的值應為97,即1997-1900的結果。所以Date中可設定的年份最小為1900;
month的值域為0~11,0代表1月,11表代表12月;
date的值域在1~31之間;
hrs的值域在0~23之間。從午夜到次日凌晨1點間hrs=0,從中午到下午1點間hrs=12;
min和sec的值域在0~59之間。
例?Date?day=new?Date(11,3,4);
//day中的時間為:04-Apr-11?12:00:00?AM
另外,還可以給出不正確的參數。
例 設定時間為1910年2月30日,它將被解釋成3月2日。
Date?day=new?Date(10,1,30,10,12,34);
System.out.println("Day's?date?is:"+day);
//打印結果為:Day's?date?is:Web?Mar?02?10:13:34?GMT+08:00?1910
下面我們給出一些Date類中常用方法。
(1)public?static?long?UTC(int?year,int?month,int?date,int?hrs.?int?min,int?sec)
該方法將利用給定參數計算UTC值。UTC是一種計時體制,與GMT(格林威治時間)的計時體系略有差別。UTC計時體系是基于原子時鐘的,而GTMT計時體系是基于天文學觀測的。計算中使用的一般為GMT計時體系。
(2)public?static?long?parse(String?s)
該方法將字符串s轉換成一個long型的日期。在介紹構造方法Date(long?date)時曾使用過這個方法。
字符串s有一定的格式,一般為:
(星期?日?年?時間GMT+時區)
若不注明時區,則為本地時區。
(3)public?void?setMonth(int?month)
(4)public?int?getMonth()
這兩個方法分別為設定和獲取月份值。
獲取的月份的值域為0~11,0代表1月,11代表12月。
(5)public?String?toString()
(6)public?String?toLocalString()
(7)public?String?toGMTString()
將給定日期對象轉換成不同格式的字符串。它們對應的具體的格式可參看例子8.1。
(8)public?int?getTimezoneOffset()
該方法用于獲取日期對象的時區偏移量。
例8.1中對上面介紹的Date類中的基本方法進行了具體的應用,并打印了相應的結果。由于使用了一些過時的方法,所以編譯時會有警告信息。另外,由于本例中的時間表示與平臺有關,不同的JDK版本對此處理不完全相同,因此不同版本的JDK執行本例的結果可能有細微差異。
例1.1?DateApp.java
import?java.lang.System;
import?java.util.Date;
public?class?DateApp{
public?static?void?main(String?args[]){
Date?today=new?Date();
//today中的日期被設成創建時刻的日期和時間,假設創建時刻為1997年3月
//23日17時51分54秒。
System.out.println("Today's?date?is?"+today);
//返回一般的時間表示法,本例中結果為
//Today's?date?is?Fri?May?23?17:51:54?1997
System.out.println("Today's?date(Internet?GMT)is:"
+today.toGMTString());
//返回結果為GMT時間表示法,本例中結果為
//Today's?date(Internet?GMT)is:?23?May?1997?09:51:54:GMT
System.out.println("Today's?date(Locale)?is:"
+today.toLocaleString());
//返回結果為本地習慣的時間表示法,結果為
//Today's?date(Locale)is:05/23/97?17:51:54
System.out.println("Today's?year?is:?"+today.getYear());
System.out.println("Today's?month?is:?"+(today.getMonth()+1));
System.out.println("Today's?date?is:?"+today.getDate());
//調用Date類中方法,獲取年月日的值。
//下面調用了不同的構造方法來創建Date類的對象。
Date?day1=new?Date(100,1,23,10,12,34);
System.out.println("Day1's?date?is:?"+day1);
Date?day2=new?Date("Sat?12?Aug?1996?13:3:00");
System.out.println("Day2's?date?is:?"+day2);
long?l=?Date.parse("Sat?5?Aug?1996?13:3:00?GMT+0800");
Date?day3=?new?Date(l);
System.out.println("Day3's?date(GMT)is:?"+day3.toGMTString());
System.out.println("Day3's?date(Locale)is:?"
+day3.toLocaleString());
System.out.println("Day3's?time?zone?offset?is:"
+day3.getTimezoneOffset());
}
}
運行結果(JDK1.3版,與原文不同,原文是JDK1.0版):
E:\java\tutorial\java01>java?DateApp
Today's?date?is?Thu?Dec?27?17:58:16?CST?2001
Today's?date(Internet?GMT)is:27?Dec?2001?09:58:16?GMT
Today's?date(Locale)?is:2001-12-27?17:58:16
Today's?year?is:?101
Today's?month?is:?12
Today's?date?is:?27
Day1's?date?is:?Wed?Feb?23?10:12:34?CST?2000
Day2's?date?is:?Fri?Aug?12?13:03:00?CST?1996
Day3's?date(GMT)is:?5?Aug?1996?05:03:00?GMT
Day3's?date(Locale)is:?1996-8-5?13:03:00
Day3's?time?zone?offset?is:-480
E:\java\tutorial\java01>
1.3?日歷類Calendar
在早期的JDK版本中,日期(Date)類附有兩大功能:(1)允許用年、月、日、時、分、秒來解釋日期:(2)允許對表示日期的字符串進行格式化和句法分析。在JDK1.1中提供了類Calendar來完成第一種功能,類DateFormat來完成第二項功能。dateFormat是java.text包中的一個類。與Date類有所不同的是,DateFormat類接受用各種語言和不同習慣表示的日期字符串。本節將介紹java.util包中的類Calendar及其它新增加的相關的類。
類Calendar是一個抽象類,它完成日期(Date)類和普通日期表示法(即用一組整型域如YEAR,MONTH,DAY,HOUR表示日期)之間的轉換。
由于所使用的規則不同,不同的日歷系統對同一個日期的解釋有所不同。在JDK1.1中提供了Calendar類一個子類GregorianCalendar??它實現了世界上普遍使用的公歷系統。當然用戶也可以通過繼承Calendar類,并增加所需規則,以實現不同的日歷系統。
第GregorianCalendar繼承了Calendar類。本節將在介紹類GregorianCalendar的同時順帶介紹Calendar類中的相關方法。
類GregorianCalendar提供了七種構造函數:
(1)public?GregorianCalendar()
創建的對象中的相關值被設置成指定時區,缺省地點的當前時間,即程序運行時所處的時區、地點的當前時間。
(2)public?GregorianCalendar(TimeZone?zone)
創建的對象中的相關值被設置成指定時區zone,缺省地點的當前時間。
(3)public?GregorianCalendar(Locale?aLocale)
創建的對象中的相關值被設置成缺省時區,指定地點aLocale的當前時間。
(4)public?GregorianCalendar(TimeZone?zone,Local?aLocale)
創建的對象中的相關值被設置成指定時區,指定地點的當前時間。
上面使用到的類TimeZone的性質如下:
TimeZone是java.util包中的一個類,其中封裝了有關時區的信息。每一個時區對應一組ID。類TimeZone提供了一些方法完成時區與對應ID兩者之間的轉換。
(Ⅰ)已知某個特定的ID,可以調用方法
public?static?synchronized?TimeZone?getTimeZone(String?ID)
來獲取對應的時區對象。
例?太平洋時區的ID為PST,用下面的方法可獲取對應于太平洋時區的時區對象:
TimeZone?tz=TimeZone.getTimeZone("PST");
調用方法getDefault()可以獲取主機所處時區的對象。
TimeZone?tz=TimeZone.getDefault();
(Ⅱ)調用以下方法可以獲取時區的ID
■public?static?synchronized?String[]?getavailableIDs(int?rawOffset)
根據給定時區偏移值獲取ID數組。同一時區的不同地區的ID可能不同,這是由于不同地區對是否實施夏時制意見不統一而造成的。
例String?s[]=TimeZone.getAvailableIDs(-7*60*60*1000);
打印s,結果為s[0]=PNT,s[1]=MST
■public?static?synchronized?String[]?getAvailableIDs()
獲取提供的所有支持的ID。
■public?String?getID()
獲取特定時區對象的ID。
例?TimeZone?tz=TimeZone.getDefault();
String?s=tz.getID();
打印s,結果為s=CTT。
上面使用類的對象代表了一個特定的地理、政治或文化區域。Locale只是一種機制,它用來標識一類對象,Local本身并不包含此類對象。
要獲取一個Locale的對象有兩種方法:
(Ⅰ)調用Locale類的構造方法
Locale(String?language,String?country)
Locale(String?language,String?country,String?variant)
參數說明:language??在ISO-639中定義的代碼,由兩個小寫字母組成。
country??在ISO-3166中定義的代碼,由兩個大寫字母組成。
variant??售貨商以及特定瀏覽器的代碼,例如使用WIN代表Windows。
(Ⅱ)調用Locale類中定義的常量
Local類提供了大量的常量供用戶創建Locale對象。
例?Locale.CHINA
為中國創建一個Locale的對象。
類TimeZone和類Locale中的其它方法,讀者可查閱API。
(5)public?GregorianCalendar(int?year,int?month,int?date)
(6)public?GregorianCalendar(int?year,int?month,int?date,int?hour,int?minute)
(7)public?GregorianCalendar(int?year,int?month,int?date,int?hour,int?minute,int?second)
用給定的日期和時間創建一個GregorianCalendar的對象。
參數說明:
year-設定日歷對象的變量YEAR;month-設定日歷對象的變量MONTH;
date-設定日歷對象的變量DATE;hour-設定日歷對象的變量HOUR_OF_DAY;
minute-設定日歷對象的變量MINUTE;second-設定日歷對象的變量SECOND。
與Date類中不同的是year的值沒有1900這個下限,而且year的值代表實際的年份。month的含義與Date類相同,0代表1月,11代表12月。
例?GregorianCalendar?cal=new?GregorianCalendar(1991,2,4)
cal的日期為1991年3月4號。
除了與Date中類似的方法外,Calendar類還提供了有關方法對日歷進行滾動計算和數學計算。計算規則由給定的日歷系統決定。進行日期計算時,有時會遇到信息不足或信息不實等特殊情況。Calendar采取了相應的方法解決這些問題。當信息不足時將采用缺省設置,在GregorianCalendar類中缺省設置一般為YEAR=1970,MONTH=JANUARY,DATE=1。
當信息不實時,Calendar將按下面的次序優先選擇相應的Calendar的變量組合,并將其它有沖突的信息丟棄。
MONTH+DAY_OF_MONTH
MONTH+WEEK_OF_MONTH+DAY_OF_WEEK
MONTH+DAY_OF_WEEK_OF_MONTH+DAY_OF_WEEK
DAY_OF+YEAR
DAY_OF_WEEK_WEEK_OF_YEAR
HOUR_OF_DAY
1.4?隨機數類Random
Java實用工具類庫中的類java.util.Random提供了產生各種類型隨機數的方法。它可以產生int、long、float、double以及Goussian等類型的隨機數。這也是它與java.lang.Math中的方法Random()最大的不同之處,后者只產生double型的隨機數。
類Random中的方法十分簡單,它只有兩個構造方法和六個普通方法。
構造方法:
(1)public?Random()
(2)public?Random(long?seed)
Java產生隨機數需要有一個基值seed,在第一種方法中基值缺省,則將系統時間作為seed。
普通方法:
(1)public?synonronized?void?setSeed(long?seed)
該方法是設定基值seed。
(2)public?int?nextInt()
該方法是產生一個整型隨機數。
(3)public?long?nextLong()
該方法是產生一個long型隨機數。
(4)public?float?nextFloat()
該方法是產生一個Float型隨機數。
(5)public?double?nextDouble()
該方法是產生一個Double型隨機數。
(6)public?synchronized?double?nextGoussian()
該方法是產生一個double型的Goussian隨機數。
例1.2?RandomApp.java。
//import?java.lang.*;
import?java.util.Random;
public?class?RandomApp{
public?static?void?main(String?args[]){
Random?ran1=new?Random();
Random?ran2=new?Random(12345);
//創建了兩個類Random的對象。
System.out.println("The?1st?set?of?random?numbers:");
System.out.println("\t?Integer:"+ran1.nextInt());
System.out.println("\t?Long:"+ran1.nextLong());
System.out.println("\t?Float:"+ran1.nextFloat());
System.out.println("\t?Double:"+ran1.nextDouble());
System.out.println("\t?Gaussian:"+ran1.nextGaussian());
//產生各種類型的隨機數
System.out.print("The?2nd?set?of?random?numbers:");
for(int?i=0;i<5;i++){
System.out.println(ran2.nextInt()+"?");
if(i==2)?System.out.println();
//產生同種類型的不同的隨機數。
System.out.println();//原文如此
}
}
}
運行結果:
E:\java01>java?RandomApp
The?1st?set?of?random?numbers:
Integer:-173899656
Long:8056223819738127077
Float:0.6293638
Double:0.7888394520265607
Gaussian:0.5015701094568733
The?2nd?set?of?random?numbers:1553932502
-2090749135
-287790814
-355989640
-716867186
E:\java01>
1.5?向量類Vector
Java.util.Vector提供了向量(Vector)類以實現類似動態數組的功能。在Java語言中。正如在一開始就提到過,是沒有指針概念的,但如果能正確靈活地使用指針又確實可以大大提高程序的質量,比如在C、C++中所謂“動態數組”一般都由指針來實現。為了彌補這點缺陷,Java提供了豐富的類庫來方便編程者使用,Vector類便是其中之一。事實上,靈活使用數組也可完成向量類的功能,但向量類中提供的大量方法大大方便了用戶的使用。
創建了一個向量類的對象后,可以往其中隨意地插入不同的類的對象,既不需顧及類型也不需預先選定向量的容量,并可方便地進行查找。對于預先不知或不愿預先定義數組大小,并需頻繁進行查找、插入和刪除工作的情況,可以考慮使用向量類。
向量類提供了三種構造方法:
public?vector()
public?vector(int?initialcapacity,int?capacityIncrement)
public?vector(int?initialcapacity)
使用第一種方法,系統會自動對向量對象進行管理。若使用后兩種方法,則系統將根據參數initialcapacity設定向量對象的容量(即向量對象可存儲數據的大小),當真正存放的數據個數超過容量時,系統會擴充向量對象的存儲容量。參數capacityIncrement給定了每次擴充的擴充值。當capacityIncrement為0時,則每次擴充一倍。利用這個功能可以優化存儲。
在Vector類中提供了各種方法方便用戶使用:
■插入功能
(1)public?final?synchronized?void?addElement(Object?obj)
將obj插入向量的尾部。obj可以是任何類的對象。對同一個向量對象,可在其中插入不同類的對象。但插入的應是對象而不是數值,所以插入數值時要注意將數值轉換成相應的對象。
例?要插入一個整數1時,不要直接調用v1.addElement(1),正確的方法為:
Vector?v1=new?Vector();
Integer?integer1=new?Integer(1);
v1.addElement(integer1);
(2)public?final?synchronized?void?setElementAt(object?obj,int?index)
將index處的對象設成obj,原來的對象將被覆蓋。
(3)public?final?synchronized?void?insertElementAt(Object?obj,int?index)
在index指定的位置插入obj,原來對象以及此后的對象依次往后順延。
■刪除功能
(1)public?final?synchronized?void?removeElement(Object?obj)
從向量中刪除obj。若有多個存在,則從向量頭開始試,刪除找到的第一個與obj相同的向量成員。
(2)public?final?synchronized?void?removeAllElement()
刪除向量中所有的對象。
(3)public?final?synchronized?void?removeElementlAt(int?index)
刪除index所指的地方的對象。
■查詢搜索功能
(1)public?final?int?indexOf(Object?obj)
從向量頭開始搜索obj?,返回所遇到的第一個obj對應的下標,若不存在此obj,返回-1。
(2)public?final?synchronized?int?indexOf(Object?obj,int?index)
從index所表示的下標處開始搜索obj。
(3)public?final?int?lastIndexOf(Object?obj)
從向量尾部開始逆向搜索obj。
(4)public?final?synchronized?int?lastIndexOf(Object?obj,int?index)
從index所表示的下標處由尾至頭逆向搜索obj。
(5)public?final?synchronized?Object?firstElement()
獲取向量對象中的首個obj。
(6)public?final?synchronized?Object?lastelement()
獲取向量對象中的最后一個obj。
了解了向量的最基本的方法后,我們來看一下例8.3VectorApp.java。
例1.3?VectorApp.java。
import?java.util.Vector;
import?java.lang.*;//這一句不應該要,但原文如此
import?java.util.Enumeration;
public?class?VectorApp{
public?static?void?main(String[]?args){
Vector?v1=new?Vector();
Integer?integer1=new?Integer(1);
v1.addElement("one");
//加入的為字符串對象
v1.addElement(integer1);
v1.addElement(integer1);
//加入的為Integer的對象
v1.addElement("two");
v1.addElement(new?Integer(2));
v1.addElement(integer1);
v1.addElement(integer1);
System.out.println("The?vector?v1?is:\n\t"+v1);
//將v1轉換成字符串并打印
v1.insertElementAt("three",2);
v1.insertElementAt(new?Float(3.9),3);
System.out.println("The?vector?v1(used?method?insertElementAt())?is:\n\t?"+v1);
//往指定位置插入新的對象,指定位置后的對象依次往后順延
v1.setElementAt("four",2);
System.out.println("The?vector?v1(used?method?setElementAt())?is:\n\t?"+v1);
//將指定位置的對象設置為新的對象
v1.removeElement(integer1);
//從向量對象v1中刪除對象integer1由于存在多個integer1所以從頭開始
//找,刪除找到的第一個integer1
Enumeration?enum=v1.elements();
System.out.print("The?vector?v1(used?method?removeElement())is:");
while(enum.hasMoreElements())
System.out.print(enum.nextElement()+"?");
System.out.println();
//使用枚舉類(Enumeration)的方法來獲取向量對象的每個元素
System.out.println("The?position?of?object?1(top-to-bottom):"
+?v1.indexOf(integer1));
System.out.println("The?position?of?object?1(tottom-to-top):"
+v1.lastIndexOf(integer1));
//按不同的方向查找對象integer1所處的位置
v1.setSize(4);
System.out.println("The?new?vector(resized?the?vector)is:"+v1);
//重新設置v1的大小,多余的元素被行棄
}
}
運行結果:
E:\java01>java?VectorApp
The?vector?v1?is:
[one,?1,?1,?two,?2,?1,?1]
The?vector?v1(used?method?insertElementAt())?is:
[one,?1,?three,?3.9,?1,?two,?2,?1,?1]
The?vector?v1(used?method?setElementAt())?is:
[one,?1,?four,?3.9,?1,?two,?2,?1,?1]
The?vector?v1(used?method?removeElement())is:one?four?3.9?1?two?2?1?1
The?position?of?object?1(top-to-bottom):3
The?position?of?object?1(tottom-to-top):7
The?new?vector(resized?the?vector)is:[one,?four,?3.9,?1]
E:\java01>
從例1.3運行的結果中可以清楚地了解上面各種方法的作用,另外還有幾點需解釋。
(1)類Vector定義了方法
public?final?int?size()
此方法用于獲取向量元素的個數。它的返回值是向是中實際存在的元素個數,而非向量容量。可以調用方法capactly()來獲取容量值。
方法:
public?final?synchronized?void?setsize(int?newsize)
此方法用來定義向量大小。若向量對象現有成員個數已超過了newsize的值,則超過部分的多余元素會丟失。
(2)程序中定義了Enumeration類的一個對象
Enumeration是java.util中的一個接口類,在Enumeration中封裝了有關枚舉數據集合的方法。
在Enumeration中提供了方法hawMoreElement()來判斷集合中是束還有其它元素和方法nextElement()來獲取下一個元素。利用這兩個方法可以依次獲得集合中元素。
Vector中提供方法:
public?final?synchronized?Enumeration?elements()
此方法將向量對象對應到一個枚舉類型。java.util包中的其它類中也大都有這類方法,以便于用戶獲取對應的枚舉類型。
1.6?棧類Stack
Stack類是Vector類的子類。它向用戶提供了堆棧這種高級的數據結構。棧的基本特性就是先進后出。即先放入棧中的元素將后被推出。Stack類中提供了相應方法完成棧的有關操作。
基本方法:
public?Object?push(Object?Hem)
將Hem壓入棧中,Hem可以是任何類的對象。
public?Object?pop()
彈出一個對象。
public?Object?peek()
返回棧頂元素,但不彈出此元素。
public?int?search(Object?obj)
搜索對象obj,返回它所處的位置。
public?boolean?empty()
判別棧是否為空。
例1.4?StackApp.java使用了上面的各種方法。
例1.4?StackApp.java。
import?java.lang.*;
import?java.util.*;
public?class?StackApp{
public?static?void?main(String?args[]){
Stack?sta=new?Stack();
sta.push("Apple");
sta.push("banana");
sta.push("Cherry");
//壓入的為字符串對象
sta.push(new?Integer(2));
//壓入的為Integer的對象,值為2
sta.push(new?Float(3.5));
//壓入的為Float的對象,值為3.5
System.out.println("The?stack?is,"+sta);
//對應棧sta
System.out.println("The?top?of?stack?is:"+sta.peek());
//對應棧頂元素,但不將此元素彈出
System.out.println("The?position?of?object?Cherry?is:"
+sta.search("cherry"));
//打印對象Cherry所處的位置
System.out.print("Pop?the?element?of?the?stack:");
while(!sta.empty())
System.out.print(sta.pop()+"?");
System.out.println();
//將棧中的元素依次彈出并打印。與第一次打印的sta的結果比較,可看出棧
//先進后出的特點
}
}
運行結果(略)
1.7?哈希表類Hashtable
哈希表是一種重要的存儲方式,也是一種常見的檢索方法。其基本思想是將關系碼的值作為自變量,通過一定的函數關系計算出對應的函數值,把這個數值解釋為結點的存儲地址,將結點存入計算得到存儲地址所對應的存儲單元。檢索時采用檢索關鍵碼的方法。現在哈希表有一套完整的算法來進行插入、刪除和解決沖突。在Java中哈希表用于存儲對象,實現快速檢索。
Java.util.Hashtable提供了種方法讓用戶使用哈希表,而不需要考慮其哈希表真正如何工作。
哈希表類中提供了三種構造方法,分別是:
public?Hashtable()
public?Hashtable(int?initialcapacity)
public?Hashtable(int?initialCapacity,float?loadFactor)
參數initialCapacity是Hashtable的初始容量,它的值應大于0。loadFactor又稱裝載因子,是一個0.0到0.1之間的float型的浮點數。它是一個百分比,表明了哈希表何時需要擴充,例如,有一哈希表,容量為100,而裝載因子為0.9,那么當哈希表90%的容量已被使用時,此哈希表會自動擴充成一個更大的哈希表。如果用戶不賦這些參數,系統會自動進行處理,而不需要用戶操心。
Hashtable提供了基本的插入、檢索等方法。
■插入
public?synchronized?void?put(Object?key,Object?value)
給對象value設定一關鍵字key,并將其加到Hashtable中。若此關鍵字已經存在,則將此關鍵字對應的舊對象更新為新的對象Value。這表明在哈希表中相同的關鍵字不可能對應不同的對象(從哈希表的基本思想來看,這也是顯而易見的)。
■檢索
public?synchronized?Object?get(Object?key)
根據給定關鍵字key獲取相對應的對象。
public?synchronized?boolean?containsKey(Object?key)
判斷哈希表中是否包含關鍵字key。
public?synchronized?boolean?contains(Object?value)
判斷value是否是哈希表中的一個元素。
■刪除
public?synchronized?object?remove(object?key)
從哈希表中刪除關鍵字key所對應的對象。
public?synchronized?void?clear()
清除哈希表
另外,Hashtalbe還提供方法獲取相對應的枚舉集合:
public?synchronized?Enumeration?keys()
返回關鍵字對應的枚舉對象。
public?synchronized?Enumeration?elements()
返回元素對應的枚舉對象。
例1.5?Hashtable.java給出了使用Hashtable的例子。
例1.5?Hashtalbe.java。
//import?java.lang.*;
import?java.util.Hashtable;
import?java.util.Enumeration;
public?class?HashApp{
public?static?void?main(String?args[]){
Hashtable?hash=new?Hashtable(2,(float)0.8);
//創建了一個哈希表的對象hash,初始容量為2,裝載因子為0.8
hash.put("Jiangsu","Nanjing");
//將字符串對象“Jiangsu”給定一關鍵字“Nanjing”,并將它加入hash
hash.put("Beijing","Beijing");
hash.put("Zhejiang","Hangzhou");
System.out.println("The?hashtable?hash1?is:?"+hash);
System.out.println("The?size?of?this?hash?table?is?"+hash.size());
//打印hash的內容和大小
Enumeration?enum1=hash.elements();
System.out.print("The?element?of?hash?is:?");
while(enum1.hasMoreElements())
System.out.print(enum1.nextElement()+"?");
System.out.println();
//依次打印hash中的內容
if(hash.containsKey("Jiangsu"))
System.out.println("The?capatial?of?Jiangsu?is?"+hash.get("Jiangsu"));
hash.remove("Beijing");
//刪除關鍵字Beijing對應對象
System.out.println("The?hashtable?hash2?is:?"+hash);
System.out.println("The?size?of?this?hash?table?is?"+hash.size());
}
}
運行結果:
The?hashtable?hash1?is:?{Beijing=Beijing,?Zhejiang=Hangzhou,?Jiangsu=Nanjing}
The?size?of?this?hash?table?is?3
The?element?of?hash?is:?Beijing?Hangzhou?Nanjing
The?capatial?of?Jiangsu?is?Nanjing
The?hashtable?hash2?is:?{Zhejiang=Hangzhou,?Jiangsu=Nanjing}
The?size?of?this?hash?table?is?2
Hashtable是Dictionary(字典)類的子類。在字典類中就把關鍵字對應到數據值。字典類是一個抽象類。在java.util中還有一個類Properties,它是Hashtable的子類。用它可以進行與對象屬性相關的操作。
1.8?位集合類BitSet
位集合類中封裝了有關一組二進制數據的操作。
我們先來看一下例8.6?BitSetApp.java。
例8.6?BitSetApp.java
//import?java.lang.*;
import?java.util.BitSet;
public?class?BitSetApp{
private?static?int?n=5;
public?static?void?main(String[]?args){
BitSet?set1=new?BitSet(n);
for(int?i=0;i<n;i++)?set1.set(i);
//將set1的各位賦1,即各位均為true
BitSet?set2=?new?BitSet();
set2=(BitSet)set1.clone();
//set2為set1的拷貝
set1.clear(0);
set2.clear(2);
//將set1的第0位set2的第2位清零
System.out.println("The?set1?is:?"+set1);
//直接將set1轉換成字符串輸出,輸出的內容是set1中值true所處的位置
//打印結果為The?set1?is:{1,2,3,4}
System.out.println("The?hash?code?of?set2?is:?"+set2.hashCode());
//打印set2的hashCode
printbit("set1",set1);
printbit("set2",set2);
//調用打印程序printbit(),打印對象中的每一個元素
//打印set1的結果為The?bit?set1?is:?false?true?true?true?true
set1.and(set2);
printbit("set1?and?set2",set1);
//完成set1?and?set2,并打印結果
set1.or(set2);
printbit("set1?or?set2",set1);
//完成set1?or?set2,并打印結果
set1.xor(set2);
printbit("set1?xor?set2",set1);
//完成set1?xor?set2,并打印結果
}
//打印BitSet對象中的內容
public?static?void?printbit(String?name,BitSet?set){
System.out.print("The?bit?"+name+"?is:?");
for(int?i=0;i<n;i++)
System.out.print(set.get(i)+"?");
System.out.println();
}
}
運行結果:
The?set1?is:?{1,?2,?3,?4}
The?hash?code?of?set2?is:?1225
The?bit?set1?is:?false?true?true?true?true
The?bit?set2?is:?true?true?false?true?true
The?bit?set1?and?set2?is:?false?true?false?true?true
The?bit?set1?or?set2?is:?true?true?false?true?true
The?bit?set1?xor?set2?is:?false?false?false?false?false
程序中使用了BitSet類提供的兩種構造方法:
public?BitSet();
public?BitSet(int?n);
參數n代表所創建的BitSet類的對象的大小。BitSet類的對象的大小在必要時會由系統自動擴充。
其它方法:
public?void?set(int?n)
將BitSet對象的第n位設置成1。
public?void?clear(int?n)
將BitSet對象的第n位清零。
public?boolean?get(int?n)
讀取位集合對象的第n位的值,它獲取的是一個布爾值。當第n位為1時,返回true;第n位為0時,返回false。
另外,如在程序中所示,當把一BitSet類的對象轉換成字符串輸出時,輸出的內容是此對象中true所處的位置。
在BitSet中提供了一組位操作,分別是:
public?void?and(BitSet?set)
public?void?or(BitSet?set)
public?void?xor(BitSet?set)
利用它們可以完成兩個位集合之間的與、或、異或操作。
BitSet類中有一方法public?int?size()來取得位集合的大小,它的返回值與初始化時設定的位集合大小n不一樣,一般為64。
小結
本章我們介紹了Java的實用工具類庫java.util中一些常用的類。java.util包中還有其它一些類。它們的具體用法用戶可以自行查閱API。?