Java集合源码分析(四)HashMap
一、HashMap简介
1.1、HashMap概述
HashMap是基于哈希表的Map接口实现的,它存储的是内容是键值对<key,value>映射。此类不保证映射的顺序,假定哈希函数将元素适当的分布在各桶之间,可为基本操作(get和put)提供稳定的性能。
在API中给出了相应的定义:
- //1、哈希表基于map接口的实现,这个实现提供了map所有的操作,并且提供了key和value可以为null,(HashMap和HashTable大致上是一样的除了hashmap是异步的和允许key和value为null),
- 这个类不确定map中元素的位置,特别要提的是,这个类也不确定元素的位置随着时间会不会保持不变。
- Hash table based implementation of the Map interface. This implementation provides all of the optional map operations, and permits null values and the null key.
- (The HashMap class is roughly equivalent to Hashtable, except that it is unsynchronized and permits nulls.) This class makes no guarantees as to the order of the map;
- in particular, it does not guarantee that the order will remain constant over time.
- //假设哈希函数将元素合适的分到了每个桶(其实就是指的数组中位置上的链表)中,则这个实现为基本的操作(get、put)提供了稳定的性能,迭代这个集合视图需要的时间跟hashMap实例(key-value映射的数量)的容量(在桶中)
- 成正比,因此,如果迭代的性能很重要的话,就不要将初始容量设置的太高或者loadfactor设置的太低,【这里的桶,相当于在数组中每个位置上放一个桶装元素】
- This implementation provides constant-time performance for the basic operations (get and put), assuming the hash function disperses the elements properly among the buckets.
- Iteration over collection views requires time proportional to the "capacity" of the HashMap instance (the number of buckets) plus its size (the number of key-value mappings
- ). Thus, it's very important not to set the initial capacity too high (or the load factor too low) if iteration performance is important.
- //HashMap的实例有两个参数影响性能,初始化容量(initialCapacity)和loadFactor加载因子,在哈希表中这个容量是桶的数量【也就是数组的长度】,一个初始化容量仅仅是在哈希表被创建时容量,在
- 容量自动增长之前加载因子是衡量哈希表被允许达到的多少的。当entry的数量在哈希表中超过了加载因子乘以当前的容量,那么哈希表被修改(内部的数据结构会被重新建立)所以哈希表有大约两倍的桶的数量
- An instance of HashMap has two parameters that affect its performance: initial capacity and load factor. The capacity is the number of buckets in the hash table,
- and the initial capacity is simply the capacity at the time the hash table is created. The load factor is a measure of how full the hash table is allowed to get before
- its capacity is automatically increased. When the number of entries in the hash table exceeds the product of the load factor and the current capacity, the hash table
- is rehashed (that is, internal data structures are rebuilt) so that the hash table has approximately twice the number of buckets.
- //通常来讲,默认的加载因子(0.75)能够在时间和空间上提供一个好的平衡,更高的值会减少空间上的开支但是会增加查询花费的时间(体现在HashMap类中get、put方法上),当设置初始化容量时,应该考虑到map中会存放
- entry的数量和加载因子,以便最少次数的进行rehash操作,如果初始容量大于最大条目数除以加载因子,则不会发生 rehash 操作。
- As a general rule, the default load factor (.) offers a good tradeoff between time and space costs. Higher values decrease the space overhead but increase the lookup
- cost (reflected in most of the operations of the HashMap class, including get and put). The expected number of entries in the map and its load factor should be taken
- into account when setting its initial capacity, so as to minimize the number of rehash operations. If the initial capacity is greater than the maximum number of
- entries divided by the load factor, no rehash operations will ever occur.
- //如果很多映射关系要存储在 HashMap 实例中,则相对于按需执行自动的 rehash 操作以增大表的容量来说,使用足够大的初始容量创建它将使得映射关系能更有效地存储。
- If many mappings are to be stored in a HashMap instance, creating it with a sufficiently large capacity will allow the mappings to be stored more efficiently than letting
- it perform automatic rehashing as needed to grow the table
HashMap的定义
1.2、HashMap在JDK1.8以前数据结构和存储原理
1)链表散列
首先我们要知道什么是链表散列?通过数组和链表结合在一起使用,就叫做链表散列。这其实就是hashmap存储的原理图。
2)HashMap的数据结构和存储原理
HashMap的数据结构就是用的链表散列。那HashMap底层是怎么样使用这个数据结构进行数据存取的呢?分成两个部分:
第一步:HashMap内部有一个entry的内部类,其中有四个属性,我们要存储一个值,则需要一个key和一个value,存到map中就会先将key和value保存在这个Entry类创建的对象中。
- static class Entry<K,V> implements Map.Entry<K,V> {
- final K key; //就是我们说的map的key
- V value; //value值,这两个都不陌生
- Entry<K,V> next;//指向下一个entry对象
- int hash;//通过key算过来的你hashcode值。
Entry的物理模型图:
第二步:构造好了entry对象,然后将该对象放入数组中,如何存放就是这hashMap的精华所在了。
大概的一个存放过程是:通过entry对象中的hash值来确定将该对象存放在数组中的哪个位置上,如果在这个位置上还有其他元素,则通过链表来存储这个元素。
3)Hash存放元素的过程
通过key、value封装成一个entry对象,然后通过key的值来计算该entry的hash值,通过entry的hash值和数组的长度length来计算出entry放在数组中的哪个位置上面,
每次存放都是将entry放在第一个位置。在这个过程中,就是通过hash值来确定将该对象存放在数组中的哪个位置上。
1.3、JDK1.8后HashMap的数据结构
上图很形象的展示了HashMap的数据结构(数组+链表+红黑树),桶中的结构可能是链表,也可能是红黑树,红黑树的引入是为了提高效率。
1.4、HashMap的属性
HashMap的实例有两个参数影响其性能。
初始容量:哈希表中桶的数量
加载因子:哈希表在其容量自动增加之前可以达到多满的一种尺度
当哈希表中条目数超出了当前容量*加载因子(其实就是HashMap的实际容量)时,则对该哈希表进行rehash操作,将哈希表扩充至两倍的桶数。
Java中默认初始容量为16,加载因子为0.75。
- static final int DEFAULT_INITIAL_CAPACITY = << ; // aka 16
- static final float DEFAULT_LOAD_FACTOR = 0.75f;
1)loadFactor加载因子
定义:loadFactor译为装载因子。装载因子用来衡量HashMap满的程度。loadFactor的默认值为0.75f。计算HashMap的实时装载因子的方法为:size/capacity,而不是占用桶的数量去除以capacity。
loadFactor加载因子是控制数组存放数据的疏密程度,loadFactor越趋近于1,那么数组中存放的数据(entry)也就越多,也就越密,也就是会让链表的长度增加,loadFactor越小,也就是趋近于0,
那么数组中存放的数据也就越稀,也就是可能数组中每个位置上就放一个元素。那有人说,就把loadFactor变为1最好吗,存的数据很多,但是这样会有一个问题,就是我们在通过key拿到我们的value时,
是先通过key的hashcode值,找到对应数组中的位置,如果该位置中有很多元素,则需要通过equals来依次比较链表中的元素,拿到我们的value值,这样花费的性能就很高,
如果能让数组上的每个位置尽量只有一个元素最好,我们就能直接得到value值了,所以有人又会说,那把loadFactor变得很小不就好了,但是如果变得太小,在数组中的位置就会太稀,也就是分散的太开,
浪费很多空间,这样也不好,所以在hashMap中loadFactor的初始值就是0.75,一般情况下不需要更改它。
- static final float DEFAULT_LOAD_FACTOR = 0.75f;
2)桶
根据前面画的HashMap存储的数据结构图,你这样想,数组中每一个位置上都放有一个桶,每个桶里就是装一个链表,链表中可以有很多个元素(entry),这就是桶的意思。也就相当于把元素都放在桶中。
3)capacity
capacity译为容量代表的数组的容量,也就是数组的长度,同时也是HashMap中桶的个数。默认值是16。
一般第一次扩容时会扩容到64,之后好像是2倍。总之,容量都是2的幂。
- static final int DEFAULT_INITIAL_CAPACITY = << ; // aka 16
4)size的含义
size就是在该HashMap的实例中实际存储的元素的个数
5)threshold的作用
threshold = capacity * loadFactor,当Size>=threshold的时候,那么就要考虑对数组的扩增了,也就是说,这个的意思就是衡量数组是否需要扩增的一个标准。
注意这里说的是考虑,因为实际上要扩增数组,除了这个size>=threshold条件外,还需要另外一个条件。
什么时候会扩增数组的大小?在put一个元素时先size>=threshold并且还要在对应数组位置上有元素,这才能扩增数组。
- int threshold;
我们通过一张HashMap的数据结构图来分析:
二、HashMap的源码分析(一)
2.1、HashMap的层次关系与继承结构
1)HashMap继承结构
上面就继承了一个abstractMap,也就是用来减轻实现Map接口的编写负担。
2)实现接口
Map<K,V>:在AbstractMap抽象类中已经实现过的接口,这里又实现,实际上是多余的。但每个集合都有这样的错误,也没过大影响
Cloneable:能够使用Clone()方法,在HashMap中,实现的是浅层次拷贝,即对拷贝对象的改变会影响被拷贝的对象。
Serializable:能够使之序列化,即可以将HashMap对象保存至本地,之后可以恢复状态。
2.2、HashMap类的属性
- public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable {
- // 序列号
- private static final long serialVersionUID = 362498820763181265L;
- // 默认的初始容量是16
- static final int DEFAULT_INITIAL_CAPACITY = << ;
- // 最大容量
- static final int MAXIMUM_CAPACITY = << ;
- // 默认的填充因子
- static final float DEFAULT_LOAD_FACTOR = 0.75f;
- // 当桶(bucket)上的结点数大于这个值时会转成红黑树
- static final int TREEIFY_THRESHOLD = ;
- // 当桶(bucket)上的结点数小于这个值时树转链表
- static final int UNTREEIFY_THRESHOLD = ;
- // 桶中结构转化为红黑树对应的table的最小大小
- static final int MIN_TREEIFY_CAPACITY = ;
- // 存储元素的数组,总是2的幂次倍
- transient Node<k,v>[] table;
- // 存放具体元素的集
- transient Set<map.entry<k,v>> entrySet;
- // 存放元素的个数,注意这个不等于数组的长度。
- transient int size;
- // 每次扩容和更改map结构的计数器
- transient int modCount;
- // 临界值 当实际大小(容量*填充因子)超过临界值时,会进行扩容
- int threshold;
- // 填充因子
- final float loadFactor;
- }
属性
2.3、HashMap的构造方法
有四个构造方法,构造方法的作用就是记录一下16这个数给threshold(这个数值最终会当作第一次数组的长度。)和初始化加载因子。注意,hashMap中table数组一开始就已经是个没有长度的数组了。
构造方法中,并没有初始化数组的大小,数组在一开始就已经被创建了,构造方法只做两件事情,一个是初始化加载因子,另一个是用threshold记录下数组初始化的大小。注意是记录。
1)HashMap()
- //看上面的注释就已经知道,DEFAULT_INITIAL_CAPACITY=16,DEFAULT_LOAD_FACTOR=0.75
- //初始化容量:也就是初始化数组的大小
- //加载因子:数组上的存放数据疏密程度。
- public HashMap() {
- this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
- }
2)HashMap(int)
- public HashMap(int initialCapacity) {
- this(initialCapacity, DEFAULT_LOAD_FACTOR);
- }
3)HashMap(int,float)
- public HashMap(int initialCapacity, float loadFactor) {
- // 初始容量不能小于0,否则报错
- if (initialCapacity < )
- throw new IllegalArgumentException("Illegal initial capacity: " +
- initialCapacity);
- // 初始容量不能大于最大值,否则为最大值
- if (initialCapacity > MAXIMUM_CAPACITY)
- initialCapacity = MAXIMUM_CAPACITY;
- // 填充因子不能小于或等于0,不能为非数字
- if (loadFactor <= || Float.isNaN(loadFactor))
- throw new IllegalArgumentException("Illegal load factor: " +
- loadFactor);
- // 初始化填充因子
- this.loadFactor = loadFactor;
- // 初始化threshold大小
- this.threshold = tableSizeFor(initialCapacity);
- }
4)HashMap(Map<? extends K, ? extends V> m)
- public HashMap(Map<? extends K, ? extends V> m) {
- // 初始化填充因子
- this.loadFactor = DEFAULT_LOAD_FACTOR;
- // 将m中的所有元素添加至HashMap中
- putMapEntries(m, false);
- }
putMapEntries(Map<? extends K, ? extends V> m, boolean evict)函数将m的所有元素存入本HashMap实例中
- final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) {
- int s = m.size();
- if (s > ) {
- // 判断table是否已经初始化
- if (table == null) { // pre-size
- // 未初始化,s为m的实际元素个数
- float ft = ((float)s / loadFactor) + 1.0F;
- int t = ((ft < (float)MAXIMUM_CAPACITY) ?
- (int)ft : MAXIMUM_CAPACITY);
- // 计算得到的t大于阈值,则初始化阈值
- if (t > threshold)
- threshold = tableSizeFor(t);
- }
- // 已初始化,并且m元素个数大于阈值,进行扩容处理
- else if (s > threshold)
- resize();
- // 将m中的所有元素添加至HashMap中
- for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) {
- K key = e.getKey();
- V value = e.getValue();
- putVal(hash(key), key, value, false, evict);
- }
- }
- }
三、HashMap源码分析(二)
这里我们来看一下我们常用的一些方法的源码
3.1、put方法
1)put(K key,V value)
- public V put(K key, V value) {
- return putVal(hash(key), key, value, false, true);
- }
2)putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict)
- final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
- boolean evict) {
- Node<K,V>[] tab; Node<K,V> p; int n, i;
- // table未初始化或者长度为0,进行扩容
- if ((tab = table) == null || (n = tab.length) == )
- n = (tab = resize()).length;
- // (n - 1) & hash 确定元素存放在哪个桶中,桶为空,新生成结点放入桶中(此时,这个结点是放在数组中)
- if ((p = tab[i = (n - ) & hash]) == null)
- tab[i] = newNode(hash, key, value, null);
- // 桶中已经存在元素
- else {
- Node<K,V> e; K k;
- // 比较桶中第一个元素(数组中的结点)的hash值相等,key相等
- if (p.hash == hash &&
- ((k = p.key) == key || (key != null && key.equals(k))))
- // 将第一个元素赋值给e,用e来记录
- e = p;
- // hash值不相等,即key不相等;为红黑树结点
- else if (p instanceof TreeNode)
- // 放入树中
- e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
- // 为链表结点
- else {
- // 在链表最末插入结点
- for (int binCount = ; ; ++binCount) {
- // 到达链表的尾部
- if ((e = p.next) == null) {
- // 在尾部插入新结点
- p.next = newNode(hash, key, value, null);
- // 结点数量达到阈值,转化为红黑树
- if (binCount >= TREEIFY_THRESHOLD - ) // -1 for 1st
- treeifyBin(tab, hash);
- // 跳出循环
- break;
- }
- // 判断链表中结点的key值与插入的元素的key值是否相等
- if (e.hash == hash &&
- ((k = e.key) == key || (key != null && key.equals(k))))
- // 相等,跳出循环
- break;
- // 用于遍历桶中的链表,与前面的e = p.next组合,可以遍历链表
- p = e;
- }
- }
- // 表示在桶中找到key值、hash值与插入元素相等的结点
- if (e != null) {
- // 记录e的value
- V oldValue = e.value;
- // onlyIfAbsent为false或者旧值为null
- if (!onlyIfAbsent || oldValue == null)
- //用新值替换旧值
- e.value = value;
- // 访问后回调
- afterNodeAccess(e);
- // 返回旧值
- return oldValue;
- }
- }
- // 结构性修改
- ++modCount;
- // 实际大小大于阈值则扩容
- if (++size > threshold)
- resize();
- // 插入后回调
- afterNodeInsertion(evict);
- return null;
- }
HashMap并没有直接提供putVal接口给用户调用,而是提供的put函数,而put函数就是通过putVal来插入元素的。
3)putAlll()
3.2、get方法
1)get(Object key)
- public V get(Object key) {
- Node<K,V> e;
- return (e = getNode(hash(key), key)) == null ? null : e.value;
- }
2)getNode(int hash,Pbject key)
- final Node<K,V> getNode(int hash, Object key) {
- Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
- // table已经初始化,长度大于0,根据hash寻找table中的项也不为空
- if ((tab = table) != null && (n = tab.length) > &&
- (first = tab[(n - ) & hash]) != null) {
- // 桶中第一项(数组元素)相等
- if (first.hash == hash && // always check first node
- ((k = first.key) == key || (key != null && key.equals(k))))
- return first;
- // 桶中不止一个结点
- if ((e = first.next) != null) {
- // 为红黑树结点
- if (first instanceof TreeNode)
- // 在红黑树中查找
- return ((TreeNode<K,V>)first).getTreeNode(hash, key);
- // 否则,在链表中查找
- do {
- if (e.hash == hash &&
- ((k = e.key) == key || (key != null && key.equals(k))))
- return e;
- } while ((e = e.next) != null);
- }
- }
- return null;
- }
HashMap并没有直接提供getNode接口给用户调用,而是提供的get函数,而get函数就是通过getNode来取得元素的。
3.3、resize方法
- final Node<K,V>[] resize() {
- // 当前table保存
- Node<K,V>[] oldTab = table;
- // 保存table大小
- int oldCap = (oldTab == null) ? : oldTab.length;
- // 保存当前阈值
- int oldThr = threshold;
- int newCap, newThr = ;
- // 之前table大小大于0
- if (oldCap > ) {
- // 之前table大于最大容量
- if (oldCap >= MAXIMUM_CAPACITY) {
- // 阈值为最大整形
- threshold = Integer.MAX_VALUE;
- return oldTab;
- }
- // 容量翻倍,使用左移,效率更高
- else if ((newCap = oldCap << ) < MAXIMUM_CAPACITY &&
- oldCap >= DEFAULT_INITIAL_CAPACITY)
- // 阈值翻倍
- newThr = oldThr << ; // double threshold
- }
- // 之前阈值大于0
- else if (oldThr > )
- newCap = oldThr;
- // oldCap = 0并且oldThr = 0,使用缺省值(如使用HashMap()构造函数,之后再插入一个元素会调用resize函数,会进入这一步)
- else {
- newCap = DEFAULT_INITIAL_CAPACITY;
- newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
- }
- // 新阈值为0
- if (newThr == ) {
- float ft = (float)newCap * loadFactor;
- newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
- (int)ft : Integer.MAX_VALUE);
- }
- threshold = newThr;
- @SuppressWarnings({"rawtypes","unchecked"})
- // 初始化table
- Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
- table = newTab;
- // 之前的table已经初始化过
- if (oldTab != null) {
- // 复制元素,重新进行hash
- for (int j = ; j < oldCap; ++j) {
- Node<K,V> e;
- if ((e = oldTab[j]) != null) {
- oldTab[j] = null;
- if (e.next == null)
- newTab[e.hash & (newCap - )] = e;
- else if (e instanceof TreeNode)
- ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
- else { // preserve order
- Node<K,V> loHead = null, loTail = null;
- Node<K,V> hiHead = null, hiTail = null;
- Node<K,V> next;
- // 将同一桶中的元素根据(e.hash & oldCap)是否为0进行分割,分成两个不同的链表,完成rehash
- do {
- next = e.next;
- if ((e.hash & oldCap) == ) {
- if (loTail == null)
- loHead = e;
- else
- loTail.next = e;
- loTail = e;
- }
- else {
- if (hiTail == null)
- hiHead = e;
- else
- hiTail.next = e;
- hiTail = e;
- }
- } while ((e = next) != null);
- if (loTail != null) {
- loTail.next = null;
- newTab[j] = loHead;
- }
- if (hiTail != null) {
- hiTail.next = null;
- newTab[j + oldCap] = hiHead;
- }
- }
- }
- }
- }
- return newTab;
- }
resize()
进行扩容,会伴随着一次重新hash分配,并且会遍历hash表中所有的元素,是非常耗时的。在编写程序中,要尽量避免resize。
在resize前和resize后的元素布局如下:
上图只是针对了数组下标为2的桶中的各个元素在扩容后的分配布局,其他各个桶中的元素布局可以以此类推。
四、总结
4.1、关于数组扩容
从putVal源代码中我们可以知道,当插入一个元素的时候size就加1,若size大于threshold的时候,就会进行扩容。假设我们的capacity大小为32,loadFator为0.75,则threshold为24 = 32 * 0.75,
此时,插入了25个元素,并且插入的这25个元素都在同一个桶中,桶中的数据结构为红黑树,则还有31个桶是空的,也会进行扩容处理,其实,此时,还有31个桶是空的,好像似乎不需要进行扩容处理,
但是是需要扩容处理的,因为此时我们的capacity大小可能不适当。我们前面知道,扩容处理会遍历所有的元素,时间复杂度很高;前面我们还知道,经过一次扩容处理后,元素会更加均匀的分布在各个桶中,
会提升访问效率。所以,说尽量避免进行扩容处理,也就意味着,遍历元素所带来的坏处大于元素在桶中均匀分布所带来的好处。
4.2、总结
1)要知道hashMap在JDK1.8以前是一个链表散列这样一个数据结构,而在JDK1.8以后是一个数组加链表加红黑树的数据结构。
2)通过源码的学习,hashMap是一个能快速通过key获取到value值得一个集合,原因是内部使用的是hash查找值得方法。
参考博文:
http://www.cnblogs.com/whgk/p/6091316.html
http://www.cnblogs.com/leesf456/p/5242233.html
Java集合源码分析(四)HashMap的更多相关文章
- java集合源码分析(六):HashMap
概述 HashMap 是 Map 接口下一个线程不安全的,基于哈希表的实现类.由于他解决哈希冲突的方式是分离链表法,也就是拉链法,因此他的数据结构是数组+链表,在 JDK8 以后,当哈希冲突严重时,H ...
- Java 集合源码分析(一)HashMap
目录 Java 集合源码分析(一)HashMap 1. 概要 2. JDK 7 的 HashMap 3. JDK 1.8 的 HashMap 4. Hashtable 5. JDK 1.7 的 Con ...
- java集合源码分析(三):ArrayList
概述 在前文:java集合源码分析(二):List与AbstractList 和 java集合源码分析(一):Collection 与 AbstractCollection 中,我们大致了解了从 Co ...
- Java集合源码分析(四)Vector<E>
Vector<E>简介 Vector也是基于数组实现的,是一个动态数组,其容量能自动增长. Vector是JDK1.0引入了,它的很多实现方法都加入了同步语句,因此是线程安全的(其实也只是 ...
- Java集合源码分析(三)LinkedList
LinkedList简介 LinkedList是基于双向循环链表(从源码中可以很容易看出)实现的,除了可以当做链表来操作外,它还可以当做栈.队列和双端队列来使用. LinkedList同样是非线程安全 ...
- Java集合源码分析(二)ArrayList
ArrayList简介 ArrayList是基于数组实现的,是一个动态数组,其容量能自动增长,类似于C语言中的动态申请内存,动态增长内存. ArrayList不是线程安全的,只能用在单线程环境下,多线 ...
- java集合源码分析几篇文章
java集合源码解析https://blog.csdn.net/ns_code/article/category/2362915
- Java集合源码分析(五)——HashMap
简介 HashMap 是一个散列表,存储的内容是键值对映射. HashMap 继承于AbstractMap,实现了Map.Cloneable.java.io.Serializable接口. HashM ...
- Java集合源码分析(七)HashMap<K, V>
一.HashMap概述 HashMap基于哈希表的 Map 接口的实现.此实现提供所有可选的映射操作,并允许使用 null 值和 null 键.(除了不同步和允许使用 null 之外,HashMap ...
随机推荐
- Android studio导入eclipse项目(亲测)
之前上网搜索的时候,网上都说先用eclipse导出gradle,之后再用Android Studio的import project导入,但是这个方法使用的过程中会出现许多错误,解决了一个又一个还是不得 ...
- Redis4.0.0 安装及配置 (Linux — Centos7)
本文中的两个配置文件可在这里找到 操作系统:Linux Linux发行版:Centos7 安装 下载地址,点这里Redis4.0.0.tar.gz 或者使用命令: wget http://downlo ...
- iOS转场动画封装
写在前面 iOS在modal 或push等操作时有默认的转场动画,但有时候我们又需要特定的转场动画效果,从iOS7开始,苹果就提供了自定义转场的API,模态推送present和dismiss.导航控制 ...
- C#备份一个文件到指定的文件夹里面
一开始我的想法是这样的: //在控制台里面操作 static void Main(string[] args) { //backup( @"D:\gg\config.xml", @ ...
- Keras的安装与配置
Keras是由Python编写的基于Tensorflow或Theano的一个高层神经网络API.具有高度模块化,极简,可扩充等特性.能够实现简易和快速的原型设计,支持CNN和RNN或者两者的结合,可以 ...
- CSS height:100%无效
本文同时发表在https://github.com/zhangyachen/zhangyachen.github.io/issues/38 浏览器根本就不计算内容的高度,除非内容超出了视窗范围(导致滚 ...
- bzoj 4515: [Sdoi2016]游戏
Description Alice 和 Bob 在玩一个游戏. 游戏在一棵有 n 个点的树上进行.最初,每个点上都只有一个数字,那个数字是 123456789123456789. 有时,Alice 会 ...
- MySQL数据库入门(建库和建表)--陈远波
建库.建表 1.建库 (1)SQL语句命令建库: Create database数据库名称 (该方法创建的数据库没有设置编码乱码) 1 2 3 4 5 -- 创建数据库时,设置数据库的编码方式 -- ...
- Python列表list对象方法总结
- Disruptor并发框架 (二)核心概念场景分析
核心术语 RingBuffer(容器): 被看作Disruptor最主要的组件,然而从3.0开始RingBuffer仅仅负责存储和更新在Disruptor中流通的数据.对一些特殊的使用场景能够被用户( ...