集合
简介
• 之前我们学习过数组,数组是可以保存多个数据(对象)的一种结构,但是数组有一定的局限性。
– 数组在内存存储方面的特点:
• 数组初始化以后,长度就确定了。
• 数组声明的类型,就决定了进行元素初始化时的类型
– 数组在存储数据方面的弊端:
• 数组初始化以后,长度就不可变了,不便于扩展
• 数组中提供的属性和方法少,不便于进行添加、删除、插入等操作,且效率不高。同时无法直接获取存储元素的个数
• 数组存储的数据是有序的、可以重复的。-
• 存储数据的特点单一。
• Java集合类可以用于存储数量不等的多个对象,还可用于保存具有映射关系的关联数组。
什么是集合?
集合也称容器,是装载一组对象的容器。例如:客户列表、订单列表
学习集合框架的总体思路
• 如何添加元素
• 如何获得元素
• 如何删除元素
• 如何遍历元素
集合框架图示
• 集合中的常用接口
– Collection接口
• Collection接口定义标准
– List
• 继承 Collection接口,存储的数据对象有序可重复
– Set
• 继承 Collection接口,存储的数据对象无序不重复
– Map
• 是一组成对的键-值对象,即所持有的是key-value 对。Map中不能有重复的key
Collection 接口
Iterator 接口
Iterator对象称为迭代器(设计模式的一种),主要用于遍历Collection集合中的元素。
迭代器模式定义为提供一种方法访问一个容器(container)对象中各个元索,而又不需暴露该对象的内部细节。迭代器模式,就是为容器而生的。类似于“公交车上的售票员”、“火车上的乘务员”、“空姐”。
Collection接口继承了java.lang.lterable接口,该接口有一个iterator()方法,那么所有实现了Collection接口的集合类都有一个iterator()方法,用以返回一个实现了Iterator接口的对象。
Iterator仅用于遍历集合,Iterator 本身并不提供承装对象的能力。如果需要创建iterator对象,则必须有一个被迭代的集合。
集合对象每次调用iterator()方法都得到一个全新的迭代器对象,默认游标都在集合的第一个元素之前。
Iterator 接口
boolean hasNext()
Object next()
void remove()
注意:
Iterator本身不是集合,不会生成集合的副本,迭代器遍历的就是集合本身。
在迭代器迭代元素的时候,想删除元素,需要使用迭代器的remove()方法,不能使用Collection自带的remove()方法。
增强for,内部依然使用的是迭代器实现的。(想深入探究是否真的是迭代器的可以自己把编译了的.class文件进行反编译,再查看源码,就会发现增强for是使用了迭代器实现的)
增强for的小练习:下面代码输出结果是什么?
运行结果:
哈哈
哈哈
哈哈
增强for的练习:
运行结果:
呵呵
呵呵
呵呵
重写equals、hashCode方法
如添加在集合中的元素为自己写的类的对象,想通过自定义的属性来区别是否为同一个元素时,我们需要重写equals、hashCode方法,重写方法可以直接使用编辑器的自动生成。(不理解的可以点击这里)
List 接口
– 特性
• 继承 Collection,允许重复,以元素安插的次序来放置元素,不会重新排列
三个主要的类:ArrayList、LinkedList、Vector的对比,
ArrayList底层是数组结构,查询快,增删慢,线程不安全,效率高。
LinkedList底层是链表数据结构,查询慢,增删快,线程不安全,效率高。
Vector底层是数组结构,查询快,增删慢,线程安全,效率低。
分析:
ArrayList 数组实现,(存放地址是连续的)实现List接口,实际上是一个动态可变数组实现(Object [] E),可以扩容,扩容的原则是增加50%
–JDK1.7前,初始化的时候直接创建了10个元素的数组,
–JDK1.8后,初始化的时候创建的是空数组,当第一次保存数据的时候,创建10个元素的数组
实际的开发中,如果能够大致知道元素多少,可以通过构造器:public ArrayList(int initialCapacity)直接给一个初始值,可以减少扩容次数,提高效率。
linkedList链接列表实现(也就是链表,还是双向链表,存放地址是不连续的)
Vector,是一个可再分配的Object(动态)数组
和ArrayList一样,只是方法有synchronized关键字,都是同步方法,线程安全的。
ArrayList是线程不安全的,在实际开发中,推荐使用线程不安全,执行效率高;线程的安全问题应该交给应用程序来控制,而不是有工具类完成,Vector在开发中比较少用。
Collections类
• 类java.util.Collections 提供了一些静态方法实现了基于List容器的一些常用算法
– void sort(List) 对List容器内的元素排序
– void shuffle(List) 随机排序
– void reverse(List) 反转
– void fill(List,Object) 用特定对象重写整个List容器
@Test
public void testCollections1() {
List list=new ArrayList();
list.add("D");
list.add("F");
list.add("A");
list.add("J");
list.add("B");
//排序
Collections.sort(list);
System.out.println(list);
//反转
Collections.reverse(list);
System.out.println(list);
//随机排序
Collections.shuffle(list);
System.out.println(list);
//填充
Collections.fill(list, "000");
System.out.println(list);
}
Comparable接口
• ?前面的算法根据什么确定对象“大小”
• 所有可以排序的类都实现了java.lang.Comparable接口,这个接口只有一个方法:
– public int compareTo(Object obj)
• 如果需要比较自己创建的对象时,需要给这个类定义一个排序规则,需要实现Comparable接口,重写compareTo()方法,定义排序规则。
• 如:
public int compareTo(Object o){
Person p = (Person)o;
return pname.compareTo(p.pname);
}
List面试题
• 面试题:输出什么内容?
运行结果:
1
2
Set 接口
• Set接口特性
– 继承 Collection,但不允许重复,无序的。
– Set接口没有引入新方法,所以Set就是一个Collection,只不过其行为不同。
• HashSet:作为Set接口的主要实现类;线程不安全的,可以存储null值。
• HashSet底层:数组+链表的结构。
底层也是数组,初始容量为16,当如果使用率超过0.75,(16*0.75=12)就会扩大容量为原来的2倍。(16扩容为32,依次为64、128、…等)
• LinkedHashSet: 作为HashSet的子类; 遍历其内部数据时,可以按照添加的顺序遍历。可以保证怎么存就怎么取。
• TreeSet: 可以按照添加对象的指定属性,进行排序。
Set接口添加元素的过程:以HashSet 为例:我们向HashSet中添加元素a,
首先调用元素a的hashCode()方法来计算元素a的哈希值,此哈希值通过某种算法再计算出元素应该在HashSet底层数组中的存放位置(即为:索引位置) ,判断数组此位置上是否已经有元素:
– 如果此位置上没有其他元素,则元素a添加成功。
– 如果此位置上有其他元素b(或以链表形式存在的多个元素),则比较元素a与元素b的hash值:
• 如果hash值不相同,则元素a添加成功(以链表方式添加到这个位置上)。
• 如果hash值相同,进而需要调用元素a所在类的equals()方法
– equals().返回true,元素a添加失败
– equals()返回false,则元素a添加成功。
交集并集
Set s1 = new HashSet();
s1.add("a");s1.add("b");s1.add("c");
Set s2 = new HashSet();
s2.add("a");s2.add("b");s2.add("f");
Set sn = new HashSet(s1);
//并集
sn.addAll(s2);
System.out.println(sn);
Set su = new HashSet(s1);
//交集
su.retainAll(s2);
System.out.println(su);
TreeSet类:
要求添加的元素必须是相同的类对象。也就是说不能添加不同类的对象的元素。并且对象是可以比较的。
TreeSet的元素支持2种排序方式:自然排序或者根据提供的Comparator进行排序。
还要特别注意一点:TreeSet判断两个对象是否相等,不是根据equals方法判断的;它是根据compareTo方法判断的,也就是说两个对象compareTo返回0,说明两个对象相等,就不会添加第二个对象了。(不会重复添加相同对象)
TreeSet排序方式:自然排序
@Test
public void testHashSet() {
Set set1=new HashSet();
set1.add("g");
set1.add("d");
set1.add("a");
set1.add("f");
System.out.println(set1);// [a, d, f, g]
}
TreeSet排序方式:定制排序。
Set面试题1
如何去除List集合中的重复元素?
Set面试题2
输出什么内容?
运行结果:
[Person [id=1002, name=BB], Person [id=1001, name=AA]]
[Person [id=1002, name=BB], Person [id=1001, name=CC]]
[Person [id=1002, name=BB], Person [id=1001, name=CC], Person [id=1001, name=CC]]
[Person [id=1002, name=BB], Person [id=1001, name=CC], Person [id=1001, name=CC], Person [id=1001, name=AA]]
Map 接口
• Map接口特征
– Map提供了一种映射关系,其中的元素是以键值对(key-value)的形式存储的,能够实现根据key快速查找value。
– Map中的键值对以Entry类型的对象实例形式存在。
– 键(key值)不可重复,value值可以重复,一个value值可以和很多key值形成对应关系,每个键最多只能映射到一个值。
– Map支持泛型,形式如:Map<K,V>。
– Map中使用put(K key,V value)方法添加.
Map接口类图结构
– 主要方法
• Map主要实现类
• HashMap:是Map的主要实现类,开发中通常用这个类。线程不安全的,效率高。可以存储null值(key-value都可以为null)。
• LinkedHashMap是HashMap的子类,使用链表方式保证元素的有序性,就是怎么存怎么取。原理是在原有的HashMap上添加了一对指针,保存上一个元素和下一个元素的指针。在频繁遍历元素的时候,效率比较高,可以考虑使用这个类。
• Hashtable:古老的实现类,线程安全的,效率低。(不能存储null值,key和value都不能是null)
• Properties类,是Hashtable类的子类,常用来处理配置文件。key和value都是String类型
• TreeMap:保证按照添加的key-value进行排序, 实现排序遍历。此时考虑key的自然排序或定制排序。
• HashMap的底层:
• 数组+链表(jdk7及之前)
• 数组+链表+红黑树(jdk 8)
• Map的底层存储说明:
Map结构的理解:
– Map中的key:无序的、不可重复的,使用set存储所有的key。
– Map中的value:无序的、可重复的,使用Collection 存储所有的value。
– 一个键值对key-value构成了一个Entry对象。
– Map中的Entry:无序的、不可重复的,使用Set存储所有的Entry。
Map的底层存储过程说明:
– HashMap的底层实现原理:(以jdk7为例说明)
– HashMap map = new HashMap():在实例化以后,底层创建了长度是16的数组Entry[] table。当调用map类的put(key1, value1)时:
– 首先,调用key1所在类的hashCode()计算key1哈希值,此哈希值经过某种算法计算以后,得到在Entry数组中的存放位置。如果此位置上的数据为空,此时的key1-value1添加成功。
– 如果此位置上的数据不为空,(意味着此位置上存在一个或多个数据(以链表形式存在)),比较key1和已经存在的一个或多个数据的哈希值:如果key1的哈希值与已经存在的数据的哈希值都不相同,此时key1 -value1添加成功。
– 如果key1的哈希值和已经存在的某一个数据(key2-value2)的哈希值相同,继续比较:调用key1所在类的equals(key2)如果equals()返回false:此时key1-vaLue1添加成功。
– 如果equals()返回true:使用value1替换vaLue2。
– 默认的扩容方式:扩容为原来容量的2倍,并将原有的数据复制过来。
jdk8相较于jdk7在底层实现方面的不同:
1.new HashMap():底层没有创建一个长度为16的数组。
2.jdk8底层的数组是: Node[], 而非Entry[]。
3.首次调用put()方法时,底层创建长度为16的数组。
4.jdk7底层结构只有:数组+链表。
jdk8中底层结构:数组+链表+红黑树。
Map演示
Map m = new HashMap();
m.put("1001",new Person("333","张三"));
m.put("1002",new Person("444","李四"));
m.put("1003",new Person("555","王五"));
//System.out.println(m.get("1002"));
//m.put("1001",new Person("200333","张三"));
Set s = m.keySet();
Iterator it = s.iterator();
while(it.hasNext()){
System.out.println(it.next());
}
LinkedHashMap的演示:有序的演示
TreeMap的演示:自然排序方式演示
TreeMap的演示:定制排序方式演示
• Properties类的演示
总结
数据结构
ArrayXxx:底层数据结构是数组,查询快,增删慢
LinkedXxx:底层数据结构是链表,查询慢,增删快
HashXxx:底层数据结构是哈希表。依赖两个方法:hashCode()和equals()
TreeXxx:底层数据结构是二叉树。两种方式排序:自然排序和比较器排序
buibuib…
继续学习…