Chapter10 集合(上)
10.1 集合框架概述
1. 背景
- 集合、数组都是对多个数据进行存储操作的结构,简称Java容器。
说明:此时的存储,主要指的是内存层面的存储,不涉及到持久化的存储(.txt, .jpg, .avi,数据库中) - 数组存在一些缺点
2. 集合框架
Collection接口: 单列数据, 定义了存取一组对象的方法的集合List:元素有序、可重复的集合ArrayListLinkedListVector
Set: 元素无序、不可重复的集合HashSetLinkedHashSetTreeSet
Map接口: 双列数据,保存具有映射关系“key-value对”的集合HashMapLinkedHashMapTreeMapHashtableProperties
10.2 Collection接口方法
1. 常用方法1
add(obj):添加size(): 获取添加的元素的个数addAll(Collection coll1): 将coll1集合中的元素添加到当前的集合中clear(): 清空集合元素isEmpty(): 判断当前集合是否为空
2. 常用方法2
contains(obj):判断当前集合中是否包含obj,调用obj对象所在类的equals()。containsAll(Collection coll1): 判断形参coll1中的所有元素是否都存在于当前集合中。remove(Object obj): 从当前集合中移除obj元素。removeAll(Collection coll1): 差集:从当前集合中移除coll1中所有的元素。
向Collection接口的实现类的对象中添加数据obj时,要求obj所在类要重写equals().
3. 常用方法3
retainAll(Collection coll1): 交集:获取当前集合和coll1集合的交集,并返回给当前集合(即删掉不同,保留相同)equals(Object obj): 要想返回true,需要当前集合和形参集合的元素都相同。hashCode(): 返回当前对象的哈希值toArray(): 集合 —>数组iterator():返回Iterator接口的实例,用于遍历集合元素。
数组–>集合:Arrays.asList()
10.3 Iterator迭代器接口
用于遍历 Collection 集合中的元素
1. hasNext() 和 next()方法
Iterator iterator = coll.iterator();
while(iterator.hasNext()){
//next():①指针下移 ②将下移以后集合位置上的元素返回
System.out.println(iterator.next());
}
2. 说明
- 集合对象每次调用
iterator()方法都得到一个全新的迭代器对象, - 默认游标都在集合的第一个元素之前。
3. remove()方法
在遍历的时候,删除集合中的元素。此方法不同于集合直接调用remove()
Iterator iterator = coll.iterator();
while (iterator.hasNext()){
// iterator.remove();
Object obj = iterator.next();
if("Tom".equals(obj)){
iterator.remove();
// iterator.remove();
}
}
//遍历集合
iterator = coll.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}如果还未调用next()或在上一次调用 next 方法之后已经调用了 remove() 方法,再调用remove()都会报IllegalStateException。
4. foreach循环
- jdk 5.0 新增了foreach循环,用于遍历集合、数组
- 格式:
for(数组元素的类型 局部变量 : 数组对象)for(String s : arr){ // }
10.4 Collection子接口之一:List接口
1. List接口实现类
ArrayList:作为List接口的主要实现类;线程不安全的,效率高;底层使用Object[] elementData存储LinkedList:对于频繁的插入、删除操作,使用此类效率比ArrayList高;底层使用双向链表存储Vector:作为List接口的古老实现类;线程安全的,效率低;底层使用Object[] elementData存储
2. ArrayList的源码分析
- jdk 7情况下
结论:建议开发中使用带参的构造器:ArrayList list = new ArrayList(int capacity)ArrayList list = new ArrayList();//底层创建了长度是10的Object[]数组elementData list.add(123);//elementData[0] = new Integer(123); ... list.add(11);//如果此次的添加导致底层elementData数组容量不够,则扩容。默认情况下,扩容为原来的容量的1.5倍,同时需要将原有数组中的数据复制到新的数组中。 - jdk 8中
ArrayList的变化:ArrayList list = new ArrayList();//底层Object[] elementData初始化为{}.并没有创建长度为10的数组 list.add(123);//第一次调用add()时,底层才创建了长度10的数组,并将数据123添加到elementData[0] ... 后续的添加和扩容操作与jdk 7 无异。 - 小结:
jdk7中的ArrayList的对象的创建类似于单例的饿汉式,而jdk8中的ArrayList的对象的创建类似于单例的懒汉式,延迟了数组的创建,节省内存。
3. LinkedList的源码分析
LinkedList list = new LinkedList(); 内部声明了Node类型的first和last属性,默认值为null
list.add(123);//将123封装到Node中,创建了Node对象。其中,Node定义为:
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;
}
}体现了LinkedList的双向链表的说法
4. Vector的源码分析
- jdk7和jdk8中通过Vector()构造器创建对象时,底层都创建了长度为10的数组。
- 在扩容方面,默认扩容为原来的数组长度的2倍。
5. List接口方法
void add(int index, Object ele):在index位置插入ele元素boolean addAll(int index, Collection eles):从index位置开始将eles中的所有元素添加进来Object get(int index):获取指定index位置的元素int indexOf(Object obj):返回obj在集合中首次出现的位置int lastIndexOf(Object obj):返回obj在当前集合中末次出现的位置Object remove(int index):移除指定index位置的元素,并返回此元素Object set(int index, Object ele):设置指定index位置的元素为eleList subList(int fromIndex, int toIndex):返回从fromIndex到toIndex位置的子集合(左闭右开)
10.5 Collection子接口之二:Set接口
1. Set接口实现类
HashSet:作为Set接口的主要实现类;线程不安全的;可以存储null值LinkedHashSet:作为HashSet的子类;遍历其内部数据时,可以按照添加的顺序遍历对于频繁的遍历操作,LinkedHashSet效率高于HashSet.
TreeSet:可以按照添加对象的指定属性,进行排序。
2. 说明
- Set接口中没有额外定义新的方法,使用的都是Collection中声明过的方法。
- 向Set(主要指:
HashSet、LinkedHashSet)中添加的数据,其所在的类一定要重写hashCode()和equals()- 重写的
hashCode()和equals()尽可能保持一致性:相等的对象必须具有相等的散列码 - 重写技巧:对象中用作
equals()方法比较的Field,都应该用来计算hashCode值
- 重写的
3. HashSet()类
Set:存储无序的、不可重复的数据,对于HashSet:- 无序性:不等于随机性。存储的数据在底层数组中并非按照数组索引的顺序添加,而是根据数据的哈希值决定的。
- 不可重复性:保证添加的元素按照equals()判断时,不能返回true.即:相同的元素只能添加一个。
- 添加元素的过程:
- 首先调用元素a所在类的
hashCode()方法,计算其哈希值,从而得到底层数组中的索引位置,判断数组此位置上是否已经有元素:- 如果此位置上没有其他元素,则元素a添加成功。 —>情况1
- 如果此位置上有其他元素b(或以链表形式存在的多个元素),则比较元素a与元素b的hash值:
- 如果
hash值不相同,则元素a添加成功。—>情况2 - 如果hash值相同,进而需要调用元素a所在类的
equals()方法:equals()返回true,元素a添加失败equals()返回false,则元素a添加成功。—>情况3
- 如果
- 对于添加成功的情况2和情况3而言:元素a 与已经存在指定索引位置上数据以链表的方式存储。
- jdk 7 :元素a放到数组中,指向原来的元素。
- jdk 8 :原来的元素在数组中,指向元素a
- 总结:七上八下
- 首先调用元素a所在类的
4. LinkedHashSet类
- LinkedHashSet作为HashSet的子类,在添加数据的同时,每个数据还维护了两个引用,记录此数据前一个数据和后一个数据。
- 优点:对于频繁的遍历操作,LinkedHashSet效率高于HashSet
5. TreeSet类
- 向TreeSet中添加的数据,要求是相同类的对象。
- 两种排序方式:自然排序(实现
Comparable接口) 和 定制排序(Comparator) - 自然排序中,比较两个对象是否相同的标准为:
compareTo()返回0.不再是equals(). - 定制排序中,比较两个对象是否相同的标准为:
compare()返回0.不再是equals().//定制排序,首先实现Comparator接口 Comparator com = new Comparator() { //按照年龄从小到大排列 @Override public int compare(Object o1, Object o2) { // } }; // 作为参数传入 TreeSet set = new TreeSet(com);