【第22期】观点:IT 行业加班,到底有没有价值?

如何看待代码中滥用HashMap?-知乎问题读后感和相关研究

原创 2017年01月03日 17:10:22

昨天在知乎上看到了一个问题如何看待代码中滥用HashMap? .日常工程中使用HashMap确实挺多的 ,简单方便快捷(至少感觉上是这样) ,但越是简单好用的东西 ,底层封装的越复杂 .

跟进去看了一下 ,朱文彬老师进行了比较直观的对比实验 ,我也查阅了其他的资料 ,最后把这个实验扒下来运行了 .

资料 HashMap的原理研究


1.HashMap的结构 ,数组Col[对应HashCode] + 链表Row[对应数据节点Entry]

transient Node<K,V>[] table


2.设置初始容量(桶/数组的数量 ,默认16) ,负载因子(判定Map满的条件 ,默认0.75)

    public HashMap(int initialCapacity, float loadFactor) {
        //初始容量不能<0
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal initial capacity: "
                    + initialCapacity);
        //初始容量不能 > 最大容量值,HashMap的最大容量值为2^30
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
        //负载因子不能 < 0
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException("Illegal load factor: "
                    + loadFactor);

        // 计算出大于 initialCapacity 的最小2^n值。
        int capacity = 1;
        while (capacity < initialCapacity)
            capacity <<= 1;

        this.loadFactor = loadFactor;
        //设置HashMap的容量极限,当HashMap的容量达到该极限时就会进行扩容操作
        threshold = (int) (capacity * loadFactor);
        //初始化table数组
        table = new Entry[capacity];
        init();
    }

3.put方法

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

3.1计算hash值/查询对应的数组列表位置

    static int hash(int h) {
        h ^= (h >>> 20) ^ (h >>> 12);
        return h ^ (h >>> 7) ^ (h >>> 4);
    }

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

HashMap通过数组加链表 ,如何均匀分布数据?

  • 排布太紧链表会很长 ,查询效率会变低(顺序查询)
  • 排布太松数组会很大 ,浪费很多空间

hash+indexFor

  • hash是纯数学计算
  • 合理分布数据需要取模 ,indexFor是特殊的”取模”运算
    • 因为底层桶的大小length是2^n ,length-1 -> 111...111(2进制)
    • 10110 & 1111 (22 & 15) -> 00110 (6)
    • 22%16 = 6
  • 利用二进制&的特点 ,可以快速的达成取模的目的(%取模比&运算复杂)

3.2添加节点/相同key替换

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

4.get操作

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

5.扩容机制

随着HashMap中的元素增加,hash冲突的几率也就变高,
因为数组的长度是固定的。为了提高查询的效率,要对数组进行扩容(元素超过 大小length*负载因子loadFactor),
而在HashMap数组扩容之后,最消耗性能的点就出现了:
原数组中的数据必须重新计算其在新数组中的位置,并put,这就是resize

    void resize(int newCapacity) {
        Entry[] oldTable = table;
        int oldCapacity = oldTable.length;
        //如果当前的数组长度已经达到最大值,则不在进行调整
        if (oldCapacity == MAXIMUM_CAPACITY) {
            threshold = Integer.MAX_VALUE;
            return;
        }
        //根据传入参数的长度定义新的数组
        Entry[] newTable = new Entry[newCapacity];
        //按照新的规则,将旧数组中的元素转移到新数组中
        transfer(newTable);
        table = newTable;
        //更新临界值
        threshold = (int)(newCapacity * loadFactor);
    }
    //旧数组中元素往新数组中迁移
    void transfer(Entry[] newTable) {
        //旧数组
        Entry[] src = table;
        //新数组长度
        int newCapacity = newTable.length;
        //遍历旧数组
        for (int j = 0; j < src.length; j++) {
            Entry<K,V> e = src[j];
            if (e != null) {
                src[j] = null;
                do {
                    Entry<K,V> next = e.next;
                    int i = indexFor(e.hash, newCapacity);//放在新数组中的index位置
                    e.next = newTable[i];//实现链表结构,新加入的放在链头,之前的的数据放在链尾
                    newTable[i] = e;
                    e = next;
                } while (e != null);
            }
        }

    }

6.线程安全

HashMap是线程不安全的 ,因此在多线程应用时 ,可以考虑使用:

  • HashTable(同步锁整个table数组 ,效率较低)
  • Collections.synchronizedMap(对每一个方法增加了synchronized ,但并不保证put/get/contain之间的同步)
  • ConcurrentHashMap(同步锁每次只锁一个桶 ,可以多线程同时读写不同桶 ,也保证了put/get同一个桶的同步)

实验一 Map/List/数组的内存占用情况

package sourceCode.javaSE;

import static sourceCode.objMemoryUtil.ObjMemoryCostUtil.*;

//import static -> 静态导入 ,导入全部(*)或指定的静态方法 ,可以直接使用 ,不用加System.这样的前缀名
import static java.lang.System.out;

/**
 * 作者:朱文彬
 * 链接:https://www.zhihu.com/question/28119895/answer/40494358
 * 来源:知乎
 * 著作权归作者所有,转载请联系作者获得授权。
 * <p>
 * 对各种map占用的内存大小进行研究
 */
public class HashMapMemoryTest {

    static void printSize(Object o) {
        out.printf("类型:%s,占用内存:%.2f MB\n", o.getClass().getSimpleName(), deepSizeOf(o) / 1024D / 1024D);
    }

    public static void main(String[] args) throws Throwable {

        int size = 30000;

        java.util.Map<Object, Object> javaUtilHashMap = new java.util.HashMap<>();
        for (int i = 0; i < size; javaUtilHashMap.put(i, i), i++) {
        }

        /**
         * Java集合框架Koloboke ,目前的版本主要是替换java.util.HashSet和java.util.HashMap
         *
         * Koloboke对每个entry使用了更少的内存
         * Koloboke目标是把键和值存储在同一行高速缓存中
         * 所有的方法都经过了实现优化,而不是像AbstractSet类或AbstractMap类那样委托给框架类(Skeleton Class)
         */
        net.openhft.koloboke.collect.map.hash.HashIntIntMap openHftHashIntIntMap = net.openhft.koloboke.collect.map.hash.HashIntIntMaps.newUpdatableMap();
        for (int i = 0; i < size; openHftHashIntIntMap.put(i, i), i++) {
        }

        java.util.ArrayList<Object> javaUtilArrayList = new java.util.ArrayList<>();
        for (int i = 0; i < size; javaUtilArrayList.add(i), i++) {
        }

        Integer[] objectArray = new Integer[size];
        for (int i = 0; i < size; objectArray[i] = i, i++) {
        }

        /**
         * hppc - High Performance Primitive Collections for Java
         * 对Java的原始集合类型如映射map、集合set、堆栈stack、列表list、队列deque等进行了扩展,提供了更佳的内存利用率,带来了更好的性能。
         */
        com.carrotsearch.hppc.IntArrayList hppcArrayList = new com.carrotsearch.hppc.IntArrayList();
        for (int i = 0; i < size; hppcArrayList.add(i), i++) {
        }

        int[] primitiveArray = new int[size];
        for (int i = 0; i < size; primitiveArray[i] = i, i++) {
        }

        out.println("java.vm.name=" + System.getProperty("java.vm.name"));
        out.println("java.vm.version=" + System.getProperty("java.vm.version"));
        out.println("容器元素总数:" + size);

        printSize(javaUtilHashMap);
        printSize(openHftHashIntIntMap);
        printSize(javaUtilArrayList);
        printSize(hppcArrayList);
        printSize(primitiveArray);
        printSize(objectArray);  
    }
}
容器元素总数:30000
类型:HashMap,占用内存:2.08 MB
类型:UpdatableLHashParallelKVIntIntMap,占用内存:0.50 MB
类型:ArrayList,占用内存:0.58 MB
类型:IntArrayList,占用内存:0.17 MB
类型:int[],占用内存:0.11 MB
类型:Integer[],占用内存:0.57 MB

内存差异的原因是:

 1. hash中为避免退化为数组(如openhft的实现可以退化为数组)或者链表(java.util.HashMap可能退化为链表)使用的空槽
 2. java.util.HashMap.Entry的额外占用的内存,用于维持链表、内存对齐等
 3. 对象内存占用:在HotSpot 64位jdk中,一个java.lang.Integer占用16字节,一个引用占用4字节,总共20字节,而一个int只占用4字节

结果分析 :

 1. 处理 `大数据量的默认类型` 时 ,使用个性化的集合类可以减少类型推断 ,节省拆装箱的内存
 2. 数组是较为底层的 ,内存使用上最少 ,但可支持的操作也很少 ,查询效率也不那么好
 3. 但面对数量巨大的key和简单的value来说 ,使用数组太耗费时间
 4. `内存优化` 和 `搜索优化` 不可调和

补充:deepSizeOf(o) - 利用Instrumentation检测JVM对象大小

朱老师并未在知乎上发布sizeOf工具类的代码 ,因此我在网上找了一个替代的工具类:如何准确计算Java对象的大小 - 博客园aprogramer .

文章内使用了java.lang.instrument.Instrumentation : Java Instrumentation指的是可以用独立于应用程序之外的代理(agent)程序来监测和协助运行在JVM上的应用程序 ,监测和协助包括但不限于获取JVM运行时状态,替换和修改类定义等 .

最后参考Java对象占用内存大小的计算方法 ,完整的计算Map和内部引用的所有成员的大小

package sourceCode.objMemoryUtil;

/*
 * @(#)MemoryCalculator.java    1.0 2010-11-8
 *
 * Copyright 2010 Richard Chen(utopia_rabbi@sse.buaa.edu.cn) All Rights Reserved.
 * PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */

import java.lang.instrument.Instrumentation;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.*;

/**
 * 利用Instrumentation去检测JVM中的情况 ,还可以分析JVM中加载的所有对象等
 * 1.编写premain函数 ,作为JVM启动时的回调函数 ,注入Instrumentation实例到工具类中
 * 2.编写MANIFEST.MF ,指定Premain-Class的位置
 * 3.单独打包工具类和MANIFEST
 * 4.在使用入口程序设置VM-operation : -javaagent:target/objMemoryUtil.jar ,指定代理的工具类jar
 */
public class ObjMemoryCostUtil {
    /**
     * JVM将在启动时通过{@link #premain}初始化此成员变量.
     */
    private static Instrumentation instrumentation = null;


    /**
     * JVM在初始化后在调用应用程序main方法前将调用本方法, 本方法中可以写任何main方法中可写的代码.
     *
     * @param agentArgs 命令行传进行来的代理参数, 内部需自行解析.
     * @param inst      JVM注入的句柄.
     */
    public static void premain(String agentArgs, Instrumentation inst) {
        instrumentation = inst;
    }

    /**
     * 计算实例本身占用的内存大小. 注意:
     * 1. 多次调用可能结果不一样, 主要跟实例的状态有关
     * 2. 实例中成员变量如果是reference类型, 则reference所指向的实例占用内存大小不统计在内 (只计算基本类型的成员)
     *
     * @param obj 待计算内存占用大小的实例.
     * @return 内存占用大小, 单位为byte.
     */
    public static long shallowSizeOf(Object obj) {
        if (instrumentation == null) {
            throw new IllegalStateException("Instrumentation initialize failed");
        }
        if (isSharedObj(obj)) {
            return 0;
        }
        return instrumentation.getObjectSize(obj);
    }

    /**
     * 计算实例占用的内存大小, 含其成员变量所引用的实例, 递归计算.
     *
     * @param obj 待计算内存占用大小的实例.
     * @return 内存占用大小, 单位为byte.
     */
    public static long deepSizeOf(Object obj) {
        Map calculated = new IdentityHashMap();
        Stack unCalculated = new Stack();
        unCalculated.push(obj);
        long result = 0;
        do {
            result += doSizeOf(unCalculated, calculated);
        } while (!unCalculated.isEmpty());
        return result;
    }

    /**
     * 判断obj是否是共享对象. 有些对象, 如interned Strings, Boolean.FALSE和Integer#valueOf()等.
     *
     * @param obj 待判断的对象.
     * @return true, 是共享对象, 否则返回false.
     */
    private static boolean isSharedObj(Object obj) {
        if (obj instanceof Comparable) {
            if (obj instanceof Enum) {
                return true;
            } else if (obj instanceof String) {
                return (obj == ((String) obj).intern());
            } else if (obj instanceof Boolean) {
                return (obj == Boolean.TRUE || obj == Boolean.FALSE);
            } else if (obj instanceof Integer) {
                return (obj == Integer.valueOf((Integer) obj));
            } else if (obj instanceof Short) {
                return (obj == Short.valueOf((Short) obj));
            } else if (obj instanceof Byte) {
                return (obj == Byte.valueOf((Byte) obj));
            } else if (obj instanceof Long) {
                return (obj == Long.valueOf((Long) obj));
            } else if (obj instanceof Character) {
                return (obj == Character.valueOf((Character) obj));
            }
        }
        return false;
    }

    /**
     * 确认是否需计算obj的内存占用, 部分情况下无需计算.
     *
     * @param obj        待判断的对象.
     * @param calculated 已计算过的对象.
     * @return true, 意指无需计算, 否则返回false.
     */
    private static boolean isEscaped(Object obj, Map calculated) {
        return obj == null || calculated.containsKey(obj)
                || isSharedObj(obj);
    }

    /**
     * 计算栈顶对象本身的内存占用.
     *
     * @param unCalculated 待计算内存占用的对象栈.
     * @param calculated   对象图谱中已计算过的对象.
     * @return 栈顶对象本身的内存占用, 单位为byte.
     */
    private static long doSizeOf(Stack unCalculated, Map calculated) {
        Object obj = unCalculated.pop();
        if (isEscaped(obj, calculated)) {
            return 0;
        }
        Class clazz = obj.getClass();
        if (clazz.isArray()) {
            doArraySizeOf(clazz, obj, unCalculated);
        } else {
            while (clazz != null) {
                Field[] fields = clazz.getDeclaredFields();
                for (Field field : fields) {
                    if (!Modifier.isStatic(field.getModifiers())
                            && !field.getType().isPrimitive()) {
                        field.setAccessible(true);
                        try {
                            unCalculated.add(field.get(obj));
                        } catch (IllegalAccessException ex) {
                            throw new RuntimeException(ex);
                        }
                    }
                }
                clazz = clazz.getSuperclass();
            }
        }
        calculated.put(obj, null);
        return shallowSizeOf(obj);
    }

    /**
     * 将数组中的所有元素加入到待计算内存占用的栈中, 等待处理.
     *
     * @param arrayClazz   数组的型别.
     * @param array        数组实例.
     * @param unCalculated 待计算内存占用的对象栈.
     */
    private static void doArraySizeOf(Class arrayClazz, Object array,
                                      Stack unCalculated) {
        if (!arrayClazz.getComponentType().isPrimitive()) {
            int length = Array.getLength(array);
            for (int i = 0; i < length; i++) {
                unCalculated.add(Array.get(array, i));
            }
        }
    }
}

Instrumentation使用方式

  • 编写MANIFEST.MF ,指定Premain-Class的位置
Manifest-Version: 1.0
Premain-Class: sourceCode.objMemoryUtil.ObjMemoryCostUtil
Created-By: 1.6.0_29
  • 单独打包ObjMemoryCostUtil类 ,并将MANIFEST加入到Jar(粗浅的学习了下maven打包)
<plugin>
    <artifactId>maven-jar-plugin</artifactId>
    <executions>
        <execution>
            <id>objMemoryCostUtil</id>
            <goals>
                <goal>jar</goal>
            </goals>
                <phase>package</phase>
            <configuration>
                <finalName>objMemoryUtil</finalName>
                <includes>
                    <include>**/objMemoryUtil/**</include>
                </includes>
                <archive>
                    <manifestFile>src/main/java/sourceCode/objMemoryUtil/MANIFEST.MF</manifestFile>
                    <manifest><addClasspath>true</addClasspath></manifest>
                </archive>
            </configuration>
        </execution>
    </executions>
</plugin>
  • 在使用的Main函数中设置VM参数 ,指定jar包位置 -javaagent:target/objMemoryUtil.jar

这里写图片描述

  • 运行即可

实验二 Map/List/数组的put性能

这个实现就是通过插入数据 ,计算插入时间

package sourceCode.javaSE;

import java.util.Collections;

import static java.lang.Math.*;
import static java.lang.System.*;
import static java.util.Arrays.*;

/**
 * 作者:朱文彬
 * 链接:https://www.zhihu.com/question/28119895/answer/40494358
 * 来源:知乎
 * 著作权归作者所有,转载请联系作者获得授权。
 * <p>
 * 对各种map存取时间进行研究
 */

public class HashMapCPUTimeTest {

    /**
     * 计算各集合put操作的时间 ,可以设置重复次数取平均
     *
     * @param type 对象类型
     * @param r    线程所做的操作
     */
    static void printTime(Class type, Runnable r) {
        double time = timeCall(r, 30);
        char[] rpad = "                                    ".toCharArray();
        type.getSimpleName().getChars(0, type.getSimpleName().length(), rpad, 0);
        out.printf("类型:%s \t 耗时:%.2g s\n", new String(rpad), time);
    }

    /**
     * 根据重复次数 ,计算所花时间的平均值
     *
     * @param call   目标线程
     * @param repeat 重复次数
     * @return
     */
    public static double timeCall(Runnable call, int repeat) {
        double[] a = new double[repeat];
        setAll(a, i -> timeCall(call));
        if (repeat > 7) { //重复次数>7 ,只对中间的60%数据计算平均
            sort(a);
            int i = round(repeat * 0.2f);
            return stream(a, i, repeat - i).average().getAsDouble();
        }
        if (repeat > 3) { //重复次数>3 ,去掉一个最高分一个最低分 ,剩下的取平均
            sort(a);
            return stream(a, 1, repeat - 1).average().getAsDouble();
        }
        return stream(a).average().getAsDouble();
    }

    /**
     * 启动线程 ,执行put操作
     *
     * @param call
     * @return
     */
    public static double timeCall(Runnable call) {
        long startA = nanoTime();//System.nanoTime提供基于系统的相对精确的时间 ,类似秒表
        long start = nanoTime();
        try {
            call.run();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return 1E-9d * (max(0, nanoTime() - start - (start - startA))); //1E-9d :1乘以10的-9次方
    }


    public static void main(String[] args) throws Throwable {
        int size = 1000000;
        out.println("java.vm.name=" + System.getProperty("java.vm.name"));
        out.println("java.vm.version=" + System.getProperty("java.vm.version"));
        out.println("容器元素总数:" + size);

        printTime(java.util.HashMap.class, () -> {
            java.util.Map<Object, Object> javaUtilHashMap = new java.util.HashMap<>();
            for (int i = 0; i < size; javaUtilHashMap.put(i, i), i++) {
            }
        });

        printTime(java.util.LinkedHashMap.class, () -> {
            java.util.Map<Object, Object> javaUtilLinkedHashMap = new java.util.LinkedHashMap<>();
            for (int i = 0; i < size; javaUtilLinkedHashMap.put(i, i), i++) {
            }
        });

        printTime(java.util.concurrent.ConcurrentHashMap.class, () -> {
            java.util.Map<Object, Object> javaUtilLinkedHashMap = new java.util.concurrent.ConcurrentHashMap<>();
            for (int i = 0; i < size; javaUtilLinkedHashMap.put(i, i), i++) {
            }
        });

        printTime(Collections.synchronizedMap(new java.util.concurrent.ConcurrentHashMap()).getClass(), () -> {
            java.util.Map<Object, Object> javaUtilLinkedHashMap = Collections.synchronizedMap(new java.util.concurrent.ConcurrentHashMap());
            for (int i = 0; i < size; javaUtilLinkedHashMap.put(i, i), i++) {
            }
        });

        printTime(java.util.TreeMap.class, () -> {
            java.util.Map<Object, Object> javaUtilTreeMap = new java.util.TreeMap<>();
            for (int i = 0; i < size; javaUtilTreeMap.put(i, i), i++) {
            }
        });

        printTime(net.openhft.koloboke.collect.map.hash.HashIntIntMaps.newUpdatableMap().getClass(), () -> {
            net.openhft.koloboke.collect.map.hash.HashIntIntMap openHftHashIntIntMap = net.openhft.koloboke.collect.map.hash.HashIntIntMaps.newUpdatableMap();
            for (int i = 0; i < size; openHftHashIntIntMap.put(i, i), i++) {
            }
        });

        printTime(java.util.ArrayList.class, () -> {
            java.util.ArrayList<Object> javaUtilArrayList = new java.util.ArrayList<>();
            for (int i = 0; i < size; javaUtilArrayList.add(i), i++) {
            }
        });

        printTime(Integer[].class, () -> {
            Integer[] objectArray = new Integer[size];
            for (int i = 0; i < size; objectArray[i] = i, i++) {
            }
        });

        printTime(com.carrotsearch.hppc.IntArrayList.class, () -> {
            com.carrotsearch.hppc.IntArrayList hppcArrayList = new com.carrotsearch.hppc.IntArrayList();
            for (int i = 0; i < size; hppcArrayList.add(i), i++) {
            }
        });

        printTime(int[].class, () -> {
            int[] primitiveArray = new int[size];
            for (int i = 0; i < size; primitiveArray[i] = i, i++) {
            }
        });

    }
}

容器元素总数:1000000
类型:HashMap 耗时:0.028 s
类型:LinkedHashMap 耗时:0.025 s
类型:ConcurrentHashMap 耗时:0.10 s
类型:SynchronizedMap 耗时:0.091 s
类型:TreeMap 耗时:0.25 s
类型:UpdatableLHashParallelKVIntIntMap 耗时:0.048 s
类型:ArrayList 耗时:0.0063 s
类型:Integer[] 耗时:0.0031 s
类型:IntArrayList 耗时:0.0033 s
类型:int[] 耗时:0.00064 s

  1. 单纯对Put操作来讲 ,和memory的实验类似 ,越是底层越是简单的结构 ,效率越高像TreeMap耗时是数组的1000倍
  2. 但对于一些后续操作 ,排序/get来说 ,TreeMap/HashMap则不知道高到哪里去
  3. 所以了解集合类的差异 ,各自的优缺点 ,合理的使用才是最重要的
版权声明:本文为博主原创文章,未经博主允许不得转载。 举报

相关文章推荐

数据通路的习题研究

指令执行的数据通路习题学习

.NET程序员的数据库面试题及答案

Q1:维护数据库的完整性、一致性、你喜欢用触发器还是自写业务逻辑?为什么答:尽可能用约束(包括CHECK、主键、唯一键、外键、非空字段)实现,这种方式的效率最好;其次用触发器,这种方式可以保证无论何种...

oracle 数据库开发面试题

最近参加了4、5场面试,总结一下竞聘oracle 开发岗位最长问到哪些问题: 1、delete 与 truncate 区别? 1)truncate 是DDL语句,delete 是DML语句; 2)...

关于那个所谓的腾讯笔试题的研究

今天看见一个腾讯笔试题,正好研究了一下大内存操作和文件映射等问题。题目是:一个文件中有40亿个整数,每个整数为四个字节,内存为1GB,写出一个算法:求出这个文件里的整数里不包含的一个整数算法一:分配5...

java方向笔试题4- 数据库

数据库部分 1、用两种方式根据部门号从高到低,工资从低到高列出每个员工的信息。 employee:      eid,ename,salary,deptid;  select * from e...

数据库基础(面试常见题)

数据库基础(面试常见题) 一、数据库基础 1. 数据抽象:物理抽象、概念抽象、视图级抽象,内模式、模式、外模式 2. SQL语言包括数据定义、数据操纵(Data Manipulation),数据...

面试中的数据库问题

如何应对面试中的数据库问题 很多同学并不是数据库的专家,面试如果问到数据库并没有太多信心,面试数据库是有窍门的,因为数据库是一个庞大的系统,面试的时候没有办法考得很深,而且有一些题目是被反复问到的,...

编写一个类,在main方法中定义一个Map对象(采用泛型),加入若干个对象,然后遍历并打印出各元素的key和value。

编写一个类,在main方法中定义一个package com.itheima; import java.util.Set; import java.util.TreeMap; /** *第3题:编...

Java面试题全集(上)

转载来自:http://blog.csdn.net/jackfrued/article/details/44921941 2013年年底的时候,我看到了网上流传的一个叫做《Java面试题大全》的东西...

Java学习笔记----------集合Set

Java集合-----Set 集合:就像一种容器,可以把多个对象放进该容器中。 Java集合分为:Set、List、Map三种体系。 Set:无序的,不可重复的; List:有序的,可重复的;...
收藏助手
不良信息举报
您举报文章:深度学习:神经网络中的前向传播和反向传播算法推导
举报原因:
原因补充:

(最多只允许输入30个字)