https://www.cnblogs.com/vitasyuan/p/9220773.html

1.HashMap-1.8介绍

HashMap为Map接口的一个实现类,实现了所有Map的操作。HashMap除了允许key和value保存null值和非线程安全外,其他实现几乎和HashTable一致。

HashMap使用散列存储的方式保存kay-value键值对,因此其不支持数据保存的顺序。如果想要使用有序容器可以使用LinkedHashMap。

在性能上当HashMap中保存的key的哈希算法能够均匀的分布在每个bucket中的是时候,HashMap在基本的get和set操作的的时间复杂度都是O(n)。

在遍历HashMap的时候,其遍历节点的个数为bucket的个数+HashMap中保存的节点个数。因此当遍历操作比较频繁的时候需要注意HashMap的初始化容量不应该太大。

这一点其实比较好理解:当保存的节点个数一致的时候,bucket越少,遍历次数越少。

另外HashMap在resize的时候会有很大的性能消耗,因此当需要在保存HashMap中保存大量数据的时候,传入适当的默认容量以避免resize可以很大的提高性能。

具体的resize操作请参考下面对此方法的分析

HashMap是非线程安全的类,当作为共享可变资源使用的时候会出现线程安全问题。需要使用线程安全容器:

Map m = new ConcurrentHashMap();或者Map m = Collections.synchronizedMap(new HashMap());

具体的HashMap会出现的线程安全问题分析请参考9中的分析。

2.数据结构介绍

HashMap使用数组+链表+树形结构的数据结构。其结构图如下所示。

3.HashMap源码分析(基于JDK1.8)

3.1关键属性分析

  transient Node<K,V>[] table;

    Node类型的数组,记我们常说的bucket数组,其中每个元素为链表或者树形结构

  transient Set<Map.Entry<K,V>> entrySet;

    保存缓存的entrySet()

  transient int size;

    HashMap中保存的数据个数

  transient int modCount;

    此哈希映射在结构上被修改的次数

  int threshold;

    HashMap需要resize操作的阈值

  final float loadFactor;

    负载因子,用于计算threshold。计算公式为:threshold = loadFactor * capacity

  备注:有默认容量capacity  2^4 = 16,默认扩容负载因子loadFactor=0.75等.用于构造函数没有指定数值情况下的默认值。

3.2Node分析

他是Hashmap的内部类,存储key,value键值对

1.4个参数

  Hash - final常量

  Key  - final常量

  value  -  值

  Node<K,V> next;

2.只有有参构造

3.key被final修饰-不能修改-只能创建的时候赋值

4.重写equals方法:key和value都相等(地址值相等)返回true

 static class Node<K,V> implements Map.Entry<K,V> {

        final int hash; //final修饰-常量-不可变

        final K key; //final修饰-常量-不可变

        V value;

        Node<K,V> next;  //下一个元素,形成链表

        Node(int hash, K key, V value, Node<K,V> next) {//只有有参构造没有无参构造

            this.hash = hash;

            this.key = key;

            this.value = value;

            this.next = next;

        }

        public final K getKey()        { return key; }

        public final V getValue()      { return value; }

        public final String toString() { return key + "=" + value; }

        public final int hashCode() {

            return Objects.hashCode(key) ^ Objects.hashCode(value);

        }

        public final V setValue(V newValue) { //value设值:旧值新值被覆盖且返回旧值

            V oldValue = value;

            value = newValue;

            return oldValue;

        }

        public final boolean equals(Object o) {//key和value相等(地址值)

            if (o == this)

                return true;

            if (o instanceof Map.Entry) {

                Map.Entry<?,?> e = (Map.Entry<?,?>)o;

                if (Objects.equals(key, e.getKey()) &&

                    Objects.equals(value, e.getValue()))

                    return true;

            }

            return false;

        }

    }

3.3关键函数源码分析

3.3.1构造函数

无参构造:默认扩容负载因子0.75

有参构造:最多两个参数 1.cap初始容量  2.loadFactor 扩容因子

有参构造虽然传入cap的值,但是没有创建tab,只是对阀值threshold 进行了赋值,赋值为传入cap最近的大于等于cap的2的整数次幂(这个值resize的时候会赋值给cap,保证cap是2的整数次幂),这个时候不创建tab(cap=tab.length就还没有确定)是为了节省空间

HashMap提供了三个不同的构造函数

static final int MAXIMUM_CAPACITY = 1 << 30;  最大容量

public HashMap() {

         this.loadFactor = DEFAULT_LOAD_FACTOR;

    //无参构造-默认扩容负载因子0.75
//static final float DEFAULT_LOAD_FACTOR = 0.75f; } public HashMap(int initialCapacity) { //有参-设定初始容量大小,默认扩容负载因子0.75
this(initialCapacity, DEFAULT_LOAD_FACTOR); } public HashMap(int initialCapacity, float loadFactor) {
    //有参-设定初始容量大小,和扩容负载因子
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
this.loadFactor = loadFactor;
this.threshold = tableSizeFor(initialCapacity);
//tableSizeFor:返回大于输入参数且最近的2的整数次幂的数(当这个值大于最大容量MAXIMUM_CAPACITY时,返回MAXIMUM_CAPACITY)。
//注意:此处的initialCapacity为数组table的大小,即bucket的个数。另外此处赋值为this.threshold,是因为构造函数的时候并不会创建table,
//只有实际插入数据的时候才会创建。目的应该是为了节省内存空间。
//在第一次插入数据的时候,会将table的capacity设置为threshold,同时将threshold更新为loadFactor * capacity
}

tableSIzeFor()方法

tableSizeFor的功能:返回大于输入参数且最近的2的整数次幂的数(当这个值大于最大容量MAXIMUM_CAPACITY时,返回MAXIMUM_CAPACITY)。比如10,则返回16。该算法源码如下

1 static final int tableSizeFor(int cap) {
2 int n = cap - 1;
3 n |= n >>> 1;
4 n |= n >>> 2;
5 n |= n >>> 4;
6 n |= n >>> 8;
7 n |= n >>> 16;
8 return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
9 }

先来分析有关n位操作部分:先来假设n的二进制为01xxx...xxx。接着

对n右移1位:001xx...xxx,再位或:011xx...xxx

对n右移2为:00011...xxx,再位或:01111...xxx

此时前面已经有四个1了,再右移4位且位或可得8个1

同理,有8个1,右移8位肯定会让后八位也为1。

综上可得,该算法让最高位的1后面的位全变为1。

最后再让结果n+1,即得到了2的整数次幂的值了。

现在回来看看第一条语句:

int n = cap - 1;

  让cap-1再赋值给n的目的是另找到的目标值大于或等于原值。例如二进制1000,十进制数值为8。如果不对它减1而直接操作,将得到答案10000,即16。显然不是结果。减1后二进制为111,再进行操作则会得到原来的数值1000,即8。

hash(Object key)方法

1 static final int hash(Object key) {
2 int h;
3 return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
4 }

看一个方法indexFor,在jdk1.7中有indexFor(int h, int length)方法。jdk1.8里没有,但原理没变,1.8中用tab[(n - 1) & hash]代替但原理一样。下面看下1.7源码

static int indexFor(int h, int length) {
return h & (length-1);
}

h:key 调用hash()获取的值,length - node数组的长度,即hashmap的capacity容量大小

indexFor这个方法返回的是这个键值对在数组中存储的位置的下标,也就是说下标的结果与hash值有关

而h & (length-1)这个计算有个规律:

当length=8时    下标运算结果取决于哈希值的低三位

当length=16时  下标运算结果取决于哈希值的低四位

当length=32时  下标运算结果取决于哈希值的低五位

当length=2的N次方, 下标运算结果取决于哈希值的低N位

如果hash()方法中不进行>>> 和 ^运算,在大多数情况下,length的值(hashma的容量)小于2^16次方,根据上面的规律,hash值的高16位是没有参与下标的结果的。那么这样子会导致获取的下标不够分散均匀。所以对key.hashCode()进行>>>和^运算后,再去进行h & (length-1)运算,那么高16位就参与了下标的结果

例如1:为了方便验证,假设length为8。HashMap的默认初始容量为16

length = 8;  (length-1) = 7;转换二进制为111;

假设一个key的 hashcode = 78897121 转换二进制:100101100111101111111100001,与(length-1)& 运算如下

0000 0100 1011 0011 1101 1111 1110 0001

&运算

0000 0000 0000 0000 0000 0000 0000 0111

=   0000 0000 0000 0000 0000 0000 0000 0001 (就是十进制1,所以下标为1)

上述运算实质是:001 与 111 & 运算。也就是哈希值的低三位与length与运算。如果让哈希值的低三位更加随机,那么&结果就更加随机,如何让哈希值的低三位更加随机,那么就是让其与高位异或。

3. 原因总结

由于和(length-1)运算,length 绝大多数情况小于2的16次方。所以始终是hashcode 的低16位(甚至更低)参与运算。要是高16位也参与运算,会让得到的下标更加散列。

所以这样高16位是用不到的,如何让高16也参与运算呢。所以才有hash(Object key)方法。让他的hashCode()和自己的高16位^运算。所以(h >>> 16)得到他的高16位与hashCode()进行^运算。

4. 为什么用^而不用&和|

因为&和|都会使得结果偏向0或者1 ,并不是均匀的概念,所以用^。

这就是为什么有hash(Object key)的原因。

3.3.2put方法

public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
} final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab;
      Node<K,V> p;
     int n,
     i;
if ((tab = table) == null || (n = tab.length) == 0)
      //1.首次添加,直接扩容resize()
n = (tab = resize()).length;
      //table 是参数 - transient Node<K,V>[] table;
if ((p = tab[i = (n - 1) & hash]) == null)
      //2.数组该下表没有元素,直接添加
tab[i] = newNode(hash, key, value, null);
else {
      //3.数组该下标有元素
Node<K,V> e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
        //3.1数组该下标元素key与put的key一致(单个元素或者链表的首个元素的key和put的key一致),e取旧的元素,e的value下面回赋值
e = p;
else if (p instanceof TreeNode)
        //3.2数组该下标元素为红黑树-处理
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
        //3.2数组该下标元素为链表且第一个元素与put的key不一致/数组该下标只有一个元素且key与put的key不一致
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) { //当链表下一个元素为null,创建新元素加入链表,结束循环
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
         //链表的下一个元素的key和put的key一致,结束循环
break;
p = e;
}
}
if (e != null) { // e-用于put的key和数组原元素相同时,记录旧的元素,这里吧put的value更新到元素中
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}

3.3.3resize方法

无参构造:默认扩容负载因子0.75

有参构造:最多两个参数 1.cap初始容量  2.loadFactor 扩容因子

虽然传入cap的值,但是没有创建tab,只是对阀值threshold 进行了赋值,赋值为传入cap最近的大于等于cap的2的整数次幂

(这个值会赋值给cap,保证cap是2的整数次幂),这个时候不创建tab(cap=tab.length就还没有确定)是为了节省空间

resize:

第一次扩容:

无参构造创建: cap:2^4  Thr:cap * 0.75

有参构造创建: 初始容量设为阀值阀值设置为threshold = cap * loadFactor

非第一次扩容:2倍扩容(最大值为MAXIMUM_CAPACITY = 1 << 30)

阀值等于threshold = cap * loadFactor

  1 final Node<K,V>[] resize() {
2
3 Node<K,V>[] oldTab = table; //旧的node数组
4
5 int oldCap = (oldTab == null) ? 0 : oldTab.length;//旧的数组容量
6
7 int oldThr = threshold;//旧的扩容阀值
8
9 int newCap, //新的容量
10
11      newThr = 0; //新的扩容阀值
12
13 if (oldCap > 0) { //1.原来容量不为0(不是第一次添加,已经扩过融了)
14
15 if (oldCap >= MAXIMUM_CAPACITY) {
16
17         // static final int MAXIMUM_CAPACITY = 1 << 30;
18
19         //如果旧容量大于等于1^30,扩容机制直接取int最大值2^32
20
21 threshold = Integer.MAX_VALUE;
22
23 return oldTab; //不扩容,直接返回旧的数组
24
25 }
26
27 else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
28
29 oldCap >= DEFAULT_INITIAL_CAPACITY)
30
31 newThr = oldThr << 1; // double threshold
32
33           //static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;
34
35           //如果旧的数组容量2倍小于最大容量并且旧的容量大于默认容量2^4,新的扩容阀值等于旧的2倍
36 }
37
38 else if (oldThr > 0)
39     //2.有参构造常见的第一次扩容 - 初始容量设置为阀值,初始化的时候有参构造传入了容量大小,但是初始化的时候只设置了threshold
40     //的值而没有设置capacity的值,而threshold 取得是大于等于传入容量大小的离他最近的一个2的整次幂的值,
41     //保证threshold 是2的整次幂,此时将threshold 赋值给capacity,保证capacity是2的整次幂
42 newCap = oldThr;
43 else {
44     // 3.无参构造创建的,第一次添加
45 newCap = DEFAULT_INITIAL_CAPACITY; //默认值 2^4
46 newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);//默认值 0.75 * 2 ^ 4 = 12 默认扩容阀值
47 }
48
49 if (newThr == 0) {
50     //扩容阀值等于容量乘以扩容因子最大值为最大int
51 float ft = (float)newCap * loadFactor;
52 newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
53 (int)ft : Integer.MAX_VALUE);
54 }
55
56 threshold = newThr;
57
58 @SuppressWarnings({"rawtypes","unchecked"})
59
60 Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
61
62 table = newTab;
63
64 if (oldTab != null) { //把旧的数组的Node放到新的数组里面
65
66 for (int j = 0; j < oldCap; ++j) {
67
68 Node<K,V> e;
69
70 if ((e = oldTab[j]) != null) { //旧的Node元素不为null时,赋值给e
71
72 oldTab[j] = null;
73
74 if (e.next == null) //Node对象中的参数 Node<K,V> next为null,说明这里不是链表结构,原数组这里只有一个元素
75
76 newTab[e.hash & (newCap - 1)] = e;//e放入新数组中
77
78 else if (e instanceof TreeNode)//如果是红黑树树形结构,红黑树的重定位;
79
80
81
82 ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
83
84 else { //当前是链表
85
86 Node<K,V> loHead = null, //用于接收新数组中下标为j这里的元素
87
88               loTail = null;
89
90 Node<K,V> hiHead = null, //用于结束新数组中下标为j+oldcap的元素
91
92               hiTail = null;
93
94 Node<K,V> next;
95
96 do {
97
98 next = e.next;
99
100 if ((e.hash & oldCap) == 0) { //处理新数组中下表为j的,这里结果是0的话,代表e.hash/oldcap的结果是2的倍数+余数,扩容后e.hash/2oldcap,余数不变,所以下标不变
101
102 if (loTail == null)
103
104 loHead = e;
105
106 else
107
108 loTail.next = e;
109
110 loTail = e;
111
112 }
113
114 else { //用于处理新数组中下表为j+capacity的
115
116 if (hiTail == null)
117
118 hiHead = e;
119
120 else
121
122 hiTail.next = e;
123
124 hiTail = e;
125
126 }
127
128 } while ((e = next) != null);
129
130 if (loTail != null) { //新数组线标j元素赋值
131
132 loTail.next = null;
133
134 newTab[j] = loHead;
135
136 }
137
138 if (hiTail != null) {//新数组线标j+oldcap元素赋值
139
140 hiTail.next = null;
141
142 newTab[j + oldCap] = hiHead;
143
144 }
145
146 }
147
148 }
149
150 }
151
152 }
153
154 return newTab;
155
156 }

resize链表处理关键代码

 1                  if ((e.hash & oldCap) == 0) {
2 if (loTail == null)
3 loHead = e;
4 else
5 loTail.next = e;
6 loTail = e;
7 }
8 else {
9 if (hiTail == null)
10 hiHead = e;
11 else
12 hiTail.next = e;
13 hiTail = e;
14 }
15 } while ((e = next) != null);

如上图:

  

  链表处理,把链表分为了两块,1.在型数组中与原数组下标相同的,2.在型数组中下标=原数组下标+原数组容量,我们只看第一种:新数组中下标和原数组中相同的(第二种是一样的):

  加入现在这个链表有4节   e1-e2-e3-e4,

  if ((e.hash & oldCap) == 0)这个判断分别是true true false true,

  那么参数e lohead lotail在上图中4次判断的内存情况分别为 黑色  绿色  黄色  红色

    第一次true, e指向 e1 lohead 指向e1 lotail指向e1

    第二次true, e = next指向e2 ,loTail.next = e---litail原指向e1,相当于e1.next=e=e2;loTail = e;lotail指向e2

    第三次false,e = next指向e3,其它两个不变

    第四次true:e = next指向e4,loTail.next = e --- lotail原指向e2,相当于e2.next = e = e4,loTail = e;lotail指向e4

    那么最终的结果就是红色的线条,我们看lohead的指向lohead = e1    e1.next = e2    e2.next = e4,所以它现在是e1-e2-e4,就是我们想要的结果。

3.4Cloneable和Serializable分析

在HashMap的定义中实现了Cloneable接口,Cloneable是一个标识接口,主要用来标识 Object.clone()的合法性,在没有实现此接口的实例中调用 Object.clone()方法会抛出CloneNotSupportedException异常。可以看到HashMap中重写了clone方法。

HashMap实现Serializable接口主要用于支持序列化。同样的Serializable也是一个标识接口,本身没有定义任何方法和属性。另外HashMap自定义了

private void writeObject(java.io.ObjectOutputStream s) throws IOException

private void readObject(java.io.ObjectInputStream s) throws IOException, ClassNotFoundException

两个方法实现了自定义序列化操作。

注意:支持序列化的类必须有无参构造函数。这点不难理解,反序列化的过程中需要通过反射创建对象。

4.HashMap线程不安全问题

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null) // 如果没有hash碰撞则直接插入元素
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}

其中第6行代码是判断是否出现hash碰撞,假设两个线程A、B都在进行put操作,并且hash函数计算出的插入下标是相同的,当线程A执行完第六行代码后由于时间片耗尽导致被挂起,而线程B得到时间片后在该下标处插入了元素,完成了正常的插入,然后线程A获得时间片,由于之前已经进行了hash碰撞的判断,所有此时不会再进行判断,而是直接进行插入,这就导致了线程B插入的数据被线程A覆盖了,从而线程不安全。

除此之前,还有就是代码的倒数第4行处有个++size,我们这样想,还是线程A、B,这两个线程同时进行put操作时,假设当前HashMap的zise大小为10,当线程A执行到第38行代码时,从主内存中获得size的值为10后准备进行+1操作,但是由于时间片耗尽只好让出CPU,线程B快乐的拿到CPU还是从主内存中拿到size的值10进行+1操作,完成了put操作并将size=11写回主内存,然后线程A再次拿到CPU并继续执行(此时size的值仍为10),当执行完put操作后,还是将size=11写回内存,此时,线程A、B都执行了一次put操作,但是size的值只增加了1,所有说还是由于数据覆盖又导致了线程不安全。

HashMap简要介绍的更多相关文章

  1. 简要介绍BASE64、MD5、SHA、HMAC几种方法。

    加密解密,曾经是我一个毕业设计的重要组件.在工作了多年以后回想当时那个加密.解密算法,实在是太单纯了.     言归正传,这里我们主要描述Java已经实现的一些加密解密算法,最后介绍数字证书.     ...

  2. Java 集合系列10之 HashMap详细介绍(源码解析)和使用示例

    概要 这一章,我们对HashMap进行学习.我们先对HashMap有个整体认识,然后再学习它的源码,最后再通过实例来学会使用HashMap.内容包括:第1部分 HashMap介绍第2部分 HashMa ...

  3. Java 集合系列 09 HashMap详细介绍(源码解析)和使用示例

    java 集合系列目录: Java 集合系列 01 总体框架 Java 集合系列 02 Collection架构 Java 集合系列 03 ArrayList详细介绍(源码解析)和使用示例 Java ...

  4. [转]Android系统Surface机制的SurfaceFlinger服务简要介绍和学习计划

    转自:Android系统Surface机制的SurfaceFlinger服务简要介绍和学习计划 前面我们从Android应用程序与SurfaceFlinger服务的关系出发,从侧面简单学习了Surfa ...

  5. [转] Android资源管理框架(Asset Manager)简要介绍和学习计划

    转自:http://blog.csdn.net/luoshengyang/article/details/8738877 Android应用程序主要由两部分内容组成:代码和资源.资源主要就是指那些与U ...

  6. Activity启动过程简要介绍

    无论是通过点击应用程序图标来启动Activity,还是通过Activity内部调用startActivity接口来启动新的Activity,都要借助于应用程序框架层的ActivityManagerSe ...

  7. Android应用程序的Activity启动过程简要介绍和学习计划

    文章转载至CSDN社区罗升阳的安卓之旅,原文地址:http://blog.csdn.net/luoshengyang/article/details/6685853 在Android系统中,Activ ...

  8. Dalvik虚拟机简要介绍和学习计划

    文章转载至CSDN社区罗升阳的安卓之旅,原文地址:http://blog.csdn.net/luoshengyang/article/details/8852432 我们知道,Android应用程序是 ...

  9. Android资源管理框架(Asset Manager)简要介绍和学习计划

    文章转载至CSDN社区罗升阳的安卓之旅,原文地址:http://blog.csdn.net/luoshengyang/article/details/8738877 Android应用程序主要由两部分 ...

  10. Android应用程序组件Content Provider简要介绍和学习计划

    文章转载至CSDN社区罗升阳的安卓之旅,原文地址:http://blog.csdn.net/luoshengyang/article/details/6946067 在Android系统中,Conte ...

随机推荐

  1. 【初赛】CSP 2020 第一轮(初赛)模拟记录

    感觉初赛不过关,洛谷上找了一套没做过的来练习. 顺便写了详细的题解. 试题用时:1h 单项选择: 第 1 题 十进制数 114 的相反数的 8 位二进制补码是: A.10001110 B.100011 ...

  2. 4.mysql-进阶

    1.事务 将多个操作步骤变成一个事务,任何一个步骤失败,则回滚到事务的所有步骤之前状态,大白话:要成功都成功:要失败都失败. 如转账操作,A扣钱.B收钱,必须两个步骤都成功,才认为转账成功 innod ...

  3. lvm+xfs的扩缩容

    ext4文件系统可以经行扩缩容操作,但xfs的文件系统只能扩容,无法缩容 所以如果需要进行xfs的缩容,可以先使用xfsdump备份文件系统,然后对逻辑卷(/分区)进行缩容操作(此时原xfs文件系统会 ...

  4. i春秋Fuzz

    点开只有三个单词plz fuzz parameter 大概意思就是让我们疯狂尝试参数... 我们通过url尝试传入参数 ?user=123 ?name=123 ?username=123 ?id=12 ...

  5. 写一个linux平台的桌面宠物

    效果图 前言 我一直在用python 写一下有趣的东西,让编程不那么无聊,之前一直有写一个桌面宠物的想法,无奈这些都是依赖资源文件,没有图片资源没办法写里面的逻辑,直到我看见了 shimiji手机桌面 ...

  6. Redis Lettuce长时间超时问题

    1. 背景 新上线了一个服务,在压测的时候大量返回错误,查看报错是io.lettuce.core.RedisCommandTimeoutException: Command timed out aft ...

  7. 03.Javascript学习笔记2

    1.逻辑运算符 在javascript中与或非对应的逻辑运算符是: && || ! const a = true; const b = false; console.log(a &am ...

  8. Linux 下使用Docker 安装 LNMP环境 超详细

    首先在阿里云购买了一台服务器 选择了华南-深圳地区 操作系统选用了 CentOS8.0 64位 1. 初始化账号密码 登陆xshell,开始装Docker 一.安装docker 1.Docker 要求 ...

  9. MySQL字符编码、存储引擎、严格模式、字段类型之浮点 字符串 枚举与集合 日期类型

    目录 字符编码与配置文件 数据路储存引擎 创建表的完整语法 字段类型之整型 严格模式 字段类型之浮点型 字段类型之字符串类型 数字的含义 字段类型之枚举与集合 字段类型之日期类型 字符编码与配置文件 ...

  10. misc之套娃编码解密

    题目: 01100101 01100110 00100000 01100010 01100101 00100000 00111001 01100110 00100000 01100011 011001 ...