Java核心技术梳理-集合
一、前言
在日常开发中,我们经常会碰到需要在运行时才知道对象个数的情况,这种情况不能使用数组,因为数组是固定数量的,这个时候我们就会使用集合,因为集合可以存储数量不确定的对象。
集合类是特别有用的工具类,不仅可以存储数量不等的对象,还可以实现常用的数据结构,并且能够存储有映射关联的关联数组。
集合类和数组不一样,数据既可以存储基本类型,也可以存储对象,而集合只能存储对象(对象的引用变量)。
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<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核心技术梳理-集合的更多相关文章
- Java核心技术梳理-泛型
一.引言 在学习集合的时候我们会发现一个问题,将一个对象丢到集合中后,集合并不记住对象的类型,统统都当做Object处理,这样我们取出来再使用时就得强制转换类型,导致代码臃肿,而且加入集合时都是以Ob ...
- Java知识点梳理——集合
1.定义:Java集合类存放于java.util包,是存放对象的容器,长度可变,只能存放对象,可以存放不同的数据类型: 2.常用集合接口: a.Collection接口:最基本的集合接口,存储不唯一, ...
- Java核心技术梳理-基础类库
一.引言 Oracle为Java提供了丰富的基础类库,Java 8 提供了4000多个基础类库,熟练掌握这些基础类库可以提高我们的开发效率,当然,记住所有的API是不可能也没必要的,我们可以通过API ...
- Java核心技术梳理-异常处理
一.引言 异常总是不可避免的,就算我们自身的代码足够优秀,但却不能保证用户都按照我们想法进行输入,就算用户按照我们的想法进行输入,我们也不能保证操作系统稳定,另外还有网络环境等,不可控因素太多,异常也 ...
- Java核心技术梳理-类加载机制与反射
一.引言 反射机制是一个非常好用的机制,C#和Java中都有反射,反射机制简单来说就是在程序运行状态时,对于任意一个类,能够知道这个类的所有属性和方法,对于任意一个对象,能够调用它的任意属性和方法,其 ...
- Java核心技术梳理-IO
一.引言 IO(输入/输出),输入是指允许程序读取外部数据(包括来自磁盘.光盘等存储设备的数据).用户输入数据.输出是指允许程序记录运行状态,将程序数据输出到磁盘.光盘等存储设备中. IO的主要内容包 ...
- Java核心技术点之集合框架
1. 概述 Java集合框架由Java类库的一系列接口.抽象类以及具体实现类组成.我们这里所说的集合就是把一组对象组织到一起,然后再根据不同的需求操纵这些数据.集合类型就是容纳这些对象的一个容 ...
- Java系列,《Java核心技术 卷1》,chapter 13,集合
13.1.2 Java类库中的集合接口和迭代器接口 删除元素,对于next和remove的调用是互相依赖的,如果调用remove之前没有调用next,则会跑出IllegalStateExcep ...
- 杨晓峰-Java核心技术-9 HashMap Hashtable TreeMap MD
目录 第9讲 | 对比Hashtable.HashMap.TreeMap有什么不同? 典型回答 考点分析 知识扩展 Map 整体结构 有序 Map HashMap 源码分析 容量.负载因子和树化 精选 ...
随机推荐
- mybatis源码解读(四)——事务的配置
上一篇博客我们介绍了mybatis中关于数据源的配置原理,本篇博客介绍mybatis的事务管理. 对于事务,我们是在mybatis-configuration.xml 文件中配置的: 关于解析 < ...
- linux 文件传输 SCP
SCP :secure copy (remote file copy program) 也是一个基于SSH安全协议的文件传输命令.与sftp不同的是,它只提供主机间的文件传输功能,没有文件管理的功能. ...
- 不使用JavaScript实现菜单的打开和关闭
我在写有菜单栏的网页时,基本都会用响应式设计来适配移动端,例如把不重要的菜单选项隐藏,或者创建一个菜单按钮来控制的菜单的打开和关闭之类的.而我之前一直是使用JavaScript来实现菜单的打开和关闭的 ...
- 【原】fetch跨域请求附带cookie(credentials)
HTTP访问控制 https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Access_control_CORS 解决跨域的方式有很多种,本文介绍" ...
- Redis的九大应用场景
毫无疑问,Redis开创了一种新的数据存储思路,使用Redis,我们不用在面对功能单调的数据库时,把精力放在如何把大象放进冰箱这样的问题上,而是利用Redis灵活多变的数据结构和数据操作,为不同的大象 ...
- java 修饰符的访问权限
private,public,protected,默认不写(firendly) 1.Class类的访问权限: public:可以供所有的类访问. 默认:默认可以称为friendly但是,java语言中 ...
- RESTful规范
一. 什么是RESTful REST与技术无关,代表的是一种软件架构风格,REST是Representational State Transfer的简称,中文翻译为“表征状态转移” REST从资源的角 ...
- python环境搭建-requests的简单安装(适合新手)
安装完python之后,一定要记住安装后的路径.这是我当前的路径. 下面是requests的安装步骤: 我们这里直接用pip安装(这样比较适合新手),新版python自带pip,python3.6.1 ...
- 聊一聊顺序消息(RocketMQ顺序消息的实现机制)
当我们说顺序时,我们在说什么? 日常思维中,顺序大部分情况会和时间关联起来,即时间的先后表示事件的顺序关系. 比如事件A发生在下午3点一刻,而事件B发生在下午4点,那么我们认为事件A发生在事件B之前, ...
- elementui左侧菜单栏刷新后还是原来的状态
<template> <div id="leftMenuList"> <el-row> <el-col :span="24&qu ...