一、前言

在日常开发中,我们经常会碰到需要在运行时才知道对象个数的情况,这种情况不能使用数组,因为数组是固定数量的,这个时候我们就会使用集合,因为集合可以存储数量不确定的对象。

集合类是特别有用的工具类,不仅可以存储数量不等的对象,还可以实现常用的数据结构,并且能够存储有映射关联的关联数组。

集合类和数组不一样,数据既可以存储基本类型,也可以存储对象,而集合只能存储对象(对象的引用变量)。

Java集合大致分为:

  • Set :无序,不可重复集合

  • List:有序,可重复集合

  • Map:具有映射关系集合

  • Queue:队列集合

Java的集合类主要是由两个接口派生出来:Collection 和Map 。

集合的框架可看此图:http://img.blog.csdn.net/20160124221843905

二、Collection 和 Iterator

2.1 Collection

Collection 接口是List、Set、Queue的父接口,其中定义了一些集合操作的通用方法,集合类似一个容器,而容器无非是添加对象、删除对象、清空容器、判断容器是否为空。

Collection collection = new ArrayList();
//添加
collection.add("晚安");
collection.add(9);
//返回长度
System.out.println(collection.size());
//移除
collection.remove(9);
//是否包含
System.out.println(collection.contains("晚安"));
//是否为空
System.out.println(collection.isEmpty());
Collection books = new HashSet();
books.add("晚安");
books.add("愿长夜无梦");
books.add("在所有夜晚安眠");
//去掉collection 包含的元素
books.removeAll(collection);
System.out.println(books);
books.add("晚安");
//保留两者都有的数据
books.retainAll(collection);
System.out.println(books);

Collection 继承了Iterable接口,Java 8为Iterable提供了forEach方法,且这个方法的参数是一个函数式接口,我们可以通过这个方法进行集合遍历,并且可以使用Lambda表达式。

books.forEach(p -> System.out.println(p));

2.2 Iterator

//获取迭代器
Iterator iterator = books.iterator();
//判断是否遍历完成
while (iterator.hasNext()){
//获取集合中的下一个元素,返回的Object对象,需要强制转换
String text = (String)iterator.next();
System.out.println(text);
//这里的删除对象是迭代器中删除,删除的是上一个next返回的方法,而且不会真正删除books中的内容
iterator.remove();
//会报错
books.remove(text);
}

我们看到这里有一个删除方法,但是删除的并不是books的内容,而且如果修改了其中的内容,实际的内容也不会改变,这里我们就可以得出结论:集合并不是把本身传给了迭代器,而是将集合中的元素传递给了迭代器

迭代器采用的是快速失败机制,一旦在迭代过程中发现集合被改变,立即抛出错误,这样可以避免共享了资源而导致数据不一致问题。

我们也可以直接通过forEachRemaining 来遍历,这也是一个函数式接口

iterator.forEachRemaining(p-> System.out.println(p));

2.3 foreach

除了迭代器之外,我们也可以直接通过 foreach遍历集合,且这种写法更便捷

for (Object s : books) {
System.out.println(s);
}

与迭代器相同,这里循环的也不是集合本身,而是元素,并且也不能修改。

2.4 Predicate

Java 8为Collection 提供了一个removeIf(Predicate<? super E> filter) 方法,这个方法是批量删除符合条件的元素,这也是一个函数式接口,我们可以使用Lambda表达式。

books.removeIf(p -> ((String) p).length() > 5);

这个Predicate 我们可以充分的利用,它可以充分简化集合运算,如:

public static int count(Predicate predicate, Collection collection) {
int total = 0;
for (Object object : collection) {
//判断是否满足条件
if (predicate.test(object)) {
total++;
}
}
return total;
}
System.out.println(count(p -> ((String) p).length() > 5, books));

2.4 Stream

Collection 还有一个Stream()流式API,流式API在JQuery中常常会用到,主要分为中间方法和末端方法,顾名思义,中间方法就是允许继续调用后续方法,而末端方法是最终的操作。Stream的引入极大的丰富了集合的操作。

常用的中间方法有

  • filter(Predicate<? super T> predicate) :过滤不符合条件的集合

  • sorted:排序

  • limit(long maxSize) :对数量进行控制,一般是排序之后的操作

  • distinct():去重

常用的末端方法有

  • forEach(Consumer<? super T> action):遍历

  • toArray():转换成数据

  • min(Comparator<? super T> comparator):获取最小值

  • max(Comparator<? super T> comparator) :获取最大值

  • count() :总数

我们可以很方便的组合这些API,而对集合进行操作,简单的例子如下:

System.out.println(books.stream().filter(p->((String) p).contains("夜")).count());

在平时的开发我们可以慢慢熟悉这些写法。

三、Set

Set不记住添加顺序,也就是并不会按照添加顺序进行排序,并且不允许包含重复元素,当添加了重复元素时,add方法会返回false,下面分别介绍其实现类HashSet,TreeSet,LinkedHashSet,EnumSet。

3.1 HashSet

顾名思义,HashSet是按照Hash算法来存储集合中的元素,因此具有很好的存储和查找性能,Hashset不是线程安全的,在多线程情况下,我们需要通过代码来保证其同步,HashSet元素值可以是null。

HashSet是通过判断两个对象equals()相等并且hashCode()的返回值也相等来决定这两个对象是否为同一对象的。

那这个时候就有些问题了,

  1. 如果两个对象的equals()为true,但是hashCode()的返回值不相等,那这个时候HashSet认为这两个对象不等,都会保存,但是其实与我们的期望就不一样了。

  2. 如果两个对象的hashCode()返回值相等,但是equals()为false,这个时候也会保存,但是会保存在同一个位置,并通过链式结构来保存,这样会对性能产生影响。

所以我们要将对象保存到HashSet中,我们就要尽量保证两个对象在equals()为true时,其返回的hashCode()的值也要相等。

3.2 LinkedHashSet

LinkedHashSet是HashSet的子类,LinkedHashSet也是通过hashCode来确定位置的,但是从名字中可以看出,它还通过链表进行了插入次序的维护,也就说是遍历的时候可以是有顺序的,但是加入了排序意味着性能的降低。

3.2 TreeSet

TreeSet是SortedSet的实现类,这就意味着TreeSet可以确保集合元素处于排序状态,既然需要排序,那就有排序规则,TreeSet有两个排序方法:自然排序和定制排序。

  • 自然排序:TreeSet是调用compareTo方法来比较元素之间的大小。

  • 定制排序:定制排序就是我们按照我们制定的规则来进行排序。

TreeSet treeSet=new TreeSet((o1,o2)->
{
String m1 = (String)o1;
String m2=(String)o2;
return m1.length()>m2.length()?-1:0;
});

由于要进行排序,所以TreeSet添加的必须是同一个类元素,否则会报错。

因为增加了排序,所以相应的也增加了一些方法:

TreeSet<Integer> treeSet1 = new TreeSet<>();
treeSet1.add(1);
treeSet1.add(2);
treeSet1.add(3);
//之前的一个元素
System.out.println(treeSet1.lower(2));
//后一个元素
System.out.println(treeSet1.higher(2));
//第一个元素
System.out.println(treeSet1.first());
//最后一个元素
System.out.println(treeSet1.last());

3.4 EnumSet

EnumSet是专门存储枚举的集合,所有的元素都必须是指定枚举类型的枚举值,EnumSet也是有序的,排序规则与枚举定义的顺序相同。

EnumSet在内部以位向量方式存储,存储非常紧凑、高效,运行效率也很好,EnumSet不允许加null。

3.5 性能选择

如何选择HashSet和TreeSet呢?从性能方面来讲,HashSet要好,因为TreeSet需要额外的红黑树算法来排序,所以如果在不需要排序的情况下,我们都是选择HashSet。

四、List

List是有序的,可重复的集合,每个元素都可以通过对应的索引来进行访问,List继承了Collection,Collection中的方法List都能使用,而List作为有序集合,也就有一些与索引相关的方法。

List list = new ArrayList();
list.add("晚安");
list.add("愿路途遥远");
list.add("都有人陪在身边");
list.forEach(p-> System.out.println(p));
list.remove(1);
//在索引处添加数据
list.add(1, "愿路途遥远");
//获取指定索引位置元素
System.out.println(list.get(2));
System.out.println(list.size());
//设置索引位置的数据,index必须在现有的长度之内
list.set(2, "想要说的话还没说完");
//返回fromIndex(包含),到toIndex(不包含)集合至新集合
List list1 = list.subList(0, 2);
//排序,比较函数
list.sort((o1, o2) -> ((String) o1).length() - ((String) o2).length());
//将字符串长度作为新的集合元素替换原来的集合
list.replaceAll(p -> ((String) p).length());
list.forEach(p-> System.out.println(p));

4.1 ArrayList 、Vector、LinkedList

ArrayList 、Vector、LinkedList 是list的三个实现类,完全支持前面list接口实现的全部功能。

ArrayList 、Vector 是基于数组实现的,内部封装了一个动态的、允许再分配的Object[] 数组,初始化是通过initialCapacity参数确定初始长度,如果不指定的话默认是10,当我们能确定数组的长度时,我们可以给出,这样可以减少重新分配的次数,而提高性能。

ArrayList 、Vector在使用上完全相同,而Vector出现的较早,所有其中的一些方法名较长,而后改成List接口的方法后增加了一些方法,但是与其之前的方法有一些重复,我们一般都喜欢使用新东西的嘛,虽然Vector 线程安全,但如果我们使用Collections工具类同样可以使ArrayList 线程安全,所以总结就是使用ArrayList 就完事了。

LinkedList的内部实现与ArrayList 、Vector完全不同,它的内部实现是通过链表来存储的,并且它还继承了Deque接口,也即是可以当做双端队列来使用,由此可见它功能的强大。

LinkedList<String> linkedList = new LinkedList();
//将字符串放入队列尾部
linkedList.offer("队列尾部字符串");
//将字符放入栈顶部
linkedList.push("栈顶部字符串");
//将字符串放入到队列的头部
linkedList.offerFirst("队列头部字符串");
linkedList.forEach(p-> System.out.println(p));
//访问不删除栈顶元素
System.out.println(linkedList.peekFirst());
//访问不删除队列的最后一个元素
System.out.println(linkedList.peekLast());
//弹出栈顶元素
System.out.println(linkedList.pop());
//访问并删除队列的最后一个元素
System.out.println(linkedList.pollLast());

五、Queue

Queue 用于模拟队列这种数据结构,也就是先进先出的容器,队列简单理解就是排队打饭,先排队的人先吃饭,后来的就到队列尾部,队列通常不允许随机访问数据(这样就相当于插队了)。有以下方法:add(E e)

  • add(E e) :添加元素到尾部。

  • offer(E e):也是添加元素到尾部,不过在使用容量有限制的队列时,效率比add要高。

  • remove():获取头部元素并删除。

  • poll():获取尾部元素并删除。

  • element():获取头部元素,但不删除。

  • peek():获取头部元素,但不删除,队列为空返回null

Queue接口有PriorityQueue 实现类,除此之外,Queue 还有一个Deque 子接口,是一个双端队列,可以从两端来添加和删除元素,这样Deque实现类既可以当队列使用,也可以当栈使用,上面的LinkedList就是其实现子类,另外还有一个ArrayDeque。

5.1 PriorityQueue

PriorityQueue并不是一个标准的队列,因为它保存队列的顺序不是按照添加的顺序,而是按照大小去进行排序的,这样其实违反了队列的基本原则:先进先出,而排序的规则与之前说的TreeSet相同,这里就不赘述了。

5.2 ArrayDeque

ArrayDeque实现的是Deque,也就是说它是双端队列,简单理解就是既可以当队列使用,又可以当栈使用,当我们需要栈这种数据结构时,推荐使用ArrayDeque,Stack是古老的集合,不推荐使用。

我们分别将ArrayDeque 当做栈和队列来使用下:

栈:

ArrayDeque<String> stack = new ArrayDeque();
stack.push("晚安");
stack.push("愿路途遥远");
stack.push("都有人陪在身边");
System.out.println(stack);
//访问第一个元素,但不弹出
System.out.println(stack.peek());
//访问第一个元素,并且弹出
System.out.println(stack.pop());
System.out.println(stack);

队列:

ArrayDeque<String> queue=new ArrayDeque<>();
queue.offer("晚安");
queue.offer("愿长夜无梦");
queue.offer("在每个夜晚安眠");
System.out.println(queue);
//访问队列头部元素,但不删除
System.out.println(queue.peek());
//访问队列头部元素,并且删除
System.out.println(queue.poll());
System.out.println(queue);

六、Map

Map用于存储具有映射关系的数据,也就是键值对,Map集合保存着两组值,一组存key,另一组存value,这两组数据可以是任何应用类型的数据,key不允许重复,key和value存在单向的一对一关系。

Map中key 组合起来是一个Set集合,key没有顺序,也不能重复,Map中有个keySet()方法就是获取key集合。

Map的一些常用方法如下:

HashMap<Integer, String> map = new HashMap<>();
//放入数据
map.put(1,"宋江");
map.put(2,"卢俊义");
map.put(3,"吴用");
//如果原先位置存在数据时会返回原先的数据
System.out.println(map.put(3,"武松"));
//是否存在某key
System.out.println(map.containsKey(2));
//是否存在某value
System.out.println(map.containsValue("武松"));
//是否为空
System.out.println(map.isEmpty());
//获取长度
System.out.println(map.size());
//循环key值
for (Object key: map.keySet()) {
//通过key值直接获取value
System.out.println(map.get(key));
}
//根据key移除元素
System.out.println(map.remove(3));
//新的循环方式
map.forEach((key,value)-> System.out.println(key+":"+value));
//获取value,不存在则返回默认值
map.getOrDefault(8,"查无此人");
//只是替换,不会新增
map.replace(2,"林冲");
//清空数据
map.clear();

6.1 HashMap与Hashtable

HashMap与Hashtable都是Map接口的典型实现类,他们关系类似ArrayList与Vector,Hashtable早出现且线程安全,但是实现并不好,HashMap性能更好但线程不安全,Hashtable的key和value不允许为空,但是HashMap可以,我们一般也是推荐使用HashMap,即使需要线程安全也可以使用Collections工具类。

我们要正确的存储key,就要让作为key的对象必须实现hashCode()和equals()方法,那我们判断两个key值是否相等,也是和HashSet相同,必须hashCode()相等,equals()返回为true。

除了key值之外,我们有时候也要比较value值是否相等containsValue(),这里判断的话只需要equals()返回为true即可。

6.2 LinkedHashMap

HashMap也有一个子类 LinkedHashMap,使用的双向链表来维护key-value的次序,链表维护了迭代顺序,迭代顺序与插入顺序相同。LinkedHashMap需要维护元素的插入顺序,那性能比HashMap要低,但因为其维护了顺序,迭代的时候就更快。

6.3 TreeMap

TreeMap是一个红黑树数据结构,每一个key-value即为红黑树的一个节点,存储时根据key进行节点排序,TreeMap保证key-value处于有序状态,也是两个排序机制,自然排序和定制排序,跟之前讲的类似。

因为TreeMap是有序的,那么就会提供一些访问前一个,后一个,第一个,最后一个这种方法,具体方法参考API文档。

6.4 WeakHashMap

从名字上就可以看出来WeakHashMap 是一个弱引用对象,HashMap的key保留了对对象的强引用,意味着只要HashMap对象不被销毁,那么HashMap所引用的对象就不会被销毁,HashMap也不会自动的删除这些key对应的key-value,而WeakHashMap则不行。

6.5 EnumMap

EnumMap 是与枚举类一起使用的,也就是说每个EnumMap 的key必须是一个枚举值,创建EnumMap时必须显示或隐式的指定对应的枚举类,EnumMap在内部已数组形式存储,紧凑而高效,并且按照枚举类定义的顺序进行排序,不允许key为null,但运行value为null。

七、Collections工具类

Collections工具类在上面已经提到过,就是用于操作集合的工具类,对集合的操作有排序、查找、同步控制、设置不可变。

7.1 排序

Collections提供了如下方法对list进行排序

ArrayList<Integer> list = new ArrayList<>();
list.add(2);
list.add(8);
list.add(5);
list.add(10);
list.add(7);
System.out.println("----自然排序----");
//自然排序
Collections.sort(list);
list.forEach(p-> System.out.println(p));
System.out.println("----反转----");
//反转
Collections.reverse(list);
list.forEach(p-> System.out.println(p));
System.out.println("----随机排序----");
//随机排序,相当于洗牌
Collections.shuffle(list);
list.forEach(p-> System.out.println(p));
System.out.println("----定制排序规则----");
//定制排序规则
Collections.sort(list,(o1,o2)->(o1-o2));
list.forEach(p-> System.out.println(p));
System.out.println("----定制排序规则----");
//调换list中指定位置的顺序
Collections.swap(list,2,4);
list.forEach(p-> System.out.println(p));
System.out.println("----将list最后的两个元素移到前面----");
//将list最后的两个元素移到前面
Collections.rotate(list,2);
list.forEach(p-> System.out.println(p));
System.out.println("----将list最后的两个元素移到前面----");
//将list中前面的两个元素移到后面
Collections.rotate(list,-2);
list.forEach(p-> System.out.println(p));

7.2 查找、替换操作

Collections提供了如下方法对list进行查找、替换操作

ArrayList<Integer> list = new ArrayList<>();
list.add(2);
list.add(8);
list.add(5);
list.add(10);
list.add(7);
list.add(7);
//自然排序
Collections.sort(list);
//二分法查找list,带入的参数为value,返回的为索引值(必须是排序之后)
System.out.println(Collections.binarySearch(list,10));
//最大值
System.out.println(Collections.max(list));
//最小值
System.out.println(Collections.min(list));
//出现的次数
System.out.println(Collections.frequency(list,8));
//新值替换所有的旧值
Collections.replaceAll(list,8,6);
list.forEach(p-> System.out.println(p));
//全部替换
Collections.fill(list,8);

7.3 同步控制

上面提过很多次可以使用Collections可以是集合变成线程安全,只要调用synchronizedXXX()便可以创建线程按照的集合

如:

Collection<Object> objects = Collections.synchronizedCollection(new ArrayList<>());

7.4 不可变集合

Collections提供了三类方法来获取不可变集合

  • emptyXXX():返回一个不可变的、空的集合对象

  • singletonXXX():返回一个只包含一个对象的,不可变的集合

  • unmodifiableXXX():返回指定集合的不可变视图

Collections.emptyList();
Collections.singletonList("原来是这样");
ArrayList<Integer> list = new ArrayList<>();
Collections.unmodifiableCollection(list);

集合的介绍和基本用法就是这样,当然这只是使用,后面还会进行源码的分析

Java核心技术梳理-集合的更多相关文章

  1. Java核心技术梳理-泛型

    一.引言 在学习集合的时候我们会发现一个问题,将一个对象丢到集合中后,集合并不记住对象的类型,统统都当做Object处理,这样我们取出来再使用时就得强制转换类型,导致代码臃肿,而且加入集合时都是以Ob ...

  2. Java知识点梳理——集合

    1.定义:Java集合类存放于java.util包,是存放对象的容器,长度可变,只能存放对象,可以存放不同的数据类型: 2.常用集合接口: a.Collection接口:最基本的集合接口,存储不唯一, ...

  3. Java核心技术梳理-基础类库

    一.引言 Oracle为Java提供了丰富的基础类库,Java 8 提供了4000多个基础类库,熟练掌握这些基础类库可以提高我们的开发效率,当然,记住所有的API是不可能也没必要的,我们可以通过API ...

  4. Java核心技术梳理-异常处理

    一.引言 异常总是不可避免的,就算我们自身的代码足够优秀,但却不能保证用户都按照我们想法进行输入,就算用户按照我们的想法进行输入,我们也不能保证操作系统稳定,另外还有网络环境等,不可控因素太多,异常也 ...

  5. Java核心技术梳理-类加载机制与反射

    一.引言 反射机制是一个非常好用的机制,C#和Java中都有反射,反射机制简单来说就是在程序运行状态时,对于任意一个类,能够知道这个类的所有属性和方法,对于任意一个对象,能够调用它的任意属性和方法,其 ...

  6. Java核心技术梳理-IO

    一.引言 IO(输入/输出),输入是指允许程序读取外部数据(包括来自磁盘.光盘等存储设备的数据).用户输入数据.输出是指允许程序记录运行状态,将程序数据输出到磁盘.光盘等存储设备中. IO的主要内容包 ...

  7. Java核心技术点之集合框架

    1. 概述     Java集合框架由Java类库的一系列接口.抽象类以及具体实现类组成.我们这里所说的集合就是把一组对象组织到一起,然后再根据不同的需求操纵这些数据.集合类型就是容纳这些对象的一个容 ...

  8. Java系列,《Java核心技术 卷1》,chapter 13,集合

    13.1.2 Java类库中的集合接口和迭代器接口     删除元素,对于next和remove的调用是互相依赖的,如果调用remove之前没有调用next,则会跑出IllegalStateExcep ...

  9. 杨晓峰-Java核心技术-9 HashMap Hashtable TreeMap MD

    目录 第9讲 | 对比Hashtable.HashMap.TreeMap有什么不同? 典型回答 考点分析 知识扩展 Map 整体结构 有序 Map HashMap 源码分析 容量.负载因子和树化 精选 ...

随机推荐

  1. mybatis源码解读(四)——事务的配置

    上一篇博客我们介绍了mybatis中关于数据源的配置原理,本篇博客介绍mybatis的事务管理. 对于事务,我们是在mybatis-configuration.xml 文件中配置的: 关于解析 < ...

  2. linux 文件传输 SCP

    SCP :secure copy (remote file copy program) 也是一个基于SSH安全协议的文件传输命令.与sftp不同的是,它只提供主机间的文件传输功能,没有文件管理的功能. ...

  3. 不使用JavaScript实现菜单的打开和关闭

    我在写有菜单栏的网页时,基本都会用响应式设计来适配移动端,例如把不重要的菜单选项隐藏,或者创建一个菜单按钮来控制的菜单的打开和关闭之类的.而我之前一直是使用JavaScript来实现菜单的打开和关闭的 ...

  4. 【原】fetch跨域请求附带cookie(credentials)

    HTTP访问控制 https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Access_control_CORS 解决跨域的方式有很多种,本文介绍" ...

  5. Redis的九大应用场景

    毫无疑问,Redis开创了一种新的数据存储思路,使用Redis,我们不用在面对功能单调的数据库时,把精力放在如何把大象放进冰箱这样的问题上,而是利用Redis灵活多变的数据结构和数据操作,为不同的大象 ...

  6. java 修饰符的访问权限

    private,public,protected,默认不写(firendly) 1.Class类的访问权限: public:可以供所有的类访问. 默认:默认可以称为friendly但是,java语言中 ...

  7. RESTful规范

    一. 什么是RESTful REST与技术无关,代表的是一种软件架构风格,REST是Representational State Transfer的简称,中文翻译为“表征状态转移” REST从资源的角 ...

  8. python环境搭建-requests的简单安装(适合新手)

    安装完python之后,一定要记住安装后的路径.这是我当前的路径. 下面是requests的安装步骤: 我们这里直接用pip安装(这样比较适合新手),新版python自带pip,python3.6.1 ...

  9. 聊一聊顺序消息(RocketMQ顺序消息的实现机制)

    当我们说顺序时,我们在说什么? 日常思维中,顺序大部分情况会和时间关联起来,即时间的先后表示事件的顺序关系. 比如事件A发生在下午3点一刻,而事件B发生在下午4点,那么我们认为事件A发生在事件B之前, ...

  10. elementui左侧菜单栏刷新后还是原来的状态

    <template> <div id="leftMenuList"> <el-row> <el-col :span="24&qu ...