Java基础知识回顾之四 ----- 集合List、Map和Set
前言
在上一篇中回顾了Java的三大特性:封装、继承和多态。本篇则来介绍下集合。
集合介绍
我们在进行Java程序开发的时候,除了最常用的基础数据类型和String对象外,也经常会用到集合相关类。
集合类存放的都是对象的引用,而非对象本身,出于表达上的便利,我们称集合中的对象就是指集合中对象的引用。
集合类型主要有3种:List、Set、和Map。
它们之间的关系可用下图来表示:
注:Map不是collections的子类,但是它们完全整合在集合中了!
List
List 接口是继承于 Collection接口并定义 一个允许重复项的有序集合。该接口不但能够对列表的一部分进行处理,还添加了面向位置的操作。
一般来说,我们在单线程中主要使用的List是ArrayList和LinkedList来实现,多线程则是使用Vector或者使用Collections.sychronizedList来装饰一个集合。
这三个的解释如下:
- ArrayList:内部是通过数组实现的,它允许对元素进行快随机访问。当从ArrayList的中间位置插入或者删除元素时,需要对数组进行复制、移动、代价比较高。因此,它适合随机查找和遍历,不适合插入和删除。
- LinkedList: 则是链表结构存储数据的,很适合数据的动态插入和删除,随机访问和遍历速度比较慢。另外,他还提供了List接口中没有定义的方法,专门用于操作表头和表尾元素,可以当作堆栈、队列和双向队列使用。
- Vector: 通过数组实现的,不同的是它支持线程的同步。访问速度ArrayList慢。
它们的用法如下:
List list1 = new ArrayList();
List list2 = new LinkedList();
List list3 = new Vector();
List list4=Collections.synchronizedList(new ArrayList())
在了解了它们的用法之后,我们来看看为什么说使用ArrayList比LinkedList查询快,使用LinkedList比ArrayList新增和删除快!
这里也用以下代码来进行说明,顺便也将Vector进行比较。
代码示例:
private final static int count=50000;
private static ArrayList arrayList = new ArrayList<>();
private static LinkedList linkedList = new LinkedList<>();
private static Vector vector = new Vector<>();
public static void main(String[] args) {
insertList(arrayList);
insertList(linkedList);
insertList(vector);
System.out.println("--------------------");
readList(arrayList);
readList(linkedList);
readList(vector);
System.out.println("--------------------");
delList(arrayList);
delList(linkedList);
delList(vector);
}
private static void insertList(List list){
long start=System.currentTimeMillis();
Object o = new Object();
for(int i=0;i<count;i++){
list.add(0, o);
}
System.out.println(getName(list)+"插入"+count+"条数据,耗时:"+(System.currentTimeMillis()-start)+"ms");
}
private static void readList(List list){
long start=System.currentTimeMillis();
Object o = new Object();
for(int i = 0 ; i < count ; i++){
list.get(i);
}
System.out.println(getName(list)+"查询"+count+"条数据,耗时:"+(System.currentTimeMillis()-start)+"ms");
}
private static void delList(List list){
long start=System.currentTimeMillis();
Object o = new Object();
for(int i = 0 ; i < count ; i++){
list.remove(0);
}
System.out.println(getName(list)+"删除"+count+"条数据,耗时:"+(System.currentTimeMillis()-start)+"ms");
}
private static String getName(List list) {
String name = "";
if(list instanceof ArrayList){
name = "ArrayList";
}
else if(list instanceof LinkedList){
name = "LinkedList";
}
else if(list instanceof Vector){
name = "Vector";
}
return name;
}
输出结果:
ArrayList插入50000条数据,耗时:281ms
LinkedList插入50000条数据,耗时:2ms
Vector插入50000条数据,耗时:274ms
--------------------
ArrayList查询50000条数据,耗时:1ms
LinkedList查询50000条数据,耗时:1060ms
Vector查询50000条数据,耗时:2ms
--------------------
ArrayList删除50000条数据,耗时:143ms
LinkedList删除50000条数据,耗时:1ms
Vector删除50000条数据,耗时:137ms
从上述结果中,可以明显看出ArrayList和LinkedList在新增、删除和查询性能上的区别。
在集合中,我们一般用于存储数据。不过有时在有多个集合的时候,我们想将这几个集合做合集、交集、差集和并集的操作。在List中,这些方法已经封装好了,我们无需在进行编写相应的代码,直接拿来使用就行。
代码示例如下:
/**
* 合集
* @param ls1
* @param ls2
* @return
*/
private static List<String> addAll(List<String> ls1,List<String>ls2){
ls1.addAll(ls2);
return ls1;
}
/**
* 交集 (retainAll 会删除 ls1在ls2中没有的元素)
* @param ls1
* @param ls2
* @return
*/
private static List<String> retainAll(List<String> ls1,List<String>ls2){
ls1.retainAll(ls2);
return ls1;
}
/**
* 差集 (删除ls2中没有ls1中的元素)
* @param ls1
* @param ls2
* @return
*/
private static List<String> removeAll(List<String> ls1,List<String>ls2){
ls1.removeAll(ls2);
return ls1;
}
/**
* 无重复的并集 (ls1和ls2中并集,并无重复)
* @param ls1
* @param ls2
* @return
*/
private static List<String> andAll(List<String> ls1,List<String>ls2){
//删除在ls1中出现的元素
ls2.removeAll(ls1);
//将剩余的ls2中的元素添加到ls1中
ls1.addAll(ls2);
return ls1;
}
当然,经常用到的还有对List进行遍历。
List数组遍历主要有这三种方法,普通的for循环,增强for循环(jdk1.5之后出现),和Iterator(迭代器)。
代码示例:
List<String> list=new ArrayList<String>();
list.add("a");
list.add("b");
list.add("c");
for(int i=0;i<list.size();i++){
System.out.println(list.get(i));
}
for (String str : list) {
System.out.println(str);
}
Iterator<String> iterator=list.iterator();
while(iterator.hasNext())
{
System.out.println(iterator.next());
}
说明:普通的for循环和增强for循环区别不大,主要区别在于普通的for循环可以获取集合的下标,而增强for循环则不可以。但增强for循环写起来方法,如果不需要获取具体集合的下标,推荐使用增强for循环。至于Iterator(迭代器)这种也是无法获取数据下标,但是该方法可以不用担心在遍历的过程中会集合的长度发生改变。也就是在遍历的时候对集合进行增加和删除。
在<阿里巴巴Java开发手册>中,对于集合操作也有这种说明。
不要在 foreach 循环里进行元素的 remove / add 操作。 remove 元素请使用Iterator方式,如果并发操作,需要对 Iterator 对象加锁。
那么为什么不要使用 foreach 循环进行元素的 remove / add 操作呢?
我们这里可以简单的做下验证。
代码示例:
List<String> list = new ArrayList<String>();
list.add("1");
list.add("2");
System.out.println("list遍历之前:"+list);
for (String item : list) {
if ("2".equals(item)) {
list.remove(item);
//如果这里不适用break的话,会直接报错的
break;
}
}
System.out.println("list遍历之后:"+list);
List<String> list1 = new ArrayList<String>();
list1.add("1");
list1.add("2");
System.out.println("list1遍历之前:"+list1);
Iterator<String> iterator = list1.iterator();
while (iterator.hasNext()) {
String item = iterator.next();
if ("2".equals(item)) {
iterator.remove();
}
}
System.out.println("list1遍历之后:"+list1);
输出结果:
list遍历之前:[1, 2]
list遍历之后:[1]
list1遍历之前:[1, 2]
list1遍历之后:[1]
注意:上述代码中,在对list进行for循环遍历的时候,加了break,
上述示例中,都正确的打印我们想要的数据,不过在foreach循环中,我在其中是加上了break。如果不加break,就会直接抛出ConcurrentModificationException异常!
Map
Map 接口并不是 Collection 接口的继承。Map提供key到value的映射。一个Map中不能包含相同的key,每个key只能映射一个value。Map接口提供3种集合的视图,Map的内容可以被当作一组key集合,一组value集合,或者一组key-value映射。
Map接口主要由HashMap、TreeMap、LinkedHashMap、Hashtable和ConcurrentHashMap这几个类实现。
它们的解释如下:
- HashMap: HashMap的键是根据HashCode来获取,所以根据键可以很快的获取相应的值。不过它的键对象是不可以重复的,它允许键为Null,但是最多只能有一条记录,不过却是可以允许多条记录的值为Null。因为HashMap是非线程安全的,所以它的效率很高。
- TreeMap:可以将保存的记录根据键进行排序,默认是按键值的升序排序(自然顺序)。也可以指定排序的比较器,当用Iterator遍历TreeMap时,得到的记录是排过序的。它也是不允许key值为空,并且不是线程安全的。
- LinkedHashMap:LinkedHashMap基本和HashMap一致。不过区别在与LinkedHashMap是维护一个双链表,可以将里面的数据按写入 的顺序读出。可以认为LinkedHashMap是HashMap+LinkedList。即它既使用HashMap操作数据结构,又使用LinkedList维护插入元素的先后顺序。它也不是线程安全的。
- Hashtable:Hashtable与HashMap类似,可以说是HashMap的线程安全版。不过它是不允许记录的键或者值为null。因为它支持线程的同步,是线程安全的,所以也导致了Hashtale在效率较低。
- ConcurrentHashMap: ConcurrentHashMap在Java 1.5作为Hashtable的替代选择新引入的。使用锁分段技术技术来保证线程安全的,可以看作是Hashtable的升级版。
在工作中,我们使用得最多的Map应该是HashMap。不过有时在使用Map的时候,需要进行自然顺序排序。这里我们就可以使用TreeMap,而不必自己实现这个功能。TreeMap的使用和HashMap差不多。不过需要注意的是TreeMap是不允许key为null。 这里简单的介绍下TreeMap的使用。
代码示例:
Map<String,Object> hashMap=new HashMap<String,Object>();
hashMap.put("a", 1);
hashMap.put("c", 3);
hashMap.put("b", 2);
System.out.println("HashMap:"+hashMap);
Map<String,Object> treeMap=new TreeMap<String,Object>();
treeMap.put("a", 1);
treeMap.put("c", 3);
treeMap.put("b", 2);
System.out.println("TreeMap:"+treeMap);
输出结果:
HashMap:{b=2, c=3, a=1}
TreeMap:{a=1, b=2, c=3}
上述中可以看出HashMap是无序的,TreeMap是有序的。
在使用Map的时候,也会对Map进行遍历。一般遍历Map的key和value有三种方式:
第一种通过Map.keySet遍历;
第二种通过Map.entrySet使用iterator遍历;
第三种是通过Map.entrySet进行遍历。
使用如下:
Map<String, String> map = new HashMap<String, String>();
for (String key : map.keySet()) {
System.out.println("key= "+ key + " and value= " + map.get(key));
}
Iterator<Map.Entry<String, String>> it = map.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<String, String> entry = it.next();
System.out.println("key= " + entry.getKey() + " and value= " + entry.getValue());
}
for (Map.Entry<String, String> entry : map.entrySet()) {
System.out.println("key= " + entry.getKey() + " and value= " + entry.getValue());
}
如果只想获取Map中value的话,可以使用foreach对Map.values()进行遍历。
for (String v : map.values()) {
System.out.println("value= " + v);
}
在上述遍历中,我们最多使用的是第一种Map.keySet,因为写起来比较简单。不过在容量大的时候,推荐使用第三种,效率会更高!
Set
Set是一种不包含重复的元素的Collection,即任意的两个元素e1和e2都有e1.equals(e2)=false,Set最多有一个null元素。因为Set是一个抽象的接口,所以是不能直接实例化一个set对象。
Set s = new Set()
这种写法是错误的。
Set接口主要是由HashSet、TreeSet和LinkedHashSet来实现。
它们简单的使用如下:
Set hashSet = new HashSet();
Set treeSet = new TreeSet();
Set linkedSet = new LinkedHashSet();
因为Set是无法拥有重复元素的,所以也经常用它来去重。例如在一个list集合中有两条相同的数据,想去掉一条,这时便可以使用Set的机制来去重。
代码示例:
public static void set(){
List<String> list = new ArrayList<String>();
list.add("Java");
list.add("C");
list.add("C++");
list.add("JavaScript");
list.add("Java");
Set<String> set = new HashSet<String>();
for (int i = 0; i < list.size(); i++) {
String items = list.get(i);
System.out.println("items:"+items);
if (!set.add(items)) {
System.out.println("重复的数据: " + items);
}
}
System.out.println("list:"+list);
}
输出结果:
items:Java
items:C
items:C++
items:JavaScript
items:Java
重复的数据: Java
list:[Java, C, C++, JavaScript, Java]
注意:如果是将对象进行去重的话,是需要重写set中的equals和hashcode方法的。
总结
关于集合中List、Map、Set这三个的总结如下:
List:List和数组类似,可以动态增长,根据实际存储的数据的长度自动增长List的长度。查找元素效率高,插入删除效率低,因为会引起其他元素位置改变 <实现类有ArrayList,LinkedList,Vector>
ArrayList:非线程安全,适合随机查找和遍历,不适合插入和删除。
LinkedList : 非线程安全,适合插入和删除,不适合查找。
Vector : 线程安全。不过不推荐。
Map:一个key到value的映射的类 。
HashMap:非线程安全,键和值都允许有null值存在。
TreeMap:非线程安全,按自然顺序或自定义顺序遍历键(key)。
LinkedHashMap:非线程安全,维护一个双链表,可以将里面的数据按写入的顺序读出。写入比HashMap强,新增和删除比HashMap差。
Hashtable:线程安全,键和值不允许有null值存在。不推荐使用。
ConcurrentHashMap:线程安全,Hashtable的升级版。推荐多线程使用。
Set:不允许重复的数据 。检索效率低下,删除和插入效率高。
- HashSet: 非线程安全、无序、数据可为空。
- TreeSet: 非线程安全、有序、数据不可为空。
- LinkedHashSet:非线程安全、无序、数据可为空。写入比HashSet强,新增和删除比HashSet差。
到此,本文结束,谢谢阅读。
版权声明:
作者:虚无境
博客园出处:http://www.cnblogs.com/xuwujing
CSDN出处:http://blog.csdn.net/qazwsxpcm
个人博客出处:http://www.panchengming.com
原创不易,转载请标明出处,谢谢!
Java基础知识回顾之四 ----- 集合List、Map和Set的更多相关文章
- Java基础知识回顾之七 ----- 总结篇
前言 在之前Java基础知识回顾中,我们回顾了基础数据类型.修饰符和String.三大特性.集合.多线程和IO.本篇文章则对之前学过的知识进行总结.除了简单的复习之外,还会增加一些相应的理解. 基础数 ...
- java基础知识回顾之---java String final类普通方法
辞职了,最近一段时间在找工作,把在大二的时候学习java基础知识回顾下,拿出来跟大家分享,如果有问题,欢迎大家的指正. /* * 按照面向对象的思想对字符串进行功能分类. * ...
- Java基础知识强化之集合框架笔记76:ConcurrentHashMap之 ConcurrentHashMap简介
1. ConcurrentHashMap简介: ConcurrentHashMap是一个线程安全的Hash Table,它的主要功能是提供了一组和Hashtable功能相同但是线程安全的方法.Conc ...
- JAVA基础第五章-集合框架Map篇
业内经常说的一句话是不要重复造轮子,但是有时候,只有自己造一个轮子了,才会深刻明白什么样的轮子适合山路,什么样的轮子适合平地! 我将会持续更新java基础知识,欢迎关注. 往期章节: JAVA基础第一 ...
- Java基础知识回顾(一):字符串小结
Java的基础知识回顾之字符串 一.引言 很多人喜欢在前面加入赘述,事实上去技术网站找相关的内容的一般都应当已经对相应知识有一定了解,因此我不再过多赘述字符串到底是什么东西,在官网中已经写得很明确了, ...
- Java基础知识强化之集合框架笔记53:Map集合之Map集合的遍历 键值对对象找键和值
1. Map集合的遍历(键值对对象找键和值) Map -- 夫妻对 思路: A: 获取所有结婚证的集合 B: 遍历结婚证的集合,得到每一个结婚证 C: 根据结婚证获取丈夫和妻子 转换: A: ...
- Java基础知识强化之集合框架笔记52:Map集合之Map集合的遍历 键找值
1. Map集合的遍历 Map -- 夫妻对 思路: A:把所有的丈夫给集中起来. B:遍历丈夫的集合,获取得到每一个丈夫. C:让丈夫去找自己的妻子. 转换: A:获取所有的键 B:遍 ...
- Java基础知识强化之集合框架笔记51:Map集合之Map集合的功能概述与测试
1. Map集合的功能概述 (1)添加功能 V put(K key,V value):添加元素.这个其实还有另一个功能?先不告诉你,等会讲 如果键是第一次存储,就直接存储元素,返回null 如果键不是 ...
- Java基础知识回顾
Java回顾之I/O Java回顾之网络通信 Java回顾之多线程 Java回顾之多线程同步 Java回顾之集合 Java回顾之序列化 Java回顾之反射 Java回顾之一些基础概念 Java回顾之J ...
随机推荐
- [UWP]创建一个ProgressControl
1. 前言 博客园终于新增了UWP的分类,我来为这个分类贡献第一篇博客吧. UWP有很多问题,先不说生态的事情,表单.验证.输入.设计等等一堆基本问题缠身.但我觉得最应该首先解决的绝对是Blend,那 ...
- JavaScript(第十九天)【DOM进阶】
学习要点: 1.DOM类型 2.DOM扩展 3.DOM操作内容 DOM自身存在很多类型,在DOM基础课程中大部分都有所接触,比如Element类型:表示的是元素节点,再比如Text类型:表示的是文本节 ...
- 《Language Implementation Patterns》之 数据聚合符号表
本章学习一种新的作用域,叫做数据聚合作用域(data aggregate scope),和其他作用域一样包含符号,并在scope tree里面占据一个位置. 区别在于:作用域之外的代码能够通过一种特殊 ...
- python 二叉堆
BinaryHeap() 创建一个新的,空的二叉堆. insert(k) 向堆添加一个新项. findMin() 返回具有最小键值的项,并将项留在堆中. delMin() 返回具有最小键值的项,从堆中 ...
- scrapy 模拟登陆
import scrapy import urllib.request from scrapy.http import Request,FormRequest class LoginspdSpider ...
- iOS 播放音频的几种方法
Phone OS 主要提供以下了几种播放音频的方法: System Sound Services AVAudioPlayer 类 Audio Queue Services OpenAL 1. Syst ...
- Flask 扩展 缓存
如果同一个请求会被多次调用,每次调用都会消耗很多资源,并且每次返回的内容都相同,就该使用缓存了 自定义缓存装饰器 在使用Flask-Cache扩展实现缓存功能之前,我们先来自己写个视图缓存装饰器,方便 ...
- System V IPC 之信号量
本文继<System V IPC 之共享内存>之后接着介绍 System V IPC 的信号量编程.在开始正式的内容前让我们先概要的了解一下 Linux 中信号量的分类. 信号量的分类 在 ...
- 识别图片中文字(百度AI)
这个是百度官方的文档 https://ai.baidu.com/docs#/OCR-API/top 通用的文字识别,如果是其他的含生僻字/含位置信息的版本,请参考官方的文档,只 ...
- 使用JavaScript实现一个俄罗斯方块
清明假期期间,闲的无聊,就做了一个小游戏玩玩,目前游戏逻辑上暂未发现bug,只不过样子稍微丑了一些-.-项目地址:https://github.com/Jiasm/tetris在线Demo:http: ...