zoukankan      html  css  js  c++  java
  • Java 数据类型:集合接口Map:HashTable;HashMap;IdentityHashMap;LinkedHashMap;Properties类读取配置文件;SortedMap接口和TreeMap实现类:【线程安全的ConcurrentHashMap】

    Map集合java.util.Map

    Map用于保存具有映射关系的数据,因此Map集合里保存着两个值,一个是用于保存Map里的key,另外一组值用于保存Map里的value。key和value都可以是任何类型的数据。
    在Map中,Key是不允许重复的。key和value之间存在一对一的关系,也就是说,我们通过key肯定能够找到唯一的,确定的value。

     

    特征:key-->value

    方法:

    • (1), void clear() : 删除该Map对象中所有的key-value对
    • (2), boolean containsKey(Object key) : 查询Map中是否包含指定的key,如果包含返回true
    • (3), boolean containsValue(Object value): 查询Map中是否包含一个或者多个value,如果包含,返回true,否则返回false.
    • (4), Set entrySet(): 返回Map中包含key-value对所组成的Set集合,每个元素都是Map.Entry对象
    • (5), Object get(Object key): 返回指定的key所对应的value,如果此Map中不包含该key,则返回null
    • (6), boolean isEmpty(): 查询该Map是否为空
    • (7), Set keySet(): 返回该Map中所有key组成的Set集合
    • (8), Object put(Object key, Object value): 添加一个key-value对,如果Map中已经存在了对应的key了,那么会覆盖原来的。
    • (9), void putAll(Map m):将制定的Map中的key-value复制到本Map中
    • (10), Object remove(Object key) : 删除指定key锁对应的key-value对,返回被删除key所关联的value,如果key不存在,那么返回null。
    • (11), boolean remove(Object key, Object value): 这个是java8新增的方法,删除制定key,value所对应的key-value对。如果从该Map中成功的删除该key-value对,该方法返回true,否则返回false
    • (12), int size(): 返回该Map李对应的key-value对的个数
    • (13), Collection values() : 返回该Map李所有value组成的Collection

    Java8新增方法:

    • Object compute(Object key, BitFunction remappingFunction): 该方法使用remappingFunction根据key-value计算一个新的value。只要value不为null,就是用新value覆盖原value,如果原value不为null,但是新value为null,则删除原来的key-value对。如果原value和新value都是null,那么该方法不改变任何的key-value对,直接返回null。
    • Object computeIfAbsent(Object key, Function mappingFunction): 如果传给该方法的key参数在Map中对应的value为null, 则使用mappingFunction根据key计算一个新的结果,如果计算结果不为null,则用计算结果覆盖原有的value。如果原Map不包含该key,那么该方法可能会添加一组key-value对。
    • void forEach(BiConsumer action): 该方法是Java8为Map新增的一个遍历Key-value对的方法,通过该方法可以更加简洁的遍历Map的key-value对。
    • Object getOrDefault(Object key, V defaultValue) : 获取指定的key对应的value。如果key不存在,那么直接返回defaultValue
    • Object merge(Object key, Object value, BiFunction remappingFunction): 该方法会先根据key参数获取该map中对象的value。如果获取的value为null,则直接用传入的value覆盖有缘的value(在这种情况下,可能要新添加一组key-value对);如果获取的value不为null,则使用remappingFunction函数根据原value,新value计算一个新的结果,并用得到的结果去覆盖原有的value。
    • Object putIfAbsent(Object key, Object value): 该方法会自动检测指定key对应value是否为null,如果该key对应的value为null,该方法将会用新的value替代原来的value。
    • Object replace(Object key, Object oldValue, Object newValue): 将Map中指定的key-value对的原value替换成新的value,如果Map中找到指定的key-value对,执行替换并返回true,否则返回false。
    • void replaceAll(BiFunction function) : 该方法使用BiFunction对原key-value对执行计算,并且将计算结果作为该key-value对的value值。

    void forEach(BiConsumer action): 测试

    persons.forEach((key,value)-> System.out.println(key + "=" + value));

    void replaceAll(BiFunction function) 测试

    import java.util.HashMap;
    import java.util.Map;
    import java.util.Set;
    import java.util.function.BiFunction;
    
    public class MapExample {
        public static void main(String[] args) {
            Map persons = new HashMap();
            //put方法就是添加key-value对
            persons.put("张三",23);
            persons.put("李四",28);
            persons.put("王五",23);
            System.out.println(persons);//{李四=28, 张三=23, 王五=23}
            //对原有元素做自定义处理运算
            persons.replaceAll((key,value) -> (int)value -1);
            System.out.println(persons); //{李四=27, 张三=22, 王五=22}
        }
    }

    Java8改进的HashMap和HashTable实现类:

    HashMap和HashTable都是Map接口的典型实现类,他们之间的关系类似于ArrayList和Vector的关系。不过Hashtable是一个古老的Map实现类,从jdk1.0开始就已经有了。
    Java8改进了HashMap的实现类,使用HashMap存在key冲突的时候,依然后很好的性能。
    HashTable和HashMap还有两个重要的区别:
    • (1),HashTable是一个线程安全的Map实现,但是HashMap是线程不安全的实现,所以它比HashTable的性能高了一些
    • (2),HashTable不允许使用null作为key和value,如果试图把null的值放进HashTable中,将会引发NullPointerException异常。但是HashMap可以使用null作为key或者value。

    HashMap和HashTable存储位置问题:

    HashMap和HashTable一个元素的存储位置,是通过key来计算的。原理和HashSet计算位置的原理一样,如果我要判断两个key是否相等,首先判断hashCode是否一样,然后判断equals方法是否返回true。

    HashMap和HashTable的containsValue(Object object )方法判断相等其实就是equals:

    注意:这里作为参数传入的对象的equals方法

    IdentityHashMap

    这个Map实现类的实现机制与HashMap基本相似,但它在处理两个key相等时比较独特:在IdentityHashMap 中,当且仅当两个key严格相等(keyl == key2)时,IdentityHashMap才认为两个key相等:对于普通的HashMap而言,只要keyl,key2 通过equals方法比较返回true,且它们的 hashCode 值相等即可

    LinkedHashMap

    HashMap有一个子类,叫做LinkedHashMap。它采用了双向链表来维护key-value对的次序,这个次序和我们插入时候的顺序一致。
    LinkedHashMap可以避免对HashMap、Hashtable里的key-value对进行排序(只要插入key-value时保持顺序即可)。不过LinkedHashMap要维护着插入时候的顺序。
    优缺点:
    • 性能要比HashMap低一些
    • 元素维持着插入时的顺序
    • 但是迭代LinkedHashMap的时候,效率会更快。
    public class LinkHahsMapExample {
        public static void main(String[] args) {
            Map linkHashMap = new LinkedHashMap();
            linkHashMap.put("张三",2);
            linkHashMap.put("李四",3);
            linkHashMap.put("老刘",4);
            linkHashMap.forEach((k,v) -> System.out.println(k + "=====>" + v));
        }
    }

    Properties类读取配置文件:

    Properties类是HashTable类的一个子类。
    用途:
    • 用来读取键值对的配置文件。
    常用的方法:
    • String getProperty(String key): 获取Properties中指定属性名对应的属性值。
    • Object setProperty(String key, String value): 设置属性值
    • void load(InputStream inStream) : 从属性文件中加载key-value对。
    • void store(OutputStream out, String comments): 将properties中的key-value对输出到指定的文件中。
    import java.io.FileReader;
    import java.io.FileWriter;
    import java.io.IOException;
    import java.util.Properties;
    
    /**
     * @ClassName PropertiesExample
     * @projectName: object1
     * @author: Zhangmingda
     * @description: XXX
     * date: 2021/4/10.
     */
    public class PropertiesExample {
        public static void main(String[] args) throws IOException {
            Properties properties = new Properties();
            properties.load(new FileReader("D:\JavaStudy2\集合\src\configTest.ini"));
            properties.forEach((k,v) -> System.out.println(k + "=" + v));
            /**如上输出
             * password=xxx
             * username=zmd
             */
            properties.setProperty("qjj","hehe"); //新增或者设置老的key对应值
            properties.store(new FileWriter("D:\JavaStudy2\集合\src\configTest2.ini"),"测试"); //保存到文件中
        }
    }

    SortedMap接口和TreeMap实现类(排序的Map):

    TreeMap就是一个红黑树数据结构,每个key-value对就是一个红核数的节点。TreeMap存储key-value对的时候,需要根据key对节点进行排序。TreeMap可以保证所有的key-value处于有序状态。TreeMap也有自然排序和定制排序两种方式。原理和TreeSet的两种排序一样。
    TreeMap也提供了一些常用的方法:
    • Map.Entry firstEntry(): 返回该Map中key最小的key-value对,如果Map为空会返回null
    • Object firstKey(): 返回该Map中最小的key值,如果该Map为空,返回null
    • Map.Entry lastEntry(): 返回该Map中最大的key对应的key-value对,如果该Map为空或不存在key-value时,则都返回null。
    • Object lastKey(): 返回该Map中最大的Key值,如果该Map为空或者不存在这样的key,则都返回null。
    • Map.Entry higherEntity(Object key): 返回该Map中位于key后一位的key-value对(即大于指定key的最小key所对应的key-value对)。如果该Map为空,则返回null.
    • Object higherKey(Object key): 返回该Map中位于key后一位的key-value对的key,如果Map为空返回null
    • Map.Entry lowerEntry(Object key): 返回该Map中位于key前一位的key-value对,如果Map为空返回null.
    • Object lowerKey(Object key): 返回该Map中位于key前一位的key的值。如果该Map为空,或者不存在这样的key,都返回null。
    • 省略若干........。
    import java.util.Map;
    import java.util.TreeMap;
    
    public class TreeMapTest {
        private static class A implements Comparable {
            int count;
            public A(int count) {
                this.count = count;
            }
            @Override
            public int compareTo(Object o) {
                A a = (A) o;
                return count - a.count;
            }
    
            @Override
            public String toString() {
                return "A{count=" + count + '}';
            }
        }
        public static void main(String[] args) {
            TreeMap treeMap = new TreeMap();
            treeMap.put(new A(9), "李一桐");
            treeMap.put(new A(7), "刘亦菲");
            treeMap.put(new A(-1), "鞠婧祎");
            treeMap.put(new A(4), "蔡依林");
            System.out.println(treeMap);
            Map.Entry firstEntry = treeMap.firstEntry();
            System.out.println(firstEntry);
            System.out.println(firstEntry.getKey() + "------->" + firstEntry.getValue());
            System.out.println("lastKey is " + treeMap.lastKey());
            System.out.println(treeMap.higherEntry(new A(4)));
        }
    }

    EnumMap

    EnumMap是一个与枚举类一起使用的Map实现,EnumMap中的所有key都必须是单个枚举类的枚举值,创建EnumMap时必须显式或隐式指定它对应的枚举类。
    import java.util.EnumMap;
    
    public class EnumMapTest {
        private enum SEASON {
            SPRING, SUMMER, FALL, WINTER
        }
        public static void main(String[] args) {
            EnumMap enumMap = new EnumMap(SEASON.class);
            enumMap.put(SEASON.SPRING, "春乱花开");
            enumMap.put(SEASON.SUMMER, "夏日炎炎");
            enumMap.put(SEASON.FALL, "秋高气爽");
            enumMap.put(SEASON.WINTER, "白雪皑皑");
            System.out.println(enumMap);
        }
    }
     

    各种Map性能分析:

    对于 Map 常用实现类而言,虽然 HashMap Hashtable 的实现机制基本一样,但由于 Hashtable 是一个古老的、线程安全的集合,因此 HashMap 通常比 Hashtable 要快。TreeMap 通常比 HasMap Hashtable 要慢( 尤其在插入、删除 key-value 对时更慢),因为 TreeMap底层采用红黑树来管理 key-value 对(红黑树的每个节点就是 key-value 对)。使用 TreeMap有一个好处: TreeMap 中的 key-value 对总 处于有序状态。
    对于一般的应用场景 程序应该多考虑使用 HashMap,因为 HashMap 正是为快速查询设计的。HashMap 底层其实也是采用数组来存储 key-value 但如果程序需要一个总是排好序的 Map 时,则可以考虑使用 TreeMap。
    LinkedHashMap 比 HashMap 慢一点,因为它需要维护链表来保持 Map key-value 时的添加顺序。IdentityHashMap 能没有特别出色之处,因为它采用与 HashMap 基本相似的实现 只是它使用==而不equals方法来判断元素相等。EnumMap的性能最好,但它只能使用同一个枚举类的枚举值作为key。
     

    总结:

    一般就用HashMap,要对key排序用TreeMap,要对我们的key保持插入的顺序用LinkedHashMap。
     

    线程安全的HashMap

    1、ConcurrentHashMap - 推荐

    private Map<String, Object> map = new ConcurrentHashMap<>();

    这是最推荐使用的线程安全的Map,也是实现方式最复杂的一个集合,每个版本的实现方式也不一样,在jdk8之前是使用分段加锁的一个方式,分成16个桶,每次只加锁其中一个桶,而在jdk8又加入了红黑树和CAS算法来实现。性能好

    2、HashTable

    private Map<String, Object> map = new Hashtable<>();

    HashTable的get/put方法都被synchronized关键字修饰,说明它们是方法级别阻塞的,它们占用共享资源锁,所以导致同时只能一个线程操作get或者put,而且get/put操作不能同时执行,所以这种同步的集合效率非常低,一般不建议使用这个集合。

     3、SynchronizedMap

    private Map<String, Object> map = Collections.synchronizedMap(new HashMap<String, Object>());

    这种是直接使用工具类里面的方法创建SynchronizedMap,把传入进行的HashMap对象进行了包装同步而已。这个同步方式实现也比较简单,看出SynchronizedMap的实现方式是加了个对象锁,每次对HashMap的操作都要先获取这个mutex的对象锁才能进入,所以性能也不会比HashTable好到哪里去,也不建议使用。

    看源码

     

    ============历史笔记==================

    Map集合java.util.Map
    以键值对的方式

    HashMap hashMap = new HashMap(); //非线程安全
    Hashtable hashtable = new Hashtable(); //线程安全
    HashMap:底层是Hash表(散列表)
    HashTable:线程安全的Hash表。
    --Properties 继承了hashTable,键、值都是String字符串用来设置/读取系统属性
    TreeMap: 实现了SortedMap接口可以根据键自然排序,要求键必须是可比较的
    键是根据红黑树原理排序的,红黑树:一种自平衡二叉树

    Map的操作:
    clear() 清除元素
    containsKey() 判断是否包含键
    containsValue() 判断是否包含值
    entrySet() 返回所有Entry的集合,
    get(Object Key) 返回当前Map中键key对应的Value值
    isEmpty() 判断是否为空
    KeySet() 返回所有键的集合
    put(K key,V value) 向Map中添加元素
    putAll(Map<?extends K, ?extend V> m)
    remove(Object key) 从map中删除键与key匹配的键值对
    remove(Object key, Object value) 从map中删除键与key匹配的键值对
    replace(K key , V value) 把map中键与key匹配的值替换为value
    size() 返回键值对的数量
    values() 返回所有值的集合
    15-1 HashMap

    15-1-1 示例代码:
    //1、定义一个map集合,使用HashMap类实现
    Map<String,Integer> map = new HashMap<>();
    //2、添加数据
    map.put("zhangsan",1300);
    map.put("lisi",2370);
    map.put("wangwu",2300);
    map.put("zhaoliu",17800);

    //3、直接打印调用HashMap的toString方法
    System.out.println(map);//{lisi=2370, zhaoliu=17800, zhangsan=1300, wangwu=2300}

    //4、Map的键不允许重复
    map.put("zhaoliu",1234);//如果已存在,用put重复添加将会更新原来的数据=replace的效果
    System.out.println(map); //{lisi=2370, zhaoliu=1234, zhangsan=1300, wangwu=2300}
    //5、更改
    map.replace("zhaoliu",1234);
    System.out.println(map);
    //6、判断
    System.out.println(map.containsKey("zhangsan"));
    System.out.println(map.containsValue(2300));
    //7、获取Value
    System.out.println(map.get("zhangsan"));
    System.out.println(map.get("zhangsi")); //不存在返回null
    //8、删除
    map.remove("zhangsan",9999); //键值都匹配上才删除
    System.out.println(map);
    map.remove("zhangsan");//键在就删除
    System.out.println(map);
    //9、遍历
    //返回所有键的集合
    Set<String> set = map.keySet();
    System.out.println(set);
    //返回所有值的集合,
    for (Integer value : map.values()) {
    System.out.println(value);
    }
    //10、遍历
    for (Map.Entry<String, Integer> entry : map.entrySet()) {
    System.out.println(entry.getKey() + "=" + entry.getValue());
    }
    典型应用示例:
    //统计一个字符串中每个字符出现的次数
    Map<Character,Integer> map = new HashMap<>();
    String text = "1234567uyutrewqwertyuytrerfdsadfghjkokmkool,,kxcmnbzsertfgvxfwerdfxdweetrgfdsewethfgcxvcz";
    for (int i = 0; i < text.length(); i++) {
    char c = text.charAt(i);
    if (map.containsKey(c)){ //如果已存在键,把值取出来+1
    map.replace(c,map.get(c) + 1); //把值取出来+1,更新键值对
    }else {
    map.put(c,1); //如果原来没有,put进去,值为1
    }
    }
    System.out.println(map);
    15-1-2 HashMap的工作原理
    put工作原理:
    1)HashMap底层是哈希表,哈希表就是一个数组,数组元素是链表。
    2)put添加键值对时:
    先根据键的哈希码,经过hash函数计算hash值,根据hash值计算数组下标,访问table[i]元素,
    如果该元素为null,就创建节点保存到元素中。
    如果该元素不为null,遍历table[i]存储的链表中所有节点
    如果有节点的key与当前键相等(Equals),就是用新的value值替换节点中的值
    如果所有节点的key都不匹配,就创建一个新的节点插入到链表的尾部
    --------传说中的哈希函数:
    static final int hash(Object key)
    int h;
    rerurn (key == null ) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    //如果key为null 返回0;
    //如果不为null,用key的hash码 和 哈希码右移16位 取异或的值。
    //(int 占用4个字节,共计32位的长度)
    get工作原理: }
    先根据键的哈希码,经过hash函数计算hash值,根据hash值计算数组下标,访问table[i]元素,
    如果该元素为null返回null
    如果不为null,遍历table[i]的链表,如果链表中有节点的键和与当前键Equals相等,就把该节点的value值返回,如果所有节点的key与当前get的键不匹配,返回null

    知识点:
    如何解决hash碰撞的?
    答:-----就是用链表法
    JDB中对HashMap做了哪些改进:
    答:1、新增节点插入链表尾部
    2、链表长度大于8个,会转换为红黑树(自平衡二叉树)
    应用案例:取出url中所有请求参数params
    Map<String,String[]> map = new HashMap<>();

    String urlText = "http://www.baihe.com/register?name=lisi&age=22&gender=女&interest=sing&interest=dance&interest=rap&interest=ball";
    //取出url中所有搜索值
    int index = urlText.indexOf("?");
    //从?后面开始取子串
    String paramText = urlText.substring(index + 1);
    System.out.println(paramText);//name=lisi&age=22&gender=女&interest=sing&interest=dance&interest=rap&interest=ball
    //取出的所有参数缠粉为数组
    String[] paramTextArray= paramText.split("[=&]");//正则表达式放到双引号里面
    System.out.println(Arrays.toString(paramTextArray));


    //对数组遍历取值//每次从paramTextArray数组中取出参数名和参数值两个数据,所以步长为2
    for (int i = 0; i < paramTextArray.length; i+=2 ) {

    //如果key不存在,就创建一个数组放进map中,key为下标为i的paramTextArray数组值
    if (! map.containsKey(paramTextArray[i])){
    //添加/创建一个数据放到map集合中,值为一个新数组,创建是传入数据初始化即可
    map.put(paramTextArray[i],new String[]{paramTextArray[i+1]});
    }else {
    //如果已存在则取出原来的数组更新
    String[] paramArray = map.get(paramTextArray[i]);
    //更新前首先要扩容数组长度
    paramArray = Arrays.copyOf(paramArray,paramArray.length +1);
    //新增数组元素
    paramArray[paramArray.length -1 ] = paramTextArray[i + 1];
    //更新元素到
    map.replace(paramTextArray[i],paramArray);
    }
    }

    //打印取值的map结果
    for (Map.Entry<String, String[]> entry : map.entrySet()) {
    System.out.println(entry.getKey() + "="+ Arrays.toString(entry.getValue()));
    }
    //输出
    // gender=[女]
    //interest=[sing, dance, rap, ball]
    //name=[lisi]
    //age=[22]
    15-2 HashTable
    1)底层都是hash表,HashTable是线程安全的,HahsMap不是线程安全的。
    2)初始化容量HashTable为11,HashMap为16
    3)加载因子:0.75 当键值对数量> 容量*加载因子 个数进行扩容。
    4)扩容:HashTable按2倍+1扩容,HashMap按2倍大小扩容
    5) 在使用时HashTable的键与值都不能为null,而HashMap的键和值都可以为null
    6)在创建是可以指定初始化容量,HashMap会吧初始化容量自动跳转为2的幂次方,为了快速计算数组下标;而HashTable指定多少就是多少

    15-2-1 Properties
    继承了HashTable
    键值都是String字符串
    经常用户设置读取系统属性
    方法
    setProperty(属性名、属性值)
    getProperty(属性名、属性值)
    //默认键值都是String,无需指定
    示例代码:
    Properties properties = new Properties();
    properties.setProperty("tdq","123123");

    System.out.println(properties.getProperty("tdq"));
    System.out.println(properties.getProperty("td"));

    //读取系统属性
    properties = System.getProperties();
    //遍历取值
    /* for (Object o : properties.keySet()) {
    System.out.println(o);//取key
    }*/
    //遍历取值entrySet
    for (Map.Entry<Object, Object> objectObjectEntry : properties.entrySet()) {
    System.out.println(objectObjectEntry.getKey() + "=" + objectObjectEntry.getValue());
    }
    读取配置文件示例代码:

    //propertiesTest02.java
    【方法一:代码文件.class.getResourceAsStream()/Thread.currentThread()】

    //1、键值默认都是String,创建Properties对象
    Properties properties = new Properties();

    //2、加载配置文件
    InputStream in = propertiesTest02.class.getResourceAsStream("/resources/config.properties");
    InputStream in2 = Thread.currentThread().getContextClassLoader().getResourceAsStream("resources/config.properties");
    properties.load(in2);//load异常Alt + Enter 选择第一个处理

    //3、读取配置文件
    System.out.println(properties.getProperty("zmd"));
    System.out.println(properties.getProperty("pwd"));

    【方法二:通过ResourceBundle.getBundle("相对路径")】
    用法ResourceBundle.getBundle("")不需要扩展名和/ ;这个是 静态方法
    ResourceBundle resourceBundle = ResourceBundle.getBundle("resources/config");
    System.out.println(resourceBundle.getString("zmd"));
    System.out.println(resourceBundle.getString("pwd"));
    -----------------------------------------------------------
    配置文件
    目录:properites
    文件:propertiesTest02.java
    目录:resources
    文件:config.properties 【创建方法IDEA new resource bundle】
    内容如下:
    name=zmd
    pwd:123
    15-3 TreeMap
    TreeMap 实现了SortedMap 接口,可以根据键自然排序,要求键必须是可比较的
    1)在构造方法中指定Comparator比较器
    2)让键实现Comparable接口
    3)TreeMap只能根据键排序,不能根据值排序
    4)使用String作为键,String本身已经实现了Comparable接口
    示例代码:
    //创建TreeMap存储<员工姓名,工资> 要求根据姓名降序排序
    TreeMap<String,Integer> treeMap = new TreeMap<>(new Comparator<String>() {
    @Override
    public int compare(String o1, String o2) {
    return o2.compareTo(o1);
    }
    });
    treeMap.put("lisi",2000);
    treeMap.put("wangwu",1234);
    treeMap.put("zhaoliu",8863);
    treeMap.put("chenqi",7800);
    System.out.println(treeMap); //{zhaoliu=8863, wangwu=1234, lisi=2000, chenqi=7800}
    //注意:TreeMap只能根据键排序,不能根据值排序

    //如果没有在构造方法中指定Comparator,键实现Comparable接口即可。基本数据类型已经实现Comparable接口
    TreeMap<String,Integer> treeMap1 = new TreeMap<>();
    treeMap1.putAll(treeMap);
    System.out.println(treeMap1);//默认根据键升序排列


    TreeMap中的键是根据共黑树原理排序,是一种自平衡二叉树
    二叉树了解:
    遍历二叉树的方式有:先序遍历
    后序遍历
    中序遍历(左子树、根、右子树)

  • 相关阅读:
    Python 函数 切片 迭代 列表生成器
    Python中各种集合 list tuple set dict
    Python学习 常识+基础基础
    《零基础学习Python》01
    原生API实现拖拽上传文件实践
    美团点评面试题小结(测试开发和前端开发)
    从实践的角度理解cookie的几个属性
    一道javascript面试题(闭包与函数柯里化)
    marked插件在线实时解析markdown的web小工具
    Github Page+Bmob实现简单动态功能
  • 原文地址:https://www.cnblogs.com/zhangmingda/p/14642532.html
Copyright © 2011-2022 走看看