Java集合


2019-07-05  12:39:09  by冲冲

1. 集合的由来

通常情况下,程序直到运行时,才知道需要创建多少个对象。但在开发阶段,我们根本不知道到底需要多少个数量的对象,甚至不知道它的准确类型。为了满足这些常规的编程需要,我们要求能在任何时候,任何地点创建任意数量的对象,而这些对象用什么来容纳呢?我们首先想到了数组,但是数组只能放统一类型的数据,而且其长度是固定的,那该怎么办呢?集合便应运而生了!Java集合框架为程序员提供了预先包装的数据结构和算法来操纵他们。

2. 集合的概念

Java集合类,存放于 java.util 包中,是一个用来存放对象的容器。

集合只能存放对象。比如你存一个 int 型数据 1 放入集合中,其实它是自动转换成 Integer 类后存入的,Java中每一种基本类型都有对应的引用类型。

② 集合存放的是对象的引用,对象本身还是放在堆内存中。

③ 集合可以存放不同类型、不限数量的对象。

④ 任何对象加入集合类后,自动转变为Object类型,所以在取出的时候,需要进行强制类型转换。(解决方案:使用泛型

3. 集合的框架

阅读Java集合框架图,不难发现:

① 所有的集合类(除Map系列)都实现了 Iterator 接口。 Iterator 接口定义了遍历集合元素的方法,主要是 hasNext(),next(),remove()三种方法。它的子接口 ListIterator 在它的基础上又添加三种方法,分别是 add(),previous(),hasPrevious()。也就是说如果实现 Iterator 接口,那么在遍历集合中元素的时候,只能往后遍历,被遍历后的元素不会再被遍历到,通常无序集合实现的都是这个接口,比如HashSet;而那些元素有序的集合,实现的一般都是 ListIterator接口,实现这个接口的集合可以双向遍历,既可以通过next()访问下一个元素,又可以通过previous()访问前一个元素,比如ArrayList。

② 在Java 1.2之前,Java提供特设类,有 Dictionary, Vector, Stack, Properties 这些类用来存储和操作对象组。虽然这些类都非常有用,但是它们缺少一个核心的统一主题。由于这个原因,使用 Vector 类的方式和使用 Properties 类的方式有着很大不同。对此,当前整个Java集合框架都围绕一组标准接口来设计。你可以直接使用这些接口的标准实现(ArrayList、HashSet等等)。当然,除此之外你也可以通过这些接口来实现满足自己需求的集合。

③ 抽象类的意义。如果要自己实现一个集合类,去实现那些抽象的接口会非常麻烦,工作量很大。这个时候就可以使用抽象类,这些抽象类中给我们提供了许多现成的实现,我们只需要根据自己的需求重写一些方法或者添加一些方法就可以实现自己需要的集合类,工作量大大降低。

④ 用于学习背诵的集合框架图

4. 集合的详解

(1)Iterator接口

Iterator 迭代器,它是Java集合的顶层接口(不包括 map 系列的集合,Map接口是 map 系列集合的顶层接口)。因此,除了 map 系列的集合,我们都能通过迭代器来对集合中的元素进行遍历。

Iterator 接口定义的三个方法有:
① Object next():返回迭代器刚越过的元素的引用,返回值是 Object,需要强制转换成自己需要的类型
② boolean hasNext():判断容器内是否还有可供访问的元素
③ void remove():删除迭代器刚越过的元素

追溯到Collection接口,发现其继承的是Iterable接口。

Iterable接口与Iterator接口的关系,发现Iterable接口封装了Iterator接口。

 1 public class Collection {     //测试Iterable接口的遍历
2 @SuppressWarnings({ "rawtypes", "unchecked" })
3 public static void main(String[] args) {
4 // 产生一个 List 集合,典型实现为 ArrayList。
5 List list = new ArrayList();
6 // 添加三个元素
7 list.add("黑奴");
8 list.add("肥猪");
9 list.add("渣狗");
10 list.add("憨熊");
11 // 构造 List 的迭代器
12 Iterator it = list.iterator();
13 // 通过迭代器遍历元素
14 while (it.hasNext()) {
15 Object obj = it.next();
16 System.out.println(obj);
17 }
18 }
19 }

输出结果:

1 黑奴
2 肥猪
3 渣狗
4 憨熊

(2)Collection接口

源码: public interface Collection<E> extends Iterable<E> {...}

 1 public class Collection {
2 public static void main(String[] args) {
3 // 将 ArrayList集合作为 Collection的实现类
4 Collection collection = new ArrayList();
5
6 // 添加元素
7 collection.add("A");
8 collection.add("B");
9
10 // 删除指定元素
11 collection.remove("A");
12 // 删除所有元素
13
14 // 检测是否存在某个元素
15 collection.removeAll(c);
16 collection.add("C");
17 collection.contains("C");
18
19 // 判断是否为空
20 collection.isEmpty();
21
22 // 利用增强for循环遍历集合
23 for (Object obj : collection) {
24 System.out.println(obj);
25 }
26
27 // 利用迭代器 Iterator遍历集合
28 Iterator iterator = collection.iterator();
29 while (iterator.hasNext()) {
30 Object obj = iterator.next();
31 System.out.println(obj);
32 }
33 }
34 }

(3)List接口:有序、元素可以重复

源码:public interface List<E> extends Collection<E> {...}

由于 List 接口是继承于 Collection 接口,所以基本的方法如上所示。
① void add(Object obj)
② void remove(Object obj)
③ void removeAll()
④ boolean contains(Object obj)
⑤ boolean isEmpty()
List 接口的三个典型实现:
① List list1 = new ArrayList(); //底层数据结构是数组,查询快,增删慢;线程不安全,效率高。动态增长为原来的1.5倍。
② List list2 = new Vector(); //底层数据结构是数组,查询快,增删慢;线程安全,效率低,几乎已经淘汰了这个集合。动态增长为原来2倍。
③ List list3 = new LinkedList(); //底层数据结构是链表,查询慢,增删快;线程不安全,效率高。允许元素为null。 理解:
① 数组就像一排已经编号站好的人,要找出第10个人很容易,根据编号就能找到。但插入、删除慢,往某个位置插入或删除一个人时,后面的人身上的编号都要变。当然,加入或删除的人是在末尾的话就快。
② 链表就像手牵着手站成一圈的人,要找第10个人不容易,必须从第一个人一个个数过去。但插入、删除快。插入时只要解开两个人的手,并重新牵上新加进来的人的手就可以。删除一样的道理。
public class Collection {  
//List接口其他方法:指定位置插入元素,替换元素,遍历还可以使用普通for循环。
public static void main(String[] args) {
// 产生一个 List 集合,典型实现为 ArrayList
List list = new ArrayList();
// 在指定地方添加元素
list.add(2,"A");
// 在指定地方替换元素
list.set(2, "a");
// 获得指定对象的索引
int i = list.indexOf("a");
System.out.println("索引为:" + i);
// 遍历:普通for循环
for (int j = 0; j < list.size(); j++) {
System.out.println(list.get(j));
}
}
}
 1 // ArrayList的三种遍历
2
3 public class Collection {
4 public static void main(String[] args) {
5 List<String> list = new ArrayList<String>();
6 list.add("AA");
7 list.add("BB");
8 list.add("CC");
9
10 // 第一种遍历:使用for-each
11 for (String str : list) { // 等价于for(int i=0;i<list.size();i++)
12 System.out.println(str);
13 }
14
15 // 第二种遍历:把线性表表变为数组
16 String[] strArray = new String[list.size()];
17 list.toArray(strArray);
18 for (int i = 0; i < strArray.length; i++) //等价于 for(String str:strArray)
19 {
20 System.out.println(strArray[i]);
21 }
22
23 // 第三种遍历:使用迭代器
24 Iterator<String> it = list.iterator();
25 while (it.hasNext()) // 判断下一个元素之后有值
26 {
27 System.out.println(it.next()); //输出当前越过的元素
28 }
29 }
30 } 

(4)Set接口:无序、元素不能重复

Set 接口的三个典型实现:
① Set hashSet = new HashSet(); //无序,元素不重复。底层是哈希表。元素可以是null,但是只能一个null。
② Set linkedHashSet = new LinkedHashSet(); //有序,元素不重复。底层是哈希表和链表,哈希表保证元素唯一性,链表保证元素有序插入。
③ Set treeSet = new TreeSet(); //有序,元素不重复。底层是红黑树。元素不能是null。默认进行排序(升序),所以元素必须相同类型。
(4.1)HashSet
HashSet的底层是哈希表,本质是一个数组,存在的意义是加快查询速度。
原因:通常元素在数组中的索引位置是随机的,即元素的取值和元素的位置之间不存在确定的关系。
因此,在数组中查找特定值时,需要把查找值和其他元素进行比较,此时的查询效率取决于查找过程中比较的次数 n 。
而 HashSet 集合底层数组的索引和值存在一个确定的关系:index=hash(value)。我们只需要调用这个公式,就能快速的找到元素或者索引。
对于 HashSet,如果两个对象通过 equals()方法返回 true,这两个对象的 hashCode 值也应该相同。
原因:当向HashSet集合中存入一个元素时,HashSet会先调用该对象的hashCode()方法来得到该对象的hashCode值,然后根据hashCode值决定该对象在HashSet中的存储位置。
如果 hashCode 值不同,直接把该元素存储到 hashCode()指定的位置。
如果 hashCode 值相同,那么会继续判断该元素和集合对象的 equals()作比较。
当 hashCode 相同,equals()为 true,则视为两者同一个对象,所以不保存在 hashSet 中。
当 hashCode 相同,equals()为 false,则存储在之前对象同槽位的链表上,但是这会非常麻烦,我们应该约束这种情况,
即保证:如果两个对象通过 equals()方法返回 true,这两个对象的 hashCode 值也应该相同。
因此,每一个存储到哈希表中的对象,都得提供 hashCode()和 equals()方法的实现,用来判断是否是同一个对象。
对于 HashSet 集合,我们要保证如果两个对象通过 equals()方法返回 true,这两个对象的 hashCode 值也应该相同。
(4.2)TreeSet
① 必须放入同类的对象。因为默认会进行排序,否则可能会发生类型转换异常.我们可以使用泛型来进行限制。
Set treeSet = new TreeSet();
treeSet.add(1); //添加一个 Integer 类型的数据
treeSet.add("a"); //添加一个 String 类型的数据
System.out.println(treeSet); //会报类型转换异常的错误
② 如果使用 TreeSet() 无参数的构造器创建一个 TreeSet 对象, 则要求放入其中的元素的类必须实现 Comparable 接口,所以在其中不能放入null元素。
TreeSet的排序:
① 自动排序:添加自定义对象的时候,必须要实现 Comparable 接口,并要覆盖 compareTo(Object obj) 方法来自定义比较规则。
如果 this > obj,返回正数 1;
如果 this < obj,返回负数 -1;
如果 this = obj,返回 0 ,则认为这两个对象相等。
其中,数值类型比较数值大小,char和string类型比较unicode值的数值大小。
 1 ② 定制排序: 当需要把一个对象放入 TreeSet 中,需要重写该对象对应的 equals() 方法,并保证该方法与 compareTo(Object obj) 方法有一致的结果。
2
3 public class TreeSetTest {
4 public static void main(String[] args) {
5 Person p1 = new Person(1);
6 Person p2 = new Person(2);
7 Person p3 = new Person(3);
8
9 Set<Person> set = new TreeSet<>(new Person());
10 set.add(p1);
11 set.add(p2);
12 set.add(p3);
13 System.out.println(set); //结果为[1, 2, 3]
14 }
15 }
16
17 class Person implements Comparator<Person>{
18 public int age;
19 public Person(){}
20 public Person(int age){
21 this.age = age;
22 }
23 @Override
24 /***
25 * 根据年龄大小进行排序
26 */
27 public int compare(Person o1, Person o2) {
28 // TODO Auto-generated method stub
29 if(o1.age > o2.age){
30 return 1;
31 }else if(o1.age < o2.age){
32 return -1;
33 }else{
34 return 0;
35 }
36 }
37
38 @Override
39 public String toString() {
40 // TODO Auto-generated method stub
41 return ""+this.age;
42 }
43 }
三个 Set 接口的实现类比较:
共同点:
① 都不允许元素重复。
② 都不是线程安全的。解决办法:Set set = Collections.synchronizedSet(set 对象);
不同点:
① HashSet:不保证元素的添加顺序,底层采用哈希表算法,查询效率高。判断两个元素是否相等,equals()方法返回true且hashCode()值相等。因此,存入HashSet中的元素要覆盖equals()方法和hashCode()方法。
② LinkedHashSet:HashSet 的子类,底层采用哈希表算法以及链表算法,既保证了元素的添加顺序,也保证了查询效率。但是整体性能要低于 HashSet。 
③ TreeSet:不保证元素的添加顺序,但是会对集合中的元素进行排序。底层采用 红-黑树算法(树结构比较适合范围查询)。
(5)Map接口:key-value 类型的键值对,key不允许重复,value可以重复

① 严格来说 Map 并不是一个集合,而是两个集合之间的映射关系。

② 两个集合之间通过构成映射关系的两个数据,可以看作是map的一条数据。即 Entry( key,value ) 。Map 可以看成是由多个 Entry 组成。

③ 因为 Map 集合即没有实现于 Collection 接口,也没有实现 Iterable 接口,所以不能对 Map 集合进行 for-each 遍历。

 1 public class Collection {
2 public static void main(String[] args) {
3 Map<String, Object> hashMap = new HashMap<>();
4 // 添加元素到Map中
5 hashMap.put("key1", "value1");
6 hashMap.put("key2", "value2");
7 hashMap.put("key3", "value3");
8 hashMap.put("key4", "value4");
9 hashMap.put("key5", "value5");
10
11 // 删除Map中的元素,通过key的值
12 hashMap.remove("key1");
13 // 通过get(key)得到Map中的value
14 Object str1 = hashMap.get("key2");
15
16 // 可以通过添加方法来修改Map中的元素
17 hashMap.put("key2", "value22");
18
19 // 第一种遍历:通过map.values()得到value集合。缺点是不能遍历key。
20 java.util.Collection<Object> value = hashMap.values();
21 for (Object obj : value) {
22 System.out.println(obj);
23 }
24
25 // 第二种遍历:通过map.keySet()得到key集合,然后通过get(key)得到value。
26 Set<String> set = hashMap.keySet();
27 for (String str : set) {
28 System.out.println(str + "=" + hashMap.get(str));
29 }
30
31 // 第三种遍历:通过Map.entrySet()得到 Map的Entry集合,然后遍历。(推荐)
32 Set<Map.Entry<String, Object>> entrys = hashMap.entrySet();
33 for (Map.Entry<String, Object> entry : entrys) {
34 String key = entry.getKey();
35 Object value2 = entry.getValue();
36 System.out.println(key + "=" + value2);
37 }
38
39 // 第四种遍历:通过Map.entrySet使用iterator遍历key和value。
40 Iterator<Entry<String, Object>> it = hashMap.entrySet().iterator();
41 while (it.hasNext()) {
42 Entry<String, Object> entry = it.next();
43 System.out.println("key= " + entry.getKey() + " and value= " + entry.getValue());
44 }
45
46 System.out.println(hashMap);
47 }
48 }

输出结果:

value22
value5
value3
value4
----------------
key2=value22
key5=value5
key3=value3
key4=value4
----------------
key2=value22
key5=value5
key3=value3
key4=value4
----------------
key= key2 and value= value22
key= key5 and value= value5
key= key3 and value= value3
key= key4 and value= value4
----------------
{key2=value22, key5=value5, key3=value3, key4=value4}
Map接口的常用实现类:
① HashMap:底层是哈希表。特点是key是无序插入,且key不能重复。key判断重复标准:key1和key2是否equals为true且hashCode相等。非线程安全。
② TreeMap:底层是红黑树。特点是key是无序插入,但是会自动升序排序,且key不能重复。key判断重复标准是:compareTo/compare的返回值是0。非线程安全。
③ LinkedHashMap:底层是链表和哈希表。特点是按添加的先后顺序进行有序插入,且key不能重复。key判断重复标准同HashMap。非线程安全。
④ HashTable:底层是哈希表,是HashMap的前身(类似于Vector是ArrayList的前身)。打死都不要用。线程安全。
⑤ Properties:HashTable的子类,要求键、值都为String类型。用来加载资源文件(.properties类型文件)。 注意:
① 定义Map时,key通常使用不可变类string,把key作为value的唯一值。
② HashMap、TreeMap、LinkedHashMap都是非线程安全的,但是性能高。解决方案:Map map = Collections.synchronizedMap( Map对象 );
③ HashTable线程安全,但是性能低。
Map 和 Set 集合的关系:
① 都有几个类型的集合。HashMap和HashSet的底层都是哈希表,TreeMap和TreeSet的底层都是红-黑树,LinkedHashMap和LinkedHashSet的底层都是哈希表和红-黑树。
② 分析 Set 的底层源码,我们可以看到,Set 集合就是由Map集合的Key组成。



参考:

https://www.cnblogs.com/ysocean/p/6555373.html

https://www.runoob.com/java/java-collections.html

【JavaSE】集合的更多相关文章

  1. javase集合 温故而知新

    复习javase集合 1.为什么要有集合? 数组长度需要在初始化时确定大小,数据结构单一.因此集合出现了 2.数组和集合的区别 区别一:数组既可以存储基本数据类型,又可以存储引用类型,集合只能存储引用 ...

  2. JavaSE集合(十)之Map

    前面给大家介绍了集合家族中的Collection家族,这一篇给大家分享的是集合中的另一个家族就是Map家族.以前的时候学习Map的时候没有很认真的去学习,我觉得很多东西还是不是很清楚. 这次我将总结的 ...

  3. JavaSE集合(八)之Map

    前面给大家介绍了集合家族中的Collection家族,这一篇给大家分享的是集合中的另一个家族就是Map家族.以前的时候学习Map的时候没有很认真的去学习,我觉得很多东西还是不是很清楚. 这次我将总结的 ...

  4. JavaSE集合基础总览

    Java集合 Java集合,也称之为容器.基本上你写所有的Java程序,都必须要用到一个包.该API基本都位于java.util工具类包中,是JavaSE中的重中之重.简单可以总结为“1136”,分别 ...

  5. JavaSE| 集合

    集合 l  Collection 层次结构中的根接口.Collection 表示一组对象,这些对象也称为 collection 的元素.一些 collection 允许有重复的元素,而另一些则不允许. ...

  6. JavaSE 集合概述

    1.对象的存储: 数组(基本数据类型 & 引用数据类型) 集合(引用数据类型) 2.集合框架 Collection 接口: 方法: iterator().toArray();  迭代器遍历集合 ...

  7. [javaSE] 集合框架(体系概述)

    为什么出现集合类 为了方便对多个对象的操作,对对象进行存储,集合就是存储对象最常用的一种方式 数组和集合的不同 数组是固定长度的,集合是可变长度的 数组可以存储基本数据类型,集合只能存储对象 数组只能 ...

  8. JavaSE 集合补充点(JDK1.9对集合添加的优化)

    通常,我们在代码中创建一个集合(例如,List 或 Set ),并直接用一些元素填充它. 实例化集合,几个 add方法调用,使得代码重复. public class Demo01 { public s ...

  9. [javaSE] 集合框架(共性方法)

    Collection接口的常用方法 add(),添加一个元素 addAll(),添加一组元素 clear(),清空 remove(),移除一个 removeAll(),移除一组 size(),元素个数 ...

  10. [javaSE] 集合框架(TreeSet)

    TreeSet:可以对Set集合中的元素排序,默认按照ascii表排序,二叉树结构 左边叉是小的,右边叉是大的 存储自定义对象 定义一个类Student实现Comparable类,使自定义类具备比较性 ...

随机推荐

  1. bzoj1834 ZJOI2010网络扩容(费用流)

    给定一张有向图,每条边都有一个容量C和一个扩容费用W.这里扩容费用是指将容量扩大1所需的费用. 求: 1.在不扩容的情况下,1到N的最大流: 2.将1到N的最大流增加K所需的最小扩容费用. 其中\(n ...

  2. Vue Router 常见问题(push报错、push重复路由刷新)

    Vue Router 常见问题 用于记录工作遇到的Vue Router bug及常用方案 router.push报错,Avoided redundant navigation to current l ...

  3. 微软Windows11安卓子系统已支持运行APK 应用(附手把手详细安装攻略)怎么安装安卓/如何安装安卓应用/支持多窗口多任务

    ​​ 10 月 21 日消息,微软博客宣称,Windows 11 上 安卓子系统运行 Android  应用程序的第一个预览版现已提供给美国 Beta 频道的 Windows 内部人员.但现在通过教程 ...

  4. 注解,@Qualifier+@Autowired 和 @Resource

    摘要: 项目中,对于AOP的使用,就是通过用注解来注入的. 更改之前的注解,是使用:@Qualifier+@Autowired   但是,通过这样注解,在项目启动阶段,需要自动扫描的过程是非常缓慢的, ...

  5. OO2020 助教工作总结

    1 我的初衷 这一学期的OO助教工作是我一段宝贵的经历,在其中我学习了很多.见识了很多,收获满满.当时报名OO的初衷主要有三方面.首先,我想说OO是我所上过的最好的一门课之一,这门课有这一套从理论讲授 ...

  6. dice_game攻防世界进阶区

    dice_game XCTF 4th-QCTF-2018 前言,不得不说,虽然是个简单题但是还是要记录一下,来让自己记住这些东西. 考察的知识点是: 1.cdll_loadlibrary加载对应库使得 ...

  7. pwn200,一道不完全考察ret2libc的小小pwn题

    pwn200 ---XDCTF-2015 每日一pwn,今天又做了一个pwn,那个pwn呢???攻防世界的进阶区里的一道小pwn题,虽然这个题考察的知识不多,rop链也比较好构建,但是还是让我又学到了 ...

  8. Noip模拟11 2021.7.11

    T1 math 其实看看题面,看看给的那机组数据即可看出规律了(然而当时并没有,只是发现模数的循环节,存了个vector,接下来就暴力了) 有个柿子: 其实就是裴蜀定理. 然后想一想的话就是 那么只要 ...

  9. Spring源码解读(一):Spring的背景起源及框架整体介绍

    一.前言 Spring起源于2002年Rod Johnson写的一本书<Expert One-on-One J2EE>,书里介绍了Java企业应用程序开发情况,并指出Java EE和EJB ...

  10. linux 内核源代码情景分析——地址映射的全过程

    linux 内核采用页式存储管理.虚拟地址空间划分成固定大小的"页面",由MMU在运行时将虚拟地址映射成某个物理内存页面中的地址.页式内存管理比段式内存管理有很多好处,但是由于In ...