黑马程序员_java集合框架的一些总结

来源:互联网 发布:laradock访问mysql 编辑:程序博客网 时间:2024/05/16 15:59
<pre name="code" class="java">

------<a href="http://www.itheima.com" target="blank">Java培训、Android培训、iOS培训、.Net培训</a>、期待与您交流! -------

一、集合框架概述

    集合框架是一组类和接口,位于java.util 包中,主要用于存储和管理对象,主要分为三大类——集合、列表和映射。我们知道数据多了可以封装成对象存储,而对象多了我们也要存储,而集合就是一个存储对象的容器,当然数组也是可以存储对象的。那么问题就来了,既然数组已经可以储存对象,为什么还要用集合呢,他们的区别是什么?集合有什么数组没有的特点?

①、集合只能存储对象,而数组还可以存储基本数据类型;
②、数组的长度是固定的,而集合的长度是可变的;
③、数组只能存储同一类型的数据,而集合可以存储不同类型的对象;
④、集合有多个子类对象,对数据的存储方式各有不同,可以根据需求选择不同的集合。

    集合中存储的都是对象的引用(即对象在堆内存中的内存地址)。

二、集合框架的体系结构

总体结构如图所示:

注:蓝色表示接口、草绿色表示抽象类、橘黄色表示实现类

上图清楚的描述了各接口和实现类的关系图,虽然有点复杂,对于我们初学者来说主要是分清个接头的功能,在实际运用中选择哪个实现类来创建对象。
为了方便理解我们可以简化上图:
 |--ArrayList
|--List--|--LinkedList    |--TreeMap
Collection---||--Vector Map--|--HashMap
     |--HashTable
|--Set--|--TreeSet
|--HashSet

如上图所示,Collection是一个集合类的接口,在它的下面有两个子接口List和set。collection是收集、集合的意思,它可以看作是List和Set向上抽取出来的,因以为它都有List和Set容器的功能。而List和Set也可以看作是从实现类中抽取出来的,他们都有实现类的共同功能。
我们发现Map接口并不是Collection的子接口,既然Map也是用于存储对象,为什么要独立出来呢?明确的原因我也还没弄清楚,我的理解是一个Collection代表一组元素,List和Set都属于单列集合,而Map属于双列集合,它是以键值对的形象存储对象的,map集合的每一个元素包含了一个键和键所对应的值。而且Map的取值方式也有别与List和set.。
在介绍各接口、实现类的特点前先介绍一下Collection常见的一些共性方法(注意这里不包含Map),因为他的子接口、实现类也同样可以使用该共性方法。

添加:

1、add(Object obj);向集合中添加元素(任意类型的对象)。
2、addAll (Collection c )将集合c中的元素都加到集合中

删除

1、remove(Object obj);移除集合中的元素obj
2、clear();清空集合

判断:

1、contains (Object obj);判断集合中是否包含元素obj
2、isEmpty();判断集合是否为空(其原理是同size()方法获取长度,长度为0则为空)

修改:

1、retainAll(Collection c)仅保留集合c中也包含的元素,也就是两个集合取交集。
2、remove(Collection c)移除集合c中也包含的元素,也就是两个集合去交集。

获取迭代器:

Iterator iterator();取得集合的迭代器,Iterator是定义在Collection的内部,用于取出元素,
迭代器中的主要方法:
①、boolean hasNext(); 如果仍有元素可以迭代,则返回true
②、E next();返回迭代的下一个元素。
具体使用方法详见代码:
import java.util.*;public class Test002 {public static void main(String[] args) {List list = new ArrayList(); //创建一个集合//向集合中添加元素list.add("aaa"); list.add("bbb");list.add("ccc");Iterator it = list.iterator();//获取集合list的迭代器while(it.hasNext()){     //迭代器还有下一个元素则继续循环,String str = it.next(); //取出迭代器的下一个元素(一般在取出前都要判断是否有下一个元素)。System.out.println(str);}}}



三、List

list集合是有序的,其中的元素可以重复,该集合体系有索引。

1、List特有的共性方法

①、void  add (int index,E element)在指定位置index处插入元素element,其他元素往后顺延。
②、E  get (int index)获取指定位置index处的元素。
③、int  indexOf (Object o)获取元素o第一次出现的位置,不包含则返回-1。
④、void  remove  (int index )异常指定位置index处的元素
⑤、void  set (int index ,E element)  指定元素element替换指定位置index处的元素。
⑥、ListIterator listIterator ()获取List集合特有的列表迭代器(因为List集合带有脚标),ListIterator是Iterator的子接口。
ListIterator中特有的方法:
1、boolean  hasPrevious () ;  迭代器前一个是否有元素(Iterator一般都是判断后一个是否有元素hasNext()  );
2、E  previous () ; 反向向前取元素(Iterator一般都是向后取元素next()  )。

2、 List接口主要实现类有:

①、ArrayList:底层数据结构是数组结构,所以它的特点是查询速度快、插入删除慢。因为查询只要找到对象脚边即可,而增删后还要把增删位置后的元素整体移动

②、LinkedList:底层数据结构是链表数据结构,所以它的特点是查询速度慢,增加删除快,链表的查询需要从列表头开始找,因为链表中的每一项都保存有上一个项和下一项的地址,只有找到前一项才能找到下一项,所欲查询较慢,但是当删除或添加某一项时,只需要改变前后两个的指针指向即可,不用整体移动,所以它的增删速度是比较快的

LinKedList特有方法:                                           

1、void addFirst(E e);   voidaddLast(E e);添加元素到裂变开头/结尾,添加失败时产生异常(可能有容量限制)
2、E getFirst();E getLast():获取列表头/尾的元素,列表为空产生异常
3、E removeFirst();E removeLast;  移除并返回列表头/尾的元素,列表为空产生异常

JKD1.6以后对上述功能提供了更实用的方法:

1、 booleanofferFirst (E e);booleanofferLast(E e);添加元素到裂变开头/结尾,添加成功返回True,失败返回false。
2、 EpeekFirst();E peekLast();获取列表头/尾的元素,如果此列表为空,则返回null
3、 E pollFirst();E pollFirst(); 移除并返回列表头/尾的元素,如果此列表为空,则返回null

③、Vactor:底层数据结构也是数组结构,其实Vactor在JDK1.2以后被ArrayList替代了,它的是线程同步,而ArrayList是线程不同步的。一般来说Vactor以不常用,即使多线程下还是会用ArrayList然后在添加同步。

Vactor中的枚举

枚举(Enumeration)是Vactor的特有取出方式,其实枚举和被迭代器(Iterator)的功能一样,被后者取代了,因为枚举的名称和方法名都太长了(后面会详细介绍迭代器的使用)。

3、ArrayList和LinkedList的选择

如果要存储的元素较多,且增加删除的操作频繁时用LinkedList。如果,查询的操作多则用ArrayList。一般来说,我们比较倾向于使用ArrayList,因为增删操作一般不会太多,区别不大时优先使用ArrayList。

四、Set

Set集合中的元素是无序的(取出的顺序和存入的顺序不同),元素不可重复。

1、 Set接口主要实现类有:

注:对数据结构不了解的建议先百度一下,这里就不赘述了。

①、HashSet:底层数据结构是哈希表

TreeSet集合中元素是不可重复的,但是什么样的元素(也就是对象)才算是同样的呢?
其实HashSet是通过HashCode()和equals()两个方法来判断两个判断两个元素是否相同的,我们首先要明白,每一个对象都有一个hashcode值,也就是调用hashCode()方法得到的,一般来说对象默认的Hashcode值(散列码)就是对象的内存地址(也有特例,比如String类复写了hashcode方法,是对字符串 内容计算得出的结果)
将对象放入到集合中时,首先会判断放入对象的hashcode值与集合中的任意一个元素的hashcode值是否相等,如果不相等直接将该对象放入集合中。如果hashcode值相等,然后再通过equals方法判断要放入对象与集合中的任意一个对象是否相等,如果equals判断不相等,直接将该元素放入到集合中,否则不放入。由此可看出,hashcode值相同才会判断equals,所以hashCode()方法的是比较关键的。
这里提一个问题,如果有一个Person类,这个类只有两个属性姓名(name)和年龄(age),现在我们new了两个Person对象,我们程序设定为,如果两个人的姓名和年龄都一样则认为是同一个人。那么问题来了,现在要存入两个对象,他们的name和age都一样,然而创建这两对象时,计算机都在内存了开辟了空间,它们有不同的内存地址,也就是说这两个对象的hashcode值是不一样的,这时用hashSet集合存储时就认为是两个不同的元素,而都全存进集合了,这样就不满足不可重复的要求。所以,为了避免以上的问题发生,我们在把对象存入Set集合前都应该复写hashCode()方法和equals()方法。具体复写方式看代码示例:
代码示例:(给Person复写hashCode()和equals()方法)

<span style="font-size:14px;">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;}@Override//复写hashCode()方法public int hashCode(){/*这里直接调用字符串对象的hashCode方法,年龄乘上一个37,只是为了尽量让计算出来是Hashcode值不同,当然这里也可以乘以其他数,但是要注意的是别让返回值超出int的范围,毕竟hashcode是属于int型。*/return name.hashCode()+37*age;}@Override//复写equals()方法,其实hashCode的复写的好,已经能够判断,不需要调用equals方法。public boolean equals (Object obj){Person p =(Person)obj;if(!(obj instanceof Person) ) //如果存入的对象不属于Person类,直接返回falsereturn false;return this.name.equals(p.name)&&this.age==p.age;  //名字和年龄一样则返回true。 }}</span>
通过上例,我们知道复写HashCode方法和equals方法会到其中的一些属性值,如果该对象的属性参与了hashcode的计算,那么以后就不要修改该对象参与hashcode计算的那些属性了,否则修改后的哈希值与最初存进set集合中的哈希值就不同了,这样会导致两种不好的结果:
1、即使在contains方法使用该对象的当前引用作为参数去HashSet中到对象,也返回找不到的结果。
2、也会导致无法从hashSet中单独删除当前对象,从而造成内存泄露。

②、TreeSet:  底层数据结构是二叉树,因为二叉树的特点,TreeSet集合可以对元素进行排序(按自然顺序)。

TreeSet中的元素也是不可重复的,它保证数据唯一性的依据是compareTo()方法,返回0表示相同,则不存入集合中。
上面既然说到TreeSet可以让元素按自然顺序排序,那我们可不可以让TreeSet按照我们需求排序?答案是肯定的,而且有两种方法:
1、复写compareTo()方法,具体方式看代码示例:
代码示例:
public int compareTo(Object obj)  //这里设计的排序方式是:先按年龄大小排,年龄一样再按姓名排,如果姓名还是一样则认为是同一对象,不存入集合{if(!(obj instanceof Person))  //判断存入的对象是否属于Person类或其子类throw new RuntimeException("不属于Person类");Person p = (Person)obj;if (this.age>p.age)      //判断年龄return 1;if (this.age==p.age)<span style="white-space:pre"></span>//年龄相等,判断姓名return this.name.compareTo(p.name);   //直接调用String类的compareTo方法return -1;}

2、利用Comparator比较器

 在第一个方法中,排序的方式直接写到了类的方法中,但是如果我们突然需要按另一种方式排序,又要去修改代码,显然比较麻烦。这里我们可以实现一个Comparator接口,然后把这个实现对象直接传给TreeSet的构造方法中。这样,但我们改变排序的方式时,只需要写一个新的比较器。具体实现方法详见示例代码。

示例代码: 

<span style="font-size:14px;"><span style="font-size:14px;">class TreeSetTest{public static void main(String[] args){TreeSet tr =new TreeSet(new MyCompare());  //创建TreeSet集合时传入一个比较器tr.add(new Person("lisi01",11));tr.add(new Person("liai02",12));tr.add(new Person("lasi03",101));tr.add(new Person("lisi03",13));tr.add(new Person("lisi04",14));Iterator it = tr.iterator();while(it.hasNext()){Person p = (Person)it.next();System.out.println(p.getName()+"---"+p.getAge());}}}class Person implements Comparable{String name;int age;Person(String name,int age){this.name=name;this.age=age;}public String getName(){return name;}public int getAge(){return age;} <span style="white-space:pre"></span>//这里复写了compareTo方法<span style="white-space:pre"></span>public int compareTo(Object obj)   {if(!(obj instanceof Person))throw new RuntimeException("不属于Person类");Person p = (Person)obj;System.out.println(this.name+"<-->"+p.name);if (this.age>p.age)return 1;if (this.age==p.age)return this.name.compareTo(p.name);return -1;}public int hashCode(){return this.name.hashCode()+this.age;}}class MyCompare implements Comparator   //自定义一个比较器,实现Comparator接口{ public int compare(Object o1,Object o2){Person p1 = (Person)o1;Person p2 = (Person)o2;int num =p1.getName().compareTo(p2.getName());if (num==0)return new Integer(p1.getAge()).compareTo(p2.getAge());return num;}}</span></span>
由上述代码可以看出,两种方式可以并存,存入的Person类型对象虽然复写了compareTo方法,但是容器TreeSet自带了比较器,还是以比较器为主。

五、Map

 Map 集合类用于存储元素对(也称键值对或映射关系。),其中每个键映射到一个值。

1、map的共性方法:

①、V  put(K key,V value );  键值对加入到集合中,如果集合中已有相同的键key,就把新的值value存入,并返回就是值;如果没有相同的键,返回null。
②、void  put(Map m );将集合m 中的所有键值对都存入到集合中。 
③、void  clear();从此映射中移除所有键值对。 
④、V  remove(K key);移除键为key的键值对,并返回该键所对用的值。
⑤、boolean  containsKey(Object  key);  判断集合中是否包含键key。
⑥、boolean  containsValue(Object value) 判断集合中是否包含值value。
⑦、boolean  isEmpty();   判断集合是否为空。
⑧、Collection<V> values();    获取集合中所有的值,并以Collection的形式返回。
⑨、Set<K> keySet();  获取集合中的所有键,并以Set的形式返回(因为键是没有重复的所以用Set存储)。
⑩、Set<Map.Entry<K,V>>  entrySet;获取集合中所有映射关系(也可以说是键值对),并以Set方式返回,
这里返回的Set里存储的Map.Entry类型的元素,而这个类型可以说是map的映射关系。

2、 接口主要实现类有:

①、HashTable:底层是哈希表结构,null不能作为键和值,是线程同步,但是效率较低,已经被HashMap替代。
②、HashMap:底层是哈希表结构,可使用null作为键和值,是线程非同步。
③、TreeMap:底层是二叉树结构,可用于map的键排序,线程不同步。

3、Map的两种取值方式

①、keySet方式:
思路:利用集合对象调用keySet()方法取得存所有键的Set集合,再利用set集合的迭代的方式取出键,再利用get(K),取得键对应的值。
具体实现详见示例代码:
public class Test003 {public static void main(String[] args) {Map<String,String> map = new HashMap<String, String>();map.put("abc", "123");map.put("xyz", "168");Set<String> set = map.keySet();  //取得集合(存有所有键)Iterator<String> it = set.iterator();//取得set集合的迭代器while(it.hasNext()){     //迭代取出所有键String key = it.next();String value = map.get(key);  //取出键s对应的值。System.out.println(key+"--"+value);} }}


②、entrySet方式:
思路:利用集合对象调用entrySet()方法取得存有所有键值对的Set集合,再利用set集合的迭代的方式取出所有键值对,再利用Map.Entry中的getKey()和getvalue()方法获取Map.Entry中的键和值。
具体实现详见示例代码:
  
public class Test003 {public static void main(String[] args) {Map<String,String> map = new HashMap<String, String>();map.put("abc", "123");map.put("xyz", "168");Set<Map.Entry<String,String>> entrySet = map.entrySet();  //取得set集合(存有所有键值对)Iterator<Map.Entry<String, String>> it = entrySet.iterator();//取得set集合的迭代器while(it.hasNext()){     //迭代取出所有键值对Map.Entry<String, String> entry = it.next();String s1 = entry.getKey();         //取出键值对中的键String s2 = entry.getValue();//取出键值对中的值System.out.println(s1+"--"+s2);} }}




1 0