• 中文
    • English
  • 注册
  • 查看作者
  • Java集合详解

    一.   前言

    最近在刷PAT的题才发现, 自己关于Java中集合相关的知识,已经忘得差不多了,正好借此机会,复习一下。

    二.  装箱拆箱

    Java 语言中的每种原语类型都有一个对应的 JDK 类:

    • boolean:java.lang.Boolean

    • byte:java.lang.Byte

    • char:java.lang.Character

    • short:java.lang.Short

    • int:java.lang.Integer

    • long:java.lang.Long

    • float:     java.lang.Float

    • double:java.lang.Double

    每个 JDK 类都提供了相应方法来解析它的内部表示,并将其转换为相应的原语类型。例如,下面这段代码将十进制值 2 转换为一个 Integer(由基本数据类型转换为引用数据类型):

    int i = 2;
    Integer b = Integer.valueOf(i);
    //或者
    Integer b = new Integer(i)
    //Integer.valueOf("")会用到IntegerCache对象,当IntegerCache中存在时就从cache中取
    //不存在时才会调用new Integer(i)构造函数返回一个Integer对象。所以Integer.valueOf("")会用到cache
    //其效率可能会比用构造函数new Integer(i)高。

    我们称上面的操作为装箱,类似的,也有拆箱操作,比如要将 Integer 表示转换为对应的 int 类(由引用数据类型转换为基本数据类型):

    Integer b = Integer.valueOf(2);
    int i = b.intValue();

    在JDK1.5之后可以自动装箱,自动拆箱:

    Integer i = 10; //自动装箱 
    int n = i; //自动拆箱

    二.  集合框架简介

    • 框架就是一个类库的集合。集合框架就是一个用来表示和操作集合的统一的架构,它包含了实现集合的接口与类。

    • 集合框架中不同的集合类有各自不同的数据结构,所以在使用中要根据应用的性能要求来选择不同的集合类。

    • 集合类在存放在java.util包中,今后进行程序编程时将大量使用集合类和相关接口。

    • 所有的集合框架都应该包含接口,实现类,算法。

    • 除了集合,该框架也定义了几个Map接口和类。Map里存储的是键/值对。尽管Map不是collections,但是它们完全整合在集合中。

    三.  集合接口

    • Iterable :迭代器接口

    • Collection :类集接口

    • List :列表接口

    • Set :数据集接口

    • Queue :队列

    • Map :键-值对组合映射表

    四.  Iterable接口

    • 实现该接口允许对象成为foreach 语句的目标,即该集合对象允许迭代。

    • 类集接口Collection是Iterable的子接口,所以所有类集对象可以迭代访问,而映射Map不行。

    方法

    Iterator<T> iterator()

    功能:

    返回一个在一组T类型的元素上进行迭代的迭代器

    五.  Collection :类集接口

    常用方法:

    • int size() :返回集合中元素的个数

    • boolean isEmpty() :判断集合是否为空

    • boolean contains(Object o) :是否包含指定对象

    • Iterator<E> iterator() :返回迭代器对象

    • Object[] toArray() :把集合转换为数组

    • boolean add(E e) :添加元素

    • boolean remove(Object o) :删除指定元素

    • void clear() :清空

    六.  List :列表接口

    List接口是一个有序的 Collection,使用此接口能够精确的控制每个元素插入的位置,能够通过索引(元素在List中位置,类似于数组的下标)来访问List中的元素,第一个元素的索引为 0,而且允许有相同的元素。因为List是一个接口,所以不能直接实例化,常用的时间方式有:ArrayList、LinkedList

    1. ArrayList

    实现了List的接口,实现了可变大小的数组,随机访问和遍历元素时,提供更好的性能。该类也是非同步的,在多线程的情况下不要使用。ArrayList 增长当前长度的50%,插入删除效率低

    构造方法:

    • ArrayList() :创建一个ArrayList对象,空数组

    • ArrayList(Collection c) :创建一个包含指定Collection对象的ArrayList对象

    • ArrayList(int capacity) :创建ArrayList对象时指定初始容量

    常用方法:

    • E get(int index) :返回此列表中指定位置上的元素

    • int indexOf(Object o) :返回此列表中首次出现的指定元素的索引,如果不包含指定指定元素,返回-1

    例一

    package tv.zhangjia.test;
    
    import java.util.ArrayList;
    import java.util.Iterator;
    import java.util.List;
    
    public class Main {
        public static void main(String[] args) {
            List<String> list = new ArrayList<String>();
            list.add("张三");//添加元素
            list.add("李四");
            list.add("王五");
            list.add(0, "赵四");//向指定位置添加元素
            list.set(0, "刘能");//为指定位置赋值
            System.out.println(list.size());
            System.out.println(list);
    
            System.out.println("___________________迭代器遍历");
            Iterator<String> it = list.iterator();
            while (it.hasNext()) {
                System.out.println(it.next());
            }
    
            System.out.println("___________________迭代器遍历2");
            for (Iterator<String> iterator = list.iterator(); iterator.hasNext(); ) {
                System.out.println(iterator.next());
            }
    
            System.out.println("___________________转换为数组遍历");
            String[] str2 = new String[list.size()];
            list.toArray(str2);
            for (int i = 0; i < str2.length; i++)
            {
                System.out.println(str2[i]);
            }
    
            System.out.println("___________________foreach遍历");
            for (String str : list) {
                System.out.println(str);
            }
    
            //查找指定元素,没有的话,默认返回-1
            System.out.println(list.indexOf("张四"));
            List<String> list2 = new ArrayList<String>();
            list2 = list; 
            //将list1的值赋给list2,实际上时将list1的引用交给list2,所有以将list1中的“张三”remove掉之后,list2中的“张三”也被remove掉了
            //如果只是想单纯的值复制,则用list2.addAll(list)方法
            list2.remove("张三");
    
            System.out.println("___________________get(int index)方法遍历");
            for (int i = 0; i < list.size(); i++) {
                System.out.println(list.get(i));
            }
            System.out.println("________________list2和list");
            for (int i = 0; i < list2.size(); i++) {
                System.out.println(list.get(i));
            }
        }
    }
    
    4
    [刘能, 张三, 李四, 王五]
    ___________________迭代器遍历
    刘能
    张三
    李四
    王五
    ___________________迭代器遍历2
    刘能
    张三
    李四
    王五
    ___________________转换为数组遍历
    刘能
    张三
    李四
    王五
    ___________________foreach遍历
    刘能
    张三
    李四
    王五
    -1
    ___________________get(int index)方法遍历
    刘能
    李四
    王五
    ________________list2和list
    刘能
    李四
    王五

    例二:

    package tv.zhangjia.test;
    
    import java.util.ArrayList;
    import java.util.Iterator;
    
    public class Main {
        public static void main(String[] args) {
            ArrayList<String> list = new ArrayList<String>();
            list.add("zhangjia");
            list.add("lisi");
            list.add("lisi");
            list.add("zhaoliu");
            list.add("zhaoliu");
            list.add(0, "wangwu");//在第0个位置插入wangwu,后面的依次后移
            list.set(4, "gougou");//将第4个元素替换为,输出gougou不是zhaoliu,切记~
    
            //使用迭代器对象统一遍历
            Iterator<String> it = list.iterator();//创建一个名为it的迭代器对象
            while (it.hasNext()) {//如果仍有元素可以迭代,返回true
                System.out.println(it.next());//返回迭代的下一个元素
            }
    
            System.out.println("---------------------------------");        //使用foreach
            for (String str : list) {
                System.out.println(str);
            }        //使用传统循环遍历
            System.out.println("---------------------------------");
            for (int i = 0; i < list.size(); i++) {
                System.out.println(list.get(i));
            }
    
            System.out.println("---------------------------------");        //indexof
            System.out.println(list.indexOf("lisi"));//第一次出现李四的位置
            System.out.println("---------------------------------");        //remov
            System.out.println(list.remove("lisi"));//返回的是布尔类型,只能删除第一个
            System.out.println(list.remove(3));//返回的是被删除的元素名
    
            System.out.println("---------------------------------");        //contains
            System.out.println(list.contains("aaaa"));
            System.out.println(list.contains("lisi"));//是否包含这个元素
            System.out.println("---------------------------------");        //get
            System.out.println(list.get(0));//取得第0个元素
            System.out.println("---------------------------------");        //isEmpty
            System.out.println(list.isEmpty());//检测容器是否没有元素,没有返回true
            list.clear();//清除容易的全部元素
            System.out.println(list.isEmpty());
    
    
        }
    }
    
    输出:
    wangwu
    zhangjia
    lisi
    lisi
    gougou
    zhaoliu
    ---------------------------------
    wangwu
    zhangjia
    lisi
    lisi
    gougou
    zhaoliu
    ---------------------------------
    wangwu
    zhangjia
    lisi
    lisi
    gougou
    zhaoliu
    ---------------------------------
    2
    ---------------------------------
    true
    gougou
    ---------------------------------
    false
    true
    ---------------------------------
    wangwu
    ---------------------------------
    false
    true

    2.  LinkedList

    该类实现了List接口,允许有null(空)元素。主要用于创建链表数据结构,该类没有同步方法,如果多个线程同时访问一个List,则必须自己实现访问同步,解决方法就是在创建List时候构造一个同步的List

    构造方法:

    • LinkedList() :创建一个默认的LinkedList对象

    • LinkedList(Collection c) :创建一个包含指定Collection对象的LinkedList对象

    常用方法:

    • void addFirst(E e) :添加元素到第一个位置

    • void addLast(E e) :添加元素到最后一个位置

    • E removeFirst() :移除第一个元素

    • E removeLast() :移除最后一个元素

    举例:

    package tv.zhangjia.test;
    
    import java.util.LinkedList;
    
    public class Main {
        public static void main(String[] args) {
            //LinkedList内部封装的是双向链表结构,他的元素是Node这个
            //静态内部类的对象,里面封装了item(添加进去的元素)next(下一个节点的引用)prev(上一个节点的引用)
            //不同的容器有不同的数据结构,不同的数据结构操作性能不一样
            //ArrayList:数组,查找效率比较高,直接通过下标就可以,但是插入,删除效率低,因为要做移位操作
            //LinkedList:链表,查找效率比较低,比如查最后一个元素,只能从第一个元素开始查找.但是插入,删除效率
            //比较高
            LinkedList<String> data = new LinkedList<String>();
            data.add("a");
            data.add("b");
            data.add("c");
            data.add("d");
            data.add("e");
            data.add("f");
            data.addFirst("插到第一个");
            data.addLast("插到最后一个");
            data.removeFirst();//移除第一个元素,如果此列表为空,抛出一个异常
            data.pollFirst();//移除第一个元素, 更安全
            for (String a : data) {
                System.out.println(a);
            }
    
            System.out.println(data);
        }
    }
    
    输出:
    b
    c
    d
    e
    f
    插到最后一个
    [b, c, d, e, f, 插到最后一个]

    3.  ArrayList和LinkedList的区别

    • ArrayList是实现了基于动态数组的数据结构,LinkedList基于链表的数据结构。

    • 对于随机访问get()和set(),ArrayList性能优于LinkedList,因为LinkedList要移动指针。

    • 对于新增和删除操作add()和remove(),LinedList比较占优势,因为ArrayList要移动数据。

    七.  Set :数据集接口

    Set容器是一个不包含重复元素的Collection,并且最多包含一个null元素,它和List容器相反,Set容器不能保证其元素的顺序。

    同样Set也是一个接口,所以不能直接实例化,两个Set接口的实现类是HashSet和TreeSet。

    1.  HashSet

    构造方法:

    • HashSet() :构造一个空的set,初始容量16,加载因子默认0.75.

    • HashSet(Collection<? extends E> c) :构造一个包含指定 collection 中的元素的新Set

    • HashSet(int initialCapacity) :构造一个指定容量的Set

    • HashSet(int initialCapacity, float loadFactor) :构造指定容量,指定加载因子的Set

    加载因子:默认0.75,系统会根据加载因子确定在什么时候对容器进行扩容

    判断重复:

    注意(HashSet添加元素的时候)

    向HashSet中添加一个对象时,先用hashCode()方法计算出该对象的哈希码。

    如果该对象哈希码与集合已存在对象的哈希码不一致,则该对象没有与其他对象重复,添加到集合中!

    如果存在于该对象相同的哈希码,那么通过equals()方法判断两个哈希码相同的对象是否为同一对象(判断的标准是:属性是否相同)

    相同对象,不添加。不同对象,添加!

    2.  Treeset

    实现了Set接口,TreeSet 里边的元素进行升序排序,访问和检索很快

    引用类型条件:

    当TreeSet里放入的元素是引用数据类型的时候,必须满足以下条件:(选其一)

    • 该引用数据类型应该实现Comparable接口,重写接口里的compareTo(T t)方法

    • 为TreeSet提供一个Comparator 比较器对象,重写compare(T o1,T o2)方法

    判断相等:

    TreeSet中判断两个元素是否相等的标准:通过比较器比较之后的结果返回0,就认为是相等的

    例一:

    public class User implements Comparable<User> {
        private String userName;
        private String password;
        private int age;   ......
    
        @Override
    
        public int compareTo(User o) {        // 根据年龄升序排序
            if (this.age > o.age) {
                return 1;
            } else if (this.age < o.age) {
                return -1;
            } else {
                return 0;
            }
        }
    }

    例二:

    TreeSet<User> treeSet=new TreeSet<User>(new Comparator<User>(){
    
    @Override
    public int compare(User o1,User o2){
            if(o1.getAge()>o2.getAge()){
            return-1;
            }else if(o1.getAge()<o2.getAge()){
            return 1;
            }else{
            return 0;
            }
            }
            });

    八.  Map :键-值对组合映射表

    映射(map)是一个存储关键字/值对的对象。给定一个关键字,可查询得到它的值,关键字和值都是对象。 关键字必须是唯一的,值可以重复。

    常用方法

    • int size() : 返回此映射中的键-值映射关系数。

    • boolean isEmpty() : 如果此映射未包含键-值映射关系,则返回 true。

    • boolean containsKey(Object key) : 是否包含指定的Key

    • boolean containsValue(Object value) : 是否包含指定的Value

    • V get(Object key) 根据指定的key获取Value

    • V put(K key, V value) : 添加一个键值对到Map

    • V remove(Object key) : 根据指定的Key删除元素

    • Collection<V> values() : 返回当前Map中所有的Value

    • Set<K> keySet() : 返回此映射中包含的键的 Set 视图。

    • Set<Map.Entry<K,V>> entrySet() : 返回此映射中包含的映射关系的 Set 视图

    1.  HashMap

    HashMap 是一个散列表,它存储的内容是键值对(key-value)映射。 该类实现了Map接口,根据键的HashCode值存储数据,具有很快的访问速度,最多允许一条记录的键为null,不支持线程同步,基于哈希表的 Map 接口的实现。此实现提供所有可选的映射操作,并允许使用 null 值和 null键。 不保证它的元素的顺序,元素加入散列映射的顺序并不一定是它们被迭代读出的顺序。

    构造方法

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

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

    HashMap(int initialCapacity, float loadFactor) : 构造一个带指定初始容量和加载因子的空 HashMap。

    HashMap(Map<? extends K,? extends V> m) : 构造一个映射关系与指定 Map 相同的新 HashMap。

    例一:

    HashMap<String, String> hashMap=new HashMap<String, String>();
            hashMap.put("zs","张三");
            hashMap.put("ls","李四");
            hashMap.put("ww","王五");
            System.out.println(hashMap.size());//3
            System.out.println(hashMap.get("user2"));
    //是否包含指定的键
            System.out.println(hashMap.containsKey("user3"));
            System.out.println(hashMap.containsKey("user4"));
    //是否包含指定的值
            System.out.println(hashMap.containsValue("张三"));
            System.out.println(hashMap.containsValue("张四"));
    //获取所有的值
            Collection<String> values=hashMap.values();
            for(String str:values){
            System.out.println(str);
            }
    //获取所有的键
            Set<String> keySet=hashMap.keySet();
            for(String str:keySet){
            System.out.println(str+"-"+hashMap.get(str));
            }
    
            Set<Entry<String, String>>entrySet=hashMap.entrySet();
            for(Entry<String, String> entry:entrySet){
            System.out.println(entry.getKey()+"-"+entry.getValue());
            }

    例二:

    package tv.zhangjia.test.;
    
    import java.util.HashMap;
    import java.util.Map.Entry;
    import java.util.Set;
    
    public class HashMapDemo1 {
    
        public static void main(String[] args) {
            HashMap<String,String> map = new HashMap<String,String>();
            map.put("luffy", "索隆");
            map.put("luffy", "路飞");//因为键相同,所以覆盖了索隆
            map.put("shanzhi", "山治");
            map.put("namei", "娜美");//输出的时候,无序
            System.out.println(map);
            //打印输出容器的所有的键
    
            System.out.println(map.keySet());//[namei, shanzhi, luffy]
          /*Set<String> keys = map.keySet();
          for(String key:keys){
             System.out.println(key);
          }*/
    
            //打印输出容器的所有的值
            System.out.println(map.values());//[娜美, 山治, 路飞]
            // 按照键值对对应关系打印,方法1
            Set<String> keys = map.keySet();
            for(String key:keys){
                System.out.println(key+"---"+map.get(key));
            }
            // 按照键值对对应关系打印,方法2
            Set<Entry<String, String>> entrys = map.entrySet();
            for(Entry<String, String> en :entrys) {
                System.out.println(en.getKey()+en.getValue());
            }
    
        }
    
    }
    
    
    {namei=娜美, shanzhi=山治, luffy=路飞}
    [namei, shanzhi, luffy]
    [娜美, 山治, 路飞]
    namei---娜美
    shanzhi---山治
    luffy---路飞
    namei娜美
    shanzhi山治
    luffy路飞

    例三:

    Map<String, String> map = new HashMap<String, String>();
    // 添加元素
    map.put("zs", "张三");
    map.put("ww", "王五");
    map.put("ls", "李四");
    // 添加重复的key,会替换掉之前的
    map.put("ww", "王老五");
    // 根据key查找value
    String zs = map.get("zs");
    System.out.println(zs);
    String ls = map.get("ls");
    System.out.println(ls);
    String string = map.get("abc");// 找不到时,返回null
    System.out.println(string);
    // 是否包含指定的key
    System.out.println(map.containsKey("zs"));// true
    System.out.println(map.containsKey("abc"));// false
    // 是否包含指定的值
    System.out.println(map.containsValue("张三"));
    // 移除元素
    map.remove("zs");
    System.out.println(map);

    例四:

    //使用map描述一本书
    Map<String, Object> book = new HashMap<String, Object>();
    book.put("id", 1);
    book.put("name", "Java");
    book.put("price", 9.9);
    System.out.println(book);

    例五:

    //统计数字出现的次数
    // 1.生成30个0-10的随机数,放入ArrayList
    Random random = new Random();
    List<Integer> nums = new ArrayList<>();
    for (int i = 0; i < 30; i++) {
    	nums.add(random.nextInt(11));
    }
    System.out.println(nums);
    // 2.准备HashMap用来统计数字出现的次数
    Map<Integer, Integer> map = new HashMap<>();
    for (Integer i : nums) {
    	if (!map.containsKey(i)) {
    		// 从未出现过
    		map.put(i, 1);
    	} else {
    		// 出现过
    		Integer count = map.get(i) + 1;
    		map.put(i, count);
    	}
    }
    Set<Entry<Integer, Integer>> entrySet = map.entrySet();
    for (Entry<Integer, Integer> entry : entrySet) {
    	System.out.println(entry.getKey() + "出现了" + entry.getValue() + "次");
    }

    例六:

    //key使用自定义对象
    // map的key存放Book对象
    Map<Book, String> map = new HashMap<>();
    Book book1 = new Book(1, "Java", 9.9);
    Book book2 = new Book(2, "Oracle", 10.5);
    Book book3 = new Book(2, "Oracle", 10.5);
    System.out.println(book3.equals(book2));// true
    System.out.println(book2.hashCode());
    System.out.println(book3.hashCode());
    map.put(book1, "b1");
    // book2和book3,
    // equals方法比较相等,hashCode返回的整数也相等
    // 所以map认为添加book3时,已存在此key
    map.put(book2, "b2");
    map.put(book3, "b3");
    System.out.println(map.size());
    System.out.println(map);

    例七:

    TreeMap<User, String> map = new TreeMap<User,String>(new Comparator<User>() {
    
    @Override
    public int compare(User o1, User o2) {
            if(o1.getAge() > o2.getAge()){
            return 1;
            }else if(o1.getAge() < o2.getAge()){
            return -1;
            }else{
            return o1.getUserName().compareTo(o2.getUserName());
    //             return 0;
            }
            }
            });
            User u1 = new User("c", "C", 10);
            User u2 = new User("a", "C", 10);
            User u3 = new User("a", "A", 10);
            map.put(u1, "C");
            map.put(u2, "D");
            map.put(u3, "B");
            /*
             * TreMap判断Key是否相等的条件:
             * 比较器比较的结果返回0,认为两个key是同一个
             */
            System.out.println(map.size());
            System.out.println(map);

    2.  TreeMap

    概述

    基于红黑树(Red-Black tree)的 Map 实现。该映射根据其键的自然顺序进行排序,或者根据创建映射时提供的 Comparator 进行排序,具体取决于使用的构造方法。

    构造方法

    • TreeMap() :使用键的自然顺序构造一个新的、空的树映射。

    • TreeMap(Comparator<? super K> comparator) :构造一个新的、空的树映射,该映射根据给定比较器进行排序。

    • TreeMap(Map<? extends K,? extends V> m) :构造一个与给定映射具有相同映射关系的新的树映射,该映射根据其键的自然顺序 进行排序。

    • TreeMap(SortedMap<K,? extends V> m) :构造一个与指定有序映射具有相同映射关系和相同排序顺序的新的树映射。

    注意

    关于TreeMap的Key需要注意的地方,和TreeSet一样,因为TreeSet内部维护的就是TreeMap,只是TreeSet只关注TreeMap的Key,所有请参考TreeSet的注意事项

    例一:

    Map<String, String> map = new TreeMap<>();
    map.put("zzq", "马一");
    map.put("ww", "王五");
    map.put("zs", "张三");
    map.put("ls", "李四");
    map.put("zz", "刘二");
    System.out.println(map);

    九.  Queue :队列

    十.  List、Set、Map的异同

    • List接口扩展了Collection,特点:有序且数据可重复的,可以动态增长,查找效率高,插入删除效率低

    • Set接口扩展了Collection,特点:无序且数据不可重复的,检索效率低下,插入和删除效率高

    • 映射(map)是一个存储关键字/值对的对象。给定一个关键字,可查询 得到它的值,关键字和值都可以是对象。映射不是Collection的子接 口。所以它本身不能使用迭代器来进行遍历。

    十一.  总结

    最后给出Java的集合框架图吧

    Java集合详解

    十二.  参考资料

    xwls:集合框架

    尘语凡心:Java – 集合框架完全解析

    菜鸟教程:Java集合框架

    IBM:Java集合

    Heaven-Wang:Integer.valueOf("")和new Integer("")之间的区别

  • 0
  • 0
  • 0
  • 3.9k
  • 梁兴健

    请登录之后再进行评论

    登录
    单栏布局 侧栏位置: