很长时间以来一直代码中用的比较多的数据列表主要是List,而且都是ArrayList,感觉有这个玩意就够了。ArrayList是用于实现动态数组的包装工具类,这样写代码的时候就可以拉进拉出,迭代遍历,蛮方便的。
 
        也不知道从什么时候开始慢慢的代码中就经常会出现HashMap和HashSet之类的工具类。应该说HashMap比较多一些,而且还是面试经典题,平时也会多看看。开始用的时候简单理解就是个键值对应表,使用键来找数据比较方便。随后深入了解后发现这玩意还有点小奥秘,特别是新版本的JDK对HashMap的改成树后,代码都有点小复杂咯。
 
        Set开始用的较少,只是无意中在一个代码中发现一个TreeSet,发现这个类可以自带排序,感觉蛮有点意思,才慢慢的发现这也是个好工具啊。
 
        代码写的多了就感觉到基础的重要性,所以在此写一篇小文简单的整理一下对集合的一些知识。
 
好了,简单的整理一下:
  • List:即是列表,支持数组、链表的功能,一般都是线性的
  • Map:即是映射表,存储的是键与值的对应关系
  • Set:即是集合的意思,主要是用于排重数据及排序
 

先来看看List

List是用于存放线性数据的一种容器,比如:用于数组的ArrayList和用于链表的LinkedList。
 

ArrayList

这是一个数组列表,不过提供了自动扩容的功能,实现List接口,外部操作都是通过接口申明的方法访问,这样即安全又方便。
 
ArrayList的关键就是自动扩容,在对象初始化时可以设定初始容量,也可以按默认的容量。如果对数组大小没有特别明确可以不指定初始大小,如果明确的话可以指定一个大小,这样减少动态扩容时产生的卡顿。说到这就要说一下扩容是怎么实现的了,看下面的代码:
    private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
grow是在ArrayList在添加元素或者一些容易检查时会触发的一个方法。主要过程:
1、得到数组的长度,并对其进行右移,这样就相当于oldCapacity/2,得到新的长度
2、如果这个长度小于最小容量那么直接就用最小容易
3、如果大于了最大容易则取一个最大值,这里会调用一个hugeCapacity方法,主要是比较minCapacity与MAX_ARRAY_SIZE的,如果minCapacity大于MAX_ARRAY_SIZE则取Integer.MAX_VALUE,否则就取MAX_ARRAY_SIZE,有意思的是MAX_ARRAY_SIZE取的是Integer.MAX_VALUE - 8;并不知道这样做的意义是什么
4、最后就是调用一个复制方法将现有数复制到一个新的数组中。
 
因为有这个复制过程,如果数组比较大,那么老是触发扩容当然就会出现卡顿的情况。所以如果一开始就知道最大值而且很容易增长到这个值,那么开始初始化时就指定大小会有一定的作用。
 

LinkedList

这是针对链表的工具类,链表的优秀是添加删除啥的比较快,但是查找会慢一些。
 
至于代码好像也没什么特别的,就是一串指针链接起来,当然Java中就使用对象来代替,建立一个Node的对象,Node本身指向了前一个Node和后一个Node,这就是链表的结构:
 private static class Node<E> {
E item;
Node<E> next;
Node<E> prev; Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
然后用两个Node指向头和尾就完成了,下面的代码:
 /**
* Pointer to first node.
* Invariant: (first == null && last == null) ||
* (first.prev == null && first.item != null)
*/
transient Node<E> first; /**
* Pointer to last node.
* Invariant: (first == null && last == null) ||
* (last.next == null && last.item != null)
*/
transient Node<E> last;
看一个add操作:
    /**
* Links e as last element.
*/
void linkLast(E e) {
final Node<E> l = last;
final Node<E> newNode = new Node<>(l, e, null);
last = newNode;
if (l == null)
first = newNode;
else
l.next = newNode;
size++;
modCount++;
}
过往就是:
 
1、获取到最后的Node并放在l中
2、创建一个新的Node,将数据取到这个Node中,创建过程会将新Node的prev指向l,这样就接上了链
3、然后将last指向这个新Node
4、然判断l是否null,如果是null说明是空链表,新node就是第一个元素,这样first也要指向newNode
5、如果不为空则将l的next指向newNode
6、累加计数器
 
删除操作也是这种Node的前后Node指向移动操作。
 
 

再来看看Map

Map是键与值做一个映射表的应用,主要的实现类:HashMap,HashTable,TreeMap
 

HashMap和HashTable

使用hash算法进行键值映射的就是HashMap啦,HashTable是带有同步的线程安全的类,它们两主要的区别就是这个。原理也类似,都是通过桶+链来组合实现。桶是用来存Key的,而由于Hash碰撞的原因值需要用一个链表来存储。
  • 的意义在于高效,通过Hash计算可以一步定位
  • 链表的意义在于存取重复hash的数据
 
具体的原理以前写过一篇《学习笔记:Hashtable和HashMap》
只不过看JDK1.8的HashMap换了存储结构,采用红黑树的结构,这样可能是解决链表查找效率问题吧?具体没有细研究。
 

TreeMap

看过TreeMap的代码后发现还是使用的树结构,红黑树。由于红黑树是有序的,所以自然带排序功能。当然也可通过comparator来指定比较方法来实现特定的排序。
 
因为采用了树结构存储那么添加和删除数据时会麻烦一些,看一下put的代码:
public V put(K key, V value) {
Entry<K,V> t = root;
if (t == null) {
compare(key, key); // type (and possibly null) check root = new Entry<>(key, value, null);
size = 1;
modCount++;
return null;
}
int cmp;
Entry<K,V> parent;
// split comparator and comparable paths
Comparator<? super K> cpr = comparator;
if (cpr != null) {
do {
parent = t;
cmp = cpr.compare(key, t.key);
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
else
return t.setValue(value);
} while (t != null);
}
else {
if (key == null)
throw new NullPointerException();
@SuppressWarnings("unchecked")
Comparable<? super K> k = (Comparable<? super K>) key;
do {
parent = t;
cmp = k.compareTo(t.key);
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
else
return t.setValue(value);
} while (t != null);
}
Entry<K,V> e = new Entry<>(key, value, parent);
if (cmp < 0)
parent.left = e;
else
parent.right = e;
fixAfterInsertion(e);
size++;
modCount++;
return null;
}
1、先是检查根节点是否存在,不存在说明是第一条数据,直接作为树的根
2、判断是否存在比较器,如果存在则使用比较器进行查找数据的存放位置,如果比较器返回结果小于0取左,大于0取右,否则直接替换当前节点的值
3、如果不存在比较器则key直接与节点的key比较,比较和前面方法一样
4、接下来就是在找到的parent上创建一个子节点,并放入左或者右子节点中
5、fixAfterInsertion是对节点进行着色
6、累加器处理
 
在remove操作时也会有点麻烦,除了删除数据外,还要重新平衡一下红黑树。
 
另外,TreeMap实现了NavigableMap<K,V>接口,所以也提供了对数据集合的一些返回操作。
 

最后看看Set

Set主要是两类应用:HashSet和TreeSet。
 

HashSet

字面意思很明确,使用了Hash的集合。这种集合的特点就是使用Hash算法存数据,所以数据不重复,存取都相对较快。怎么做到的呢?
 
    public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
原来是存在一个map对象中,再看map是个啥?
private transient HashMap<E,Object> map;

是个HashMap,了解HashMap的就明白,这样的数据是不会重复的。因为存入时是鼗对象本身作为Key来存的,所以在HashMap中只会存在一份。

 
了解了这点其他的东西就非常明白了。
 

TreeSet

这个集合是用于对集合进行排序的,也就是除了带有排重的能力外,还可以自带排序功能。只不过看了TreeSet的代码发现,其就是在TreeMap的基础实现的。更准确的说应该是NavigableMap的派生类。默认不指定map情况下TreeSet是以TreeMap为基础的。
 
    public TreeSet() {
this(new TreeMap<E,Object>());
}
所以,这里可能更关注的是TreeSet是如何排重呢?看一下add的方法吧:
    public boolean add(E e) {
return m.put(e, PRESENT)==null;
}
和HashSet有点类似,都是基于Map的特性来实现排重。确实简单而且有效。
 
 

多用多学之Java中的Set,List,Map的更多相关文章

  1. Java中集合List,Map和Set的区别

    Java中集合List,Map和Set的区别 1.List和Set的父接口是Collection,而Map不是 2.List中的元素是有序的,可以重复的 3.Map是Key-Value映射关系,且Ke ...

  2. JAVA 中的 Collection 和 Map 以及相关派生类的概念

    JAVA中Collection接口和Map接口的主要实现类   Collection接口 Collection是最基本的集合接口,一个Collection代表一组Object,即Collection的 ...

  3. Java中的Set,List,Map的区别

    1. 对JAVA的集合的理解是想对于数组 数组是大小固定的,并且同一个数组只能存放类型一样的数据(基本类型/引用类型) JAVA集合可以存储和操作数目不固定的一组数据. 所有的JAVA集合都位于 ja ...

  4. Java中集合List,Map和Set的差别

    Java中集合List,Map和Set的差别 1.List和Set的父接口是Collection.而Map不是 2.List中的元素是有序的,能够反复的 3.Map是Key-Value映射关系,且Ke ...

  5. Java中的Set, List, Map漫谈

    在编程语言中,集合是指代表一组对象的对象.Java平台专门有一个集合框架(Collections Framework).集合框架是指表示和操作集合的统一架构,隔离了集合的操作和实现细节. 集合框架中的 ...

  6. JAVA中Collection接口和Map接口的主要实现类

    Collection接口 Collection是最基本的集合接口,一个Collection代表一组Object,即Collection的元素(Elements).一些Collection允许相同的元素 ...

  7. Java中的集合框架-Map

    前两篇<Java中的集合框架-Commection(一)>和<Java中的集合框架-Commection(二)>把集合框架中的Collection开发常用知识点作了一下记录,从 ...

  8. Java中四种遍历Map对象的方法

    方法一:在for-each循环中使用entry来遍历,通过Map.entrySet遍历key和value,这是最常见的并且在大多数情况下也是最可取的遍历方式.在键值都需要时使用. Map<Int ...

  9. Java中的 List Set Map

    类层次关系如下: Collection ├List │├LinkedList │├ArrayList │└Vector │ └Stack └Set Map ├Hashtable ├HashMap └W ...

随机推荐

  1. SQL Server中的高可用性(2)----文件与文件组

        在谈到SQL Server的高可用性之前,我们首先要谈一谈单实例的高可用性.在单实例的高可用性中,不可忽略的就是文件和文件组的高可用性.SQL Server允许在某些文件损坏或离线的情况下,允 ...

  2. 异步编程 In .NET

    概述 在之前写的一篇关于async和await的前世今生的文章之后,大家似乎在async和await提高网站处理能力方面还有一些疑问,博客园本身也做了不少的尝试.今天我们再来回答一下这个问题,同时我们 ...

  3. 移动web基本知识

    1.pixel像素基础 1.px:csspixel 逻辑像素,浏览器所使用的抽象单位 2.dp,pt:设备无关像素 3.devicePixelPatio 设备像素缩放比例 2.viewport 1. ...

  4. [Java 缓存] Java Cache之 DCache的简单应用.

    前言 上次总结了下本地缓存Guava Cache的简单应用, 这次来继续说下项目中使用的DCache的简单使用. 这里分为几部分进行总结, 1)DCache介绍; 2)DCache配置及使用; 3)使 ...

  5. iOS热更新-8种实现方式

    一.JSPatch 热更新时,从服务器拉去js脚本.理论上可以修改和新建所有的模块,但是不建议这样做. 建议 用来做紧急的小需求和 修复严重的线上bug. 二.lua脚本 比如: wax.热更新时,从 ...

  6. 使用CSS3实现一个3D相册

    CSS3系列我已经写过两篇文章,感兴趣的同学可以先看一下CSS3初体验之奇技淫巧,CSS3 3D立方体效果-transform也不过如此 第一篇主要列出了一些常用或经典的CSS3技巧和方法:第二篇是一 ...

  7. PHP-会员登录与注册例子解析-学习笔记

    1.开始 最近开始学习李炎恢老师的<PHP第二季度视频>中的“章节5:使用OOP注册会员”,做一个学习笔记,通过绘制基本页面流程和UML类图,来对加深理解. 2.基本页面流程 3.通过UM ...

  8. 23种设计模式--工厂模式-Factory Pattern

    一.工厂模式的介绍       工厂模式让我们相到的就是工厂,那么生活中的工厂是生产产品的,在代码中的工厂是生产实例的,在直白一点就是生产实例的类,代码中我们常用new关键字,那么这个new出来的实例 ...

  9. python+uwsgi导致redis无法长链接引起性能下降问题记录

    今天在部署python代码到预生产环境时,web站老是出现redis链接未初始化,无法连接到服务的提示,比对了一下开发环境与测试环境代码,完全一致,然后就是查看各种日志,排查了半天也没有查明是什么原因 ...

  10. 读python源码--对象模型

    学python的人都知道,python中一切皆是对象,如class生成的对象是对象,class本身也是对象,int是对象,str是对象,dict是对象....所以,我很好奇,python是怎样实现这些 ...