第16章 集合类

作者: yangsg | 来源:发表于2019-04-12 10:18 被阅读517次

    1. 集合的概念

    集合是一种数学概念,表述的是“由一个或多个确定的元素所构成的整体”。

    1.1 Java中集合与数组的比较

    数组

    • 数组在初始化时是定长的,初始化后数组长度不可改变。(长度表示的是最大容量)
    • 数组中只能存放同类型的数据,比如int数组只能存数字不能存String
    • 数组中没有现成API方法使用,数组只有一个length属性可用

    集合

    • 集合在初始化时可以不设置长度,随着元素放入集合,长度可变。(集合中已经存放了多少个元素)
    • 集合在没有约束泛型的情况下,其可以存放任意类型的数据
    • 集合类中提供了大量的API方法,方便程序开发者操作集合
    1.2 集合的分类
    集合的继承体系
    • Collection接口(线性集合)
      (1) List接口(有序号可重复)
      ArrayList实现类基于数组方式实现
      LinkedList实现类基于链表方式实现
      (2) Set接口(无序号不重复)
      HashSet实现类基于Hash表算法
    • Map接口(键值集合)
      HashMap实现类基于Hash表算法
    • Iterator接口(迭代器)

    2. 线性集合(Collection接口)

    2.1 线性集合的概念

    Collection接口表述的是“线性集合”,线性结构是n个数据元素的有序(次序)集合
    线性集合的特征
    1.集合中必存在唯一的一个"第一个元素"。
    2.集合中必存在唯一的一个"最后的元素"。
    3.除最后元素之外,其它数据元素均有唯一的"后继"。
    4.除第一元素之外,其它数据元素均有唯一的"前驱"。

    2.2 线性集合(Collection)的实现类
    2.2.1 List接口

    有序集合(也称为序列 )。 该界面的用户可以精确控制列表中每个元素的插入位置。 用户可以通过整数索引(下标)访问元素,并搜索列表中的元素。允许集合中存放重复的数据。
    有序号可重复

    2.2.1.1 ArrayList实现类

    底层是基于数组实现的,方法都是线程不安全的
    API方法

    • add(Object o)返回值是boolean
      在集合的结尾处添加一个元素
    • add(int index, Object o)返回值是void
      在index位置插入一个元素,插入后,后面的元素会自动后移
    • addAll(Collection c)返回值是boolean
      在集合结尾处添加集合c中的所有元素
    • clear()返回值是void
      清空集合,保留集合对象
    • contains(Object o)返回值是boolean
      在集合中查找o是否存在,存在判断依据是元素是否“equals”o
      如果在集合中找到了“相等”的元素,返回true,否则返回false
    • get(int index)返回值是Object
      获得集合中index位置上的元素,不会移除这个元素
    • set(int index, Object o)返回值是Object
      用指定元素o替换集合中index位置上的元素,并返回被替代的元素
    • remove(int index)返回值是Object
      移除集合中index位置上的元素,并返回这个元素
    • remove(Object o)返回值是boolean
      移除集合中的第一个o元素(判断依据是集合中的某个元素“equals”o),返回是否移除成功。
    • size()返回值是int
      返回集合中元素的个数(集合的长度)
    • indexOf(Object o)返回值是Object
      查找o在集合中位置,如果没找到返回-1
    public class Test {
    
        public static void main(String[] args) {
            ArrayList list = new ArrayList();
            System.out.println(list.size());
            list.add(new String("哈哈"));
            list.add(23);
            list.add(new Date());
            list.add("哈哈");
            list.add(3.14);
            System.out.println(list.size());
            System.out.println("插入了5个元素==========================");
            for(int i = 0; i < list.size(); i++) {
                Object o = list.get(i);
                System.out.println(o);
            }
            System.out.println("在2位置插入了嘻嘻==========================");
            list.add(2, "嘻嘻");
            for(int i = 0; i < list.size(); i++) {
                Object o = list.get(i);
                System.out.println(o);
            }
            System.out.println("替换2位置为嘿嘿==========================");
            list.set(2, "嘿嘿");
            for(int i = 0; i < list.size(); i++) {
                Object o = list.get(i);
                System.out.println(o);
            }
            System.out.println("移除位置2的元素==========================");
            list.remove(2);
            for(int i = 0; i < list.size(); i++) {
                Object o = list.get(i);
                System.out.println(o);
            }
            System.out.println("移除元素“3.14”的元素==========================");
            list.remove(3.14);
            for(int i = 0; i < list.size(); i++) {
                Object o = list.get(i);
                System.out.println(o);
            }
            System.out.println("移除元素“哈哈”的元素==========================");
            list.remove(new String("哈哈"));
            for(int i = 0; i < list.size(); i++) {
                Object o = list.get(i);
                System.out.println(o);
            }
            System.out.println("判断集合中是否有“哈哈”的元素==========================");
            boolean b = list.contains("哈哈");
            System.out.println(b);
            System.out.println("判断集合中是否有“3.14”的元素==========================");
            boolean x = list.contains(3.14);
            System.out.println(x);
            System.out.println("清空集合==========================");
            list.clear();
            System.out.println(list.size());
        }
    }
    

    如果遇到了集合中有元素2,且还需要移除2下标时,注意写法

    list.remove(2);  //移除下标位置2的元素
    list.remove(new Integer(2)); //移除集合中元素2
    

    2.2.1.2 LinkedList实现类

    底层是基于链表实现的
    主要的增删改查的方法参照ArrayList类的API学习
    特有API方法

    • addFirst(Object o)和addLast(Object o)返回值void
      在集合的开始和结尾处插入一个元素o
    • getFirst()和getLast()返回值Object
      返回集合的开始和结尾处的元素

    将LinkedList看做为一个栈

    • peek()返回值Object
      查看栈顶元素,但不移除它
    • poll()和pop()返回值Object
      出栈
    • push(Object o)返回值void
      将Object o入栈
    public class Test3 {
        public static void main(String[] args) {
            LinkedList<Object> list = new LinkedList<Object>();     
            list.push("哈哈");
            list.push("嘻嘻");
            list.push(100);
            list.push(3.14);
            list.push(new Date());
            
            for(int i = 0; i < list.size(); i++) {
                Object o = list.get(i);
                System.out.println(o);
            }
            System.out.println("查看栈顶==============================");
            Object x = list.peek(); //查看栈顶
            System.out.println(x);
            for(int i = 0; i < list.size(); i++) {
                Object o = list.get(i);
                System.out.println(o);
            }
            System.out.println("出栈==============================");
            Object y = list.pop();
            System.out.println(y);
            System.out.println("====");
            for(int i = 0; i < list.size(); i++) {
                Object o = list.get(i);
                System.out.println(o);
            }
            System.out.println("入栈==============================");
            list.push("abc");
            for(int i = 0; i < list.size(); i++) {
                Object o = list.get(i);
                System.out.println(o);
            }
            System.out.println("出栈==============================");
            Object z = list.poll();
            System.out.println(z);
            System.out.println("====");
            for(int i = 0; i < list.size(); i++) {
                Object o = list.get(i);
                System.out.println(o);
            }
        }
    }
    
    2.2.1.3 Vector实现类

    底层是基于数组实现的,方法都是线程安全的
    API方法参照ArrayList学习

    笔试题/面试题
    (1)ArrayList和LinkedList的区别?
    本质上就是问“数组”与“链表”的区别。
    ArrayList基于数组实现的,LinkedList基于链表实现
    数组:在查询元素速度快,插入删除速度慢
    链表:在查询元素速度慢,插入删除速度快

    (2)ArrayList和Vector的区别?
    ArrayList线程不安全,Vector线程安全

    2.2.2 Set接口

    不包含重复元素的集合。如果有元素e1和e2(e1.equals(e2) 为true),e1和e2只能保留一个元素。Set接口的特征是使用者无法控制元素的存放顺序,其存放顺序有Set接口的实现类自身的算法决定

    2.2.2.1 HashSet实现类

    实现算法是基于Hash表算法,存放顺序不由使用者控制,允许存放一个null值
    Object类中有hashcode方法,返回int值,该int值是由对象的内存地址计算出来的
    一个对象会有一个HashCode(哈希码,Hash码,HashCode),它有如下特征:

    • 如果两个对象拥有相同的内存地址,他们一定有相同的哈希码
    • 如果两个对象拥有相同的哈希码,他们的内存地址可能不一样
      哈希表算法,按照哈希码的大小顺序存放数据。

    比如:字符串“哈哈”的哈希码是34,字符串“嘻嘻”的哈希码是20。
    使用代码存入集合的顺序:哈哈,嘻嘻
    实际集合中存储顺序:嘻嘻,哈哈

    比如:字符串“哈哈”的哈希码是34,字符串“嘻嘻”的哈希码是20,字符串“嘿嘿”的哈希码也是34。
    使用代码存入集合的顺序:哈哈,嘻嘻
    实际集合中存储顺序:嘻嘻,(哈哈,嘿嘿)


    类似的存储结构

    API方法

    • add(Object o)返回值boolean
      将o对象放入集合,放入的位置由集合的算法自身决定
    • clear() 返回值void
      清空集合
    • isEmpty()返回值boolean
      判断集合是否为空
    • remove(Object o)返回值boolean
      在集合中移除o元素
    • size()返回值int
      返回集合的长度
    • iterator()返回值Iterator
      返回set集合对应的迭代器

    因为Set集合无法使用下标,所以不能通过下标进行遍历访问集合中的元素。只能通过迭代器去遍历Set集合

    2.2.3 Iterator(迭代器)

    负责通过一个向前的“游标”不断的访问元素和移动,完成Set集合的遍历。


    理解迭代器

    API方法

    • hasNext()返回值:boolean
      判断游标当前位置之后是否有数据
    • next()返回值:Object
      取得游标当前位置之后的数据,再将游标向后移动一位
      由于next()包含了移动的操作,所以在循环中应该只调用一次

    示例:利用迭代器遍历set集合

    public class Test1 {
    
        public static void main(String[] args) {
            HashSet<Object> set = new HashSet<Object>();
            set.add("哈哈");
            set.add(3.14);
            set.add(new Date());
            set.add("hehe");
            set.add(23);    
            set.add(12);
            System.out.println(set.size());
            
            Iterator<Object> ite = set.iterator();//获得了set集合对应的迭代器
            while(ite.hasNext()) {
                Object o = ite.next();
                System.out.println(o);
            }
        }
    }
    

    面试题/笔试题:
    (1)List集合和Set集合的区别?
    List集合:有序可重复,可以通过下标访问数据,存放顺序可控,允许存放重复数据
    Set集合:无序不重复,不能通过下标访问数据,存放数据由实现类自己决定,重复数据只能保留一个
    (2)HashSet和TreeSet的区别?
    HashSet基于哈希表算法实现
    TreeSet基于红黑树算法实现

    很多时候使用Set集合完成去重复的操作。

    3. 键值集合(Map接口)

    Map集合是一种以键值对为单位,进行数据存储的一种结构,它与线性集合有所区别:线性集合一个位置上只存储一个数据,键值对集合一个位置上存储的是一个键(key)和一个值(value)。不同数据之间键(key)是不能重复的,值(value)是也可重复的。存放数据时,必须提供key和value,取得数据时一般是根据key来获取value
    Map的主要实现类

    3.1 HashMap和HashTable

    基于哈希表算法实现存储

    3.1.1 构造方法
    • HashMap()创建一个初始容量是16,负载因子是0.75的默认的对象
      初始容量:对象初始默认可以存放16个键值对
      负载因子:表示当容量达到最大容量的75%时(比如,初始16,16的75%是12),,容量会加倍扩容(16*2=32)。
      这个构造方法是比较常用的
    • HashMap(int init)创建一个初始容量是init,负载因子是0.75的对象
    • HashMap(int init, float ext)创建一个初始容量是init,负载因子是ext的对象
    • HashMap(Map map)利用map的数据重新构建一个同样数据的HashMap
    3.1.2 API方法
    • put(Object key, Object value)返回值:Object
      将key-value组成的键值对,放入集合;如果集合中已经存在了同key的键值对,会利用新的key-value替换原有的key-value
    HashMap<Integer, String> map = new HashMap<Integer, String>();
    map.put(101, "赵四");  //向集合中添加一条数据
    map.put(102, "刘能");  //向集合中添加一条数据
    map.put(101, "哈哈");  //替换集合中原来101这条数据
    
    • remove(Object key)返回值:Object
      根据key值,删除集合中的一个键值对,并返回对应value
      这个方法是使用频率较高的
    map.remove(103);
    
    • remove(Object key, Object value)返回值:boolean
      根据key-value值,删除集合中的一个键值对。返回是否删除成功
    map.remove(104,"刘能"); //成功,有这对数据
    map.remove(102,"aaa");//失败,虽然有102,102中不是aaa
    
    • clear()返回值:void
      清空整个集合
    map.clear();
    
    • get(Object key)返回值:Object
      根据key值,返回集合中key对应value,如果集合中没有这个key,返回null
    String v1 = map.get(103);
    String v2 = map.get(105);
    
    • size()返回值:int
      返回集合中键值对的个数
    System.out.println("集合中的元素个数是:"+map.size());    
    
    • entrySet()返回值:Set<Entry>
      一个entry对象表示一个键值对
      返回所有键值对组成的一个Set集合
    Set<Entry<Integer, String>> set = map.entrySet();//得到键值对的Set集合
    Iterator<Entry<Integer, String>> ite = set.iterator();//将set集合变成迭代器
    while(ite.hasNext()) { //遍历迭代器
        Entry<Integer, String> e = ite.next(); //一个键值对
        System.out.println(e.getKey()+":"+e.getValue());
    }
    
    • keySet()返回值:Set<Object>
      返回所有key值组成的一个Set集合
    Set<Integer> kset = map.keySet(); //所有key值组成的set集合
    Iterator<Integer> kite = kset.iterator(); //将set集合变成迭代器
    while(kite.hasNext()) { //遍历迭代器
        int key = kite.next();
        String value = map.get(key); //取得value
        System.out.println(key+":"+value);
    }
    
    • isEmpty()返回值:boolean
      判断集合是否为空集合
    boolean x = map.isEmpty();
    
    • containsKey(Object key)返回值boolean
      判断集合中的key是否包含参数key,(判断依据:equals)
    boolean b1 = map.containsKey(101);
    boolean b2 = map.containsKey(105);
    
    • containsValue(Object value)返回值boolean
      判断集合中的value是否包含参数value,(判断依据:equals)
    boolean b3 = map.containsValue("刘能");
    boolean b4 = map.containsValue("AAA");
    

    面试题/笔试题
    (1)HashMap与HashTable的区别
    HashMap是线程不安全的,HashTable是线程安全。
    HashMap中允许null作为key,HashTable不允许null作为key

    (2)HashMap与TreeMap的区别
    HashMap底层基于Hash表算法
    TreeMap底层基于二叉树算法

    (3)HashMap的桶容量(扩容机制)
    以默认hashmap()构造方法解释:,默认初始值16,负载因子0.75.
    初始时hashmap的最大容量是16,当存储数据至12时(16*0.75),进行扩容,扩容方式是16*2=32; 当继续存储数据至24时(32*0.75),继续扩容,扩容方式32*2=64

    3.2 TreeMap

    基于红黑树算法实现存储
    参照HashMap的API去理解

    • put
    • get
    • remove
    • keySet
    • entrySet
    • containsKey

    4. 泛型

    泛型是用于约束集合中输入数据类型的。集合默认是可以将任何数据都放入其中,很多情况下我们需要约束集合中输入的元素类型,借助于泛型可以达到效果。
    泛型的优势

    • 约束输入至集合的数据类型,控制数据准确
    • 从集合输出数据时,无需考虑类型转换问题
    • 可以使用for...each句式快速遍历集合

    约束输入类型为String

    ArrayList<String> list = new ArrayList<String>();
    

    由于泛型约束只支持引用数据类型,如果需要约束一个整数集合,需要使用包装类

    ArrayList<Integer> list = new ArrayList<Integer>();
    

    泛型约束Employee类的集合

    ArrayList<Employee> list = new ArrayList<Employee>();
    

    泛型约束其他的集合

    ArrayList<HashSet<String>> list = new ArrayList<HashSet<String>>();
    

    for...each句式:使用变量s去遍历list集合中每(each)一个元素

    ArrayList<String> list = new ArrayList<String>();
    for(String s : list) {
        System.out.println(s);
    }
    

    5. Collections类(集合工具类)

    主要提供了大量的关于集合操作的方法,方便操作集合

    • copy(List dest, List src)返回值:void
      将集合src复制到集合dest中
      list2一开始必须有不少于list1的数据,否则会报出异常
    public class Test2 {
    
        public static void main(String[] args) {
            ArrayList<Integer> list1 = new ArrayList<Integer>();
            ArrayList<Integer> list2 = new ArrayList<Integer>();
            Random r = new Random();
            for(int i = 0; i < 10; i++) {
                int x = r.nextInt(100);
                list1.add(x);
            }
            for(int i = 0; i < 10; i++) {
                int x = r.nextInt(100);
                list2.add(x);
            }
            System.out.println(list1);
            System.out.println(list2);
            System.out.println("=======================");
            //集合copy
            Collections.copy(list2, list1);
            
            System.out.println(list1);
            System.out.println(list2);
        }
    }
    
    • sort(List list)返回值:void
      将集合按照“自然排序”(实际上就是升序)
    public class Test3 {
        public static void main(String[] args) {
            ArrayList<Integer> list1 = new ArrayList<Integer>();
            Random r = new Random();
            for(int i = 0; i < 10; i++) {
                int x = r.nextInt(100);
                list1.add(x);
            }
            System.out.println(list1);
            Collections.sort(list1);//升序
            System.out.println(list1); 
        }
    }
    

    如果想比较的是自己编写的类,可以通过让自己的类实现Comparable接口并完成CompareTo方法
    Student类

    public class Student implements Comparable<Student>{
        
        private int sno;
        private String sname;
        
        public Student() {}
        
        public Student(int sno, String sname) {
            this.sno = sno;
            this.sname = sname;
        }
    
        public int getSno() {
            return sno;
        }
    
        public void setSno(int sno) {
            this.sno = sno;
        }
    
        public String getSname() {
            return sname;
        }
    
        public void setSname(String sname) {
            this.sname = sname;
        }
    
        @Override
        public int compareTo(Student o) {
            if(this.sno < o.sno) {
                return -1;
            }else if(this.sno == o.sno) {
                return 0;
            }else {
                return 1;
            }
        }
    }
    

    利用sort方法进行排序

    public class Test {
    
        public static void main(String[] args) {
            ArrayList<Student> list = new ArrayList<Student>();
            
            Student s1 = new Student(1002,"赵四");
            Student s2 = new Student(1004,"刘能");
            Student s3 = new Student(1001,"谢广坤");
            Student s4 = new Student(1003,"王大拿");
            
            list.add(s1);
            list.add(s2);
            list.add(s3);
            list.add(s4);
            
            for(Student s : list) {
                System.out.println(s.getSno()+"\t"+s.getSname());
            }
            System.out.println("============================");
            Collections.sort(list);
            
            for(Student s : list) {
                System.out.println(s.getSno()+"\t"+s.getSname());
            }   
        }
    }
    
    • sort(List list, Comparator c)返回值:void
      将集合按照“ Comparator”的规则进行排序
      Student类
    public class Student{
        
        private int sno;
        private String sname;
        
        public Student() {}
        
        public Student(int sno, String sname) {
            this.sno = sno;
            this.sname = sname;
        }
    
        public int getSno() {
            return sno;
        }
    
        public void setSno(int sno) {
            this.sno = sno;
        }
    
        public String getSname() {
            return sname;
        }
    
        public void setSname(String sname) {
            this.sname = sname;
        }
    
    }
    

    编写一个比较器

    public class StudentComparator implements Comparator<Student> {
    
        @Override
        public int compare(Student o1, Student o2) {
            if(o1.getSno() < o2.getSno()) {
                return -1;
            }else if(o1.getSno() == o2.getSno()) {
                return 0;
            }else {
                return 1;
            }
        }
    }
    

    利用sort进行排序

    public class Test {
    
        public static void main(String[] args) {
            ArrayList<Student> list = new ArrayList<Student>();
            
            Student s1 = new Student(1002,"赵四");
            Student s2 = new Student(1004,"刘能");
            Student s3 = new Student(1001,"谢广坤");
            Student s4 = new Student(1003,"王大拿");
            
            list.add(s1);
            list.add(s2);
            list.add(s3);
            list.add(s4);
            
            for(Student s : list) {
                System.out.println(s.getSno()+"\t"+s.getSname());
            }
            System.out.println("============================");
            Collections.sort(list, new StudentComparator());
            
            for(Student s : list) {
                System.out.println(s.getSno()+"\t"+s.getSname());
            }       
        }
    }
    
    • reverse(List list)返回值:void
      将集合list中的所有数据反转
    public class Test3 {
        public static void main(String[] args) {
            ArrayList<Integer> list1 = new ArrayList<Integer>();
            Random r = new Random();
            for(int i = 0; i < 10; i++) {
                int x = r.nextInt(100);
                list1.add(x);
            }
            System.out.println(list1);
            Collections.sort(list1);//升序
            System.out.println(list1); 
            Collections.reverse(list1);//降序
            System.out.println(list1); 
        }
    }
    

    相关文章

      网友评论

        本文标题:第16章 集合类

        本文链接:https://www.haomeiwen.com/subject/lakqwqtx.html