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