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。?