Java基础复习-集合

Java基础复习-集合

本文仅对java学习知识的查缺补漏

集合框架概述

  1. 集合、数组都是对多个数据进行存储操作的结构,简称Java容器。(这里讲的存储都指的是对内存的操作)
  2. 数组在存储多个数据方面的特点:
    • 一旦初始化以后,长度就确定了,长度不可修改;
    • 数组一旦定义好后,其元素的类型也就确定了。比如:String[] arr; int[] arr1;
    • 数组中提供的方法非常有限,对于添加、删除、插入数据等操作,非常不便。
    • 数组存储数据的特点:有序、可重复;对于无序、不可重复的需求,不能满足。

Java集合可以分为CollectionMap两个体系

  • Collection接口:单列数据,定义了存取一组对象的方法的集合
    • List:元素有序,可重复的集合(动态数组)
      • ArrayListLinkedListVector
    • Set:元素无序,不可重复的集合(集合)
      • HashSetLinkedHashSetTreeSet
  • Map接口:双列数据,保存具有映射关系key-value对的集合
    • HashMapLinkedHashMapTreeMapHashtableProperties

Collection常用方法

Collection接口的实现类的对象中添加数据obj时,要求obj所在类重写equals()

例子1

@Test
public void test1(){
    Collection collection = new ArrayList();

    //add(Object o):将元素o添加到集合collection中
    collection.add("AA");
    collection.add("BB");
    collection.add(1234);   //自动装箱
    collection.add(new Date());

    //size():获取添加元素的个数
    System.out.println(collection.size());

    //addAll(Collection o):将集合o中的所有元素添加到当前集合中
    Collection collection1 = new ArrayList();
    collection1.add("BCD");
    collection1.add(456);
    collection.addAll(collection1);

    System.out.println(collection.size());
    System.out.println(collection);

    //clear():清空集合元素
    collection.clear();

    //isEmpty():判断当前集合是否为空
    System.out.println(collection.isEmpty());
}

例子2

@Test
public void test1(){

    Collection collection = new ArrayList();
    collection.add(123);
    collection.add(456);
    collection.add(new String("Mike"));
    collection.add(true);
    Person person = new Person("老麦", 100);
    collection.add(person);
    collection.add(new Person("老麦", 100));

    //1.contains(Object obj):判断当前集合是否包含obj
    //在判断时会调用obj对象所在类的equals()方法
    System.out.println(collection.contains(123));   //true

    System.out.println(collection.contains(new String("Mike")));    //true,这里调用的是String的equals方法
    System.out.println(collection.contains(person));    //true
    System.out.println(collection.contains(new Person("老麦", 100))); //false,因为Person类没有重写equals方法,所以调用的是父类Object的equals方法

    //2.containsAll(Collection coll):判断形参coll中所有元素是否都存在于当前集合中
    Collection collection1 = Arrays.asList(456,123);
    System.out.println(collection.containsAll(collection1));
}

例子3

@Test
public void test2(){
    Collection collection = new ArrayList();
    collection.add(123);
    collection.add(456);
    collection.add(new String("Mike"));
    collection.add(true);
    collection.add(new Person("老麦", 100));
    collection.add(123);

    //remove(Object obj):只移除第一个相同的元素,后面的相同不会移除
    //        collection.remove(123);
    System.out.println(collection);

    //removeAll(Collection coll):差集,从当前集合中移除coll中所有元素,所有有相同的元素都会移除
    Collection collection1 = Arrays.asList(123,4567);
    collection.removeAll(collection1);
    System.out.println(collection);

    //retainAll(Collection coll):交集,获取当前集合和coll集合的交集,并返回给当前集合
    Collection collection2 = Arrays.asList(123,456,789);
    collection.retainAll(collection2);
    System.out.println(collection);

    //equals(Collection coll):判断集合中所有元素是否相同,包括元素的顺序,这里省略代码
}

@Test
public void test3(){
    Collection collection = new ArrayList();
    collection.add(123);
    collection.add(456);
    collection.add(new String("Mike"));
    collection.add(true);
    collection.add(new Person("老麦", 100));
    collection.add(123);
    //hashCode():返回当前对象的哈希值
    System.out.println(collection.hashCode());

    //集合---->数组:toArray()
    Object[] objects = collection.toArray();
    for (int i = 0; i < objects.length; i++) {
        System.out.println(objects[i]);
    }

    System.out.println("*************************");
    //数组---->集合:asList()
    List<String> list = Arrays.asList(new String[]{"abc", "def", "mno"});
    System.out.println(list);
    //细节:如果用基本数据类型当数组形参的话,asList方法会将所有元素当成一个数,不是多个数
    List<int[]> ints = Arrays.asList(new int[]{123, 456});
    System.out.println(ints);
    System.out.println(ints.size());

    //改正:用包装类或者不new,直接填值
    List<Integer> list1 = Arrays.asList(new Integer[]{123, 456});
    System.out.println(list1);
    System.out.println(list1.size());
}

Iterator:迭代器

  • Iterator:仅用于遍历集合Iterator本身并不提供承载对象的能力。如果需要创建Iterator对象,则必须有一个被迭代的集合。
  • 集合对象每次调用iterator()方法都会得到一个全新的迭代器对象,默认游标都在集合的第一个元素之前。
  • next():下一个元素(执行的时候,指针先下移一位,再拿到元素)。
  • hasNext():有没有下一个元素。
  • remove():移除某个元素,没有调用next()之前不可以调用移除的方法,因为指针还指在要移除元素的前一个。
  • 迭代器操作的是原来的集合,不是副本。
  • 注意:迭代器用完一次后,要重新建一个新的,相当于指针遍历到后面了,需要重新把指针指到第一个元素的前一位。
/**
 * 集合元素遍历,使用迭代器Iterator接口
 * 内部方法:hasNext() 和 next()
 * @Author: fxx
 * @Date: 2020/12/25 22:13
 */
@Test
public void test(){
    Collection collection = new ArrayList();
    collection.add(123);
    collection.add(456);
    collection.add(new String("Mike"));
    collection.add(true);
    collection.add(new Person("老麦", 100));
    collection.add(123);

    Iterator iterator = collection.iterator();
    //迭代器正确用法
    while(iterator.hasNext()){
        System.out.println(iterator.next());
    }

    //下面是错误用法
    //错误用法1:
    while((iterator.next()) != null){
        System.out.println(iterator.next());
    }

    //错误用法2:
    while(collection.iterator().hasNext()){
        System.out.println(collection.iterator().next());
    }
}

foreach

@Test
public void test(){
    Collection collection = new ArrayList();
    collection.add(123);
    collection.add(456);
    collection.add(new String("Mike"));
    collection.add(true);
    collection.add(new Person("老麦", 100));
    collection.add(123);
    //        Iterator iterator = collection.iterator();
    //for(集合元素的类型 局部变量 :集合对象)
    //foreach循环,内部调用的还是迭代器,每次取到对应元素的值,赋给局部变量,
    //所以foreach循环不能用来修改值
    for (Object o :
         collection) {
        System.out.println(o);
    }
}

List

  • ArrayList:作为List接口的主要实现类;线程不安全,效率高;底层使用Object[]存储(数组顺序存储)
  • LinkedList:对于频繁的插入、删除操作,使用此类效率比ArrayList高;底层使用双向链表存储;
  • Vector:作为List接口的古老实现类;线程安全,效率低;底层使用Object[]存储(数组存储)
说明+常用方法
/**
 * List接口的类
 * ArrayList源码分析:
 *
 * jdk7下:
 * ArrayList list = new ArrayList();底层创建了长度为10的Object[]数组elementData
 * list.add(123);   //elementData[0] = new Integer(123);
 * 如果添加的时候发现elementData数组容量不够,则扩容
 * 默认情况下,扩容为原来容量的1.5倍
 * (原来的容量右移一位+原来容量:int newCapacity = oldCapacity + (oldCapacity >> 1);),
 * 同时需要将原有数组中的数据复制到新数组中。
 *
 * 结论:建议开发中使用带参的构造器:ArrayList(int capacity)知道容量的情况下
 *
 * jdk8下:
 *  ArrayList list = new ArrayList();底层Object[] elementData初始化为{},并没有创建长度为10的数组
 * list.add(123);   //第一次调用add()时,底层才创建了长度为10的数组,并将数据123添加到elementData中
 * 后续添加和扩容操作与jdk7一样
 *
 * 小结:jdk7中的ArrayList对象的创建类似单例模式下的饿汉式;而jdk8中的ArrayList对象的创建类似
 *          于单例模式下的懒汉式,延迟了数组的创建,节省内存。
 *
 *
 * LinkedList源码分析:
 * LinkedList list = new LinkedList();  内部声明了Node类型的first和last属性,默认值为null
 * list.add(123);   //将123封装在Node中,创建了Node对象
 *
 * 其中,Node定义为:体现了LinkedList的双向链表的说法
 *
 *
 * Vector源码分析:
 * jdk7和jdk8中通过Vector()构造器创建对象时,底层都创建了长度为10的数组。
 * 在扩容方面,默认扩容为原来的数组长度的2倍
 *
 * @Author: fxx
 * @Date: 2020/12/25 23:02
 */
public class ListTest {

    @Test
    public void test1(){
        ArrayList arrayList = new ArrayList();
        arrayList.add(123);
        arrayList.add(456);
        arrayList.add(new Person("Mike",12));
        arrayList.add(123);
        System.out.println(arrayList);

        //add(int index, Object obj):在索引index处添加对象obj
        arrayList.add(1,789);
        System.out.println(arrayList);

        //addAll(Collection coll):添加集合coll,可以加上索引
        List<String> list = Arrays.asList("abc", "def");
        arrayList.addAll(list);
        System.out.println(arrayList);

        //get(int index):获取索引index处的元素
        System.out.println(arrayList.get(1));

        //indexOf(Object obj):获取obj首次出现的位置,没有就返回-1
        System.out.println(arrayList.indexOf(123));
        System.out.println(arrayList.indexOf(159));

        //Object remove(int index):移除指定index位置的元素,并返回该元素
        //boolean Object remove(Object obj):移除指定obj,并返回移除是否成功
        System.out.println(arrayList.remove(0));

        //int lastIndexOf(Object obj):返回obj在当前集合中最后一次出现的位置
        System.out.println(arrayList.lastIndexOf(123));
        //Object set(int index, Object ele):设置索引index处的元素为ele
        arrayList.set(1, "elc");
        System.out.println(arrayList);
        //List subList(int fromIndex, int toIndex):返回从fromIndex到toIndex位置的左闭右开子集合
        List list1 = arrayList.subList(0, 3);
        System.out.println(list1);
    }
}

Set

HashSet
  • 遍历结果与添加过程不一定一致
  • 其他的详见代码中的注释
重写hashCode()
/**
* idea重写出来的hashCode方法
* 为什么要乘上31,因为一般为了减少冲突,选的数都会大一点,但也不能太大,太大就超过数组的界限了(出界),
* 一般采用的是2的n次幂,但是2的n次幂计算出来的都是偶数,偶数的运算结果,容易与其他数的运算结果相同,
* 所以一般采用的是素数:素数的因子只有素数本身和1,这样子做是为了减少计算结果冲突的概率
* 31 = 2^5 -1
*/
@Override
public int hashCode() {
    int result = name != null ? name.hashCode() : 0;
    result = 31 * result + age;
    return result;
}
LinkedHashSet
  • LinkedHashSet作为HashSet的子类,在添加数据的同时,每个数据多加了2个指针:分别指向前驱和后继
  • 遍历结果与添加过程一致
  • 优点:对于频繁的遍历操作,LinkedHashSet的效率高于HashSet
TreeSet

底层采用红黑树存储

说明
/**
 * Set接口的类
 * HashSet:作为Set接口的主要实现类;线程不安全;可以存储null值
 *      LinkedHashSet:作为HashSet的子类,遍历其内部数据时,可以按照添加的顺序进行遍历
 * TreeSet:可以按照添加对象的指定属性,进行排序,存储的对象必须是同一类型的。
 *
 * Set:存储无序的,不重复的数据
 *                          以HashSet为例
 *  无序性:不等于随机性。存储的数据在底层数组中并非按照数组索引的顺序添加,
 *          而是由数据的哈希值决定的,联想哈希表存放方式,即为无序。
 *  不可重复性:保证添加的元素按照equals()判断时,不会返回true。即:相同元素只能添加一个。
 *
 *  添加元素的过程:以HashSet为例:
 *      向HashSet中添加元素a,首先调用元素a所在类的hashCode()方法,计算元素a的哈希值,
 *      此哈希值接着通过某种算法计算出在HashSet底层数组中存放的位置(即:索引位置),
 *      判断数组此位置上是否已经有元素:
 *          如果没有,则元素a添加成功。---->添加成功
 *          如果有,存在元素b甚至更多元素(以链表方式存在),则比较元素a与元素b的hash值:
 *              如果hash值不同,那么把元素a添加进用于存储重复位置的链表;---->添加成功
 *              如果hash值相同,那么需要调用元素a所在类的equals()方法:
 *                  equals()返回true,则所添加元素a已经存在Set中,添加失败;
 *                  equals()返回false,则把元素a添加进用于存储重复位置的链表;---->添加成功
 *
 *  HashSet底层:数组+链表(前提:jdk7)(jdk8使用HashMap)
 *
 *  要求:
 *      1.向Set中添加的数据,其所在类一定要重写hashCode()和equals()。
 *      2.重写的hashCode()和equals()计算相同元素得出的哈希值必须一致,所以2个方法中用到的属性尽量相同。
 *
 *
 *                      TreeSet
 * 1.向TreeSet中添加数据,要求是相同类的对象
 * 2.两种排序方式:自然排序(实现Comparable接口) 和 定制排序(Comparator)
 *      如果两种同时出现,会优先使用定制排序
 *  注意:
 *      自然排序中:TreeSet中比较两个元素是否相同,不再使用equals()方法,而是用重写的compareTo()方法,
 *                  如果compareTo()返回了0,就相当于两个元素一样了
 *      定制排序中:TreeSet中比较两个元素是否相同,不再使用equals()方法,而是用重写的compare()方法,
 *  *                  如果compare()返回了0,就相当于两个元素一样了
 *
 *  TreeSet和TreeMap采用的是 红黑树 存储结构,特点:有序,查询速度比List快
 *
 *
 * @Author: fxx
 * @Date: 2020/12/26 14:38
 */
public class SetTest {

    @Test
    public void test(){
        TreeSet treeSet = new TreeSet();
        treeSet.add(666);
        treeSet.add(456);
        treeSet.add(-9);
        treeSet.add(0);
        treeSet.add(596);
        System.out.println(treeSet);	//TreeSet输出是排好序的,默认从小到大
    }
}

Map

Map接口继承树

  • Map:存储key-value
    • HashMap:作为Map的主要实现类;线程不安全,效率高;可以存储null的key和value
      • LinkedHashMap:保证在遍历map元素时,可以按照添加的顺序实现遍历。原因:在原有的HashMap底层结构基础上,添加了一对指针,指向前驱和后继。对于频繁的遍历操作,这个类的遍历效率会高于Hashmap
    • TreeMap:保证按照添加的key-value对进行排序,实现排序遍历。(Comparator或者Comparable),底层使用红黑树
    • Hashtable:作为古老的实现类;线程安全,效率低;可以存储null的key和value
      • Properties:常用来处理配置文件。key和value都是String类型
  • HashMap底层:
    • 数组+链表(jdk7及以前)
    • 数组+链表+红黑树(jdk8)

Map结果的理解

  • Map中的key:无序的,不可重复的,使用Set存储所有的key---->key所在的类要重写equals()hashCode()(以HashMap为例,如果是TreeMap,则关注的是ComparatorComparable
  • Map中的value:无序的,可重复的,使用Collection存储所有的value---->value所在类要重写equals()方法
  • 一个键值对:key-value构成了一个Entry对象
  • Map中的entry:无序的,不可重复的,使用Set存储所有的entry

常用方法

  • 添加:put(Object obj, Object value)
  • 删除:remove(Object key)
  • 修改:put(Object obj, Object value)
  • 查询:get(Object key)
  • 长度:size()
  • 遍历:keySet()values()entrySet()

Properties

public void test() throws Exception{
    Properties pro = new Properties();
    FileInputStream fis = new FileInputStream("jdbc.properties");
    pros.load(fis);	//加载流对应的文件
    String name = pros.getProperty("name");
    String password = pros.getProperty("password");
    System.out.println("name="+name+",password="+password);
    //后面才复习流,关于流的关闭等操作具体看后面流的文章
}

HashMap等其他Map(重点)

/**
 * HashMap的底层实现原理?  以jdk7为例说明:
 *  HashMap map = new HashMap();
 *  在实例化以后,底层创建了长度为16的一维数组Entry[] table。
 *  map.put(key1, value1):
 *  首先,调用key1所在类的hashCode()计算key1哈希值,此哈希值经过某种算法计算以后,得到在Entry
 *  数组中的存放位置,
 *      如果此位置上没有数据,那么添加key1-value1(即Entry)--->添加成功
 *      如果此位置上有数据(一个或多个,多个以链表方式存储),比较key1和已经存在的一个或多个数据
 *        的哈希值:
 *              如果key1的哈希值与已经存在的数据的哈希值不同,那么添加key1-value1(即Entry)--->添加成功
 *              如果key1的哈希值与已经存在的数据(key2-value2)的哈希值相同,则继续比较:
 *                  调用key1所在类的equals(key2)
 *                      如果equals()返回false:那么添加key1-value1(即Entry)--->添加成功
 *                      如果equals()返回true:使用value1替换value2(重点!!!)
 *
 *      在不断的添加过程中,会涉及到扩容问题,当超出临界值(且要存放的位置非空时),扩容。
 *      默认的扩容方式:扩容为原来容量的2倍,并将原有的数据复制过来,但是复制过来的时候,
 *      需要重新计算原有元素的hash值,重新放到新数组里。
 *
 *
 *      jdk8相较于jdk7在底层实现方面不同:
 *      1.new HashMap():底层没有创建一个长度为16的数组
 *      2.jdk 8 底层的数组是:Node[],而非Entry[]
 *      3.首次调用put()方法时,底层创建长度为16的数组
 *      4.jdk7底层结构只有:数组+链表。jdk8中底层结构:数组+链表+红黑树。
 *      5.当数组中某一位置上的元素以链表形式存在的数据个数 > 8 且当前数组的长度 > 64时,
 *          索引位置上的所有数据改为使用红黑树存储。
 *
 *
 *      DEFAULT_INITIAL_CAPACITY:HashMap的默认容量:16
 *      DEFAULT_LOAD_FACTOR:HashMap的默认加载因子:0.75(决定什么时候扩容)
 *      threshold:扩容的临界值 = 容量 * 加载因子:16 * 0.75 = 12(已经put的个数)
 *      TREEIFY_THRESHOLD:Bucket中链表长度大于该默认值,转化为红黑树:8
 *      MIN_THREEIFY_CAPACITY:桶中的Node被树化时最小的hash表容量:64
 *
 *   LinkedHashMap底层原理:
 *      源码中,大部分源码还是跟父类HashMap一样,只是在put的时候重写了父类的newNode方法和Node类
 *      static class Entry<K,V> extends HashMap.Node<K,V>{
 *          Entry<K,V> before, after;       //能够记录添加的时候元素的前后顺序
 *          Entry(int hash, K key, V value, Node<K,V> next){
 *              super(hash, key, value, next);
 *             }
 *      }
 *
 *      jdk8里的HashSet底层是new了HashMap,add的时候调用的是HashMap的put(key, value)方法,
 *      key就是add(Object obj)里的obj,而value是一个Object,没有值,只是为了防止null,而且
 *      这个Object是static的,也就是说在内存里只有一份,所有的key都指向了同一个Object,节省内存
 *
 *
 *                      TreeMap
 *       向TreeMap中添加key-value,要求key必须是由同一个类创建的对象,因为要按照key进行排序:
 *       自然排序、定制排序
 *       具体参考TreeSet,方式差不多
 *
 *
 * @Author: fxx
 * @Date: 2020/12/26 20:53
 */
public class MapTest {

    /**
     * 元视图操作:
     * Set ketSet():返回所有key构成的Set集合
     * Collection values():返回所有value构成的Collection集合
     * Set entrySet():返回所有key-value构成的Set集合
     */
    @Test
    public void test(){
        HashMap hashMap = new HashMap();
        hashMap.put("AA",123);
        hashMap.put("BB",456);
        hashMap.put("CC",789);
        Set set = hashMap.keySet();
        Iterator iterator = set.iterator();
        while(iterator.hasNext())
            System.out.println(iterator.next());

        Collection values = hashMap.values();
        Iterator iterator1 = values.iterator();
        while(iterator1.hasNext())
            System.out.println(iterator1.next());

        Set set1 = hashMap.entrySet();
        Iterator iterator2 = set1.iterator();
        while(iterator2.hasNext())
            System.out.println(iterator2.next());
    }
}

Collections工具类

  • Collections是一个操作SetListMap等集合的工具类
  • Collections中提供了一系列静态的方法对集合元素进行排序、查询和修改等操作,还提供了对集合对象设置不可变、对集合对象实现同步控制等方法。
  • 排序操作(static):
    • reverse(List):反转List中元素的顺序
    • shuffle(List):对List集合元素进行随机排序
    • sort(List):根据元素的自然顺序对指定List集合元素按升序排序
    • sort(List, Comparator):根据指定的Comparator产生的顺序对List集合元素进行排序
    • swap(List, int, int):将指定list集合中的i处元素和j出元素进行交换
    • Object max(Collection):根据元素的自然排序,返回给定集合中最大元素
    • Object max(Collection, Comparator):根据定制排序指定的顺序,返回给定集合中最大元素
    • Object min(Collection):同上(最小)
    • Object min(Collection, Comparator):同上(最小)
    • int frequency(Collection Object):返回指定集合中指定元素的出现次数
    • void copy(List dest, List src):将src中的内容复制到dest
    • boolean replaceAll(List list, Object oldVal, Object newVal):使用新值替换List中对象的所有旧值
/**
 * Collections是操作Collection和Map的工具类
 * @Author: fxx
 * @Date: 2020/12/27 13:46
 */
public class CollectionsTest {

    @Test
    public void test(){
        ArrayList list = new ArrayList();
        list.add(123);
        list.add(46);
        list.add(765);
        list.add(-97);
        list.add(0);
        System.out.println(list);

        //copy的错误用法
//        List list1 = new ArrayList();
//        Collections.copy(list1,list);   //这样会报异常,因为list1的长度必须>=list才能复制

        //copy正确的用法
        List list1 = Arrays.asList(new Object[list.size()]);    //把数组长度撑起来
        System.out.println(list1.size());
        Collections.copy(list1, list);
        System.out.println(list1);

        ArrayList arrayList = new ArrayList();
        List list2 = Collections.synchronizedList(arrayList);
    }
}

集合类的线程安全问题

Collections类中提供了多个synchronizedXxx()方法,该方法可将指定集合包装成线程同步的集合,从而可以解决多线程并发访问集合时的线程安全问题

//返回的list2就是线程安全的
ArrayList arrayList = new ArrayList();
List list2 = Collections.synchronizedList(arrayList);

面试题

1

ArrayList、LinkedList、Vector三者的异同?

  • 相同:
    • 三个类都实现了List接口,存储数据的特点相同:存储有序的,可重复的数据。
  • 不同:
    • 见上面源码部分

2

@Test
public void test(){
    List list = new ArrayList();
    list.add(1);
    list.add(2);
    list.add(3);
    updateList(list);
    System.out.println(list);	//1,2
}

public void updateList(List list){
    list.remove(2);	//这里remove的参数是索引,不是值
   // list.remove(new Integer(2));	//要删掉值的话new对象
}

3

//Person类已经重写equals()和hashCode()
@Test
public void test(){
    HashSet set = new HashSet();
    Person p1 = new Person(1001, "AA");
    Person p2 = new Person(1002, "BB");
    
    set.add(p1);
    set.add(p2);
    System.out.println(set);	//1001,AA,1002,BB
    
    p1.name = "CC";	//这一步修改了原来的AA为CC,但hashCode值依然是用AA计算出来的
    set.remove(p1);
    System.out.println(set);	//1001,CC,1002,BB
    //所以下面这步添加的时候,计算出的CC的hashCode值很可能跟AA的不一样,所以是可以添加成功的
    set.add(new Person(1001, "CC"));	//1001,CC,1002,BB,1001,CC
    System.out.println(set);
    //而这一步,因为原来的AA已经变成了CC,这里虽然用新AA计算出的hashCode值和原来的AA一样了,但是用equals()比较发现值不一样,AA不等于CC,所以新AA还是可以添加成功
    set.add(new Person(1001, "AA"));	//1001,CC,1002,BB,1001,CC,1001,AA
    System.out.println(set);
}

4

HashMap底层实现原理?

见源码分析

5

HashMapHashtable异同?

见源码分析

6

CurrentHashMapHashtable 异同?

7

CollectionCollections的区别?

后者是操作前者的工具类

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值