Java核心技术梳理-集合

目录

一、前言
二、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);

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

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

时间: 2024-10-11 23:50:03

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

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

一.引言 反射机制是一个非常好用的机制,C#和Java中都有反射,反射机制简单来说就是在程序运行状态时,对于任意一个类,能够知道这个类的所有属性和方法,对于任意一个对象,能够调用它的任意属性和方法,其实初听就知道反射是一个比较暴力的机制,它可能会破坏封装性. 通过反射的定义我们可以想到反射的好处:可以灵活的编写代码,代码可以在运行时装配,降低代码的耦合度,动态代理的实现也离不开反射. 为了更好的理解反射,我们先了解下JVM中的类加载机制. 二.类加载机制 当程序要使用某个类时,如果这个类还未加载

java多线程核心技术梳理(附源码)

java多线程核心技术梳理(附源码) java多线程核心技术梳理附源码 写在前面 java多线程 对象及变量的并发访问 线程间通信 Lock的使用 定时器 单例模式与多线程 拾遗补增 参考资料 本文对多线程基础知识进行梳理,主要包括多线程的基本使用,对象及变量的并发访问,线程间通信,lock的使用,定时器,单例模式,以及线程状态与线程组. 写在前面 花了一周时间阅读<java多线程编程核心技术>(高洪岩 著),本文算是此书的整理归纳,书中几乎所有示例,我都亲手敲了一遍,并上传到了我的githu

Java核心技术点之集合框架

概述 Java集合框架由Java类库的一系列接口.抽象类以及具体实现类组成.我们这里所说的集合就是把一组对象组织到一起,然后再根据不同的需求操纵这些数据.集合类型就是容纳这些对象的一个容器.也就是说,最基本的集合特性就是把一组对象放一起集中管理.根据集合中是否允许有重复的对象.对象组织在一起是否按某种顺序等标准来划分的话,集合类型又可以细分为许多种不同的子类型. Java集合框架为我们提供了一组基本机制以及这些机制的参考实现,其中基本的集合接口是Collection接口,其他相关的接口还有Ite

《Java核心技术 卷1 基础知识 原书第9版》pdf

下载地址:网盘下载 内容简介 编辑 CayS.Horstmann等编著,公飞编译的<Java核心技术>(CoreJava)自第1版出版以来,一直备受广大Java程序设计人员的青睐,畅销不衰,是Java经典书籍.第8版针对JavaSE6平台进行了全面更新,囊括了Java平台标准版(JavaSE/J2SE)的全部基础知识,提供了大量完整且具有实际意义的应用实例,详细介绍了Java语言基础知识.面向对象程序设计.接口与内部类.事件监听器模型.swing图形用户界面程序设计.打包应用程序.异常处理.登

Java学习分享--&gt;集合--&gt;链表

链表是一个有序集合,它将每个对象存放在独立的结点中,每个结点还存放着下一个结点的引用.在Java中由于链表是双向链接的,每个结点还存放着前一个结点的引用. (图片引自Java核心技术 卷1 基础知识) 删除链表中间的一个元素,只需要更新被删除元素附近的结点.假设我们有三个结点,删除第二个结点后,第一个结点将原本存放第二个结点的引用更新为第三个结点的引用(这里对应我们前面提到的"每个结点还存放着下一个结点的引用"),而第三个结点将原本存放第二个结点的引用更新为第一个结点的引用(这里对应我

[基础] Java目录(摘自Java核心技术·卷1 基础知识)

Java核心技术·卷1 基础知识(原书第9版) 第1章 Java程序设计概述 1.1 Java程序设计平台 1.2 Java"白皮书"的关键术语 1.2.1 简单性 1.2.2 面向对象 1.2.3 网络技能 1.2.4 健壮性 1.2.5 安全性 1.2.6 体系结构中立 1.2.7 可移植性 1.2.8 解释型 1.2.9 高性能 1.2.10 多线程 1.2.11 动态性 1.3 Java applet与Internet 1.4 Java发展简史 1.5 关于Java的常见误解

Java核心技术-5-继承

5 继 承 5.1 类.超类和子类 用super访问父类. 由于子类无法直接访问超类的私有域,所以子类对超类私有域的初始化必须调用超类的构造器. 如果子类的构造器没有显式调用超类的构造器,则自动调用超类的无参构造器. this用途:一是引用隐式参数,二是调用该类的其他构造器. super用途:一是调用超类的方法,二是调用超类的构造器. 一个对象变量可以指示多种实际类型的对象被称为多态.在运行时能够自动选择调用哪个方法被称为动态绑定. 1 继承层次 由一个公共超类派生出来的所有类的集合被称为继承层

java核心技术卷一

java核心技术卷一 java基础类型 整型 数据类型 字节数 取值范围 int 4 +_2^4*8-1 short 2 +_2^2*8-1 long 8 +_2^8*8-1 byte 1 -128-127       浮点类型 数据类型 字节数 取值范围 小数位数 float 4 10^-38~10^38和-10^-38~-10^38 小数位数6-7 double 4 10^-308~10^308和-10^-308~-10^308 15位小数         boolean 类型和char 类

《Java核心技术卷I》观赏指南

Tomxin7 如果你有想看书的计划,但是还在纠结哪些书值得看,可以简单看看"观赏指南"系列,本文会简单列出书中内容,给还没有买书的朋友提供一个参考. 前言 秋招过去很久了,虽然在学校的时候有恶补java基础,但是都是为了面试而准备的,有些技术并没有进行全面的了解,再加上java现在疯狂的更新版本,很多新东西没有了解过,所以就打算再过一遍.java的书籍很多,也不想重复的看,所以就买了两本写的比较系统的<java核心技术卷I II>,本篇文章主要是想分享一下卷I,如果你正打