首页 > 编程语言 > 详细

Java核心技术梳理-集合

时间:2019-04-29 15:49:21      阅读:108      评论:0      收藏:0      [点我收藏+]
目录

一、前言
二、Collection 和 Iterator
2.1 Collection
2.2 Iterator
2.3 foreach
2.4 Predicate
2.4 Stream
三、Set
3.1 HashSet
3.2 LinkedHashSet
3.2 TreeSet
3.4 EnumSet
3.5 性能选择
四、List
4.1 ArrayList 、Vector、LinkedList
五、Queue
5.1 PriorityQueue
5.2 ArrayDeque
六、Map
6.1 HashMap与Hashtable
6.2 LinkedHashMap
6.3 TreeMap
6.4 WeakHashMap
6.5 EnumMap
七、Collections工具类
7.1 排序
7.2 查找、替换操作
7.3 同步控制
7.4 不可变集合

一、前言

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

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

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

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()的返回值也相等来决定这两个对象是否为同一对象的。

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

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

如果两个对象的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&lt;Object&gt; objects = Collections.synchronizedCollection(new ArrayList&lt;&gt;());
7.4 不可变集合

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

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

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

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

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

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

Java核心技术梳理-集合

原文:https://blog.51cto.com/14262994/2386646

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!