Java集合框架
一、Java集合框架概述
1、数组与集合的区别:
1)数组长度不可变化而且无法保存具有映射关系的数据;集合类用于保存数量不确定的数据,以及保存具有映射关系的数据。
2)数组元素既可以是基本类型的值,也可以是对象;集合只能保存对象。
2、Java集合类主要由两个根接口Collection和Map派生出来的,Collection派生出了三个子接口:List、Set、Queue(Java5新增的队列),因此Java集合大致也可分成List(有序、可重复集合、可直接根据元素的索引来访问)、Set(无序、不可重复集合、只能根据元素本身来访问)、Queue(队列集合)、Map(存储key-value对的集合,可根据元素的key来访问value)这四种。
二、Java集合常见接口及实现类
1、Collection接口常见方法
2、List集合
实现List接口的集合主要有:ArrayList、LinkedList、Vector、Stack。
它们都可以容纳所有类型的对象,包括 null,允许重复,并且都保证元素的存储顺序。(可重复,有顺序)
1)ArrayList
ArrayList 对数组进行了封装,实现了长度可变的数组(动态数组),和数组采用相同存储方式,在内存中分配连续的空间,它的优点在于遍历元素和随即访问元素的效率比较高。
ArrayList特点:线程不安全,查询速度快;底层数据结构是数组结构;扩容增量:原容量的 1.5倍, 如 ArrayList的容量为10,一次扩容后是容量为15。
2)LinkedList
LinkedList是List接口的另一个实现,除了可以根据索引访问集合元素外,LinkedList还实现了Deque接口,可以当作双端队列来使用,也就是说,既可以当作“栈”使用,又可以当作队列使用。
LinkedList 采用链表存储方式,优点在于插入、删除元素时效率比较高,它提供了额外的 addFirst()、addLast()、removeFirst()和 removeLast()等方法,可以在LinkedList 的首部或尾部进行插入或者删除操作。
3)Vector
与ArrayList相似,但是Vector是同步的。所以说Vector是线程安全的动态数组。它的操作与ArrayList几乎一样。
3、Set集合
实现Set集合的接口主要有:HashSet、TreeSet、LinkedHashSet;
Set集合与Collection的方法相同,由于Set集合不允许存储相同的元素,所以如果把两个相同元素添加到同一个Set集合,则添加操作失败,新元素不会被加入,add()方法返回false。(无序、不重复)
1)HashSet
HashSet是按照hash算法来存储元素的,因此具有很好的存取和查找性能。
HashSet存储原理如下:
当向HashSet集合存储一个元素时,HashSet会调用该对象的hashCode()方法得到其hashCode值,然后根据hashCode值决定该对象的存储位置。HashSet集合判断两个元素相等的标准是(1)两个对象通过equals()方法比较返回true;(2)两个对象的hashCode()方法返回值相等。因此,如果(1)和(2)有一个不满足条件,则认为这两个对象不相等,可以添加成功。如果两个对象的hashCode()方法返回值相等,但是两个对象通过equals()方法比较返回false,HashSet会以链式结构将两个对象保存在同一位置,这将导致性能下降,因此在编码时应避免出现这种情况。
HashSet查找原理如下:
基于HashSet以上的存储原理,在查找元素时,HashSet先计算元素的HashCode值(也就是调用对象的hashCode方法的返回值),然后直接到hashCode值对应的位置去取出元素即可,这就是HashSet速度很快的原因。
HashSet特点:
不能保证元素的顺序;集合元素值可以是null;线程不安全,存取速度快;底层实现是一个HashMap(保存数据),实现Set接口;默认初始容量为16;加载因子为0.75:即当 元素个数 超过 容量长度的0.75倍 时,进行扩容;扩容增量:原容量的 1 倍;如 HashSet的容量为16,一次扩容后是容量为32。
2)LinkedHashSet
LinkedHashSet是HashSet的一个子类,具有HashSet的特性,也是根据元素的hashCode值来决定元素的存储位置。但它使用链表维护元素的次序,元素的顺序与添加顺序一致。由于LinkedHashSet需要维护元素的插入顺序,因此性能略低于HashSet,但在迭代访问Set里的全部元素时由很好的性能。
3)TreeSet
TreeSet可以保证元素处于排序状态,它采用红黑树的数据结构来存储集合元素。TreeSet支持两种排序方法:自然排序和定制排序,默认采用自然排序。
♦ 自然排序
TreeSet会调用集合元素的compareTo(Object obj)方法来比较元素的大小关系,然后将元素按照升序排列,这就是自然排序。如果试图将一个对象添加到TreeSet集合中,则该对象必须实现Comparable接口,否则会抛出异常。当一个对象调用方法与另一个对象比较时,例如obj1.compareTo(obj2),如果该方法返回0,则两个对象相等;如果返回一个正数,则obj1大于obj2;如果返回一个负数,则obj1小于obj2。
♦ 定制排序
想要实现定制排序,需要在创建TreeSet集合对象时,提供一个Comparator对象与该TreeSet集合关联,由Comparator对象负责集合元素的排序逻辑。
综上:自然排序实现的是Comparable接口,定制排序实现的是Comparator接口。
4、Map集合
Map接口采用键值对Map
常用实现类主要有HashMap、LinkedHashMap、TreeMap。
1)HashMap
对于HashMap而言,key是唯一的,不可以重复的。所以,以相同的key 把不同的value插入到 Map中会导致旧元素被覆盖,只留下最后插入的元素。不过,同一个对象可以作为值插入到map中,只要对应的key不一样 。
HashMap由数组+链表组成的,数组是HashMap的主体,链表则是主要为了解决哈希冲突而存在的。
HashMap工作原理如下:
HashMap基于hashing原理,通过put()和get()方法存储和获取对象。当我们将键值对传递给put()方法时,它调用建对象的hashCode()方法来计算hashCode值,然后找到bucket位置来储存值对象。当获取对象时,通过建对象的equals()方法找到正确的键值对,然后返回对象。HashMap使用链表来解决碰撞问题,当发生碰撞了,对象将会存储在链表的下一个节点中。
HashMap特点:
- Map提供了一种映射关系,元素是以键值对(key-value)的形式存储的,能根据key快速查找value;
- Map中的键值对以Entry类型的对象实例形式存在;
- key值不能重复,value值可以重复;
- key对value是多(一)对一的关系;
- Map接口提供了返回key值集合、value值集合、Entry值集合,的方法;
- Map支持泛型,形式如:Map
; - 默认初始容量为16;
- 加载因子为0.75:即当 元素个数 超过 容量长度的0.75倍 时,进行扩容;
- 扩容增量:原容量的 1 倍。
2)LinkedHashMap
LinkedHashMap使用双向链表来维护key-value对的次序(其实只需要考虑key的次序即可),该链表负责维护Map的迭代顺序,与插入顺序一致,因此性能比HashMap低,但在迭代访问Map里的全部元素时有较好的性能。
大多数情况下,只要不涉及线程安全问题,Map基本都可以使用HashMap,不过HashMap有一个问题,是迭代HashMap的顺序并不是HashMap放置的顺序,也就是无序。HashMap的这一缺点往往会带来困扰,因为有些场景,我们期待一个有序的Map。这个时候,LinkedHashMap就闪亮登场了,它虽然增加了时间和空间上的开销,但是通过维护一个运行于所有条目的双向链表,LinkedHashMap保证了元素迭代的顺序。该迭代顺序可以是插入顺序或者是访问顺序。
LinkedHashMap可以认为是HashMap+LinkedList,即它既使用HashMap操作数据结构,又使用LinkedList维护插入元素的先后顺序。
LinkedHashMap的基本实现思想就是----多态。
3)TreeMap
TreeMap是SortedMap的实现类,是一个红黑树的数据结构,每个key-value对作为红黑树的一个节点。TreeMap存储key-value对时,需要根据key对节点进行排序。TreeMap也有两种排序方式:
♦ 自然排序:TreeMap的所有key必须实现Comparable接口,而且所有的key应该是同一个类的对象,否则会抛出ClassCastException。
♦ 定制排序:创建TreeMap时,传入一个Comparator对象,该对象负责对TreeMap中的所有key进行排序。
TreeMap特点:
·TreeMap是非线程安全的;
·TreeMap是用键来进行升序顺序来排序的。通过Comparable 或 Comparator来排序;
·和HashMap一样,如果插入重复的元素,后面的元素会覆盖前面的;
·键不可以为null(如果比较器对null做了处理,就可以为null),但是值可以为null。
5、遍历
第一种:for循环遍历
1 for (int i = 0; i < heros.size(); i++) { 2 Hero h = heros.get(i); 3 System.out.println(h); 4 }
第二种:迭代器遍历
1 System.out.println("--------使用while的iterator-------"); 2 Iteratorit= heros.iterator(); 3 //从最开始的位置判断"下一个"位置是否有数据 4 //如果有就通过next取出来,并且把指针向下移动 5 //直到"下一个"位置没有数据 6 while(it.hasNext()){ 7 Hero h = it.next(); 8 System.out.println(h); 9 } 10 //迭代器的for写法 11 System.out.println("--------使用for的iterator-------"); 12 for (Iterator iterator = heros.iterator(); iterator.hasNext();) { 13 Hero hero = (Hero) iterator.next(); 14 System.out.println(hero); 15 }
第三种:增强for循环
1 System.out.println("--------增强型for循环-------"); 2 for (Hero h : heros) { 3 System.out.println(h); 4 }