一、集合框架的概述
1.集合、数组都是对多个数据进行存储操作的结构,简称java容器。此时的存储,主要是指内存层面的存储,不涉及到持久化存储(如.txt,.jpg)
2.数组在存储多个数据方面的缺点:
- 一旦初始化,其长度就不可改变。
- 数组中提供的方法非常有限,对于增加、删除、插入数据等操作,非常不便,同时效率不高。
- 获取数组中实际元素的个数的需求,数组没有现成的属性或方法可用。
- 数组存储数据的特点:有序、可重复。对于无序、不可重复的需求不能满足。
3.集合的概念图:
二、集合框架
1.Collection接口:单列集合,用来存储一个一个对象。
1.1list接口:存储有序、可重复的数据(动态数组)
具体实现类:ArrayList、LinkedList、Vector(并列关系)
常用方法:
①contains(Object obj):判断当前集合中是否包含obj,判断的原理其实是调用obj对象所在类的equals(),所有如果obj的是String,因为String重写了equals,所有new相同的内容返回的比较结果是true。但是如果是比较一个自定义类,那么调用的便是Object中的equals,比较的就是地址,返回结果就是false。
②containsAll(Collection coll):判断形参coll中的所有元素是否都存在当前集合中。
③remove(Object obj):移除前需要比较是否相等,所有也要重写equals
④removeAll(Collection coll):从当前集合中移除coll中所有的元素。差集
⑤retainAll(Collection coll):获取当前集合和coll集合的交集,返回当前集合
⑥equals(Object obj):要向返回true,需要当前集合和形参集合的元素都相同。
⑦hashcode():返回当前对象的哈希值
⑧集合——>数组:toArray()
⑨数组——>集合:调用Arrays类的静态方法asList()
Arrays.asList(new int【】{123,456});//这个时候,会转换为一个集合元素
Arrays.asList(new Integer【】{123,456});//这个时候,才会转换为二个集合
⑩iterator():返回Iterator接口的实例,用于遍历集合元素。
使用方式一:
一个个输出,但是不清除有多少个元素,容易报异常:
System.out.println(iterator.next());。。。。
如果超出集合中元素的个数,就会报异常:NoSuchElementException
使用方式二:
用for循环来打印,这样不会手抖多打印,而且简单方便
使用方式三:推荐方式:
while(iterator.hasNext()){ System.out.println(iterator.next()); }
错误使用方式一:
while(coll.iterator().hasNext()){ System.out.println(coll.iterator.next());//结果会第一个元素无限循环,因为每调用一次iterator都会生成一个新的集合 }
错误使用方式二:
while((iterator.next())!=null){ System.out.println(iterator.next());//返回不全且报异常,因为判断上调用了next指针下移,输出再次下移,等于输出一个数,指针移动两次 }
删除操作remove():
可以在遍历时,删除集合中的元素,此方法不同于集合中的remove()。
Iterator iter = coll.iterator();//回到起点 while(iter.hasNext()){ Object obj = iter.next(); if(obj.equals(“Tom”)){ iter.remove(); } }
注意:如果还未调用next()或在上一次调用next方法之后已经调用了remove方法,再调用remove会报illegalStateException。
JDK5,0新增了foreach循环,用于遍历集合、数组,使用方法:
for(Object obj : coll){//集合元素的类型 局部变量 : 集合对象 System.out.println(obj);//内部其实还是iterator迭代器,也就是把coll里面的元素放入obj中 }
---ArrayList:作为List接口的主要实现类;线程不安全的,效率高;底层使用Object【】 elementData存储。
ArrayList源码分析:
①在JDK7.0的时候:
ArrayList list = new ArrayList();//底层创建了一个长度为10的Object【】数组elementData。 list.add(123);//elementData【0】 = new Integer(123); 。。。 list.add(11);//如果这次的添加导致底层elementData数组容量不够,则扩容。默认情况下,扩容为原来的1.5倍,同时需要将原有数组中的数据复制到新的数组中。
结论:建议开发中使用带参的构造器:ArrayList list = new ArrayList(int capacity)
②在JDK8.0中的变化:
ArrayList list = new ArrayList();//底层Object【】 elementData初始化为{},并没有创建长度。 list.add(123);//第一次调用add()时,底层才去通过grow()创建了长度为10的数组,并将数据123添加到elementData中。 ... //后续的添加和扩容操作和7.0没有区别
结论:JDK7.0中的ArrayList的对象的创建类似于单例的饿汉式,而8.0中的创建类似于单例中的懒汉式,延迟了数组的创建,节省内存。
常用方法:
增:void add(Object obj)
删:remove(int index)/remove(Object obj)
改:set(int index,Object ele)
查:get(int index)
插:add(int index,Object ele)
长度:size()
遍历:1.Iterator迭代器方式
2.增强for循环
3.普通循环
---LinkedList:对于频繁的插入、删除操作,使用此类效率比ArrayList高;底层使用双向链表存储。
源码分析:
LinkedList list = new LinkedList();//内部声明了Node类型的first和last属性,默认值为null list.add(123);//将123封装到Node中,创建了Node对象。
其中,Node定义为(体现了LinkedList双向链表的说法):
private static class Node<E>{ E item; Node<E> next; Node<E> prev; Node(Node<E> prve,E element,Node<E> next){ this.item = element; this.next = next; this.prev = prev; } }
---Vector:作为List接口的古老实现类;线程安全的,效率低;底层使用Object【】 elementData。
源码分析:
JDK7.0和JDK8.0中通过Vector()构造器创建对象时,底层都创建了长度为10的数组,在扩容方面,默认扩容为原来数组长度的2倍。
1.2set接口:存储无序、不可重复的数据
具体实现类:HashSet、LinkedHashSet、TreeSet
注意:Set所用的方法都是Collection里面定义的。
向Set中添加的数据,其所在的类一定要重写hashCode()和equals()
重写的hashCode()和equals()尽可能保持一致性:相等的对象必须具有相等的散列码
重写两个方法的小技巧:对象中用作equals()比较的Field,都应该用来计算hashCode值。
---HashSet:作为Set接口的主要实现类;线程不安全的;可以存储null值
1.无序性:不等于随机性。存储的数据在底层数组中并非按照数组索引的顺序添加,而是根据数据的哈希值。
2.不可重复性:保证添加的元素按照其equals()判断时,不能返回true。即:相同的元素只能添加一个。
3.添加元素的过程:
我们向HashSet中添加元素a,首先调用元素a所在类的hashCode(),计算元素a的哈希值,此哈希值接着通过某种算法计算出在HashSet底层数组中的存放位置。(即:索引位置),判断数组此位置上是否已经有元素:
如果此位置上没有其他元素,则元素a添加成功。——>情况1
如果此位置上有其他元素b(或以链表形式存在多个元素),,则比较元素a和元素b的hash值:
如果hash值不相同,则元素a添加成功。——>情况2
如果hash值相同,进而需要调用元素a所在类的equals():
equals()返回true,则元素a添加失败。
equals()返回false,则元素a添加成功。——>情况3
对于添加成功的情况2和情况3而言:元素a与已经存在指定索引位置上数据以链表的方式存储。
JDK7.0:元素a放到数组中,指向原来的元素。
JDK8.0:原来的元素在数组中,指向元素a。(七上八下)HashSet底层:数组+链表结构。
---LinkedHashSet:作为HashSet的子类;遍历其内部数据时,可以按照添加的顺序遍历
原理其实同HashSet一样,额外的每个数据还维护了两个引用,记录此数据前一个数据和后一个数据。,优点就是对于频繁的遍历操作,LInkedHashSet效率高于HashSet。原理图解如下:
---TreeSet:可以按照添加对象的指定属性,进行排序。
1.向TreeSet中添加的数据,要求是相同类的对象。
2.两种排序方式:自然排序(实现Comparable接口)和定制排序(Comparator)
3.自然排序中,比较两个对象是否相同的标准为:comparaTo()返回0,不再是equals()
4.定制排序中,比较两个对象是否相同的标准为:compara()返回0,不再是equals()
自然排序代码例子:
TreeSet sa = new TreeSet(); sa.add(new TisiGui("刘某人",20)); sa.add(new TisiGui("张某人",34)); sa.add(new TisiGui("欧某人",30)); sa.add(new TisiGui("四某人",2)); sa.add(new TisiGui("色某人",60)); Iterator iterator = sa.iterator(); while (iterator.hasNext()){ System.out.println(iterator.next()); } //TisiGui类中 @Override public int compareTo(Object o) { if (o instanceof TisiGui){ TisiGui e = (TisiGui)o; return this.name.compareTo(e.name); }else{ throw new RuntimeException(); } }