JAVA集合体系回顾

来源:互联网 发布:如何用python做贪吃蛇 编辑:程序博客网 时间:2024/05/18 15:24

这次来讲一些java基础知识,关于集合大家都不陌生了,几乎每天都在使用, 本篇文章适合新手学习.
好了废话不多说,下面开始介绍把.

什么是集合?

存储对象的容器,面向对象语言对事物的体现都是以对象的形式,所以为了方便对多个对象的操作,存储对象,集合是存储对象最常用的一种方式。
集合的出现就是为了持有对象。集合中可以存储任意类型的对象, 而且长度可变。在程序中有可能无法预先知道需要多少个对象, 那么用数组来装对象的话, 长度不好定义, 而集合解决了这样的问题。

集合和数组的区别

1.数组和集合类都是容器。
2.数组长度是固定的,集合长度是可变的。
3.数组中可以存储基本数据类型,集合只能存储对象。
4.数组中存储数据类型是单一的,集合中可以存储任意类型的对象。
5.数组和集合在存储对象的时候都存储的都是对象的引用(内存地址),而非对象本身。
6.集合可以很方便的操作对象引用的增删改查。

集合的体系图

这里写图片描述
为什么出现这么多集合容器,因为每一个容器对数据的存储方式不同,这种存储方式称之为数据结构(data structure)

单列集合和双列集合

集合分为单列集合和双列集合.单列集合的根接口是Collection,Collection的继承关系图:
这里写图片描述

其中List和Set是2个Collection接口中最常用的子接口.
1.List集合的特点:存储有序,元素可以重复

  • ArrayList: 底层是数组数据结构,查询快,增删慢,在增和删的时候 会牵扯到数组增容, 以及拷贝元素所以慢。数组是可以直接按索引查找, 所以查找时较快,如果查询较多, 那么使用ArrayList
  • LinkedList: 底层是链表数据结构,查询慢,增删快,增加时只要让前一个元素记住自己就可以, 删除时让前一个元素记住后一个元 素, 后一个元素记住前一个元素. 这样的增删效率较高但查询时需要一个一个的遍历, 所以效率较低。如果增删较多, 那么使用LinkedList
  • Vector: 底层是数组数据结构,线程安全的,效率低,如果需要线程安全, 那么使用Vector

2.Set集合的特点:元素不可以重复存储,存取速度都快.

  • HashSet: 底层是哈希表数据结构,存储元素无序,通过hashCode和equals方法保证元素的唯一性,Set集合的实现类,如果不需要对元素排序, 使用HashSet, HashSet比TreeSet效率高.
  • TreeSet: 底层是二叉树数据结构,存储元素有自然顺序,通过比较性来保证元素的唯一性,Set集合的实现类,如果需要对元素排序, 那么使用TreeSet.
  • LinkedHashSet: 存储元素有序,且元素不可重复,这点和上面2个不同,Set集合的实现类.

双列集合的根接口Map的特点:存在键值的映射关系,键有唯一性,键相同则替换值,存储无序.

  • HashMap: 底层是哈希表数据结构,存取快,通过hashCode和equals方法保证键有唯一性
    性.,HashMap最多只允许一条记录的键为NULL,允许多条记录的值为NULL,Map的实现类.
  • TreeMap:底层是二叉树数据结构,存取都快,存储无序,按照自然顺序或自定义比较性存储键,通过比较性保证键有唯一性,Map的实现类.
  • HashTable: 底层是哈希表数据结构,与HashMap类似,不同的是它不允许记录的键或者值为空;
    它支持线程的同步,即任一时刻只有一个线程能写Hashtable,因此也导致了Hashtable在写入时会比较慢,Map的实现类.
  • LinkedHashMap: 存储有序,这点和上面的不同,键有唯一性,是HashMap的一个子类,允许使用null值和null键;
    固定容量的基于最近最少使用算法(LRU)的LinkedHashMap.可用作简单缓存.使用方法与LinkedHashMap一致;
    利用LinkedHashMap实现简单的缓存,必须实现removeEldestEntry方法,默认没有开启LRU算法,通过构造方法设置accessOrder参数为true则开启LRU算法存储数据.

注意:
上面说说的集合的存储有序和无序是指存入元素的顺序和取出元素的顺序。如果是有序的话,是指存入元素时的顺序和打印出来的顺序是一样的;而如果是无序的话是指存入元素的顺序和取出元素的顺序不一致。

Collection接口的共性方法

操作 含义 返回值 add(Object o) 将指定对象存储到容器末尾处,add 方法的参数类型是Object 便于接收任意对象,添加成功返回true,否则false boolean addAll(Collection c) 将指定集合中的元素添加到调用该方法的集合中 boolean remove(Object o) 将指定的对象从集合中删除 boolean removeAll(Collection c) 将指定集合中的元素删除 boolean clear() 清空集合中的所有元素 void isEmpty() 判断集合是否为空 boolean contains() 判断集合何中是否包含指定对象 boolean containsAll() 判断集合中是否包含指定集合 boolean size() 返回集合容器的大小 int toArray() 集合转换数组 Object[] iterator() 返回在此collection迭代元素的迭代器 Iterator

list接口的共性方法

操作 含义 返回值 addadd(int index, E element) 指定位置添加元素 void addAll(int index, Collection c) 指定位置添加集合 boolean remove(int index) 删除指定位置元素并返回 Object set(int index, E element) 修改指定位置上的元素并返回 Object get(int index) 获取指定位置上的元素,注意IndexOutOfBoundsException Object indexOf(Object o) 获取指定元素第一次出现的角标,和String类的indexOf相似, 找不到返回-1 int lastIndexOf(Object o) 获取指定元素最后一次出现的角标,和String类的indexOf相似 int subList(int fromIndex, int toIndex) 获取指定开始和结束位置的子集合,包头不包尾 List listIterator() 返回此列表元素的列表迭代器,它是Iterator的子接口 ListIterator

提示:List集合的特有方法都是和角标index有关的。

list集合元素遍历的几种方式

// 使用toArray方法将集合变成数组来遍历Object[] arr = list.toArray();for (int i = 0; i < arr.length; i++) {    System.out.print(arr[i] + ",");}// get方法逐个获取集合的元素for (int index = 0; index < list.size(); index++) {    System.out.print(list.get(index) + ",");}/* * 使用列表迭代器listIterator的方式遍历集合,listIterator是Iterator的子接口,特有方法如下: * hasPrevious()是否有上一个元素  * previous()游标先向上移动一个单元,然后取出当前游标指向的元素 * next()首先取出当前游标执行的元素,然后游标向下移动一个单元 * add(E e)把元素添加到当前游标指向的位置 * set(E e)替换迭代器最后一次返回的元素  * remove()从列表中移除由next或 previous返回的最后一个元素 * 如果需要在遍历的过程中修改集合中的元素个数,则只能使用listIterator,而不能使用Iterator * 否则会抛出ConcurrentModificationException异常 */ListIterator it = list.listIterator();while (it.hasNext()) {    System.out.print(it.next() + ",");}//使用Collection接口的Iterator方法遍历集合Iterator itt = list.iterator();while(itt.hasNext()){    System.out.print(itt.next() + ",");}

除去ArraysList集合中的重复元素

注意:ArrayList集合是可以存储重复元素的,如果要去除重复元素,可以使用contains方法,contains方法底层会调用传入参数的equals方法,所以还需要复写元素的equals方法,自定义去除重复的规则。

需求 : 使用ArrayList存储一批书籍,然后该片书籍是有重复元素的,定义一个函数清除集合中的重复元素,返回一个没有重复元素的集合。只要id号相同的书籍就认为是同一本书。

实体类:

/** * 实体类 * @author mChenys * */public class Book {    private int id;    private String name;    public Book(int id, String name) {        this.id = id;        this.name = name;    }    // 复写Object的equals方法,否则默认比较的是对象的内存地址    @Override    public boolean equals(Object obj) {        if (obj instanceof Book) {            Book b = (Book) obj;            if (this.id == b.id) {                return true;// 如果id相同,则认为是同一个对象            }        }        return super.equals(obj);    }    @Override    public String toString() {        return "{name=" + this.name + ",id=" + this.id + "}";    }}

去重操作

/** * ArrayList的去重复操作 * @author mChenys * */public class ListSort {    public static void main(String[] args) {        List<Book> books = new ArrayList();        books.add(new Book(110, "java神书"));        books.add(new Book(220, "java核心技术"));        books.add(new Book(330, "精通javascript"));        books.add(new Book(110, "java神书2"));        System.out.println("去重复前:"+books);        books = removeRepeate(books);        System.out.println("去重复后:"+books);    }    private static List<Book> removeRepeate(List<Book> books) {        ArrayList newList = new ArrayList();//创建新集合        for(Book b : books){            if(newList.contains(b)){//contains方法底层会调用所传参数的equals方法                continue;            }            newList.add(b);        }        return newList;    }}

输出结果:
这里写图片描述
从上面的输出结果中可以看到,ArrayList中的书籍已经成功的去重复的,同时还可以发现元素的存储是有序的.

LinkedList的特有方法

操作 含义 返回值 addFirst(E e) 把元素添加到集合的首位置上 void addLast(E e): 把元素添加到集合的末尾处 void getFirst() 获取集合的首元素 Object getLast() 获取集合的末尾元素 Object removeFirst() 删除首元素并返回所删除的元素 Object removeLast() 删除末尾的元素并返回删除的元素 Object push() jdk1.6出现,栈数据结构(后进先出),往栈中添加元素,调用addFirst()实现 void pop() 与push对应,往栈中弹出元素,调用removeFirst()实现 Object offer() jdk1.5出现,双端队列数据结构(先进先出),往队列中添加元素,调用add()实现 void poll() 与offer对应,从队列中删除元素,类似remove() Object descendingIterator() 返回逆序的迭代器对象,输出的结果和Iterator及listIterator的遍历结果顺序相反 Iterator

将ArrayList中的元素进行自定义的排序

需求:使用ArrayList存储一批人对象进去,然后定义一个方法对集合中的人按照年龄排序。
实体类

/** * 实体类 *  * @author mChenys *  */public class Person {    public String name;    public int age;    public Person(String name, int age) {        this.name = name;        this.age = age;    }    @Override    public String toString() {        return "{姓名:" + this.name + " 年龄:" + this.age + "}";    }}

按年龄排序集合

/** * 对ArrayList集合进行排序 * @author mChenys * */public class ListSort {    public static void main(String[] args) {        List<Person> list = new ArrayList<Person>();        list.add(new Person("哈哈", 12));        list.add(new Person("呵呵", 7));        list.add(new Person("痴痴", 32));        list.add(new Person("嘻嘻", 22));        System.out.println("排序前: " + list);        sortList(list);        System.out.println("排序后:" + list);    }    private static void sortList(List<Person> list) {        // 直接排序的思想        for (int i = 0; i < list.size() - 1; i++) {            for (int j = i + 1; j < list.size(); j++) {                // 取出两个要比较的人。                Person p1 = (Person) list.get(i);                Person p2 = (Person) list.get(j);                if (p1.age > p2.age) {                    // 交换位置,此处不需要定义第三方变量,因为这里集合存储的是对象的内存地址池                    list.set(i, p2); // set方法就是设置指定索引值的位置替换指定的元素。                    list.set(j, p1);                }            }        }    }}

输出结果:
这里写图片描述

Set集合

该接口没有特有的方法,都是继承了Collection父接口的方法,特点: 无序,不可重复。

HashSet存储的原理

往HashSet添加元素的时候,首先会调用元素的hashCode方法得到该元素的哈希码值,然后经过一些运算就可以算出该元素在哈希表的存储位置。
情况1:根据元素的hashCode方法算出的位置如果目前没有任何的元素存储的话,那么该元素可以直接添加到哈希表中。
情况2:如果根据元素的哈希码值算出的位置目前已经有存储元素了,那么接着还会调用该元素的equals方法与该位置的元素再比较一次,如果equals返回的是true,那么就视为同一个元素,这时候就不允许在添加该元素了,如果返回的是false,那么才可以添加该元素。
注意:HashSet的add和remove方法会依赖于hashCode方法和equals方法;因为add方法需要确保元素的唯一性,remove方法需要在哈希表中找出元素的位置。

自定义hashCode方法和equals方法

注意:一般底层数据结构是使用哈希表的时候才需要重写hashCode和equals方法.
1.自定义hashCode方法的原因是:
hashCode方法是Object的方法,该方法默认比较的是对象的内存地址值,每一个对象的内存地址值都是不一样的。而HashSet集合中是使用该方法来计算出哈希表的,当我们存储自定义对象的时候,例如person对象,现实生活中同一个人的话,哈希值就应该是一样的,但是如果不复写Object 的hashCode方法的话,HashSet集合在调用该方法的时候得到的值就是person对象的内存地址值,即会出现同一个人,却有不同的哈希值,这就违背了我们认为的同一个人的意思。最严重的问题还会造成该集合可以存储多个重复的人对象,因为当HashSet集合判断了对象的hashcode值,如果不相同的话,就不会再判断对象的equals结果了,直接就存储到集合中了
在String类中已经复写了hashCode方法,如果字符串的内容相同且字符顺序也相同的话,则hashCode返回值是一样的。

2.自定义equals方法的原因是:
HashSet集合的特点是当添加对象的时候,如果发现添加对象的hashCode值一样的情况的话,HashSet集合底层还会调用对象的equals方法来比较该这两个对象是否为同一个对象,由于equals方法是Object的方法,默认比较的还是对象的内存地址值。同样对于Person类对象,在显示生活中同一个人的话equals比较的内容应该也是相同的,但是如果不复写equals方法的话,得到的结果就是一样的。这就会造成该集合可以存储多个重复的人对象了。
在String类中已经复写了equals方法,对于字符串对象通过equals方法比较的是字符串内容是否相同而不是内存地址值。

使用HashSet自定义去重规则

需求:实现一个注册用户的功能,接收键盘录入一个帐号、密码。 如果帐号与密码一致,那么视为同一个用户,不允许添加到hashset中。
实体类

/** * 实体类 * @author mChenys * */public class User {    String name;    int password;    public User(String name, int password) {        this.name = name;        this.password = password;    }    /**     * 类已经重写了Object的hashCode方法,返回的int值是根据字符串的字符编码来生成的,     * 所以如果字符串的内容相同且字符顺序也相同的话,这hashCode返回值是一样的。     */    @Override    public int hashCode() {        return this.name.hashCode() + this.password;    }    /**     * 定义比较是否重复的依据,如果hashcode值相同就会的调用equals方法再比较是否相同。     */    @Override    public boolean equals(Object obj) {        if (obj instanceof User) {            User p = (User) obj;            // 这里的equals是比较的字符串内容是否相同,字符串比较不能使用==来比较,因为==用于字符串比较的话比较的是内存地址。            return this.name.equals(p.name) && this.password == p.password;        }        return false;    }    @Override    public String toString() {        return "{" + this.name + "," + this.password + "}";    }}

去重操作

public class SetRemoveRepeat {    public static HashSet hashset = new HashSet();    public static void main(String[] args) {        login();    }    public static void login() {        while (true) {            Scanner sc = new Scanner(System.in);            System.out.println("请输入你的姓名");            String name = sc.next(); // 该方法返回的是通过new创建的字符串对象。因此内存地址是不同的。            System.out.println("请输入你的密码");            int password = sc.nextInt();            boolean success = hashset.add(new User(name, password));            if (success) {                System.out.println("恭喜你注册成功");            } else {                System.out.println("注册失败,该名字已被注册");            }            System.out.println("当前的用户有" + hashset);        }    }}

运行结果
这里写图片描述

TreeSet集合类的使用

TreeSet类是Set接口的其中一个实现类,元素有存储顺序且不可重复的(通过定义比较规则实现).特点:往TreeSet添加元素的时候,如果添加的元素具备了自然顺序的特性,那么treeSet会按照元素的自然顺序进行排序存储。否则需要自定义比较规则。

TreeSet出现的原因

由于ArrayList 、 LinkedList存储的元素虽然存储是有序的,但是元素却可以出现重复的;而HashSet存储的元素虽然是不能重复的,但是存储顺序却是无序的;为了解决这个问题,TreeSet集合就诞生了,TreeSet集合的元素是有存储顺序序的也是无重复的。

TreeSet添加元素需要注意的事项

1.TreeSet在存储元素的时候必须要保证存入的元素是具有可比性的,否则添加后会编译失败,提示ClassCastException异常。

2.往TreeSet添加元素的时候,如果元素本身具备了自然顺序的特性,那么treeSet会按照元素的自然顺序特性排序进行存储。

3.如果添加的元素本身不具备比较性,那么元素所属的类必须要实现Comparable接口或者自定义类实现Comparator接口来构建”比较器”。建议采用实现后者的方式,因为这种方法的灵活性高。

4.如果采用实现Comparable接口的方式,那么需要复写compareTo方法,在该方法中自定义元素比较的规则。compareTo方法返回的结果是0,则表示添加的是重复的元素,不允许添加。

5.如果采用自定义类构建比较器的方式,那么需要该自定义的类必须要复写Comparator接口的compare方法,并在方法中定义元素比较的规则。然后在创建TreeSet集合对象的时候,把该自定义的类对象作为参数传递到TreeSet集合的构造方法中。

6.如果添加的元素本身不具备自然顺序,且元素所属的类也实现了Comparable接口,然后也定义了比较器。那么在创建TreeSet集合对象的时候,优先采用比较器的方式。

7.如果采用比较器的方式,可以根据需要自定义多种不同比较方式的比较器,然后使用 的时候只需要在创建TreeSet集合对象的时候传入所需的比较器即可满足不同的需求。

实现Comparable接口实现比较性

/** * 定义比较性,按照员工工资进行排序 *  * @author mChenys *  */public class Emp implements Comparable { // 元素所属的类必须实现Comparable接口    int id;    String name;    int salary;    public Emp(int id, String name, int salary) {        this.id = id;        this.name = name;        this.salary = salary;    }    @Override    public String toString() {        return "{ 编号:" + this.id + " 名字:" + this.name + " 薪水:" + this.salary                + "}";    }    /**     * 复写compareTo方法,实现按照工资来比较     */    public int compareTo(Object obj) {        Emp emp = (Emp) obj;        return this.salary - emp.salary;    }}

TreeSet的排序

/** * 自定义TreeSet的比较性,按照员工的工资来排序 *  * @author mChenys *  */public class TreeSetSort {    public static void main(String[] args) {        // 创建一个TreeSet对象        TreeSet<Emp> tree = new TreeSet<Emp>();        tree.add(new Emp(117, "锦涛", 3000));        tree.add(new Emp(220, "泽东", 1000));        tree.add(new Emp(119, "家宝", 2000));        tree.add(new Emp(115, "近平", 400));        Iterator<Emp> it = tree.iterator();        while(it.hasNext()){            System.out.println(it.next());        }    }}

运行结果:
这里写图片描述

创建Comparable实现类定义比较器

/** * 自定义一个比较器类,通过id *  * @author mChenys *  */public class MyComparator implements Comparator {    /**     * Compare方法中就写两个元素比较的规则。      * 如果o1小于o2,返回一个负数;如果o1大于o2,返回一个正数;如果他们相等,则返回0;     */    public int compare(Object o1, Object o2) {        if (o1 instanceof Emp && o2 instanceof Emp) {            Emp e1 = (Emp) o1;            Emp e2 = (Emp) o2;            return e1.id - e2.id;        }        return 0;    }}
/** * 通过自定义比较器按照员工的id进行排序 * @author mChenys * */public class TreeSetSort2 {    public static void main(String[] args) {        // 1.创建一个比较器对象        MyComparator comparator = new MyComparator();        // 2.创建一个TreeSet对象,将比较器对象传入TeeSet构造方法        TreeSet<Emp> tree = new TreeSet<Emp>(comparator);        tree.add(new Emp(117, "锦涛", 3000));        tree.add(new Emp(220, "泽东", 1000));        tree.add(new Emp(119, "家宝", 2000));        tree.add(new Emp(115, "近平", 400));        Iterator<Emp> it = tree.iterator();        while(it.hasNext()){            System.out.println(it.next());        }    }}

运行结果:
这里写图片描述

String对象的自然排序

String字符串也是有自然顺序的。因为String类已经实现了Comparable接口,所以可以直接调用compareTo方法进行排序了。
这点通过源码可以发现:
这里写图片描述
字符串的比较规则
情况1:可以找到对应的不同字符,那么比较的就是第一个对应的不同字符。

情况2:找不到对应不同的字符,那么比较的就是字符串的长度。长度小的排在前面,长度大的排在后面。

注意: 即使字符串的长度不一致,但是有对应不同的字符,那么比较的还是对应不同的字符。 对应字符的编码值小的排在前面,即从小到大排序。

一个demo了解字符串的自然顺序规则

/** * 字符串的自然顺序规则 * @author mChenys * */public class StringSort {    public static void main(String[] args) {        TreeSet<String> tree = new TreeSet<String>();        tree.add("abc");        tree.add("bca");        tree.add("bc");        System.out.println(tree); // [abc, bc, bca]        String str1 = "100";        String str2 = "2";        // 结果是-1;因为比较的是第一个对应的不同字符即:1比2小        System.out.println(str1.compareTo(str2));    }}

使用TreeSet集合对字符串对象进行排序

最后一个demo加强对TreeSet集合的认识
需求:目前有字符串 String str=”8 10 15 5 2 7”; 对字符串里面的数字排序。返回一个有序的字符串数据.”2 5 7 8 10 15”
思路
1.将字符串变成字符数组toCharArray()
2.遍历字符数组,然后将数组中的字符转成int类型的数据并存储到TreeSet集合中,这时集合中的元素就有了自然顺序了。
3.通过迭代器遍历集合,取出集合中的int类型数据,将其添加到StringBuilder容器中,然后通过toString方法返回的就是字符串对象了。

public class TreeSetSort3 {    public static void main(String[] args) {        String str = "8 10 15 5 2 7";        System.out.println("有序的字符串数据为:" + sortString(str));    }    public static String sortString(String str) {        String[] datas = str.split(" "); // 切割        // 创建一个treeSet对象        TreeSet<Integer> tree = new TreeSet<Integer>();        // 遍历数组,把数组的元素添加到treeSet中。        for (int i = 0; i < datas.length; i++) {            int temp = Integer.parseInt(datas[i]);            // Integer.parseInt 把字符串转成了int类型 的数据,因为字符串的比较方式是有问题的。            tree.add(temp); // 添加到TreeSet集合的时候,这些int类型的数值就变的有序了。        }        // 遍历treeSet集合。        StringBuilder sb = new StringBuilder();        Iterator<Integer> it = tree.iterator(); // 获取到了迭代器        while (it.hasNext()) {            sb.append(it.next() + " ");// 加一个空格        }        return sb.toString();    }}

运行结果:
这里写图片描述

0 0
原创粉丝点击