ArrayList是List接口的一個可變長數(shù)組實現(xiàn)。實現(xiàn)了所有List接口的操作,并允許存儲null值。除了沒有進行同步,ArrayList基本等同于Vector。在Vector中幾乎對所有的方法都進行了同步,但ArrayList僅對writeObject和readObject進行了同步,其它比如add(Object)、remove(int)等都沒有同步。

1.存儲
ArrayList使用一個Object的數(shù)組存儲元素。
private?transient?Object?elementData[];
ArrayList實現(xiàn)了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數(shù)組的長度可以容納一個新元素。在“自動變長機制”中將詳細講解。

????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采用一個對象數(shù)組存儲元素,所以在刪除一個元素時需要把后面的元素前移。刪除一個元素時只是把該元素在elementData數(shù)組中的引用置為null,具體的對象的銷毀由垃圾收集器負責。
modCount的作用將在下面的“iterator()中的同步”中說明。
注:在前移時使用了System提供的一個實用方法:arraycopy(),在本例中可以看出System.arraycopy()方法可以對同一個數(shù)組進行操作,這個方法是一個native方法,如果對同一個數(shù)組進行操作時,會首先把從源部分拷貝到一個臨時數(shù)組,在把臨時數(shù)組的元素拷貝到目標位置。

3.自動變長機制
在實例化一個ArrayList時,你可以指定一個初始容量。這個容量就是elementData數(shù)組的初始長度。如果你使用:

????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)用了一個ensureCapacity(int?miniCapacity)方法,這個方法保證elementData數(shù)組的長度不小于miniCapacity。ArrayList的自動變長機制就是在這個方法中實現(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);?
????}?
????}?

從這個方法實現(xiàn)中可以看出ArrayList每次擴容,都擴大到原來大小的1.5倍。
每種add()方法的實現(xiàn)都大同小異,下面給出add(Object)方法的實現(xiàn):

????public?boolean?add(Object?o)?{?
????ensureCapacity(size?+?1);??//?Increments?modCount!!?
????elementData[size++]?=?o;?
????return?true;?
????}?


4.iterator()中的同步
在父類AbstractList中定義了一個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直接繼承了這個方法)使用了一個私有內(nèi)部成員類Itr,生成一個Itr對象(Iterator接口)返回:

????public?Iterator?iterator()?{?
????return?new?Itr();?
????}?

Itr實現(xiàn)了Iterator()接口,其中也定義了一個int型的屬性:expectedModCount,這個屬性在Itr類初始化時被賦予ArrayList對象的modCount屬性的值。

????int?expectedModCount?=?modCount;?

注:內(nèi)部成員類Itr也是ArrayList類的一個成員,它可以訪問所有的AbstractList的屬性和方法。理解了這一點,Itr類的實現(xiàn)就容易理解了。

在Itr.hasNext()方法中:

????public?boolean?hasNext()?{?
????????return?cursor?!=?size();?
????}?

調(diào)用了AbstractList的size()方法,比較當前光標位置是否越界。

在Itr.next()方法中,Itr也調(diào)用了定義在AbstractList中的get(int)方法,返回當前光標處的元素:

????public?Object?next()?{?
????????try?{?
????????Object?next?=?get(cursor);?
????????checkForComodification();?
????????lastRet?=?cursor++;?
????????return?next;?
????????}?catch(IndexOutOfBoundsException?e)?{?
????????checkForComodification();?
????????throw?new?NoSuchElementException();?
????????}?
????}?

注意,在next()方法中調(diào)用了checkForComodification()方法,進行對修改的同步檢查:

????final?void?checkForComodification()?{?
????????if?(modCount?!=?expectedModCount)?
????????throw?new?ConcurrentModificationException();?
????}?

現(xiàn)在對modCount和expectedModCount的作用應(yīng)該非常清楚了。在對一個集合對象進行跌代操作的同時,并不限制對集合對象的元素進行操作,這些操作包括一些可能引起跌代錯誤的add()或remove()等危險操作。在AbstractList中,使用了一個簡單的機制來規(guī)避這些風險。這就是modCount和expectedModCount的作用所在。

5.序列化支持
ArrayList實現(xiàn)了java.io.Serializable接口,所以ArrayList對象可以序列化到持久存儲介質(zhì)中。ArrayList的主要屬性定義如下:

private?static?final?long?serialVersionUID?=?8683452581122892189L;

private?transient?Object?elementData[];

private?int?size;

可以看出serialVersionUID和size都將自動序列化到介質(zhì)中,但elementData數(shù)組對象卻定義為transient了。也就是說ArrayList中的所有這些元素都不會自動系列化到介質(zhì)中。為什么要這樣實現(xiàn)?因為elementData數(shù)組中存儲的“元素”其實僅是對這些元素的一個引用,并不是真正的對象,序列化一個對象的引用是毫無意義的,因為序列化是為了反序列化,當你反序列化時,這些對象的引用已經(jīng)不可能指向原來的對象了。所以在這兒需要手工的對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]);?
????}?

這樣元素數(shù)組elementData中的所以元素對象就可以正確地序列化到存儲介質(zhì)了。
對應(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的實用工具類庫java.util包。在這個包中,Java提供了一些實用的方法和數(shù)據(jù)結(jié)構(gòu)。例如,Java提供日期(Data)類、日歷(Calendar)類來產(chǎn)生和獲取日期及時間,提供隨機數(shù)(Random)類產(chǎn)生各種類型的隨機數(shù),還提供了堆棧(Stack)、向量(Vector)?、位集合(Bitset)以及哈希表(Hashtable)等類來表示相應(yīng)的數(shù)據(jù)結(jié)構(gòu)。
  圖1.1給出了java.util包的基本層次結(jié)構(gòu)圖。下面我們將具體介紹其中幾個重要的類。
           ┌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)日期和時間的信息,用戶可以通過調(diào)用相應(yīng)的方法來獲取系統(tǒng)時間或設(shè)置日期和時間。Date類中有很多方法在JDK1.0公布后已經(jīng)過時了,在8.3中我們將介紹JDK1.0中新加的用于替代Date的功能的其它類。
  在日期類中共定義了六種構(gòu)造函數(shù)。
  (1)public?Date()
  創(chuàng)建的日期類對象的日期時間被設(shè)置成創(chuàng)建時刻相對應(yīng)的日期時間。
  例?Date?today=new?Date();//today被設(shè)置成創(chuàng)建時刻相對應(yīng)的日期時間。
  (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中時間為1997年?1月6號星期一,13:3:00。
  (3)public?Date(String?s)
  按字符串s產(chǎn)生一日期對象。s的格式與方法parse中字符串參數(shù)的模式相同。
  例?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)
  按給定的參數(shù)創(chuàng)建一日期對象。
  參數(shù)說明:
  year的值為:需設(shè)定的年份-1900。例如需設(shè)定的年份是1997則year的值應(yīng)為97,即1997-1900的結(jié)果。所以Date中可設(shè)定的年份最小為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
另外,還可以給出不正確的參數(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ù)計算UTC值。UTC是一種計時體制,與GMT(格林威治時間)的計時體系略有差別。UTC計時體系是基于原子時鐘的,而GTMT計時體系是基于天文學觀測的。計算中使用的一般為GMT計時體系。
  (2)public?static?long?parse(String?s)
  該方法將字符串s轉(zhuǎn)換成一個long型的日期。在介紹構(gòu)造方法Date(long?date)時曾使用過這個方法。
  字符串s有一定的格式,一般為:
  (星期?日?年?時間GMT+時區(qū))
  若不注明時區(qū),則為本地時區(qū)。
  (3)public?void?setMonth(int?month)
  (4)public?int?getMonth()
  這兩個方法分別為設(shè)定和獲取月份值。
  獲取的月份的值域為0~11,0代表1月,11代表12月。
  (5)public?String?toString()
  (6)public?String?toLocalString()
  (7)public?String?toGMTString()
  將給定日期對象轉(zhuǎn)換成不同格式的字符串。它們對應(yīng)的具體的格式可參看例子8.1。
  (8)public?int?getTimezoneOffset()
  該方法用于獲取日期對象的時區(qū)偏移量。
  例8.1中對上面介紹的Date類中的基本方法進行了具體的應(yīng)用,并打印了相應(yīng)的結(jié)果。由于使用了一些過時的方法,所以編譯時會有警告信息。另外,由于本例中的時間表示與平臺有關(guān),不同的JDK版本對此處理不完全相同,因此不同版本的JDK執(zhí)行本例的結(jié)果可能有細微差異。
  例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è)創(chuàng)建時刻為1997年3月
    //23日17時51分54秒。
    System.out.println("Today's?date?is?"+today);
    //返回一般的時間表示法,本例中結(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時間表示法,本例中結(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é)果為本地習慣的時間表示法,結(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類的對象。
    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());
   }
  }

  運行結(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)允許用年、月、日、時、分、秒來解釋日期:(2)允許對表示日期的字符串進行格式化和句法分析。在JDK1.1中提供了類Calendar來完成第一種功能,類DateFormat來完成第二項功能。dateFormat是java.text包中的一個類。與Date類有所不同的是,DateFormat類接受用各種語言和不同習慣表示的日期字符串。本節(jié)將介紹java.util包中的類Calendar及其它新增加的相關(guān)的類。
  類Calendar是一個抽象類,它完成日期(Date)類和普通日期表示法(即用一組整型域如YEAR,MONTH,DAY,HOUR表示日期)之間的轉(zhuǎn)換。
  由于所使用的規(guī)則不同,不同的日歷系統(tǒng)對同一個日期的解釋有所不同。在JDK1.1中提供了Calendar類一個子類GregorianCalendar??它實現(xiàn)了世界上普遍使用的公歷系統(tǒng)。當然用戶也可以通過繼承Calendar類,并增加所需規(guī)則,以實現(xiàn)不同的日歷系統(tǒng)。
  第GregorianCalendar繼承了Calendar類。本節(jié)將在介紹類GregorianCalendar的同時順帶介紹Calendar類中的相關(guān)方法。
  類GregorianCalendar提供了七種構(gòu)造函數(shù):
  (1)public?GregorianCalendar()
  創(chuàng)建的對象中的相關(guān)值被設(shè)置成指定時區(qū),缺省地點的當前時間,即程序運行時所處的時區(qū)、地點的當前時間。
  (2)public?GregorianCalendar(TimeZone?zone)
  創(chuàng)建的對象中的相關(guān)值被設(shè)置成指定時區(qū)zone,缺省地點的當前時間。
  (3)public?GregorianCalendar(Locale?aLocale)
  創(chuàng)建的對象中的相關(guān)值被設(shè)置成缺省時區(qū),指定地點aLocale的當前時間。
  (4)public?GregorianCalendar(TimeZone?zone,Local?aLocale)
  創(chuàng)建的對象中的相關(guān)值被設(shè)置成指定時區(qū),指定地點的當前時間。
  上面使用到的類TimeZone的性質(zhì)如下:
  TimeZone是java.util包中的一個類,其中封裝了有關(guān)時區(qū)的信息。每一個時區(qū)對應(yīng)一組ID。類TimeZone提供了一些方法完成時區(qū)與對應(yīng)ID兩者之間的轉(zhuǎn)換。
  (Ⅰ)已知某個特定的ID,可以調(diào)用方法
  public?static?synchronized?TimeZone?getTimeZone(String?ID)
來獲取對應(yīng)的時區(qū)對象。
  例?太平洋時區(qū)的ID為PST,用下面的方法可獲取對應(yīng)于太平洋時區(qū)的時區(qū)對象:
  TimeZone?tz=TimeZone.getTimeZone("PST");
  調(diào)用方法getDefault()可以獲取主機所處時區(qū)的對象。
  TimeZone?tz=TimeZone.getDefault();
  (Ⅱ)調(diào)用以下方法可以獲取時區(qū)的ID
  ■public?static?synchronized?String[]?getavailableIDs(int?rawOffset)
  根據(jù)給定時區(qū)偏移值獲取ID數(shù)組。同一時區(qū)的不同地區(qū)的ID可能不同,這是由于不同地區(qū)對是否實施夏時制意見不統(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()
  獲取特定時區(qū)對象的ID。
  例?TimeZone?tz=TimeZone.getDefault();
  String?s=tz.getID();
  打印s,結(jié)果為s=CTT。
  上面使用類的對象代表了一個特定的地理、政治或文化區(qū)域。Locale只是一種機制,它用來標識一類對象,Local本身并不包含此類對象。
  要獲取一個Locale的對象有兩種方法:
  (Ⅰ)調(diào)用Locale類的構(gòu)造方法
  Locale(String?language,String?country)
  Locale(String?language,String?country,String?variant)
  參數(shù)說明:language??在ISO-639中定義的代碼,由兩個小寫字母組成。
       country??在ISO-3166中定義的代碼,由兩個大寫字母組成。
       variant??售貨商以及特定瀏覽器的代碼,例如使用WIN代表Windows。
  (Ⅱ)調(diào)用Locale類中定義的常量
  Local類提供了大量的常量供用戶創(chuàng)建Locale對象。
  例?Locale.CHINA
    為中國創(chuàng)建一個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)
  用給定的日期和時間創(chuàng)建一個GregorianCalendar的對象。
  參數(shù)說明:
  year-設(shè)定日歷對象的變量YEAR;month-設(shè)定日歷對象的變量MONTH;
  date-設(shè)定日歷對象的變量DATE;hour-設(shè)定日歷對象的變量HOUR_OF_DAY;
  minute-設(shè)定日歷對象的變量MINUTE;second-設(shè)定日歷對象的變量SECOND。
  與Date類中不同的是year的值沒有1900這個下限,而且year的值代表實際的年份。month的含義與Date類相同,0代表1月,11代表12月。
  例?GregorianCalendar?cal=new?GregorianCalendar(1991,2,4)
  cal的日期為1991年3月4號。
  除了與Date中類似的方法外,Calendar類還提供了有關(guān)方法對日歷進行滾動計算和數(shù)學計算。計算規(guī)則由給定的日歷系統(tǒng)決定。進行日期計算時,有時會遇到信息不足或信息不實等特殊情況。Calendar采取了相應(yīng)的方法解決這些問題。當信息不足時將采用缺省設(shè)置,在GregorianCalendar類中缺省設(shè)置一般為YEAR=1970,MONTH=JANUARY,DATE=1。
  當信息不實時,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?隨機數(shù)類Random

  Java實用工具類庫中的類java.util.Random提供了產(chǎn)生各種類型隨機數(shù)的方法。它可以產(chǎn)生int、long、float、double以及Goussian等類型的隨機數(shù)。這也是它與java.lang.Math中的方法Random()最大的不同之處,后者只產(chǎn)生double型的隨機數(shù)。
  類Random中的方法十分簡單,它只有兩個構(gòu)造方法和六個普通方法。
  構(gòu)造方法:
  (1)public?Random()
  (2)public?Random(long?seed)
  Java產(chǎn)生隨機數(shù)需要有一個基值seed,在第一種方法中基值缺省,則將系統(tǒng)時間作為seed。
  普通方法:
  (1)public?synonronized?void?setSeed(long?seed)
  該方法是設(shè)定基值seed。
  (2)public?int?nextInt()
  該方法是產(chǎn)生一個整型隨機數(shù)。
  (3)public?long?nextLong()
  該方法是產(chǎn)生一個long型隨機數(shù)。
  (4)public?float?nextFloat()
  該方法是產(chǎn)生一個Float型隨機數(shù)。
  (5)public?double?nextDouble()
  該方法是產(chǎn)生一個Double型隨機數(shù)。
  (6)public?synchronized?double?nextGoussian()
  該方法是產(chǎn)生一個double型的Goussian隨機數(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)建了兩個類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());
    //產(chǎn)生各種類型的隨機數(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)生同種類型的不同的隨機數(shù)。
     System.out.println();//原文如此
    }
   }
  }

  運行結(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)類以實現(xiàn)類似動態(tài)數(shù)組的功能。在Java語言中。正如在一開始就提到過,是沒有指針概念的,但如果能正確靈活地使用指針又確實可以大大提高程序的質(zhì)量,比如在C、C++中所謂“動態(tài)數(shù)組”一般都由指針來實現(xiàn)。為了彌補這點缺陷,Java提供了豐富的類庫來方便編程者使用,Vector類便是其中之一。事實上,靈活使用數(shù)組也可完成向量類的功能,但向量類中提供的大量方法大大方便了用戶的使用。
  創(chuàng)建了一個向量類的對象后,可以往其中隨意地插入不同的類的對象,既不需顧及類型也不需預(yù)先選定向量的容量,并可方便地進行查找。對于預(yù)先不知或不愿預(yù)先定義數(shù)組大小,并需頻繁進行查找、插入和刪除工作的情況,可以考慮使用向量類。
  向量類提供了三種構(gòu)造方法:
  public?vector()
  public?vector(int?initialcapacity,int?capacityIncrement)
  public?vector(int?initialcapacity)
  使用第一種方法,系統(tǒng)會自動對向量對象進行管理。若使用后兩種方法,則系統(tǒng)將根據(jù)參數(shù)initialcapacity設(shè)定向量對象的容量(即向量對象可存儲數(shù)據(jù)的大小),當真正存放的數(shù)據(jù)個數(shù)超過容量時,系統(tǒng)會擴充向量對象的存儲容量。參數(shù)capacityIncrement給定了每次擴充的擴充值。當capacityIncrement為0時,則每次擴充一倍。利用這個功能可以優(yōu)化存儲。
  在Vector類中提供了各種方法方便用戶使用:
  ■插入功能
  (1)public?final?synchronized?void?addElement(Object?obj)
  將obj插入向量的尾部。obj可以是任何類的對象。對同一個向量對象,可在其中插入不同類的對象。但插入的應(yīng)是對象而不是數(shù)值,所以插入數(shù)值時要注意將數(shù)值轉(zhuǎn)換成相應(yīng)的對象。
  例?要插入一個整數(shù)1時,不要直接調(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處的對象設(shè)成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對應(yīng)的下標,若不存在此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.*;//這一句不應(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");
    //加入的為字符串對象
    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轉(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);
    //往指定位置插入新的對象,指定位置后的對象依次往后順延
    v1.setElementAt("four",2);
    System.out.println("The?vector?v1(used?method?setElementAt())?is:\n\t?"+v1);
    //將指定位置的對象設(shè)置為新的對象
    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);
    //重新設(shè)置v1的大小,多余的元素被行棄
   }
  }
  運行結(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運行的結(jié)果中可以清楚地了解上面各種方法的作用,另外還有幾點需解釋。
  (1)類Vector定義了方法
  public?final?int?size()
  此方法用于獲取向量元素的個數(shù)。它的返回值是向是中實際存在的元素個數(shù),而非向量容量。可以調(diào)用方法capactly()來獲取容量值。
  方法:
  public?final?synchronized?void?setsize(int?newsize)
  此方法用來定義向量大小。若向量對象現(xiàn)有成員個數(shù)已超過了newsize的值,則超過部分的多余元素會丟失。
  (2)程序中定義了Enumeration類的一個對象
  Enumeration是java.util中的一個接口類,在Enumeration中封裝了有關(guān)枚舉數(shù)據(jù)集合的方法。
  在Enumeration中提供了方法hawMoreElement()來判斷集合中是束還有其它元素和方法nextElement()來獲取下一個元素。利用這兩個方法可以依次獲得集合中元素。
  Vector中提供方法:
  public?final?synchronized?Enumeration?elements()
  此方法將向量對象對應(yīng)到一個枚舉類型。java.util包中的其它類中也大都有這類方法,以便于用戶獲取對應(yīng)的枚舉類型。

1.6?棧類Stack

  Stack類是Vector類的子類。它向用戶提供了堆棧這種高級的數(shù)據(jù)結(jié)構(gòu)。棧的基本特性就是先進后出。即先放入棧中的元素將后被推出。Stack類中提供了相應(yīng)方法完成棧的有關(guān)操作。
  基本方法:
  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);
    //對應(yīng)棧sta
    System.out.println("The?top?of?stack?is:"+sta.peek());
    //對應(yīng)棧頂元素,但不將此元素彈出
    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的結(jié)果比較,可看出棧
    //先進后出的特點
   }
  }
  運行結(jié)果(略)


1.7?哈希表類Hashtable

  哈希表是一種重要的存儲方式,也是一種常見的檢索方法。其基本思想是將關(guān)系碼的值作為自變量,通過一定的函數(shù)關(guān)系計算出對應(yīng)的函數(shù)值,把這個數(shù)值解釋為結(jié)點的存儲地址,將結(jié)點存入計算得到存儲地址所對應(yīng)的存儲單元。檢索時采用檢索關(guān)鍵碼的方法。現(xiàn)在哈希表有一套完整的算法來進行插入、刪除和解決沖突。在Java中哈希表用于存儲對象,實現(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又稱裝載因子,是一個0.0到0.1之間的float型的浮點數(shù)。它是一個百分比,表明了哈希表何時需要擴充,例如,有一哈希表,容量為100,而裝載因子為0.9,那么當哈希表90%的容量已被使用時,此哈希表會自動擴充成一個更大的哈希表。如果用戶不賦這些參數(shù),系統(tǒng)會自動進行處理,而不需要用戶操心。
  Hashtable提供了基本的插入、檢索等方法。
  ■插入
  public?synchronized?void?put(Object?key,Object?value)
給對象value設(shè)定一關(guān)鍵字key,并將其加到Hashtable中。若此關(guān)鍵字已經(jīng)存在,則將此關(guān)鍵字對應(yīng)的舊對象更新為新的對象Value。這表明在哈希表中相同的關(guān)鍵字不可能對應(yīng)不同的對象(從哈希表的基本思想來看,這也是顯而易見的)。
  ■檢索
  public?synchronized?Object?get(Object?key)
  根據(jù)給定關(guān)鍵字key獲取相對應(yīng)的對象。
  public?synchronized?boolean?containsKey(Object?key)
  判斷哈希表中是否包含關(guān)鍵字key。
  public?synchronized?boolean?contains(Object?value)
  判斷value是否是哈希表中的一個元素。
  ■刪除
  public?synchronized?object?remove(object?key)
  從哈希表中刪除關(guān)鍵字key所對應(yīng)的對象。
  public?synchronized?void?clear()
  清除哈希表
  另外,Hashtalbe還提供方法獲取相對應(yīng)的枚舉集合:
  public?synchronized?Enumeration?keys()
  返回關(guān)鍵字對應(yīng)的枚舉對象。
  public?synchronized?Enumeration?elements()
  返回元素對應(yīng)的枚舉對象。
  例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)建了一個哈希表的對象hash,初始容量為2,裝載因子為0.8

    hash.put("Jiangsu","Nanjing");
    //將字符串對象“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對應(yīng)對象
    System.out.println("The?hashtable?hash2?is:?"+hash);
    System.out.println("The?size?of?this?hash?table?is?"+hash.size());
   }
  }

  運行結(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)鍵字對應(yīng)到數(shù)據(jù)值。字典類是一個抽象類。在java.util中還有一個類Properties,它是Hashtable的子類。用它可以進行與對象屬性相關(guān)的操作。

1.8?位集合類BitSet

  位集合類中封裝了有關(guā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(),打印對象中的每一個元素
    //打印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對象中的內(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();
   }
  }

  運行結(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類的對象的大小。BitSet類的對象的大小在必要時會由系統(tǒng)自動擴充。
  其它方法:
  public?void?set(int?n)
  將BitSet對象的第n位設(shè)置成1。
  public?void?clear(int?n)
  將BitSet對象的第n位清零。
  public?boolean?get(int?n)
  讀取位集合對象的第n位的值,它獲取的是一個布爾值。當?shù)趎位為1時,返回true;第n位為0時,返回false。
  另外,如在程序中所示,當把一BitSet類的對象轉(zhuǎn)換成字符串輸出時,輸出的內(nèi)容是此對象中true所處的位置。
  在BitSet中提供了一組位操作,分別是:
  public?void?and(BitSet?set)
  public?void?or(BitSet?set)
  public?void?xor(BitSet?set)
利用它們可以完成兩個位集合之間的與、或、異或操作。
  BitSet類中有一方法public?int?size()來取得位集合的大小,它的返回值與初始化時設(shè)定的位集合大小n不一樣,一般為64。

小結(jié)

  本章我們介紹了Java的實用工具類庫java.util中一些常用的類。java.util包中還有其它一些類。它們的具體用法用戶可以自行查閱API。?