HashMap继承自抽象类AbstractMap,抽象类AbstractMap实现了Map接口。关系图例如以下所看到的:

Java中的Map<key, value>接口同意我们将一个对象作为key。也就是能够用一个对象作为key去查找还有一个对象。
在我们探讨HashMap的实现原理之前,我们先自己实现了一个SimpleMap类,该类继承自AbstractMap类。

详细实现例如以下:

  1. import java.util.*;
  2.  
  3. public class SimpleMap<K,V> extends AbstractMap<K,V> {
  4. //keys存储全部的键
  5. private List<K> keys = new ArrayList<K>();
  6. //values存储全部的值
  7. private List<V> values = new ArrayList<V>();
  8.  
  9. /**
  10. * 该方法获取Map中全部的键值对
  11. */
  12. @Override
  13. public Set entrySet() {
  14. Set<Map.Entry<K, V>> set = new SimpleSet<Map.Entry<K,V>>();
  15.  
  16. //keys的size和values的size应该一直是一样大的
  17. Iterator<K> keyIterator = keys.iterator();
  18. Iterator<V> valueIterator = values.iterator();
  19. while(keyIterator.hasNext() && valueIterator.hasNext()){
  20. K key = keyIterator.next();
  21. V value = valueIterator.next();
  22. SimpleEntry<K,V> entry = new SimpleEntry<K,V>(key, value);
  23. set.add(entry);
  24. }
  25.  
  26. return set;
  27. }
  28.  
  29. @Override
  30. public V put(K key, V value) {
  31. V oldValue = null;
  32. int index = this.keys.indexOf(key);
  33. if(index >= 0){
  34. //keys中已经存在键key,更新key相应的value
  35. oldValue = this.values.get(index);
  36. this.values.set(index, value);
  37. }else{
  38. //keys中不存在键key,将key和value作为键值对加入进去
  39. this.keys.add(key);
  40. this.values.add(value);
  41. }
  42. return oldValue;
  43. }
  44.  
  45. @Override
  46. public V get(Object key) {
  47. V value = null;
  48. int index = this.keys.indexOf(key);
  49. if(index >= 0){
  50. value = this.values.get(index);
  51. }
  52. return value;
  53. }
  54.  
  55. @Override
  56. public V remove(Object key) {
  57. V oldValue = null;
  58. int index = this.keys.indexOf(key);
  59. if(index >= 0){
  60. oldValue = this.values.get(index);
  61. this.keys.remove(index);
  62. this.values.remove(index);
  63. }
  64. return oldValue;
  65. }
  66.  
  67. @Override
  68. public void clear() {
  69. this.keys.clear();
  70. this.values.clear();
  71. }
  72.  
  73. @Override
  74. public Set keySet() {
  75. Set<K> set = new SimpleSet<K>();
  76. Iterator<K> keyIterator = this.keys.iterator();
  77. while(keyIterator.hasNext()){
  78. set.add(keyIterator.next());
  79. }
  80. return set;
  81. }
  82.  
  83. @Override
  84. public int size() {
  85. return this.keys.size();
  86. }
  87.  
  88. @Override
  89. public boolean containsValue(Object value) {
  90. return this.values.contains(value);
  91. }
  92.  
  93. @Override
  94. public boolean containsKey(Object key) {
  95. return this.keys.contains(key);
  96. }
  97.  
  98. @Override
  99. public Collection values() {
  100. return this.values();
  101. }
  102.  
  103. }

当子类继承自AbstractMap类时,我们仅仅须要实现AbstractMap类中的entrySet方法和put方法就可以,entrySet方法是用来返回该Map全部键值对的一个Set,put方法是实现将一个键值对放入到该Map中。
大家能够看到。我们上面的代码不仅除了实现entrySet和put方法外,我们还重写了get、remove、clear、keySet、values等诸多方法。

事实上我们仅仅要重写entrySet和put方法,该类就能够正确执行。那我们为什么还要重写剩余的那些方法呢?AbstractMap这种方法做了非常多处理操作。Map中的非常多方法在AbstractMap都实现了,而且非常多方法都依赖于entrySet方法,举个样例。Map接口中的values方法是让我们返回该Map中全部的值的Collection。我们能够看一下AbstractMap中对values方法的实现:

  1. public Collection<V> values() {
  2. if (values == null) {
  3. values = new AbstractCollection<V>() {
  4. public Iterator<V> iterator() {
  5. return new Iterator<V>() {
  6. private Iterator<Entry<K,V>> i = entrySet().iterator();
  7.  
  8. public boolean hasNext() {
  9. return i.hasNext();
  10. }
  11.  
  12. public V next() {
  13. return i.next().getValue();
  14. }
  15.  
  16. public void remove() {
  17. i.remove();
  18. }
  19. };
  20. }
  21.  
  22. public int size() {
  23. return AbstractMap.this.size();
  24. }
  25.  
  26. public boolean isEmpty() {
  27. return AbstractMap.this.isEmpty();
  28. }
  29.  
  30. public void clear() {
  31. AbstractMap.this.clear();
  32. }
  33.  
  34. public boolean contains(Object v) {
  35. return AbstractMap.this.containsValue(v);
  36. }
  37. };
  38. }
  39. return values;
  40. }

大家能够看到。代码不少。主要的思路是先通过entrySet生成包括全部键值对的Set,然后通过迭代获取当中的value值。当中生成包括全部键值对的Set肯定须要开销。所以我们在自己的实现里面重写了values方法,就一句话,return this.values,直接返回我们的values字段。

所以我们重写大部分方法的目的都是让方法的实现更快更简洁。

大家还须要注意一下,我们在重写entrySet方法时,须要返回一个包括当前Map全部键值对的Set。首先键值对时一种类型,全部的键值对类都要实现Map.Entry<K,V>这个接口。其次,因为entrySet要让我们返回一个Set,这里我们没有使用Java中已有的Set类型(比方HashSet、TreeSet),有双方面的原因:
1. Java中HashSet这个类内部其有用HashMap实现的,本博客的目的就是要研究HashMap,所以我们不用此类。
2. Java中Set的实现也不是非常麻烦,自己实现一下AbstractSet,加深一下对Set的理解。

下面是我们自己实现的键值对类SimpleEntry。实现了Map.Entry<K,V>接口,代码例如以下:

  1. import java.util.Map;
  2.  
  3. //Map中存储的键值对,键值对须要实现Map.Entry这个接口
  4. public class SimpleEntry<K,V> implements Map.Entry<K, V>{
  5.  
  6. private K key = null;//键
  7.  
  8. private V value = null;//值
  9.  
  10. public SimpleEntry(K k, V v){
  11. this.key = k;
  12. this.value = v;
  13. }
  14.  
  15. @Override
  16. public K getKey() {
  17. return this.key;
  18. }
  19.  
  20. @Override
  21. public V getValue() {
  22. return this.value;
  23. }
  24.  
  25. @Override
  26. public V setValue(V v) {
  27. V oldValue = this.value;
  28. this.value = v;
  29. return oldValue;
  30. }
  31.  
  32. }

下面是我们自己实现的集合类SimpleSet,继承自抽象类AbstractSet<K,V>,代码例如以下:

  1. import java.util.AbstractSet;
  2. import java.util.ArrayList;
  3. import java.util.Iterator;
  4.  
  5. public class SimpleSet<E> extends AbstractSet<E> {
  6.  
  7. private ArrayList<E> list = new ArrayList<E>();
  8.  
  9. @Override
  10. public Iterator<E> iterator() {
  11. return this.list.iterator();
  12. }
  13.  
  14. @Override
  15. public int size() {
  16. return this.list.size();
  17. }
  18.  
  19. @Override
  20. public boolean contains(Object o) {
  21. return this.list.contains(o);
  22. }
  23.  
  24. @Override
  25. public boolean add(E e) {
  26. boolean isChanged = false;
  27. if(!this.list.contains(e)){
  28. this.list.add(e);
  29. isChanged = true;
  30. }
  31. return isChanged;
  32. }
  33.  
  34. @Override
  35. public boolean remove(Object o) {
  36. return this.list.remove(o);
  37. }
  38.  
  39. @Override
  40. public void clear() {
  41. this.list.clear();
  42. }
  43.  
  44. }

我们測试下我们写的SimpleMap这个类,測试包括两部分。一部分是測试我们写的SimpleMap是不是正确,第二部分測试性能怎样,測试代码例如以下:

  1. import java.util.HashMap;
  2. import java.util.HashSet;
  3. import java.util.Map;
  4.  
  5. public class Test {
  6.  
  7. public static void main(String[] args) {
  8. //測试SimpleMap的正确性
  9. SimpleMap<String, String> map = new SimpleMap<String, String>();
  10. map.put("iSpring", "27");
  11. System.out.println(map);
  12. System.out.println(map.get("iSpring"));
  13. System.out.println("-----------------------------");
  14.  
  15. map.put("iSpring", "28");
  16. System.out.println(map);
  17. System.out.println(map.get("iSpring"));
  18. System.out.println("-----------------------------");
  19.  
  20. map.remove("iSpring");
  21. System.out.println(map);
  22. System.out.println(map.get("iSpring"));
  23. System.out.println("-----------------------------");
  24.  
  25. //測试性能怎样
  26. testPerformance(map);
  27. }
  28.  
  29. public static void testPerformance(Map<String, String> map){
  30. map.clear();
  31.  
  32. for(int i = 0; i < 10000; i++){
  33. String key = "key" + i;
  34. String value = "value" + i;
  35. map.put(key, value);
  36. }
  37.  
  38. long startTime = System.currentTimeMillis();
  39.  
  40. for(int i = 0; i < 10000; i++){
  41. String key = "key" + i;
  42. map.get(key);
  43. }
  44.  
  45. long endTime = System.currentTimeMillis();
  46.  
  47. long time = endTime - startTime;
  48.  
  49. System.out.println("遍历时间:" + time + "毫秒");
  50. }
  51.  
  52. }

输出结果例如以下:
{iSpring=27}
27
-----------------------------
{iSpring=28}
28
-----------------------------
{}
null
-----------------------------
遍历时间:956毫秒

从结果里面我们看到输出结果是正确的。也就是我们写的SimpleMap基本实现都是对的。我们往Map中插入了10000个键值对。我们測试的是从Map中取出这10000条键值对的性能开销。也就是測试Map的遍历的性能开销,结果是956毫秒。
没有对照就不知性能强弱,我们測试下HashMap读取这10000条键值对的时间开销,測试方法全然一样。仅仅是我们传入的是HashMap的实例。測试代码例如以下:
  1. //创建HashMap的实例
  2. HashMap<String, String> map = new HashMap<String, String>();
  3.  
  4. //測试性能怎样
  5. testPerformance(map);

測试结果例如以下:
遍历时间:32毫秒
我去,不比不知道,一比吓一跳啊。HashMap比我们自己实现的SimpleMap快的那不是一点半点啊。为什么我们的SimpleMap性能这么差?而HashMap的性能如此高呢?我们分别研究。
首先分析SimpleMap性能为什么这么差。

我们的SimpleMap是用ArrayList来存储keys和values的。ArrayList本质是用数组实现的,我们的SimpleMap的get方法是这样实现的:

  1. @Override
  2. public V put(K key, V value) {
  3. V oldValue = null;
  4. int index = this.keys.indexOf(key);
  5. if(index >= 0){
  6. //keys中已经存在键key,更新key相应的value
  7. oldValue = this.values.get(index);
  8. this.values.set(index, value);
  9. }else{
  10. //keys中不存在键key,将key和value作为键值对加入进去
  11. this.keys.add(key);
  12. this.values.add(value);
  13. }
  14. return oldValue;
  15. }

须要性能开销的主要是this.keys.indexOf(key)这句代码,这句代码从ArrayList中查找指定元素的索引,本质就是从数组开头走,往后找。直至数组的末尾。例如以下图所看到的:

这样从头開始查找,而且每次在遍历元素的时候。都须要调用元素的equals方法。所以从头開始查找就会导致调用非常多次equals方法,这就造成了SimpleMap效率低下。比方我们将全国的车辆放入到SimpleMap中时,我们是依次将车辆放到ArrayList的最后面,依次往后插入值,车牌号就相当于key,车辆就好比是value,所以SimpleMap中有两个长度非常长的ArrayList,分别存储keys和values,假设要在该SimpleMap中查找一辆车,车牌是"鲁E.DE829",那假设用ArrayList查找的话就要从全国的的全部车辆中去查找了,这样太慢。
那么HashMap为何效率如此高呢?
HashMap比較聪明。大家能够看看HashMash.java的源代码。HashMap把里面的元素分类放置了。还拿上面依据车牌号查找车辆的样例来说,当把我们把车辆往HashMap里面放的时候,HashMap将它们分类处理了,首先来一辆车的时候,先看其车牌号。比方车牌号是"鲁E.DE829"。一看是鲁,就知道是山东的车辆,那么HashMap就开辟了一块空间。专门放山东的车,就把这辆车放到这块山东专属的区间了。下次又要向HashMap放入一辆车牌号为“浙A.GX588",HashMap一看是浙江的车。就将这辆车放入到浙江的专属区间了。依次类推。说的再通俗点,假设我们有一种非常大的桶。该桶就是相应的区间,能够装下非常多车,例如以下图所看到的:
当我们从HashMap中依据车牌号查找指定的车辆时,比方查找车牌号为为"鲁E.DE829"的车,当调用HashMap的get方法时,HashMap一看车牌号是鲁。那么HashMap就去标为鲁的那个大桶。也就是山东区间去找这辆车了。这样就没有必要从全国的车辆中挨个找这辆车了。这就大大缩短了查找空间。提高了效率。
我们能够看看HashMap.java中详细的源代码实现,HashMap中用一个名为table的字段存储着一个Entry数组,table存储着HashMap里面的全部键值对,每一个键值对都是一个Entry对象。每一个Entry对象都存储着一个key和value,除此之外每一个Entry内部还存着一个next字段,next也是Entry类型。数组table的默认长度是DEFAULT_INITIAL_CAPACITY,即初始长度为16,当容器须要很多其它的空间存取Entry时,它会自己主动扩容。
下面是HashMap的put方法的源代码实现:
  1. public V put(K key, V value) {
  2. if (key == null)
  3. return putForNullKey(value);
  4. int hash = hash(key.hashCode());
  5. int i = indexFor(hash, table.length);
  6. for (Entry<K,V> e = table[i]; e != null; e = e.next) {
  7. Object k;
  8. if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
  9. V oldValue = e.value;
  10. e.value = value;
  11. e.recordAccess(this);
  12. return oldValue;
  13. }
  14. }
  15.  
  16. modCount++;
  17. addEntry(hash, key, value, i);
  18. return null;
  19. }

在put方法中,。调用了对象的hashCode方法,该方法返回一个int类型的值。是个初始的哈希值,这个值就相当于车牌号,比如"鲁E.DE829",HashMap中有个hash方法。该hash方法将我们得到的初始的哈希值做进一步处理。得到终于的哈希值,就好比我们将车牌号传入hash方法。然后返回该存放车辆的大桶,即返回"鲁",这样HashMap就把这辆车放到标有“鲁”的大桶里面了。上面说到的hash方法叫做哈希函数。专门负责依据传入的值返回指定的终于哈希值。详细实现例如以下:

  1. static int hash(int h) {
  2. // This function ensures that hashCodes that differ only by
  3. // constant multiples at each bit position have a bounded
  4. // number of collisions (approximately 8 at default load factor).
  5. h ^= (h >>> 20) ^ (h >>> 12);
  6. return h ^ (h >>> 7) ^ (h >>> 4);
  7. }

能够看出来。HashMap中主要是通过位操作符实现哈希函数的。

这里简单说一下哈希函数,哈希函数有多种实现方式。比方最简单的就是取余法,比方对i%10取余,然后依照余数创建不同的区块或桶。

比方有100个数,各自是从1到100,那么分别对10取余,那么就能够把这100个数放到10个桶子里面了,这就是所谓的哈希函数。

仅仅只是HashMap中的hash函数看起来比較复杂,进行的是位操作,可是其作用与简单的取余哈希法的作用是等价的。就是把元素分类放置。

详细将键值对放入到HashMap中的方法是addEntry。代码例如以下:
  1. void addEntry(int hash, K key, V value, int bucketIndex) {
  2. Entry<K,V> e = table[bucketIndex];
  3. table[bucketIndex] = new Entry<>(hash, key, value, e);
  4. if (size++ >= threshold)
  5. resize(2 * table.length);
  6. }

键值对都是Map.Entry<K,V>对象,而且Map.Entry具有next字段,也就是桶里面的元素都是通过单向链表的形式将Map.Entry串连起来的。这样我们就能够从桶上的第一个元素通过next依次遍历完桶里面全部的元素。比方桶中有例如以下键值对:
桶-->e1-->e2-->e3-->e4-->e5-->e6-->e7-->e8-->e9-->...
addEntry代码首先取出桶里面的第一个键值对e1,然后将新的键值对e置于桶中第一个元素的位置。然后将键值对e1放置于新键值对e后面,放置完之后,桶中新的键值对例如以下:
桶-->e-->e1-->e2-->e3-->e4-->e5-->e6-->e7-->e8-->e9-->...
这样就把新的键值对放到了桶中了。也就将键值对放到HashMap中了。
那么当我们从HashMap中查找某个键值对时,怎么查找呢?原理与我们将键值对放入HashMap类似。下面是HashMap的get方法的源代码实现:

  1. public V get(Object key) {
  2. if (key == null)
  3. return getForNullKey();
  4. int hash = hash(key.hashCode());
  5. for (Entry<K,V> e = table[indexFor(hash, table.length)];
  6. e != null;
  7. e = e.next) {
  8. Object k;
  9. if (e.hash == hash && ((k = e.key) == key || key.equals(k)))
  10. return e.value;
  11. }
  12. return null;
  13. }

在get方法中。也是先调用了对象的hashCode方法。就相当于车牌号,然后再将该值让hash函数处理得到终于的哈希值,也就是桶的索引。

然后我们再去这个标有“鲁”的桶里面去找我们的键值对,首先先取出桶里面第一个键值对,比对一下是不是我们要找的元素,假设是就直接返回了。假设不是就通过键值对的next顺藤摸瓜通过单向链表继续找下去,直至找到。  例如以下图所看到的:

下面我们再写一个Car类。该类有一个字段String类型的字段num。而且我们重写了Car的equals方法,我们觉得仅仅要车牌号相等就觉得这是同一辆车。代码例如以下所看到的:
  1. import java.util.HashMap;
  2.  
  3. public class Car {
  4.  
  5. private final String num;//车牌号
  6.  
  7. public Car(String n){
  8. this.num = n;
  9. }
  10.  
  11. public String getNum(){
  12. return this.num;
  13. }
  14.  
  15. @Override
  16. public boolean equals(Object obj) {
  17. if(obj == null){
  18. return false;
  19. }
  20. if(obj instanceof Car){
  21. Car car = (Car)obj;
  22. return this.num.equals(car.num);
  23. }
  24. return false;
  25. }
  26.  
  27. public static void main(String[] args){
  28. HashMap<Car, String> map = new HashMap<Car, String>();
  29. String num = "鲁E.DE829";
  30. Car car1 = new Car(num);
  31. Car car2 = new Car(num);
  32. System.out.println("Car1 hash code: " + car1.hashCode());
  33. System.out.println("Car2 hash code: " + car2.hashCode());
  34. System.out.println("Car1 equals Car2: " + car1.equals(car2));
  35. map.put(car1, new String("Car1"));
  36. map.put(car2, new String("Car2"));
  37. System.out.println("map.size(): " + map.size());
  38. }
  39.  
  40. }

我们在main函数中写了一些測试代码,我们创建了一个HashMap。该HashMap的用Car作为键,用字符串作为值。我们用同一个字符串实例化了两个Car,分别为car1和car2,然后将这两个car都放入到HashMap中,输出结果例如以下:

Car1 hash code: 404267176
Car2 hash code: 2027651571
Car1 equals Car2: true
map.size(): 2
从结果能够看出来。Car1和Car2是相等的,既然二者是相等的,也就是两者作为键来说是相等的键,所以HashMap里面仅仅能放当中一个作为键,可是实际结果中map的长度却是2个,为什么会这样呢?关键在于Car的hashCode方法,准确的说是Object的hashCode方法。Object的hashCode方法默认情况下返回的是对象内存地址。因为内存地址是唯一的。
我们没有重写Car的hashCode方法。所以car1的hashCode返回的值和car2的hashCode返回的值肯定不同。

通过我们前面研究可知,假设是两个元素相等。那么这两个元素应该放到同一个HashMap的桶里。可是因为我们的car1和car2的hashCode不同,所以HashMap将car1和car2分别放到不同的桶子里面了,这就出问题了。相等(equals)的两个元素(car1和car2)假设hashCode返回值不同,那么这两个元素就会放到HashMap不同的区间里面。

所以我们写代码的时候要保证相互equals的两个对象的哈希值必然要相等。即必须保证hashCode的返回值相等。那怎样解决问题?我们仅仅须要重写hashCode方法就可以,代码例如以下:

  1. @Override
  2. public int hashCode() {
  3. return this.num.hashCode();
  4. }

又一次执行main中的測试代码,输出结果例如以下:

Car1 hash code: 607836628
Car2 hash code: 607836628
Car1 equals Car2: true
map.size(): 1
之前我们说了,相互equals的对象必须返回同样的哈希值,同样哈希值的对象都在一个桶里面。可是反过来。具有同样哈希值的对象(也就是在同一个桶里面的对象)不必相互equals。

总结:
1. HashMap为了提高查找的效率使用了分块查找的原理。对象的hashCode返回的哈希值进行进一步处理,这样就有规律的把不同的元素放到了不同的区块或桶中。

下次查找该对象的时候,还是计算其哈希值,依据哈希值确定区块或桶,然后在这个小范围内查找元素,这样就快多了。

2. 假设重写了equals方法,那么必须重写hashCode方法,保证假设两个对象相互equals。那么二者的hashCode的返回值必然相等。

3. 假设两个对象的hashCode返回值相等,这两个对象不必是equals的。

深入理解Java中的HashMap的实现原理的更多相关文章

  1. Java中的HashMap的工作原理是什么?

    问答题23 /120 Java中的HashMap的工作原理是什么? 参考答案 Java中的HashMap是以键值对(key-value)的形式存储元素的.HashMap需要一个hash函数,它使用ha ...

  2. JDK学习---深入理解java中的HashMap、HashSet底层实现

    本文参考资料: 1.<大话数据结构> 2.http://www.cnblogs.com/dassmeta/p/5338955.html 3.http://www.cnblogs.com/d ...

  3. JDK学习---深入理解java中的LinkedList

    本文参考资料: 1.<大话数据结构> 2.http://blog.csdn.net/jzhf2012/article/details/8540543 3.http://blog.csdn. ...

  4. 理解Java中的弱引用(Weak Reference)

    本篇文章尝试从What.Why.How这三个角度来探索Java中的弱引用,理解Java中弱引用的定义.基本使用场景和使用方法.由于个人水平有限,叙述中难免存在不准确或是不清晰的地方,希望大家可以指出, ...

  5. 深入理解Java中的不可变对象

    深入理解Java中的不可变对象 不可变对象想必大部分朋友都不陌生,大家在平时写代码的过程中100%会使用到不可变对象,比如最常见的String对象.包装器对象等,那么到底为何Java语言要这么设计,真 ...

  6. 沉淀再出发:java中的HashMap、ConcurrentHashMap和Hashtable的认识

    沉淀再出发:java中的HashMap.ConcurrentHashMap和Hashtable的认识 一.前言 很多知识在学习或者使用了之后总是会忘记的,但是如果把这些只是背后的原理理解了,并且记忆下 ...

  7. 关于Java中的HashMap的深浅拷贝的测试与几点思考

    0.前言 工作忙起来后,许久不看算法,竟然DFA敏感词算法都要看好一阵才能理解...真是和三阶魔方还原手法一样,田园将芜,非常可惜啊. 在DFA算法中,第一步是需要理解它的数据结构,在此基础上,涉及到 ...

  8. java 中遍历hashmap 和hashset 的方法

    一.java中遍历hashmap:    for (Map.Entry<String, Integer> entry : tempMap.entrySet()) {     String ...

  9. Java中关于HashMap的元素遍历的顺序问题

    Java中关于HashMap的元素遍历的顺序问题 今天在使用如下的方式遍历HashMap里面的元素时 1 for (Entry<String, String> entry : hashMa ...

随机推荐

  1. 03008_ServletContext

    1.什么是ServletContext? (1)ServletContext代表是一个web应用的环境(上下文)对象,ServletContext对象    内部封装是该web应用的信息,Servle ...

  2. 如何理解redo和undo的作用

    目录 如何理解redo和undo的作用 redo undo UNDO和REDO的区别 如何理解redo和undo的作用 redo 重做日志(redo)包含所有数据产生的历史改变记录,是oracle在线 ...

  3. webdriver高级应用- 禁止IE的保护模式

    #encoding=utf-8 from selenium import webdriver from selenium.webdriver.common.desired_capabilities i ...

  4. Selenium WebDriver- 操作JavaScript的prompt弹窗(使用率低)

    #encoding=utf-8 import unittest import time from selenium import webdriver from selenium.webdriver i ...

  5. 爬取本blog的所有标题和链接

    #coding=utf-8 from bs4 import BeautifulSoup import urllib.request for i in range(1,54): url = " ...

  6. oracle中xhost报错

    一.命令找不到 xhost:command not found yum whatprovides "*/xhost" Loaded plugins: product-id, sec ...

  7. HDU1071 The area

    Ignatius bought a land last week, but he didn't know the area of the land because the land is enclos ...

  8. [Codeforces Round #297 Div. 2] E. Anya and Cubes

    http://codeforces.com/contest/525/problem/E 学习了传说中的折半DFS/双向DFS 先搜前一半数,记录结果,然后再搜后一半数,匹配之前结果. #include ...

  9. NOI热身赛A. 小w、小j和小z

    $n \leq 100000$个点在数轴上运动,给初始位置和速度.能删$k$个点,问最晚什么时候发生第一次碰撞. 这个贪心题有点惊.. 首先肯定二分答案,然后就是判断怎么删这$k$个点.我想可以把有冲 ...

  10. hdu 5971 Wrestling Match 二分图染色

    题目链接 题意 \(n\)人进行\(m\)场比赛,给定\(m\)场比赛的双方编号:再给定已知的为\(good\ player\)的\(x\)个人的编号,已知的为\(bad\ player\)的\(y\ ...