Java基础--说集合框架

版权所有,转载注明出处。

1,Java中,集合是什么?为什么会出现?

  根据数学的定义,集合是一个元素或多个元素的构成,即集合一个装有元素的容器。

  Java中已经有数组这一装有元素的容器,为什么还要新建其它容器?废话,肯定是数组满足不了实际需求。

      数组满足不了的需求:

      • 动态改变容器的大小,如果加入的元素个数不确定,我们需要动态改变容器的大小。
      • 数组提供的功能和性能不完善,例如查找效率低,元素之间是无序的。作为成熟的编程语言,Java需要提供更加功能更加丰富的容器。

      鉴于此,Java创建了集合框架的相关接口和实现类。  

2,集合框架的设计思想是什么?

  由上所述,Java集合框架首先要可以动态改变添加和删除元素,此外还要提供更丰富的方法来处理数据。

      由于集合是依赖数据元素而存在的,考虑元素的构成,我们可以将程序需要处理的数据抽象成类似于关系数据库的形式,每一行是一个数据,并且有索引(主键),我们将数据分为<索引:数据>这样的键值对形式,Java中用Map接口就是基于此产生。

  考虑数据为单一元素时(基本数据类型或引用类型),虽然仍可以用Map存储(可以用一个自增值作为键)但意义不大。为了适应这种情况Collection接口被创造出来。

      以上就是Java集合框架顶层接口Map和Collection的设计初衷。

3,具体集合类有哪些?它们特点是什么?

 3.1、首先考虑简单的Collection接口,对于单个元素存储来说,根据单个元素属性来创建Collection的子接口或者实现类。这些属性有是否要存储相同的,存储是否是有序的。在Java中我们根据是否要存储相同元素设计了Collection常用的两个子接口List和Set.相同和不相同的判断默认判断标准是e1.equals(e2)。

    • List:可以存储重复元素
    • Set:只存不同元素

List:实现List接口常见的类有:

      • ArrayList:底层是可变数组数据结构实现,因此查询快(地址连续,跳转少),增删慢(地址改变),但线程不安全,多线程用时注意。
      • Vector:线程安全的ArrayList,每个方法都被同步了,因此使用效率低,实际中使用较少。
      • LinkedList:底层用双向链表实现,因此查询慢(地址跳转多),增删快。

使用原则:不考虑性能,常用ArrayList。当考虑性能时,如果需要查询操作较多用ArrayList,增删操作较多则用LinkedList

      Set:实现Set接口常见的类有,Set用默认用元素的hashCode()方法保证元素的唯一性。

      • HashSet:底层用哈希表实现,无序,加入顺序和存储顺序没有必然联系。
      • TreeSet:底层用红黑树(自平衡的二叉树)。使用元素的自然顺序,e1.equals(e2)比较作为大小判断标准。

使用原则:根据是否需要对添加的元素进行划分。需要用TreeSet,否则用HashSet

        3.2 Map接口,Map接口较为简单,不过需要强调的是Map进行比较等操作都是针对键来说的。常用的实现类:

      • HashMap:无序
      • TreeMap:有序
      • HashTable:HashMap的线程安全实现,不允许NULL,作为键值。

4,具体集合类常用方法?注意事项?

  对于集合类来讲,添加,查找,遍历,删除是必须的。其中添加,查找,遍历是基础操作,其它都是依赖于这些操作进行的。

     4.1Collection: 

           添加:boolean  add(E e) 

           查找:boolean contains(Object o)是否包含

           遍历:用迭代器接口Iterator<E> iterator()

      使用样例:   

Iterator it = c.iterator();
while(it.hasNext()){
    it.next();
}
View Code

           以ArrayList为例:  

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

public class ArrayListDemo {
    public static void main(String[] args) {
        Collection<String> al = new ArrayList<String>();
        
        al.add("a");//添加
        al.add("b");
        al.add("c");
        al.add("d");
        //查询是否含有
        System.out.println(al.contains("a"));
        
        //遍历
        Iterator<String> i = al.iterator();
        while(i.hasNext()){
            String tmp = i.next();
            System.out.println(tmp);
        }
        for(String str:al){System.out.println(str);}
    }
}
View Code

        类特有的方法

    ArrayList,由于ArrayList是用可变数组实现的因此会有关索引的操作。

      ArrayList:扩容如果初始不指明ArrayList大小,则初始化为10

           当需要扩容时,可以指定扩容大小,默认增长为原来的1.5倍-1(Vector扩容为原来的一倍。)

                ArrayList()是顺序添加元素。先进位置越靠前。

        E get(int index):返回索引位置的的数据

        E set(int index, E element):修改索引位置的元素

        E remove(int index)

        boolean remove(Object o)

            LinkedList,采用双向链表实现,因此具备一个反向的迭代器。

ListIterator<String> i =  lk.listIterator(lk.size());
while(i.hasPrevious()){
        String tmp = i.previous();
        System.out.println(tmp);
    }
for(String str:lk){System.out.println(str);}

/**
 * d
 * c
 * b
 * a
 * a
 * b
 * c
 * d
 */
View Code

        Set:

    • 保证元素的唯一性机制

      查看Set的add方法源码:boolean add(E e);是一个抽象方法,

      因此定位到HashSet的add()方法

//HashSet的add源码 map是一个HashMap对象
public boolean add(E e) {
        return map.put(e, PRESENT)==null;
    }
View Code

      发现HashSet是使用HashMap来保证元素的唯一性。追踪的HashMap的put()方法源码:

int hash = hash(key);
int i = indexFor(hash, table.length);
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
    Object k;
    if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {                   
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
 }
modCount++;
addEntry(hash, key, value, i);
return null;
View Code

              添加元素时先计算待添加元素的哈希值,然后从table中查找,如果查找到,则e不为空,不进入循环,则原来的元素不动,否则进入循环再利用equals() 方法判断是否和其它元素相同。

      综上Set用来保证元素唯一性的机制是:

     比较hashCode(),根据hashCode()查找元素:

    查找:

      yes-->原来位置的元素不发生改变

          no:

        用equals()和其他元素相比

         相同  原来位置的元素不发生改变

         不同  加入新元素

             同理追踪TreeSet的源码可以得到它也是由TreeMap来保证有序的。

    •  HashSet的使用  

和ArrayList类似没有很特殊的方法,如果需要加入的元素的不同一般使用HashSet

    • TreeSet的使用

TreeSet可以保证元素以自然顺序(equals())排列,默认递增。

    • 使用Set注意事项

      保证引用类型元素唯一:

其实,Set使用元素的hashCode()返回值和equals()保证唯一,用默认的比较器保证有序,这在元素是基本数据类型时没有问题的,但当数据元素是引用类型时,由于相同对象的不同引用可能在现实中代表同一个对象实例。例如一下情景:

Student类

//假设业务要求姓名和年龄相同代表同一个学生
public class Student(){
    String name;
    int age;
    public Student(String name, int age){
         this.name = name;
         this.age = age;
    }
}
View Code

      HashSetDemo主类

import java.util.HashSet;
import java.util.Set;

public class HashSetDemo {
    public static void main(String[] args) {
        Set<Student> s = new HashSet<Student>();
        Student s1 = new Student("小明",16); 
        Student s2 = new Student("小明",16);
        s.add(s1);
        s.add(s2);
        for(Student st:s){System.out.println(st);}
    }
}
/*
 * 输出:
 *     collection.Student@4b0bc3c9
 *  collection.Student@5f0ee5b8
 * */
View Code

      可以看到HashSet将相同的一个人加了两次,因此为了这种情况,在数据元素是自定义类时,我们通常需要自定义hashCode()方法和equals()方法。 

         就需要改写方法元素的hashCode()和equals()方法。在Eclipse中可以自动生成。Source-->Generate hashCode()和equals()

                 修改后的Student类 

public class Student {
      @Override
    public String toString() {
        return "Student [name=" + name + ", age=" + age + "]";
    }
    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + age;
        result = prime * result + ((name == null) ? 0 : name.hashCode());
        return result;
    }
    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        Student other = (Student) obj;
        if (age != other.age)
            return false;
        if (name == null) {
            if (other.name != null)
                return false;
        } else if (!name.equals(other.name))
            return false;
        return true;
    }
    String name;
    int age;
      public Student(String name, int age){
          this.name = name;
          this.age = age;
      }
}
View Code

      TreeSet保证引用类型有序

      TreeSet构造方法

                 TreeSet(Comparator<? super E> comparator).Comparator是一个比较器接口,我们新建TreeSet时不声明比较器,这时TreeSet按照元素的自然顺序(equals())上升排序,当我们自定义Comparator类后TreeSet就可以按照我们业务要求来写比较器。下面是一个针对学生类的比较器

      业务规定:按年龄从大到小排序

      比较器类

public class MyComparator implements Comparator<Student>{

    @Override
    public int compare(Student o1, Student o2) {
        if(o1.age>o2.age){
            return -1;
        }
        if(o1.age<o2.age){
            return 1;
        }
        if(o1.equals(o2)&&o1.hashCode()==o2.hashCode()){
            return 0;
        }
        return 1;
    }
}
View Code

                 之所以比较hashCode和equals方法是因为TreeSet加入元素时如果如果返回为0会认为两个元素相同,只添加一个(原来的值)。

      主类调用    

import java.util.Set;
import java.util.TreeSet;

public class TreeSetDemo {
    public static void main(String[] args) {
        Set<Student> s = new TreeSet<Student>(new MyComparator());
        Student s1 = new Student("小明",16); 
        Student s2 = new Student("小lizi",19);
        Student s3 = new Student("小lizi",20);
        Student s4 = new Student("小lizie",20);
        s.add(s1);
        s.add(s2);
        s.add(s3);
        s.add(s4);
        for(Student st:s){System.out.println(st.toString());}
    }
    
    /*
     * (Student [name=小lizi, age=20]
     * Student [name=小lizie, age=20]
         * Student [name=小lizi, age=19]
         * Student [name=小明, age=16]
     */
}
View Code

    4.2 Map

     Map是针对元素为键值对形式数据的容器,由于Set数据的加入依赖Map的实现类HashMap和TreeMao,故Set使用事项的描述也适用于Map。即在Map加入引用数据类型作为键值时,也应该重写键值对象的hashCode()和equals()方法,同时TreeMap再加入元素时,需要自定义比较器。

     Map和Collection使用上形式上最大的不同是遍历操作:

      Set<Map.Entry<K,V>> entrySet()

      使用规则:

Map<String,String> map = new HashMap<String,String>();
map.put("a", "a");
map.put("b", "b");
map.put("c", "c");
map.put("d", "d");
Set<Map.Entry<String, String>> myset = map.entrySet();
for(Map.Entry<String, String> me:myset){
    System.out.println(me.getKey());
    System.out.println(me.getValue());
}
View Code

               此外Map提供获取所有的键集,值集等操作,可查阅API使用,较为简单。

5,总结集合框架

      5.1结构

         Collection

      |List

        |ArrayList:一般使用,数组实现,查询快,增删慢,线程不安全

        |Vector:较为少用,线程安全,但效率较低

        |LinkedList:双向链表实现,查询慢,增删快,线程不安全

      |Set

        |HashSet:HashMap

        |TreeSet:红黑二叉树实现

 

      Map:

      |HashMap 无序

      |TreeMap  有序

        5.2 泛型

    List<String> a = new ArrayList<String>();//a中存储的数据类型在编译器就确定下来

    List a = new ArrayList();//a中存储的数据类型运行时才知道

 

  ps.

 

    

posted @ 2016-07-22 17:07  moye  阅读(628)  评论(0编辑  收藏  举报