Java基础:集合总结

  ------- android培训java培训、期待与您交流! ----------

 

一、集合概念

 

       相信大家都知道,java是一门面向对象的编程语言,而对事物的体现都是以对象的形式,所以为了方便对多个对象的操作,就对对象进行存储进行存储,集合就是存储对象最常用的一种方式,我们可以把集合看成是一个容器。

  同样,数组也是一种容器,那么集合和它有什么不同?

  1.数组虽然也可以存储对象,但是长度是固定的,而集合长度是可变的。
  2.数组中可以存储基本数据类型数据,集合只能存储对象。
  3.数组中储存的基本数据类型数据或对象要类型相同,而集合可以存储不同类型的对象。

       所以,当事先不知道要存放数据的个数,或者需要一种比数组下标存取机制更灵活的方法时,就需要用到集合类。

 

 

二、集合的构成与分类

      下面是一张网上找到的图片,对于集合中的类和接口有比较清晰的表现:

 

 

  1.Collection接口:

 

  Collection是最基本的集合接口,一个Collection代表一组对象,即Collection的元素(Elements)。一些 Collection允许相同的元素而另一些不行。一些能排序而另一些不行。Java JDK不提供直接继承自Collection的类,Java JDK提供的类都是继承自Collection的”子接口“如List和Set。

  所有实现Collection接口的类都必须提供两个标准的构造函数:无参数的构造函数用于创建一个空的Collection,另一个 Collection参数的构造函数用于创建一个新的Collection,这个新的Collection与传入的Collection有相同的元素。其实后一个构造函数就是复制一个Collection。

  那么如何取出Collection中的元素?无论Collection的实际类型如何,它都支持一个iterator()的方法,该方法返回一个迭代器,使用该迭代器即可逐一访问Collection中每一个元素。典型的用法如下:

 

    Iterator it = collection.iterator(); // 获得一个迭代器。
    while(it.hasNext()) {    //通过迭代器判断集合中是否还有元素。
      Object obj = it.next(); // 得到下一个元素,类型为Object。
    }

 

 

 

  Collection定义了集合框架的共性功能(方法):
  ①添加:add(e);  addAll(collection);
  ②删除:remove(e);  removeAll(collection);  clear();
  ③判断:contains(e);  isEmpty();
  ④获取:iterator();  size();
    ⑤获取交集:retainAll();
  ⑥集合变数组:toArray();

  Collection接口派生的两个接口是List和Set。

 

 

  (1)List接口

 

       List是有序的Collection,使用此接口能够精确的控制每个元素插入的位置。用户能够使用索引(元素在List中的位置,类似于数组下标)来访问List中的元素,这类似于Java的数组。List允许有相同的元素,List集合判断元素是否相同是通过对象的equals()方法。

  除了具有Collection接口必备的iterator()方法外,List还提供一个listIterator()方法,返回一个ListIterator接口,和标准的Iterator接口相比,ListIterator多了一些add()之类的方法,允许添加,删除,设定元素,还能向前或向后遍历。

  实现List接口的常用类有ArrayList,LinkedList,Vector。

 

  ArrayList类:

 

  ArrayList实现了可变大小的数组。它允许所有元素,包括null。

  每个ArrayList实例都有一个容量(Capacity),即用于存储元素的数组(数组结构)的大小。这个容量可随着不断添加新元素而自动增加。当需要插入大量元素时,在插入前可以调用ensureCapacity方法来增加ArrayList的容量以提高插入效率。

  注意ArrayListList是非同步的,如果多个线程同时访问一个ArrayList实例,而其中至少一个线程从结构上修改了该列表,则它必须保持外部同步。一种解决方法是在创建ArrayList时构造一个同步的List来包装它: 

 

    List list = Collections.synchronizedList(new ArrayList(...));   

 

  数组结构(每个元素都有角标):

   

 

  LinkedList类:

 

  LinkedList实现了List接口,存储数据的方式是双链表,允许null元素。此外LinkedList 类还为在列表的开头及结尾 get、remove 和 insert 元素提供了统一的命名方法。这些操作使LinkedList可被用作堆栈(stack),队列(queue)或双向队列(deque)。

  注意LinkedList是非同步的,如果多个线程同时访问一个链接列表,而其中至少一个线程从结构上修改了该列表,则它必须保持外部同步。一种解决方法是在创建LinkedList时构造一个同步的List来包装它:

 

  List list = Collections.synchronizedList(new LinkedList(...));

 

  双链表结构(每个元素只知道前后的元素):

  Arraylist和Linkedlist的不同处:
  ①ArrayList是实现了基于动态数组的数据结构,LinkedList基于链表的数据结构。
  ②对于随机访问get和set,ArrayList优于LinkedList,因为LinkedList要移动指针。
  ③对于新增和删除操作add和remove,LinedList比较占优势,因为ArrayList要移动数据。

 

  Vector类:

 

  Vector非常类似ArrayList,也是数组结构存储数据,但是 Vector是同步的。由Vector创建的Iterator,虽然和 ArrayList创建的Iterator是同一接口,但是,因为Vector是同步的,当一个Iterator被创建而且正在被使用,另一个线程改变了 Vector的状态(例如,添加或删除了一些元素),这时调用Iterator的方法时将抛出 ConcurrentModificationException,因此必须捕获该异常。同样,当需要插入大量元素时,在插入前可以调用 ensureCapacity方法来增加ArrayList的容量以提高插入效率。另外,Vector还有特有的取出元素的方式,通过它的 elements()获取Enumeration枚举来得到元素,实际上Enumeration和Iterator功能是一样的。

  Vector与ArrayList不同之处是:
  ①Vector是线程同步的,所以它也是线程安全的,而Arraylist是线程异步的,是不安全的。如果不考虑到线程的安全因素,一般用arraylist效率比较高。
  ②如果集合中的元素的数目大于目前集合数组的长度时,vector增长率为目前数组长度的100%,而arraylist增长率为目前数组长度的50%.如过在集合中使用数据量比较大的数据,用vector有一定的优势。
  ③Vector还有特有的取出元素的方式,Enumeration枚举。

 

 

  (2)Set接口  

 

  Set是一种不包含重复的元素的Collection,即任意的两个元素e1和e2都有e1.equals(e2)=false,Set最多有一个null元素。

  另外要注意,必须小心操作可变对象(Mutable Object)。如果一个Set中的可变元素改变了自身状态导致Object1.equals(Object2)=true将导致一些问题。

  实现Set接口的常用类有HashSet和TreeSet。

 

  HashSet类:

 

  Hashset存储数据的结构是哈希表,它不保证 set 的迭代顺序,特别是它不保证该顺序恒久不变。也就是说,HashSet中的元素是无序的。它是通过元素的hashCode()和equals()方法来保证元素的唯一性。如果元素的hashCode值不同,则不需调用equals()进行比较。如果元素的HashCode值相同,才会调用equals() 判断两个元素是否相同。基于这种唯一性标准,HashSet中判断元素是否存在,以及删除等操作,依赖的方法也是元素的hashCode()和equals()方法。

  注意HashSet是非同步的,如果多个线程同时访问一个HashSet,而其中至少一个线程修改了该 set,那么它必须保持外部同步。一种解决方法是在创建HashSet时构造一个同步的Set来包装它:

 

    Set s = Collections.synchronizedSet(new HashSet(...));

 

  哈希表结构(当元素比较hashCode不同时,元素处于不同区域;当元素比较hashCode相同,equals比较不同时,元素会在同个区域):

 

  TreeSet类:

 

  TreeSet存储数据的结果是二叉树(红黑树),基于实现 NavigableSet接口,它是使用元素的自然顺序对元素进行排序(当元素自身具有比较性时),或者根据创建 set 时提供的 Comparator进行排序(当元素自身不具备比较性,或者比较性不合需求时),具体取决于使用的构造方法。也就是说,TreeSet中的元素是有序的。它是通过元素的compareTo()或者构造时使用的比较器Comparator中的compare()方法来保证元素的唯一性。

  注意TreeSet是非同步的,如果多个线程同时访问一个TreeSet,而其中至少一个线程修改了该 set,那么它必须外部同步。一种解决方法是在创建TreeSet时构造一个同步的Set来包装它:

 

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

 

  二叉树结构(比较后,小的元素在左子树,大的元素在右子树):

      

 

 

  2.Map接口

 

  Map没有继承 Collection接口,Map提供key(键)到value(值)的映射。一个Map中不能包含相同的key,每个key只能映射一个 value。Map接口提供3种集合的视图,允许以键集、值集或键-值映射关系集的形式查看某个映射的内容。映射顺序定义为迭代器在映射的 collection 视图上返回其元素的顺序。某些映射实现可明确保证其顺序,如 TreeMap 类;另一些映射实现则不保证顺序,如 HashMap 类。Map集合没有直接取出元素的方法,想获取Map集合中的元素,需要先用entrySet()方法获取键-值映射关系的Set集合,或者使用 keySet()方法获取键的Set集合。

  Map集合的共性功能(方法):
  ①添加:put(K key, V value);  putAll(Map<? extends K,? extends V>m);
  ②删除: remove(Object key);  clear();
  ③判断:containsValue(Object value);  containsKey(Object key); isEmpty();
  ④获取:get(Object key);  size();  values(); entrySet();  keySet();

  实现Map接口的常用类有HashMap,TreeMap。

 

  HashMap类:

 

  基于哈希表的 Map 接口的实现。此实现提供所有可选的映射操作,并允许使用null键和null值(替代Hashtable)。其键集上的对象是通过元素的hashCode()和equals()方法来保证元素的唯一性。如果元素的hashCode值不同,则不需调用equals()进行比较。如果元素的HashCode值相同,才会调用equals()判断两个元素是否相同。此类不保证映射的顺序,特别是它不保证该顺序恒久不变。

  注意HashMap是非同步的,如果多个线程同时访问一个哈希映射,而其中至少一个线程从结构上修改了该映射,则它必须保持外部同步。一种解决方法是在创建HashMap时构造一个同步的Map来包装它:

 

  Map m = Collections.synchronizedMap(new HashMap(...));

 

 

 

 

 

  TreeMap类:

 

  基于二叉树的 Map 接口的实现。它所包含的映射关系是有序的(以键为标准),其键的排序方式和TreeSet中的元素相同。

  注意TreeMap是非同步的,如果多个线程同时访问一个映射,并且其中至少一个线程从结构上修改了该映射,则其必须外部同步。一种解决方法是在创建TreeMap时构造一个同步的Map来包装它:

 

  Map m = Collections.synchronizedMap(new HashMap(...));

 

 

 

 

 

 

三、集合的应用

 

       1.例子一(将自定义对象作为元素存到ArrayList集合中,并去除重复元素):

 

import java.util.ArrayList;
import java.util.Iterator;

/*
将自定义对象作为元素存到ArrayList集合中,并去除重复元素。
比如:存人对象。同姓名同年龄,视为同一个人。为重复元素。

思路:
1,对人描述,将数据封装进人对象。
2,定义容器,将人存入。
3,取出。

List集合判断元素是否相同,依据是元素的equals方法。
*/
class Person {
    private String name;
    private int age;
    Person(String name,int age) {
        this.name = name;
        this.age = age;
    }
    
    public boolean equals(Object obj) {
        if(!(obj instanceof Person))
            return false;
        Person p = (Person)obj;
        return this.name.equals(p.name) && this.age == p.age;
    }
    public String getName() {
        return name;
    }
    public int getAge() {
        return age;
    }
}

public class ArrayListTest {
    public static void main(String[] args) {
        ArrayList<Person> al = new ArrayList<Person>();        //使用泛型指定集合元素类型。

        al.add(new Person("lisi01",30));
        al.add(new Person("lisi02",32));
        al.add(new Person("lisi02",32));
        al.add(new Person("lisi04",35));
        al.add(new Person("lisi03",33));
        al.add(new Person("lisi04",35));
        
        al = singleElement(al);
        System.out.println("remove 03 :"+al.remove(new Person("lisi03",33)));//remove方法底层也是依赖于元素的equals方法。

        Iterator<Person> it = al.iterator();
        while(it.hasNext()) {    //判断集合中是否还有元素。
            Person p = it.next();
            System.out.println(p.getName()+"::"+p.getAge());
        }
    }

//    去掉重复元素。
    public static ArrayList<Person> singleElement(ArrayList<Person> al) {
        //定义一个临时容器。
        ArrayList<Person> newAl = new ArrayList<Person>();
        Iterator<Person> it = al.iterator();
        while(it.hasNext()) {
            Person p = it.next();

            if(!newAl.contains(p))
                newAl.add(p);
        }
        return newAl;
    }
}

 

    运行结果为:

   remove 03 :true
   lisi01::30
   lisi02::32
   lisi04::35

 

    2.例子二(使用LinkList模拟一个堆栈或者队列数据结构):

 

import java.util.LinkedList;

/*
使用LinkList模拟一个堆栈或者队列数据结构。

堆栈:先进后出,如同一个杯子。
队列:先进先出,如同一个水管。
 */
class DuiLie {
    private LinkedList link;
    private LinkedList link_2;
    
    DuiLie() {
        link = new LinkedList();
        link_2 = new LinkedList();
    }

    public void myAdd(Object obj) {
        link.addFirst(obj);
        link_2.addFirst(obj);
    }

    public Object myGet() {
        return link.removeFirst();        
    }

    public Object myGet_2() {
        return link.removeLast();
    }

    public boolean isNull() {
        if (link.isEmpty()) {
            try {
                return link.isEmpty();
            }
            finally {
                link.addAll(link_2);
            }
        }
        return link.isEmpty();                
    }
}


public class LinkedListTest {
    public static void main(String[] args) {
        DuiLie dl = new DuiLie();

        dl.myAdd("java01");
        dl.myAdd("java02");
        dl.myAdd("java03");
        dl.myAdd("java04");
        
        //堆栈形式;先进后出
        while (!dl.isNull())
            System.out.println(dl.myGet());
        System.out.println();

        //队列形式:先进先出
        while (!dl.isNull())
            System.out.println(dl.myGet_2());
    }
}

 

   运行结果为:

   java04
   java03
   java02
   java01

   java01
   java02
   java03
   java04

 

   3.例子三(Vector枚举):

 

import java.util.Enumeration;
import java.util.Vector;

/*
枚举就是Vector特有的取出方式。
发现枚举和迭代器很像。
其实枚举和迭代是一样的。

因为枚举的名称以及方法的名称都过长。
所以被迭代器取代了。
*/
public class VectorDemo {
    public static void main(String[] args) {
        Vector<String> v = new Vector<String>();

        v.add("java01");
        v.add("java02");
        v.add("java03");
        v.add("java04");

        Enumeration<String> en = v.elements();
        while(en.hasMoreElements()) {    //判断集合中是否还有元素。
            System.out.println(en.nextElement());
        }
    }
}

 

   运行结果为:

   java01
   java02
   java03
   java04

 

   4.例子四(往HashSet集合中存入自定义对象):

 

import java.util.HashSet;
import java.util.Iterator;

/*
往HashSet集合中存入自定义对象。
以人为例:
姓名和年龄相同视为同一个人,重复元素。
*/
public class HashSetTest {
    public static void main(String[] args) {
        HashSet<Person> hs = new HashSet<Person>();

        hs.add(new Person("a1",11));
        hs.add(new Person("a2",12));
        hs.add(new Person("a3",13));
        hs.add(new Person("a2",12));
//        查看是否包含指定元素。
        System.out.println("a1:"+hs.contains(new Person("a2",12)));
//        删除一个元素。
        System.out.println(hs.remove(new Person("a3",13)));

        Iterator<Person> it = hs.iterator();
        while (it.hasNext()) {
            Person p = it.next();
            System.out.println(p.getName()+"..."+p.getAge());
        }
    }
}


class Person {
    private String name;
    private int age;

    Person(String name,int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

    public int hashCode() {
        return name.hashCode()+age*39;
    }

    public boolean equals(Object obj) {
        if (!(obj instanceof Person))
            return false;
        Person p = (Person)obj;
        return this.name.equals(p.name) && this.age == p.age;
    }
}

 

   运行结果为:

   a1:true
   true
   a2...12
   a1...11

 

   5.例子五(使用TreeSet添加字符串,字符串按长度排序):

 

import java.util.Comparator;
import java.util.Iterator;
import java.util.TreeSet;

/*
按照字符串长度排序。

字符串本身具备比较性,但是它的比较方式不是所需要的。
这时只能使用比较器。
 */
public class TreeSetTest {
    public static void main(String[] args) {
//        元素本身的比较性不合需求,构造时加上比较器。
        TreeSet<String> ts = new TreeSet<String>(new StrLenCompare());

        ts.add("abcd");
        ts.add("cc");
        ts.add("cba");
        ts.add("aaa");
        ts.add("z");
        ts.add("hahaha");

        Iterator<String> it = ts.iterator();
        while (it.hasNext())
        {
            String s = (String)it.next();
            System.out.println(s+"...length..."+s.length());
        }
    }
}

//比较器比较内容。
class StrLenCompare implements Comparator<String> {
    public int compare(String s1,String s2) {
        int num = new Integer(s1.length()).compareTo(new Integer(s2.length()));
        if (num==0)
            return s1.compareTo(s2);
        return num;
    }
}

 

   运行结果为:

   z...length...1
   cc...length...2
   aaa...length...3
   cba...length...3
   abcd...length...4
   hahaha...length...6

 

   6.例子六(使用HashMap添加不重复的元素(映射关系)):

 

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

/*
每一个学生都与对应的归属地。
学生Student,地址String。
学生属性:姓名,年龄。
注意:姓名和年龄相同的视为同一个学生。
保证学生的唯一性。

思路:
1,描述学生。
2,因为学生和地址存在映射关系。定义Map容器,将学生作为键,地址作为值存入。
3,获取Map集合中的元素。
*/
class Student implements Comparable<Student> {
    private String name;
    private int age;
    Student(String name,int age) {
        this.name = name;
        this.age = age;
    }

    public int compareTo(Student s) {
        int num = new Integer(this.age).compareTo(new Integer(s.age));
        if (num==0) {
            return this.name.compareTo(s.name);
        }
        return num;
    }

    public int hashCode() {
        return this.name.hashCode()+this.age*34;
    }

    public boolean equals(Object obj) {    //这里重写的是父类Object的equals方法,参数类型必须Object,否则重写失败。
        if(!(obj instanceof Student))
            throw new ClassCastException("类型不匹配");
        Student s = (Student)obj;
        return this.name.equals(s.name) && this.age==s.age;
    }    

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;    
    }

    public String toString() {
        return name+":::"+age;
    }
}


public class HashMapTest {
    public static void main(String[] args) {
        HashMap<Student,String> hm = new HashMap<Student,String>();

        hm.put(new Student("lisi1",21),"beijing");
        hm.put(new Student("lisi1",21),"tianjin");
        hm.put(new Student("lisi2",22),"shanghai");
        hm.put(new Student("lisi3",23),"nanjing");
        hm.put(new Student("lisi4",24),"wuhan");

        //第一种取出方式:keySet
        Set<Student> keySet = hm.keySet();
        Iterator<Student> it = keySet.iterator();

        while (it.hasNext()) {
            Student s = it.next();
            String addr = hm.get(s);
            System.out.println(s+"..."+addr);
        }
        System.out.println();
        
/*        //第二种取出方式:entrySet
        Set<Map.Entry<Student,String>> entrySet = hm.entrySet();
        Iterator<Map.Entry<Student,String>> iter = entrySet.iterator();

        while (iter.hasNext()) {
            Map.Entry<Student,String> me = iter.next();
            Student s = me.getKey();
            String addr = me.getValue();
            System.out.println(s+"....."+addr);
        }*/
    }
}

 

   运行结果为:

   lisi3:::23...nanjing
   lisi1:::21...tianjin
   lisi2:::22...shanghai
   lisi4:::24...wuhan

 

   7.例子七(使用TreeMap添加有序的元素(映射关系)):

 

import java.util.Comparator;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;

/*
需求:每一个学生都与对应的归属地。
学生Student,地址String。
学生属性:姓名,年龄。
对学生对象的年龄进行升序排序。

因为数据是以键值对形式存在的。
所以要是哟昂可以排序的Map集合,TreeMap。
*/
class StuNameComparator implements Comparator<Student> {
    public int compare(Student s1,Student s2) {
        int num = s1.getName().compareTo(s2.getName());
        if (num==0)
            return new Integer(s1.getAge()).compareTo(new Integer(s2.getAge()));
        return num;
    }
}

public class TreeMapTest {
    public static void main(String[] args) {
        //因为Student本身定义有比较性,可以直接满足需求。如果是按姓名排序,则需要加上StuNameComparator比较器。
        TreeMap<Student,String> tm = new TreeMap<Student,String>(/*new StuNameComparator()*/);

        tm.put(new Student("blisi3",23),"nanjing");
        tm.put(new Student("lisi1",21),"beijing");
        tm.put(new Student("alisi14",24),"wuhan");
        tm.put(new Student("lisi1",21),"tianjin");
        tm.put(new Student("alisi11",21),"tianjin");
        tm.put(new Student("alisi12",22),"shanghai");

        Set<Map.Entry<Student,String>> entrySet = tm.entrySet();
        Iterator<Map.Entry<Student,String>> it = entrySet.iterator();
        while (it.hasNext()) {
            Map.Entry<Student,String> me = it.next();
            Student s = me.getKey();
            String addr = me.getValue();
            System.out.println(s+"..."+addr);
        }
    }
}


class Student implements Comparable<Student> {
    private String name;
    private int age;
    Student(String name,int age) {
        this.name = name;
        this.age = age;
    }

    public int compareTo(Student s) {
        int num = new Integer(this.age).compareTo(new Integer(s.age));
        if (num == 0) {
            return this.name.compareTo(s.name);
        }
        return num;
    }

    public int hashCode() {
        return this.name.hashCode()+this.age*34;
    }

    public boolean equals(Object obj) {
        if(!(obj instanceof Student))
            throw new ClassCastException("类型错误");
        Student s = (Student)obj;
        return s.name.equals(this.name) && this.age == s.age;
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

    public String toString() {
        return name+":::"+age;
    }
}

 

   运行结果为:

   alisi11:::21...tianjin
   lisi1:::21...tianjin
   alisi12:::22...shanghai
   blisi3:::23...nanjing
   alisi14:::24...wuhan

 

 

四、总结

 

   如果涉及到堆栈,队列等操作,应该考虑用List,对于需要快速插入,删除元素,应该使用LinkedList,如果需要快速随机访问元素,应该使用ArrayList。

  如果程序在单线程环境中,或者访问仅仅在一个线程中进行,考虑非同步的类,其效率较高,如果多个线程可能同时操作一个类,应该使用同步的类或者在类意外加上同步。

  要特别注意对哈希表的操作,作为key的对象要正确复写equals和hashCode方法。

  尽量返回接口而非实际的类型,如返回List而非ArrayList,这样如果以后需要将ArrayList换成LinkedList时,客户端代码不用改变。这就是针对抽象编程。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值