一.前言

  HashMap也是我们使用非常多的Collection,它是基于哈希表的 Map 接口的实现,以key-value的形式存在。在HashMap中,key-value总是会当做一个整体来处理,系统会根据hash算法来来计算key-value的存储位置,我们总是可以通过key快速地存、取value。

二.特点和常见问题

  • 非线程安全
  • hashMap的映射不是有序的
  • key、value都可以为null
  • key 不能重复

二.接口定义

  查看源码. HashMap实现了Map接口,继承AbstractMap。

  1. public class HashMap<K,V>
  2. extends AbstractMap<K,V>
  3. implements Map<K,V>, Cloneable, Serializable

三.初始化构造函数

  HashMap提供了三个构造函数:

1.HashMap();

  构造一个具有默认初始容量 (16) 和默认加载因子 (0.75) 的空 HashMap。

  1. /**
  2. * Constructs an empty <tt>HashMap</tt> with the default initial capacity
  3. * (16) and the default load factor (0.75).
  4. * 一个空的构造方法,默认初始化容量16,负载因子0.75
  5. */
  6. public HashMap() {
  7. this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
  8. }

  负载因子: loadFactor是map的负载因子,要大于0,且是非无穷大的数字.

  有这样一个公式:initailCapacity*loadFactor=HashMap的容量.

  比如,泛型是String,String 类型的空的HashMap.(泛型: 集合内元素的类型)

  1. HashMap map1 = new HashMap<String,String>();

2.HashMap(int initialCapacity);

  构造一个带指定初始容量和默认加载因子 (0.75) 的空 HashMap。

  1. public HashMap(int initialCapacity) {
  2. this(initialCapacity, DEFAULT_LOAD_FACTOR);
  3. }

  比如初始一个容量为5的hashMap集合.

  1. HashMap map2 = new HashMap<String,String>(5);
  2. System.out.println(map2.size());// 输出0, 此时数组容量为5, 但内部元素为0.

3.HashMap(int initialCapacity, float loadFactor);

  构造一个带指定初始容量和加载因子的空 HashMap。

  默认负载因子是0.75, HashMap提供可修改的负载因子的构造方法.

  1. HashMap map2 = new HashMap<String,String>(5,0.5f);

四.HashMap内部结构

  数据结构的物理存储结构只有两种:顺序存储结构和链式存储结构(像栈,队列,树,图等是从逻辑结构去抽象的,映射到内存中,也这两种物理组织形式),在数组中根据下标查找某个元素,一次定位就可以达到,哈希表利用了这种特性,哈希表的主干就是数组。

  简单概括主干是数组, 每个数组元素内容部是链表.

  源码如下:

  1. public HashMap(int initialCapacity, float loadFactor) {
  2. //初始容量不能<0
  3. if (initialCapacity < 0)
  4. throw new IllegalArgumentException("Illegal initial capacity: "
  5. + initialCapacity);
  6. //初始容量不能 > 最大容量值,HashMap的最大容量值为2^30
  7. if (initialCapacity > MAXIMUM_CAPACITY)
  8. initialCapacity = MAXIMUM_CAPACITY;
  9. //负载因子不能 < 0
  10. if (loadFactor <= 0 || Float.isNaN(loadFactor))
  11. throw new IllegalArgumentException("Illegal load factor: "
  12. + loadFactor);
  13. // 计算出大于 initialCapacity 的最小的 2 的 n 次方值。
  14. int capacity = 1;
  15. while (capacity < initialCapacity)
  16. capacity <<= 1;
  17. this.loadFactor = loadFactor;
  18. //设置HashMap的容量极限,当HashMap的容量达到该极限时就会进行扩容操作
  19. threshold = (int) (capacity * loadFactor);
  20. //初始化table数组
  21. table = new Entry[capacity];
  22. init();
  23. }

  从源码中可以看出,每次新建一个HashMap时,都会初始化一个table数组。table数组的元素为Entry节点。

  1. static class Entry<K,V> implements Map.Entry<K,V> {
  2. final K key;
  3. V value;
  4. Entry<K,V> next;
  5. final int hash;
  6. /**
  7. * Creates new entry.
  8. */
  9. Entry(int h, K k, V v, Entry<K,V> n) {
  10. value = v;
  11. next = n;
  12. key = k;
  13. hash = h;
  14. }
  15. .......
  16. }

  其中Entry为HashMap的内部类,它包含了键key、值value、下一个节点next,以及hash值,这是非常重要的,正是由于Entry才构成了table数组的项为链表。

  随着HashMap中元素的数量越来越多,发生碰撞的概率就越来越大,所产生的链表长度就会越来越长,这样势必会影响HashMap的速度,为了保证HashMap的效率,系统必须要在某个临界点进行扩容处理。该临界点在当HashMap中元素的数量等于table数组长度*加载因子。但是扩容是一个非常耗时的过程,因为它需要重新计算这些数据在新table数组中的位置并进行复制处理。所以如果我们已经预知HashMap中元素的个数,那么预设元素的个数能够有效的提高HashMap的性能。

五.HashMap的存储分析

  源码如下:

  1. public V put(K key, V value) {
  2. //当key为null,调用putForNullKey方法,保存null与table第一个位置中,这是HashMap允许为null的原因
  3. if (key == null)
  4. return putForNullKey(value);
  5. //计算key的hash值
  6. int hash = hash(key.hashCode()); ------(1)
  7. //计算key hash 值在 table 数组中的位置
  8. int i = indexFor(hash, table.length); ------(2)
  9. //从i出开始迭代 e,找到 key 保存的位置
  10. for (Entry<K, V> e = table[i]; e != null; e = e.next) {
  11. Object k;
  12. //判断该条链上是否有hash值相同的(key相同)
  13. //若存在相同,则直接覆盖value,返回旧value
  14. if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
  15. V oldValue = e.value; //旧值 = 新值
  16. e.value = value;
  17. e.recordAccess(this);
  18. return oldValue; //返回旧值
  19. }
  20. }
  21. //修改次数增加1
  22. modCount++;
  23. //将key、value添加至i位置处
  24. addEntry(hash, key, value, i);
  25. return null;
  26. }

  分析下源码中保存数据的过程为:

  首先判断key是否为null,若为null,则直接调用putForNullKey方法。

  若不为空则先计算key的hash值,然后根据hash值搜索在table数组中的索引位置,如果table数组在该位置处有元素,则通过比较是否存在相同的key,若存在则覆盖原来key的value,否则将该元素保存在链头(最先保存的元素放在链尾)。

  若table在该处没有元素,则直接保存。

  HashMap中不存在相同的Key, 先看迭代处。此处迭代原因就是为了防止存在相同的key值,若发现两个hash值(key)相同时,HashMap的处理方式是用新value替换旧value,这里并没有处理key,这就解释了HashMap中没有两个相同的key。

  再来分析下添加一个新key-value 的内部实现:

  1. void addEntry(int hash, K key, V value, int bucketIndex) {
  2. //获取bucketIndex处的Entry
  3. Entry<K, V> e = table[bucketIndex];
  4. //将新创建的 Entry 放入 bucketIndex 索引处,并让新的 Entry 指向原来的 Entry
  5. table[bucketIndex] = new Entry<K, V>(hash, key, value, e);
  6. //若HashMap中元素的个数超过极限了,则容量扩大两倍
  7. if (size++ >= threshold)
  8. resize(2 * table.length);
  9. }

  这是一个非常优雅的设计。系统总是将新的Entry对象添加到bucketIndex处。如果bucketIndex处已经有了对象,那么新添加的Entry对象将指向原有的Entry对象,形成一条Entry链,但是若bucketIndex处没有Entry对象,也就是e==null,那么新添加的Entry对象指向null,也就不会产生Entry链了

六.HashMap的读取分析

  相对于HashMap的存而言,取就显得比较简单了。通过key的hash值找到在table数组中的索引处的Entry,然后返回该key对应的value即可。

  1. public V get(Object key) {
  2. // 若为null,调用getForNullKey方法返回相对应的value
  3. if (key == null)
  4. return getForNullKey();
  5. // 根据该 key 的 hashCode 值计算它的 hash 码
  6. int hash = hash(key.hashCode());
  7. // 取出 table 数组中指定索引处的值
  8. for (Entry<K, V> e = table[indexFor(hash, table.length)]; e != null; e = e.next) {
  9. Object k;
  10. //若搜索的key与查找的key相同,则返回相对应的value
  11. if (e.hash == hash && ((k = e.key) == key || key.equals(k)))
  12. return e.value;
  13. }
  14. return null;
  15. }

  能够根据key快速的取到value除了和HashMap的数据结构密不可分外,还和Entry有莫大的关系,在前面就提到过,HashMap在存储过程中并没有将key,value分开来存储,而是当做一个整体key-value来处理的,这个整体就是Entry对象。同时value也只相当于key的附属而已。在存储的过程中,系统根据key的hashcode来决定Entry在table数组中的存储位置,在取的过程中同样根据key的hashcode取出相对应的Entry对象。

七.常用方法

1.put(K key, V value)

  将键(key)/值(value)映射存放到Map集合中。若key已经存在,则更新替换掉旧值.

  1. HashMap<String, String> map1 = new HashMap<String, String>();
  2. map1.put("key1", "val01");
  3. map1.put("key2", "val02");
  4. System.out.println(map1.get("key1"));
  5. System.out.println(map1.get("b"));
  6. map1.put("key1", "updated");
  7. System.out.println(map1.get("key1"));

输出结果

  1. val01
  2. null
  3. updated

2.putAll(Map<? extends K, ? extends V> m)

  合并集合,把参数集合中的元素合并到原来集合中, 若参数集合中的key 与原来集合中有重复, 则更新覆盖原来集合对应key 的value.

  1. // 两个map具有不同的key
  2. HashMap<String, String> map1 = new HashMap<String, String>();
  3. map1.put("1", "A");
  4. HashMap<String, String> map2 = new HashMap<String, String>();
  5. map2.put("2", "B");
  6. map2.put("3", "C");
  7. map1.putAll(map2);
  8. System.out.println(map1);
  9. // 两个map具有重复的key
  10. HashMap<String, String> map3 = new HashMap<String, String>();
  11. map3.put("1", "A");
  12. HashMap<String, String> map4 = new HashMap<String, String>();
  13. map4.put("1", "B");
  14. map4.put("3", "C");
  15. map3.putAll(map4);
  16. System.out.println(map3);

输出结果:

  1. {1=A, 2=B, 3=C}
  2. {1=B, 3=C}

2.get(Object key)

  返回指定键所映射的值,没有该key对应的值则返回 null。

  1. HashMap<String,String> map2=new HashMap<String,String>();
  2. map2.put("key1","val01");
  3. map2.put("key2", "val02");
  4. System.out.println(map2.get("key1"));
  5. System.out.println(map2.get("key2"));

  输出结果:

  1. val01
  2. val02

3.size()

  返回Map集合中数据数量。

  1. HashMap<String,String> map2=new HashMap<String,String>();
  2. map2.put("key1","val01");
  3. map2.put("key2", "val02");
  4. System.out.println(map2.size());

  输出结果:

  1. 2

4.clear()

  清空Map集合

  1. HashMap<String,String> map3=new HashMap<String,String>();
  2. map3.put("key1","val01");
  3. map3.put("key2", "val02");
  4. System.out.println(map3.size());
  5. map3.clear();
  6. System.out.println(map3.size());

  输出结果:

  1. 2
  2. 0

5.isEmpty ()

  判断Map集合中是否有数据,如果没有则返回true,否则返回false; 观察源码,仅判断元素个数是否为0, 不能判断null.

  1. public boolean isEmpty() {
  2. return size == 0;
  3. }

6.remove(Object key)

  删除Map集合中键为key的数据并返回其所对应value值。

  1. HashMap<String,String> map4=new HashMap<String,String>();
  2. map4.put("key1","val01");
  3. map4.put("key2", "val02");
  4. System.out.println(map4.remove("key1"));
  5. System.out.println(map4.size());

  输出结果:

  1. val01
  2. 1

7.values()

  返回Map集合中所有value组成的以Collection数据类型格式数据。

  1. HashMap<String,String> map4=new HashMap<String,String>();
  2. map4.put("key1","val01");
  3. map4.put("key2", "val02");
  4. System.out.println(map4.values());

  输出结果:

  1. [val01, val02]

8.keySet()

  返回Map集合中所有key组成的Set集合

  1. HashMap<String,String> map4=new HashMap<String,String>();
  2. map4.put("key1","val01");
  3. map4.put("key2", "val02");
  4. System.out.println(map4.keySet());

  输出结果:

  1. [key1, key2]

9.containsKey(Object key)

  判断集合中是否包含指定键,包含返回 true,否则返回false.

  1. HashMap<String,String> map4=new HashMap<String,String>();
  2. map4.put("key1","val01");
  3. map4.put("key2", "val02");
  4. System.out.println(map4.containsKey("key1"));
  5. System.out.println(map4.containsKey("key3"));

  输出结果:

  1. true
  2. false

10.containsValue(Object value)

  判断集合中是否包含指定值,包含返回 true,否则返回false。

  1. HashMap<String,String> map4=new HashMap<String,String>();
  2. map4.put("key1","val01");
  3. map4.put("key2", "val02");
  4. System.out.println(map4.containsValue("val01"));
  5. System.out.println(map4.containsValue("val03"));

  输出结果:

  1. true
  2. false

八.HashMap 的java8 新特性

1.特性

  • 非线程安全

  • hashMap的映射不是有序的

  • key、value都可以为null

    1. HashMap<String, String> map1 = new HashMap<String, String>();
    2. map1.put(null, null);
    3. System.out.println(map1);

  输出结果:

  1. {null=null}

2.V replace(K key, V value)

  替换指定key 的value, 并返回替换前的value.

  1. HashMap<String, String> map1 = new HashMap<String, String>();
  2. map1.put("1", "a");
  3. map1.put("2", "b");
  4. map1.put("3", "c");
  5. System.out.println(map1);
  6. System.out.println(map1.replace("1", "aReplace"));
  7. System.out.println(map1);

  输出结果

  1. {1=a, 2=b, 3=c}
  2. a

3.boolean replace(key, oldValue, newValue)

  用newValue 替换指定key 的oldValue; 当key 不存在 | 对应key的value 和oldValue 不相等 则返回false;

  源码如下:

  1. @Override
  2. public boolean replace(K key, V oldValue, V newValue) {
  3. Node<K,V> e; V v;
  4. if ((e = getNode(hash(key), key)) != null &&
  5. ((v = e.value) == oldValue || (v != null && v.equals(oldValue)))) {
  6. e.value = newValue;
  7. afterNodeAccess(e);
  8. return true;
  9. }
  10. return false;
  11. }

  例子:

  1. HashMap<String, String> map1 = new HashMap<String, String>();
  2. map1.put("1", "a");
  3. map1.put("2", "b");
  4. map1.put("3", "c");
  5. // key 不存在
  6. System.out.println(map1.replace("4", "bb", "newValue"));
  7. // 参数oldValue 和对应key 的值不相等
  8. System.out.println(map1.replace("2", "bb", "newValue"));
  9. System.out.println(map1);
  10. // 条件满足
  11. System.out.println(map1.replace("2", "b", "newValue"));
  12. System.out.println(map1);

  输出结果:

  1. false
  2. false
  3. {1=a, 2=b, 3=c}
  4. true
  5. {1=a, 2=newValue, 3=c}

4.void replaceAll(function)

  function, java8的流式写法, 个人感觉个replaceAll 没一毛钱关系, function由我们自定义的.

  1. HashMap<String, String> map1 = new HashMap<String, String>();
  2. map1.put("1", "a");
  3. map1.put("2", "b");
  4. map1.put("3", "c");
  5. map1.replaceAll((k,v) -> {
  6. System.out.println(k);
  7. System.out.println(v);
  8. return null;
  9. });

  (k,v) k v 对应HashMap 中的key 和value. 内部是一次遍历操作.

  输出结果:

  1. 1a2b3c

5.putIfAbsent(key, value)

  如果传入的key 已经存在, 则返回存在key 的value, 不进行更新替换value;

  如果传入的key 不存在, 则和put 作用相同, 添加新的key 和value; 并且固定返回null;

  1. HashMap<String, String> map1 = new HashMap<String, String>();
  2. map1.put("1", "a");
  3. map1.put("2", "b");
  4. map1.put("3", "c");
  5. System.out.println("执行前: " + map1);
  6. // 1.key不存在
  7. System.out.println(map1.putIfAbsent("4", "d"));
  8. // 2.key存在, value不同
  9. System.out.println(map1.putIfAbsent("1", "aa"));
  10. System.out.println("执行后: " + map1);

  输出结果:

  1. 执行前: {1=a, 2=b, 3=c}
  2. null
  3. a
  4. 执行后: {1=a, 2=b, 3=c, 4=d}

6.getOrDefault

  作用是根据传入的key 获取value; 若Map 中没有这个key 则返回指定的defaultValue; 若有,则相当于get方法.

  源码如下, 内部采用三元运算符进行判断key 是否存在决定返回的value.

  1. @Override
  2. public V getOrDefault(Object key, V defaultValue) {
  3. Node<K,V> e;
  4. return (e = getNode(hash(key), key)) == null ? defaultValue : e.value;
  5. }

  比如:

  1. HashMap<String, String> map1 = new HashMap<String, String>();
  2. map1.put("1", "a");
  3. map1.put("2", "b");
  4. map1.put("3", "c");
  5. System.out.println(map1.getOrDefault("4", "d"));
  6. System.out.println(map1.getOrDefault("1", "a"));

  输出结果:

  1. d
  2. a

7.merge

  如果key存在,则执行lambda表达式,并将表达式的值更新到对应的key 中. 表达式入参为oldVal和newVal(neVal即merge()的第二个参数)。表达式返回最终put的val。如果key不存在,则直接putnewVal.

  比如:

  1. HashMap<String, String> map1 = new HashMap<String, String>();
  2. map1.put("1", "a");
  3. map1.put("2", "b");
  4. map1.put("3", "c");
  5. String retVal = map1.merge("1", "A", (oldVal, newVal) -> oldVal + newVal);
  6. System.out.println(retVal);
  7. System.out.println(map1);

  输出结果:

  1. aA
  2. {1=aA, 2=b, 3=c}

  作用等同于下段代码(JDK1.7以前版本)

  1. if(map.containsKey(k)) {
  2. map.put(k, map.get(k) + newVal);
  3. } else {
  4. map.put(k, newVal);
  5. }

  源码如下, 解释见注释部分.

  1. public V merge(K key, V value,
  2. BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
  3. // 删除无关代码
  4. int hash = hash(key);
  5. Node<K,V>[] tab; Node<K,V> first; int n, i;
  6. int binCount = 0;
  7. TreeNode<K,V> t = null;
  8. Node<K,V> old = null; // 该key原来的节点对象
  9. if (size > threshold || (tab = table) == null ||
  10. (n = tab.length) == 0) //第一个if,判断是否需要扩容
  11. n = (tab = resize()).length;
  12. if ((first = tab[i = (n - 1) & hash]) != null) {
  13. // 第二个if,取出old Node对象
  14. // 继续省略
  15. }
  16. if (old != null) {// 第三个if,如果 old Node 存在
  17. V v;
  18. if (old.value != null)
  19. // 如果old存在,执行lambda,算出新的val并写入old Node后返回。
  20. v = remappingFunction.apply(old.value, value);
  21. else
  22. v = value;
  23. if (v != null) {
  24. old.value = v;
  25. afterNodeAccess(old);
  26. }
  27. else
  28. removeNode(hash, key, null, false, true);
  29. return v;
  30. }
  31. if (value != null) {
  32. //如果old不存在且传入的newVal不为null,则put新的kv
  33. if (t != null)
  34. t.putTreeVal(this, tab, hash, key, value);
  35. else {
  36. tab[i] = newNode(hash, key, value, first);
  37. if (binCount >= TREEIFY_THRESHOLD - 1)
  38. treeifyBin(tab, hash);
  39. }
  40. // 省略
  41. }
  42. return value;
  43. }

8.compute()

  根据已知的 k v 算出新的v并put。如果无此key,那么oldVal为null,lambda中涉及到oldVal的计算会报空指针。源码和merge大同小异,就不放了。

  1. HashMap<String, String> map1 = new HashMap<String, String>();
  2. map1.put("1", "a");
  3. map1.put("2", "b");
  4. map1.put("3", "c");
  5. String retVal = map1.compute("1", (key, oldVal) -> oldVal + "AAA");
  6. System.out.println(retVal);
  7. System.out.println(map1);

  输出结果:

  1. aAAA
  2. {1=aAAA, 2=b, 3=c}

  和merge 方法相比, 功能类似, 区别在于传入的参数不同;

9.compute() 的补充方法

1) computeIfAbsent()

  当key不存在时,才compute[见8]. 类似compute ,故偷个懒不作例子了

2) computeIfPresent()

  当key存在时,才compute[见8].类似compute ,故偷个懒不作例子了

java基础进阶篇(四)_HashMap------【java源码栈】的更多相关文章

  1. java 日志体系(四)log4j 源码分析

    java 日志体系(四)log4j 源码分析 logback.log4j2.jul 都是在 log4j 的基础上扩展的,其实现的逻辑都差不多,下面以 log4j 为例剖析一下日志框架的基本组件. 一. ...

  2. Android逆向之旅---动态方式破解apk进阶篇(IDA调试so源码)

    Android逆向之旅---动态方式破解apk进阶篇(IDA调试so源码) 来源 https://blog.csdn.net/jiangwei0910410003/article/details/51 ...

  3. 动态方式破解apk进阶篇(IDA调试so源码)

    动态方式破解apk进阶篇(IDA调试so源码) 来源 https://blog.csdn.net/qq_21051503/article/details/74907449 下面就说关于在IDA中And ...

  4. java基础进阶篇(二)_Arraylist ------【java源码栈】

    前言 ArrayList 在开发中用到的频率很高,其中原生态提供的方法有一些很好用的重载版本,其中有的坑该跳得跳啊. 一.ArrayList的6种初始化方法1.构造方法 参数为空2.构造方法 参数为L ...

  5. Java 集合系列(四)—— ListIterator 源码分析

    以脑图的形式来展示Java集合知识,让零碎知识点形成体系 Iterator 对比   Iterator(迭代器)是一种设计模式,是一个对象,用于遍历集合中的所有元素.  Iterator 包含四个方法 ...

  6. java基础,集合,HashMap,源码解析

    最怕,你以为你懂咯,其实你还不懂: 见贤思齐,看看那些我们习以为常的集合,通过相关定义.源码,思考分析,加深对其的理解,提高编码能力,能做一个略懂的程序员: 做几个我们常用的集合类.开篇HashMap ...

  7. Android动态方式破解apk进阶篇(IDA调试so源码)

    一.前言 今天我们继续来看破解apk的相关知识,在前一篇:Eclipse动态调试smali源码破解apk 我们今天主要来看如何使用IDA来调试Android中的native源码,因为现在一些app,为 ...

  8. Android进阶:四、RxJava2 源码解析 1

    本文适合使用过Rxjava2或者了解Rxjava2的基本用法的同学阅读 一.Rxjava是什么 Rxjava在GitHub 主页上的自我介绍是 "a library for composin ...

  9. java基础知识(四)java内存机制

    Java内存管理:深入Java内存区域 上面的文章对于java的内存管理机制讲的非常细致,在这里我们只是为了便于后面内容的理解,对java内存机制做一个简单的梳理. 程序计数器:当前线程所执行的字节码 ...

随机推荐

  1. [JSOI2019]节日庆典(Z-algorithm)

    要想让一个位置作为最小循环,其必须是最小后缀,然后一个字符串的最小后缀不超过O(logn)个,于是维护备选集合即可. 然而要在O(n)复杂度求解,需要求出原串后缀与原串的LCP长度,需要用Z-algo ...

  2. pycharm、Django+node.js、vue搭建web项目

    参考文章:https://www.wandouip.com/t5i35466/  在此感谢 本篇接着上一篇:windows10使用npm安装vue.vue-cli 首先Django项目是搭建好的,就是 ...

  3. zabbix监控Linux服务器CPU使用率大于40%的时候报警(实践版)

    zabbix自带的模板里面有监控项,所以监控项就不用创建了,直接创建触发器就可以了,触发器细节如下: 名称:CPU使用率大于40% 严重性:严重 表达式:{121.201.54.50:system.c ...

  4. Linux svn 服务器配置--转

    转自 http://my.oschina.net/lionel45/blog/298305 留存备份. Linux搭建SVN 服务器 作者: 沈小然    日期:2014年 8月 5日 1       ...

  5. SVN服务器的搭建(一)

    1.基本概念 1.1.什么是版本控制 简单点来说,版本控制就是数据仓库,它可以记录你对文件的每次更改.这样,就算你在昏天黑地的改了几个月后老板说不要了,还是按照过去那样,你也不会抓狂,简单的恢复版本操 ...

  6. winform显示word和ppt文档

    最近所做的项目中需要在Winform窗体中显示Office文档.刚开始就使用webBrowser控件实现的,但是后来发现这个控件在显示Office文档的时候有个限制:只支持Office2003之前的版 ...

  7. 调用其他VBA工程中的过程和函数以及API函数

    Excel VBA中,同一个应用程序下面包括多个工作簿,每个工作簿都有自己独立的VBAProject 在同一个VBA工程中,使用Call即可调用其他模块中的过程和函数,例如: Call Module2 ...

  8. ckeditor+ckfinder添加水印。

    1.修改ckfinder文件下面的config.php:添加一句include_once "plugins/watermark/plugin.php";//水印配置文件 2.修改p ...

  9. [从 0 开始的 Angular 生活]No.38 实现一个 Angular Router 切换组件页面(一)

    前言 今天是进入公司的第三天,为了能尽快投入项目与成为团队可用的战力,我正在努力啃官方文档学习 Angular 的知识,所以这一篇文章主要是记录我如何阅读官方文档后,实现这个非常基本的.带导航的网页应 ...

  10. python语法基础-面向对象-基础-长期维护

    ###############    类的基本操作    ############## """ 类的基本认识: 1,类就是一个模子 2,dict,list都是类,具体的一 ...