Java集合
集合
1. Java集合框架的概述
集合与数组都是用来存储多个数据的结构,通常称为Java容器。此时的存储指的是存储在内存之中,不涉及到持久化存储(.txt、.jpg、数据库等)。
数组存储的特点:
- 一旦初始化之后,数组的长度就已经确定了。
- 一旦定义好,数组存储数据的类型就确定了,后续操作只能操作同一种数据类型的数据
数组存储的弊端:
- 一旦初始化之后,数组的长度就不能更改了。
- 数组中的相关方法很少,尤其是在插入、删除等操作的时候,非常的不方便,效率也比较低。
- 获取数组中实际添加元素的个数非常不方便。
- 数组存储数据的时候是有序可重复的,对于无序不可重复的需求无法实现。
集合存储可以解决数组存储方面的弊端。
集合中的框架结构:
Collection接口:单列集合,存储一个一个的元素。
List:存储有序可重复的数据。(ArrayList、LinkedList、Vector)
Set:存储无序不可重复的数据。(HashSet、TreeSet、LinkedHashSet)
Map接口:双列集合,通过键值对(Key-Value)存储一对一对的数据。(HashMap、LinkedHashMap、TreeMap、Properties、HashTable)
2. Collection接口方法
add(E e):将元素添加到集合里面。
size():获取添加的元素的个数。
addAll(Collection list):将一个集合元素list添加到另外一个集合里面。
clear():清空集合中的元素。
isEmpty():判断集合是否为空(是否有元素)。
contains(Object o):判断集合中是否包含元素o,此时contains会调用对象所在类的equals方法。
containsAll(Collection list):判断当前集合中是否包含集合list
remove(Object o):从集合中移除元素o
removeAll(Collection list):从当前集合中移除集合list
retainAll(Collection list):获取当前集合与集合list的交集并将结果赋给当前集合
equals(Object o):比较当前集合与集合o是否相等。
hashCode():返回当前集合的哈希值
toArray():将当前集合转为数组。数组转集合可以调用Arrays.asList()
iterator():返回Iterator接口的实例,用来遍历集合元素。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
public class CollectionTest { @Test public void test1(){ Collection collection=new ArrayList(); //add(Object e):将元素e添加到collection中 collection.add("AA"); collection.add(123); collection.add(new Date()); //size():获取添加的元素的个数 System.out.println(collection.size()); Collection collection1=new ArrayList(); collection1.add("CC"); collection1.add(456); //addAll(Collection list): 将一个集合添加到另外一个集合中 collection1.addAll(collection); System.out.println(collection1.size()); //clear():清空集合元素 // collection1.clear(); //isEmpty():判断当前集合是否为空 boolean empty = collection1.isEmpty(); System.out.println(empty); System.out.println("*******************"); //contains(Object o):判断集合中是否包含元素o boolean cc = collection1.contains("CC"); System.out.println(cc); //来个有意思的测试,此时contains调用了String类的equals方法而不是用==判断的(String类重写了equals方法) //如果contains里面的参数是对象的话,此时调用的相当于是Object中的equals方法, // 而object中的equals方法本质上是==。所以结果为false, 如果想比较结果为true,则需要重新写自定义类中的equals方法 //contains会调用对象所在类的equals方法,所以在向Collection接口的实现类中添加对象时,要求对象的所在类要重写equals方法。 collection1.add(new String("Java")); System.out.println(collection1.contains(new String("Java")));//true //containsAll(Collection list):判断当前集合中是否存在list集合中的所有元素. System.out.println(collection1.containsAll(collection)); //remove(Object o):移除集合中的某个对象 collection1.remove(new String("Java")); System.out.println(collection1.toString()); //removeAll(Collection list):从当前集合中移除list中的所有相同的元素,并修改当前集合 //retainAll(Collection list):获取list与当前集合的交集并修改当前集合为交集的值。 //equals(Object o):判断当前集合与集合o元素是否相同(在ArrayList需注意是否有序) //hashCode():返回当前对象的hash值。 System.out.println(collection1.hashCode()); //toArray():集合转成数组 Object[] objects = collection1.toArray(); System.out.println(objects); //数组转成集合:Arrays.asList() List<String> strings = Arrays.asList(new String[]{"AA", "BB"}); System.out.println(strings); System.out.println(strings.size()); //小坑 List ints = Arrays.asList(new int[]{123, 456}); System.out.println(ints); System.out.println(ints.size()); //避免方式一 List integers = Arrays.asList(123, 456); System.out.println(integers.size()); System.out.println(integers); //避免方式二 List integers1 = Arrays.asList(new Integer[]{123, 456}); System.out.println(integers1.size()); System.out.println(integers1); //iterator():返回Iterator接口的实例,用来遍历集合元素 }
3. Iterator迭代器接口
iterator对象称为迭代器(设计模式的一种),主要用来遍历Collection集合中的元素。
内部的方法:
hasnext():判断是否还有下一个元素
next():①指针下移②返回指针下移之后集合位置上的元素
remove():删除集合中的某个元素,此时调用的不是集合中的remove()方法。
JDK5.0新增了增强for循环:foreach。用来遍历集合、数组。内部依然调用了迭代器
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
@Test public void test1(){ Collection coll1=new ArrayList(); coll1.add(123); coll1.add("司藤"); coll1.add(456); coll1.add(new String("前路奔腾,未来可期")); coll1.add(new Date()); coll1.add(789); Iterator iterator = coll1.iterator();//创建迭代器接口, // 此时可以看作已经有了一个迭代器的指针,指向集合中第一个元素前方空的位置,可以理解为-1 // System.out.println(iterator.next()); // //hasNext:判断是否还有下一个元素 // while (iterator.hasNext()) // //next:①指针下移②将下移以后集合位置上的元素返回 // System.out.println(iterator.next()); // // //**********************两种错误写法 // while(iterator.next()!=null){//错误写法一:①会出现.NoSuchElementException异常②跳着输出 // System.out.println(iterator.next()); // } // //错误写法二:出现死循环,而且只输出第一个元素。 // // 原因:集合对象每次调用iterator都会新生成一个iterator对象,指针总是指向第一个元素的前方。 // while(coll1.iterator().hasNext()) // { // System.out.println(coll1.iterator().next()); // } // //****************remove方法 // while(iterator.hasNext()){ // Object next = iterator.next(); // if ("司藤".equals(next)){ // iterator.remove(); // } // } // //注意点:再次遍历coll时,需要重新定义iterator对象 // iterator=coll1.iterator(); // while (iterator.hasNext()){ // System.out.println(iterator.next()); // } // jdk5.0 新增增强for循环:foreach // for(集合中的元素类型 局部变量:集合名称),内部仍然调用了迭代器 for (Object o : coll1) { System.out.println(o); } }
4. Collection子接口一:List
List集合类中的元素特点:有序且可重复,集合中的每个元素都有其对应的顺序索引。
三个实现类:
ArrayList:作为list接口的主要实现类,线程不安全,执行效率高,底层使用Object类型的数组存储
LinkedList:底层结构使用的是双向链表存储,对于频繁的插入删除操作,该类的效率要高
Vector:作为list接口的古老实现类,线程安全,执行效率低,底层使用Object类型的数组存储
面试题:ArrayList、LinkedList、Vector的异同?
同:都实现了List接口,存储数据的特点相同,都是有序的,可重复的,ArrayList和Vector底层存储都是使用object类型的数组
异:
ArrayList源码分析:
jdk7.0情况下:
ArrayList list=new ArrayList():此时底层创建了一个长度为10的object类型的数组。
list.add():list.add(1)//elementData[0]=new Integer(1);...当添加的数据个数超过10时,此时需要扩容,默认情况下,扩容为原来容量的1.5倍,同时需要将原有数组中的数据复制到新的数组中。建议开发中使用带参的构造器ArrayList list=new ArrayList(int capacity)。
jdk8.0情况下:
ArrayList list=new ArrayList():此时数组初始化时长度为{},只有第一次调用add方法时,才创建了长度为10的数组,扩容时与jdk7.0相同。
总结:jdk7.0的创建类似于单例模式中的饿汉式,jdk8.0中创建类似于单例的懒汉式,延迟了数组的创建,节省了内存空间。
LinkedList源码分析:
LinkedList list=new LinkedList():内部声明了first和last属性,默认值为null。当调用add方法时,创建了Node对象。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
private static class Node<E> { E item; Node<E> next; Node<E> prev; Node(Node<E> prev, E element, Node<E> next) { this.item = element; this.next = next; this.prev = prev; } }
Vector源码分析:该类基本不怎么使用,与ArrayList不同的时扩容方式,扩容大小为原来数组长度的2倍
5. Collection子接口二:Set
set存储数据的特点是无序的不可重复的。set中没有定义新的方法,都是collection中定义过的
HashSet:作为set接口的主要实现类,是一个线程不安全的,可以存储null值。
LinkedHashSet:是HashSet的一个子类,遍历其内部数据时,可以按照添加顺序遍历
TreeSet:可以按照添加对象的指定属性进行排序
如何理解set存储数据是无序的、不可重复的?
无序性:不等于随机性,每次输出都是一样的顺序。以HashSet为例,存储的数据在底层数组中并非按照索引顺序进行的添加,而是根据数据的Hash值决定的。
不可重复性:保证添加的对象重写了equals方法,且判断时不可返回true,即相同的元素只能添加一个。
set中添加元素的过程(以HashSet为例)
在jdk1.8中HashSet的底层是由HashMap实现的
当添加元素a时,调用a元素所在类的hashCode()方法算出hash值,此hash值通过某种算法算出元素a在hashSet底层数组中的位置,如果该位置没有元素,则直接添加,如果当前位置有元素,判断新添加的元素a与当前位置上的所有元素的hash值是否一样,如果都不一样,以链表的方式添加a元素(数组中原来的元素指向元素a),如果一样,新添加的元素a调用所在类的equals()方法与相同hash值的元素比较,如果返回结果为false,按照链表的方式添加,如果返回结果为true,则违背了不可重复性,新元素不可添加。
向set中添加数据,要求该数据所在的类需要重写hashCode()和equals()方法,重写的hashcode和equals方法尽量保持一致性:相等的对象必须拥有相等的散列码。
LinkedHashSet作为HashSet的子类,在添加数据的同时每个数据还添加了引用,用来记录前一个数据和后一个数据,好处是,对于频繁的遍历操作,效率要高于HashSet。
TreeSet添加数据时,要求是由同一个类的对象,否则会添加失败。该类主要涉及到排序方面的问题,有两种排序方式,自然排序(实现comparable的接口)、定制排序(comparator)
自然排序中,比较两个对象是否相等的方法,是看compareto方法返回值是否为0,不在通过equals方法比较。
定制排序中,比较两个对象是否相等的方法,是看compare方法返回值是否为0
6. Map接口
Map存储的是双列数据,使用键值对(key-value)的形式来存储数据。
HashMap:底层存储结构(数组+链表+红黑树(jdk1.8中)),可以存储null值,是线程不安全的。
LinkedHashMap:是HashMap的子类,但是使用Entry替换了HashMap中的Node,增加了entry类型的before、after属性,可以知道新添加元素的前一个元素和后一个元素
Hashtable:作为古老的Map实现类,是线程安全的,但是不能存储null值。
Properties:常用来处理配置文件,key和value都是String类型的
TreeMap:底层结构是红黑树,可以按照添加的key进行排序遍历,与TreeSet类似,有自然排序(key所在的类需要实现Comparable接口)、定制排序(comparator),要求key必须是由同一个类创建的对象(和TreeSet类似)
HashMap的底层实现原理:
在jdk1.7中,HashMap在实例化的时候创建了一个长度为16的底层数组Entry[] table,调用添加键值对中key所在类的hashcode方法计算出哈希值,根据根据某种算法计算出添加元素在数组中存放的位置(下标)。
如果当前位置上没有元素,则新添加的元素存放成功。
如果当前位置上已经存在了一个或者多个元素(多个元素以链表的形式存在),判断已有元素中是否有与新添加的元素的哈希值相同
如果都不相同,则新元素添加成功
如果有一个或者多个相同,调用新添加元素中key值所在类的equals方法,判断是否存在key值相同的情况
如果返回结果为false,使用头插法将新元素添加到当前位置上链表的头部。
如果返回结果为true,则新元素添加失败
在jdk1.8中,hashMap在实例化时没有创建长度为16的数组,是在首次调用put方法时才进行创建。在jdk1.8中,entry数组被替换为node数组。底层结构相较于jdk7来说,增加了红黑树,当数组某个索引位置上的元素个数超过了8并且此时数组长度扩容到了64以上(且该索引位置上已经有元素了)此时该索引位置上的元素采用红黑树的结构来存储,提高了遍历速度。
jdk7和jdk8的扩容一样,根据负载因子和数组的长度算出扩容的临界值。在达到该临界值时扩容为原来数组的2倍。
负载因子:
默认为0.75,负载因子越大,hashMap的数据密度越大,出现hash碰撞的几率越大,数组中链表的长度也会越长,在进行添加和查询的时候比较的次数就会增多,影响效率。负载因子小可以比较容易的触发扩容,提高插入或者查询的效率,但是如果过小会浪费一定的内容空间,经常扩容也会影响性能。经过长时间的研究实验,负载因子经常设置为0.7~0.75;
key特点:无序不可重复,使用set存储
value特点:无序可重复,使用collection存储
entry特点:无序不可重复,使用set存储
常用方法:(Map整体是无序的,所以不存在插入相关的方法)
增:put(Object key,Object value)
删:remove(Object key)
改:put(Object key,Object value)
查:get(Object key)
长度:size()
遍历:
keyset():遍历map中所有的key值,返回一个set集合
values():遍历map中所有的value值,返回一个collection
entrySet():遍历map中所有的key-value,返回一个set集合
7. Collections工具类
操作Collection和Map的工具类
collection和conllections的区别:
collection是存储单列数据的集合接口,常见的子接口是list和set,collections是操作collection和map的工具类。
常见方法:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
@Test public void test3(){ List list = new ArrayList(); list.add(132); list.add(123); list.add(154); list.add(112); list.add(-421); list.add(-75); list.add(0); System.out.println(list); //reverse():反转list // Collections.reverse(list); //shuffle():随机排序 // Collections.shuffle(list); //sort(List list):根据元素的自然顺序对指定的集合元素进行升序排序 // Collections.sort(list); //sort(List list,Comparator):按照定制排序对指定的集合元素排序 //swap(list,int,int):将集合中i位置和j位置的元素交换位置 // Collections.swap(list,1,2); //frequency():返回指定集合中指定元素出现的次数。 // int frequency = Collections.frequency(list, 123); // System.out.println(frequency); //copy(List dect,List src):将src中的内容复制到dect中,需要注意的是,dect中的数据长度必须要大于等于list中的数据长度 // List dect = Arrays.asList(new Object[list.size()]); // Collections.copy(dect,list); // System.out.println(dect); //replaceAll(list,Object oldValue,Obejct newValue):使用新的元素替换掉list集合中的旧元素 System.out.println(list); }
除了上述方法,还提供了synchronizedXXX相关的方法,可以返回一个线程安全的集合。