List、set集合

集合框架图:

collection接口主要定义了一些操作集合元素的方法:

boolean

add(E e)

Ensures that this collection contains the specified element (optional operation).如果插入成功,返回true

boolean

addAll(Collection<? extends E> c)

Adds all of the elements in the specified collection to this collection (optional operation).改变返回true

void

clear()

Removes all of the elements from this collection (optional operation).

boolean

contains(Object o)

Returns true if this collection contains the specified element.

boolean

containsAll(Collection<?> c)

Returns true if this collection contains all of the elements in the specified collection.

boolean

equals(Object o)

Compares the specified object with this collection for equality.

int

hashCode()

Returns the hash code value for this collection.

。。。。。。具体可看文档

2.使用Iterator接口遍历几何元素

Iterrator接口隐藏了各种Collection实现类的细节,向应用程序提供了遍历Collection集合元素的统一编程接口。Iterator接口里定义了如下三个方法:

   Boolean hashNext(): 如果被迭代的集合元素还没有被遍历,则返回true.

   Object next(): 返回集合里的下一个元素。

   Void remove(): 删除集合里上一次next方法返回的元素。

**当使用Iterator迭代访问Collection集合元素时,Collection集合里的元素不能被改变,只有通过Iterator的remove方法删除上一次next方法返回的集合元素才可以;否则将引发java.util.Concurrent ModificationException异常。

接下来分别介绍list和set集合

一、List集合

List集合代表一个元素有序,可重复的集合,集合中每个元素都有对应的顺序索引。List接口中增加了一些根据索引操作元素的方法:

   void add(int index,E element )  在列表的指定位置插入该元素。

   boolean addAll(int index,Collection c)  将集合c包含的所有元素都插入到List集合的index处。

   Object get(int index)    返回集合index索引出的元素。

  。。。。。详见

1.ListIterator接口:List额外提供的一个listIterator()方法,提供了专门操作List的方法。

 ListIterator接口在Iterator的基础上增加了如下方法:

 boolean hasPrevious(): 返回该迭代器关联的集合是否还有上一个元素。

 Object previous(): 返回该迭代器的上一个元素。

 void add((E e): 在指定位置插入一个元素。

 示例:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

import java.util.*;

public class TestListIterator

{

    public static void main(String[] args)

    {

        String[] books = {

            "我是帅哥",

            "帅哥是我"

        };

        List bookList = new ArrayList();

        for (int i = 0; i < books.length ; i++ )

        {

            bookList.add(books[i]);

        }

        ListIterator lit = bookList.listIterator();

        while (lit.hasNext())

        {

            System.out.println(lit.next());

            lit.add("-------分隔符-------");  //加入一个元素

        }

        System.out.println("==========下面开始反向迭代===========");

        while(lit.hasPrevious())

        {

            System.out.println(lit.previous());

        }

    }

}

 输出结果:

      我是帅哥
      帅哥是我
      ==========下面开始反向迭代===========
      -------分隔符-------
      帅哥是我
     -------分隔符-------
     我是帅哥                                       

输出完成 (耗时 0 秒) - 正常终止

2.ArrayList实现类和Vector实现类:

ArrayList和Vector是基于数组实现的list类,所以ArrayList和Vector封装了一个动态的,允许再分配的Object[]数组,不指定的话长度默认为10。ArrayList和Vector对象使用initialCapacity参数来设置该数组的长度,当向集合添加大量元素时,可以使用ensureCapac(int minCapacity)方法一次性的增加initialCapacity。

ArrayList和Vector在用法上几乎完全相同,但Vector比较古老,方法名比较长,最好是不使用。ArrayList是线程不安全的,Vector是线程安全的,但这个完全可以手动将一个ArrayList变成线程安全的。

ArrayList示例:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

import java.util.*;

public class TestList

{

    public static void main(String[] args)

    {

        List books = new ArrayList();

        //向books集合中添加三个元素

        books.add(new String("轻量级J2EE企业应用实战"));

        books.add(new String("Struts2权威指南"));

        books.add(new String("基于J2EE的Ajax宝典"));

        System.out.println(books);

        //将新字符串对象插入在第二个位置

        books.add(1 new String("ROR敏捷开发最佳实践"));//add是插入,插入到当前位置,当前的元素向后退,并没有覆盖!

        for (int i = 0 ; i < books.size() ; i++ )

        {

            System.out.println(books.get(i));

        }

        System.out.println("size:"+books.size());

        //删除第三个元素

        books.remove(2);

        System.out.println(books);

        //判断指定元素在List集合中位置:输出1,表明位于第二位

        System.out.println(books.indexOf(new String("ROR敏捷开发最佳实践")));//1

        //将第二个元素替换成新的字符串对象

        books.set(1new String("Struts2权威指南"));

        System.out.println(books);

        //将books集合的第二个元素(包括)到第三个元素(不包括)截取称子集合

        System.out.println(books.subList(1 2));

    }

}

  输出结果:

---------- java运行 ----------
[轻量级J2EE企业应用实战, Struts2权威指南, 基于J2EE的Ajax宝典]
轻量级J2EE企业应用实战
ROR敏捷开发最佳实践
Struts2权威指南
基于J2EE的Ajax宝典
size:4
[轻量级J2EE企业应用实战, ROR敏捷开发最佳实践, 基于J2EE的Ajax宝典]
1
[轻量级J2EE企业应用实战, Struts2权威指南, 基于J2EE的Ajax宝典]
[Struts2权威指南]

输出完成 (耗时 0 秒) - 正常终止

二、Queue集合

Queue用于模拟队列这种数据结构,先进先出。

Queue接口定义的方法如下:

 boolean add(E e):   将指定的元素插入此队列(如果立即可行且不会违反容量限制),在成功时返回 true,如果当前没有可用的空间,则抛出 IllegalStateException。

 E element(): 获取队列头部元素,但不删除该元素。

 boolean offer(E e):  将指定的元素插入此队列,当使用有容量限制的队列时,此方法通常要优于add(E)

 E peek():   获取但不移除此队列的头;如果此队列为空,则返回 null。

 E poll(): 获取并移除此队列的头,如果此队列为空,则返回 null。

E remove(): 获取并移除此队列的头。

1.PriorityQueue实现类

PriorityQueue是一个比较标准的队列实现类,之所以说比较标准,而不是绝对标准,是因为PriorityQueue保存队列元素的顺序并不是按加入队列的顺序,而是按队列元素的大小进行重新排序。

示例:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

import java.util.*;

public class TestPriorityQueue

{

    public static void main(String[] args)

    {

        PriorityQueue pq = new PriorityQueue();

        //下面代码依次向pq中加入四个元素

        pq.offer(6);

        pq.offer(-3);

        pq.offer(9);

        pq.offer(0);

        //输出pq队列,并不是按元素的加入顺序排列,而是按元素的大小顺序排列

        System.out.println(pq);

        //访问队列第一个元素,其实就是队列中最小的元素:-3

        System.out.println(pq.peek());

    }

}

  输出结果:

---------- java运行 ----------
[-3, 0, 9, 6]
-3

输出完成 (耗时 0 秒) - 正常终止

2.Deque接口与ArrayQueue实现类

Deque接口是Queue接口的子接口,它代表一个双端队列,Deque接口里定义了一些双端队列的方法,允许从两端来操作队列的元素。

Void addFirst(Object e):将指定元素插入该双端队列的开头。

Void addLast(Object e):将指定队列插入该双端队列的末尾。

Iterator descendingIterator():返回该双端队列对应的迭代器,该迭代器将以逆向顺序来迭代队列中的元素。

Object getFirst(): 获取但不删除队列的第一个元素。

详细参考api

ArrayQueue是Deque接口的典型实现类,他是一个基于数组实现的双端队列,底部也是采用动态的、可重新分配的Object[]数组存储集合元素。

示例:把ArrayQueue当”栈“使用

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

import java.util.*;

public class ArrayDequeTest

{

    public static void main(String[] args)

    {

        ArrayDeque stack = new ArrayDeque();

        //依次将三个元素push入栈

        stack.push("java");

        stack.push("java EE");

        stack.push("Android");

        System.out.println(stack);  //输出

        //访问第一个元素,但不将其pop出栈

        System.out.println(stack.peek());//

        //

        System.out.println(stack);

        //pop出第一个元素

        System.out.println(stack.pop());

         //

        System.out.println(stack);

    }

}

  输出结果:

[Android, java EE, java]
Android
[Android, java EE, java]
Android
[java EE, java]

**在现在的程序中需要使用“栈”这种数据结构时,推荐使用ArrayDeque或LinkedList,而不是Stack。

3.LinkedList实现类

LinkedList实现了List接口和Deque接口(好像图上没有画出来。。。。。),因此他是一个List集合还可以被当成双端队列来使用。

 LinkedList与ArrayList,ArrayDeque的实现机制完全不同,ArrayList、ArrayDeque内部以数组的形式来保存集合中的元素,因此随机访问集合元素时有较好的性能;而LinkedList内部以链表的形式来保存集合中的元素,因此随机访问性能较差,但是插入、删除元素时非常快。

示例:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

import java.util.*;

public class TestLinkedList

{

    public static void main(String[] args)

    {

        LinkedList books = new LinkedList();

        //将字符串元素加入队列的尾部

        books.offer("Java");

        //将一个字符串元素入栈

        books.push("J2EE");

        //将字符串元素添加到队列的头部

        books.offerFirst("Android");

        for (int i = 0; i < books.size() ; i++ )

        {

            System.out.println(books.get(i));

        }

        //访问、并不删除队列的第一个元素

        System.out.println(books.peekFirst());

        //访问、并不删除队列的最后一个元素

        System.out.println(books.peekLast());

        //采用出栈的方式将第一个元素pop出队列

        System.out.println(books.pop());

        //下面输出将看到队列中第一个元素被删除

        System.out.println(books);

        //访问、并删除队列的最后一个元素

        System.out.println(books.pollLast());

        //下面输出将看到队列中只剩下中间一个元素:轻量级J2EE企业应用实战

        System.out.println(books);

    }

}

  输出结果:

Android
J2EE
Java
Android
Java
Android
[J2EE, Java]
Java
[J2EE]

**上面的代码分别示范了双端队列,栈的用法,所以LinkedList是一个功能非常强大的集合类。

4.各种线性表的性能分析:

实现机制

随机访问排名

迭代操作排名

插入操作排名

删除操作排名

数组

连续内存区保存元素

1

不支持

不支持

不支持

ArrayList/ArrayDeque

以数组保存元素

2

2

2

2

Vector

以数组保存元素

3

3

3

3

LinkedList

链表保存元素

4

1

1

1

  示例:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

import java.util.*;

/**

 * Description:

 * <br/>Copyright (C), 2005-2008, Yeeku.H.Lee

 * <br/>This program is protected by copyright laws.

 * <br/>Program Name:

 * <br/>Date:

 * @author  Yeeku.H.Lee [email protected]

 * @version  1.0

 */

public class TestPerformance

{

    public static void main(String[] args)

    {

        //创建一个字符串数组

        String[] tst1 = new String[900000];

        //动态初始化数组元素

        for (int i = 0; i < 900000; i++)

        {

            tst1[i] = String.valueOf(i);

        }

         

        ArrayList al = new ArrayList();

        //将所有数组元素加入ArrayList集合中

        long start = System.currentTimeMillis();

        for (int i = 0; i < 900000 ; i++)

        {

            al.add(tst1[i]);

        }

        System.out.println("ArrayList集合添加元素的时间:" + (System.currentTimeMillis() - start));

        LinkedList ll = new LinkedList();

        //将所有数组元素加入LinkedList集合中

        start = System.currentTimeMillis();

        for (int i = 0; i < 900000 ; i++)

        {

            ll.add(tst1[i]);

        }

        System.out.println("LinkedList集合添加元素的时间:" + (System.currentTimeMillis() - start));

        //迭代访问ArrayList集合的所有元素,并输出迭代时间

        start = System.currentTimeMillis();

        for (Iterator it = al.iterator();it.hasNext() ; )

        {

            it.next();

        }

        System.out.println("迭代ArrayList集合元素的时间:" + (System.currentTimeMillis() - start));

        //迭代访问LinkedList集合的所有元素,并输出迭代时间

        start = System.currentTimeMillis();

        for (Iterator it = ll.iterator();it.hasNext() ; )

        {

            it.next();

        }

        System.out.println("迭代LinkedList集合元素的时间:" + (System.currentTimeMillis() - start));

         

    }

}

  输出结果:

ArrayList集合添加元素的时间:446
LinkedList集合添加元素的时间:16
迭代ArrayList集合元素的时间:11
迭代LinkedList集合元素的时间:12

**可以看出LinkedList添加元素特别快,是ArrayList的几十倍,但遍历时不相上下。用foreach遍历LinkedList也比ArrayList快。

java中的集合到这里基本上总结完啦。。。

下篇会总结下操作集合的工具类:Collections,用它可以方便的把集合变成线程安全的。

二、set集合

Set集合的方法与Collection基本上完全一样,它没有提供额外的方法。实际上Set就是Collection,只是行为略有不同(Set不允许包含重复元素)。

Set判断两个对象是否相同是根据equals方法。也就是说,只要两个对象用equals方法方法比较返回false,Set就会接受这两个对象。

 1.HashSet是Set的典型实现,HashSet按Hash算法来存储集合中的元素,因此具有很好的存取和查找性能。

   特点:不能保证元素的排列顺序;不是同步的,不是线程安全;集合值可以是null。

   HashSet集合判断两个元素的相等的标准是两个对象通过equals方法比较相等,并且两个对象的hashCode()方法返回值也相等。

 示例:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

import java.util.*;

//类A的equals方法总是返回true,但没有重写其hashCode()方法

class A

{

    public boolean equals(Object obj)

    {

        return true;

    }

}

//类B的hashCode()方法总是返回1,但没有重写其equals()方法

class B

{

    public int hashCode()

    {

        return 1;

    }

}

//类C的hashCode()方法总是返回2,但没有重写其equals()方法

class C

{

    public int hashCode()

    {

        return 2;

    }

    public boolean equals(Object obj)

    {

        return true;

    }

}

public class TestHashSet

{

    public static void main(String[] args)

    {

        HashSet books = new HashSet();

        //分别向books集合中添加2个A对象,2个B对象,2个C对象

        books.add(new A());

        books.add(new A());

        books.add(new B());

        books.add(new B());

        books.add(new C());

        books.add(new C());

        System.out.println(books);

    }

}

  输出:[B@1, B@1, C@2, A@659e0bfd, A@2a139a55]

可以看出HashSet把A,B当成两个对象,C只有一个。

2.LinkedHashSet类

LinkedHashSet集合同样是根据元素的hashCode值来决定元素的存储位置,但是它同时使用链表维护元素的次序。这样使得元素看起 来像是以插入顺序保存的,也就是说,当遍历该集合时候,LinkedHashSet将会以元素的添加顺序访问集合的元素。

LinkedHashSet在迭代访问Set中的全部元素时,性能比HashSet好,但是插入时性能稍微逊色于HashSet。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

import java.util.*;

public class TestLinkedHashSet

{

    public static void main(String[] args)

    {

        LinkedHashSet books = new LinkedHashSet();

        books.add("第一个");

        books.add("第二个");

        //删除 Struts2权威指南

        books.remove("第一个");

        //重新添加 Struts2权威指南

        books.add("第一个");

        System.out.println(books);//[第二个, 第一个]

    }

}

    输出:[第二个, 第一个]      可以看到顺序是按插入顺序排列的。

3.TreeSet类

TreeSet是SortedSet接口的实现类,TreeSet可以确保集合元素处于排序状态。TreeSet支持两种排序方式,自然排序 和定制排序,其中自然排序为默认的排序方式。向TreeSet中加入的应该是同一个类的对象。

TreeSet判断两个对象不相等的方式是两个对象通过equals方法返回false,或者通过CompareTo方法比较没有返回0。向TreeSet中添加的应该是同一个类的对象,且最好是不可变对象。

1.自然排序

自然排序使用要排序元素的CompareTo(Object obj)方法来比较元素之间大小关系,然后将元素按照升序排列。

Java提供了一个Comparable接口,该接口里定义了一个compareTo(Object obj)方法,该方法返回一个整数值,实现了该接口的对象就可以比较大小。

obj1.compareTo(obj2)方法如果返回0,则说明被比较的两个对象相等,如果返回一个正数,则表明obj1大于obj2,如果是 负数,则表明obj1小于obj2。

如果我们将两个对象的equals方法总是返回true,则这两个对象的compareTo方法返回应该返回0

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

import java.util.*;

class implements Comparable

{

    int count;

    public R(int count)

    {

        this.count = count;

    }

    public String toString()

    {

        return "R(count属性:" + count + ")";

    }

    public boolean equals(Object obj)

    {

        if (obj instanceof R)

        {

            R r = (R)obj;

            if (r.count == this.count)

            {

                return true;

            }

        }

        return false;

    }

    public int compareTo(Object obj)

    {

        R r = (R)obj;

        if (this.count > r.count)

        {

            return 1;

        }

        else if (this.count == r.count)

        {

            return 0;

        }

        else

        {

            return -1;

        }

    }

}

public class TestTreeSet2

{

    public static void main(String[] args)

    {

        TreeSet ts = new TreeSet();

        ts.add(new R(5));

        ts.add(new R(-3));

        ts.add(new R(9));

        ts.add(new R(-2));

        //打印TreeSet集合,集合元素是有序排列的

        System.out.println(ts);

        //取出第一个元素

        R first = (R)ts.first();

        //为第一个元素的count属性赋值

        first.count = 20;

        //取出最后一个元素

        R last = (R)ts.last();

        //为最后一个元素的count属性赋值,与倒数第二个元素count属性相同

        last.count = -2;

        //再次输出count将看到TreeSet里的元素处于无序状态,且有重复元素

        System.out.println(ts);

        //删除属性被改变的元素,删除失败

        ts.remove(new R(-2));

        System.out.println(ts);

        //删除属性没有改变的元素,删除成功

        ts.remove(new R(5));<br>                ts.remove(new R(20));

        System.out.println(ts);

    }

}

 输出结果:

/*
---------- java运行 ----------
[R(count属性:-3), R(count属性:-2), R(count属性:5), R(count属性:9)]   
[R(count属性:20), R(count属性:-2), R(count属性:5), R(count属性:-2)]   //这里改变之后并没有重新排序,所以TreeSet中最好放不可改变的对象。
[R(count属性:20), R(count属性:-2), R(count属性:5), R(count属性:-2)]   //删除-2失败,因为属性被改变
[R(count属性:20), R(count属性:-2), R(count属性:-2)]                           //没有改变的5可以删除

输出完成 (耗时 0 秒) - 正常终止*/

2.定制排序

自然排序是根据集合元素的大小,以升序排列,如果要定制排序,应该使用Comparator接口,实现 int compare(T o1,T o2)方法,该方法用于比较o1和o2的大小:如果该方法返回正整数,则表示o1大于o2;如果方法返回0,则表示o1等于o2,如果该方法返回负整数,则表示o1小于o2。

如果需要定制排序,则需要在创建TreeSet集合时提供一个Comparator对象与该TreeSet集合关联,由Comparator对象负责几何元素的排序逻辑:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

import java.util.*;

class M

{

    int age;

    public M(int age)

    {

        this.age = age;

    }

    public String toString(){

       return "M age:"+age;

    }

}

public class TestTreeSet3

{

    public static void main(String[] args)

    {

        TreeSet ts = new TreeSet(new Comparator()

        {

            public int compare(Object o1, Object o2)

            {

                /*int age1 = o1 instanceof M ? ((M)o1).age :((N)o1).age;

                int age2 = o1 instanceof M ? ((M)o2).age :((N)o2).age;

                return age1 - age2;*/

                 

                M m1 = (M)o1;

                M m2 = (M)o2;

                if (m1.age > m2.age)

                {

                    return -1;

                }

                else if (m1.age == m2.age)

                {

                    return 0;

                }

                else

                {

                    return 1;

                }

                 

            }

        });

        ts.add(new M(5));

        ts.add(new M(-3));

        ts.add(new M(9));

        System.out.println(ts);//[M age:9, M age:5, M age:-3]

    }

}

  输出结果·(降序):[M age:9, M age:5, M age:-3]

4.各Set实现类比较:

HashSet和TreeSet是set的两个典型实现,HashSet的性能比TreeSet好(特别是最常用的添加,查询元素等操作).只有当需要一个保持排序的Set时,才应该使用TreeSet,否则使用HashSet

LinkedHashSet:对于普通的插入删除操作,比HashSet慢,遍历会更快。

另外:Set的三个实现类HashSet,TreeSet和EnemSet都是线程不安全的,如果有多个线程访问一个Set集合,则必须手动保持同步:

可用Collections的工具类:例如:

SortedSet s = Collections.synchronizedSortedSet(new TreeSet(…));

Set集合终于总结完啦。

转载请注明出处:http://www.cnblogs.com/jycboy/p/javalist.html

猜你喜欢

转载自blog.csdn.net/qq_32444825/article/details/81178605