集合知识总结

集合

在学完数组后,我们可以知道,数组在定义后,长度就不可变了,所以我们要存储更多的东西时,需要频繁的创建数组,所以数组是不合适的,就需要其他容器------集合

集合是长度可变的,可以存储不同类数据的容器,例如可以同时存储整型,浮点型,对象等。

1. Collection

Collection是Collection 层次结构 中的根接口。Collection 表示一组对象,这些对象也称为 collection 的元素。一些 collection 允许有重复的元素,而另一些则不允许。一些 collection 是有序的,而另一些则是无序的。JDK 不提供此接口的任何直接 实现:它提供更具体的子接口(如 SetList)实现。此接口通常用来传递 collection,并在需要最大普遍性的地方操作这些 collection。

1.1 Collection中的方法

  • add
  • addAll
  • clear
  • contains
  • containsAll
  • equals
  • hashCode
  • isEmpty
  • remove
  • removeAll
  • toArray
  • size
import java.util.ArrayList;
import java.util.Collection;

public class CollectionDemmo {

    public static void main(String[] args) {

        Goods goods1 = new Goods("瓜子",100,9.9);
        Goods goods2 = new Goods("花生",200,9.9);
        Goods goods3 = new Goods("啤酒",20000,12.99);
        Goods goods4 = new Goods("麻辣条",100,3);

        // 使用多态的形式 Collection接口  List子接口的实现类 
        Collection  collection = new ArrayList();
        //集合是个容器 存储元素
        //基本数据类型和对象都可以进行存储
            collection.add("123");
            collection.add(123);
            collection.add('a');
            collection.add(99.99);
            collection.add(goods1);

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

            //集合的长度是可以扩增的
            collection.add(goods2);
            collection.add(goods3);
            System.out.println(collection.size());
            System.out.println(collection);

            //集合提供remove方法移除元素  集合的长度自动减少
            collection.remove(goods3);
            System.out.println(collection.size());
            System.out.println(collection);        
    }

}
public static void method1() {
        Goods goods1 = new Goods("瓜子",100,9.9);
        Goods goods2 = new Goods("花生",200,9.9);
        Goods goods3 = new Goods("啤酒",20000,12.99);
        Goods goods4 = new Goods("麻辣条",100,3.0);

        // 使用多态的形式 Collection接口  List子接口的实现类 
        Collection  collection = new ArrayList();
        //集合是个容器 存储元素
        //基本数据类型和对象都可以进行存储
            collection.add("123");
            collection.add(123);
            collection.add('a');
            collection.add(99.99);
            collection.add(goods1);

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

            //集合的长度是可以扩增的
            collection.add(goods2);
            collection.add(goods3);
            System.out.println(collection.size());
            System.out.println(collection);

            //集合提供remove方法移除元素  集合的长度自动减少
            collection.remove(goods3);
            System.out.println(collection.size());
            System.out.println(collection);
    }
    //集合 的其他的方法
    public static void method2() {
        Collection collection1 = new ArrayList();
            collection1.add("123");
            collection1.add(123);
            collection1.add('a');
            collection1.add(99.99);
        Collection collection2 = new ArrayList();
            Goods goods1 = new Goods("瓜子",100,9.9);
            Goods goods2 = new Goods("花生",200,9.9);
            collection2.add(goods1);
            collection2.add(goods2);
            //集合合并
        collection1.addAll(collection2);
        System.out.println(collection1);
        //是否包含某一个元素
        System.out.println(collection2.contains(goods1));//true
        //判断集合中数据是否相等
        System.out.println(collection1.equals(collection2));//false
        //判断集合是否为空
        System.out.println(collection1.isEmpty());//false

        System.out.println(collection1.removeAll(collection2));//true
        System.out.println(collection1);

            Object [] obj =     collection1.toArray();
            System.out.println(obj);
    }

1.2 迭代器

提供集合的通用的遍历方式

  1. hasNext()判断是否有下一个可迭代的元素
  2. next()获取下一位迭代的元素
  3. remove()移除下一个迭代元素
public static void method3() {
        //数组集合
        Collection collection2 = new ArrayList();
            Goods goods1 = new Goods("瓜子",100,9.9);
            Goods goods2 = new Goods("花生",200,9.9);
            collection2.add(goods1);
            collection2.add(goods2);
            System.out.println(collection2);
        //集合的遍历
        //hasNext()  next()
        Iterator  it = collection2.iterator();    
        while(it.hasNext()) {
            //数据的强制转换
            //铺垫泛型
            Goods  good = (Goods)it.next();
            System.out.println(good);
        }
    }

不合法的状态异常:

public static void method4() {
        //数组集合
        Collection collection2 = new ArrayList();
            Goods goods1 = new Goods("瓜子",100,9.9);
            Goods goods2 = new Goods("花生",200,9.9);
            collection2.add(goods1);
            collection2.add(goods2);
            System.out.println(collection2);
        //集合的遍历
        //hasNext()  next()
        Iterator  it = collection2.iterator();    
        while(it.hasNext()) {
            //数据的强制转换
            //铺垫泛型
            //java.lang.IllegalStateException
            //先移除在获取发生不合法的状态异常
            //it.remove();
            Goods  good = (Goods)it.next();
            //it.remove();
            System.out.println(good);
        }
    }

ConcurrentModificationException并发修改异常:

public static void method5() {
        //数组集合
        Collection collection2 = new ArrayList();
            Goods goods1 = new Goods("瓜子",100,9.9);
            Goods goods2 = new Goods("花生",200,9.9);
            collection2.add(goods1);
            collection2.add(goods2);
            System.out.println(collection2);
        //集合的遍历
        //hasNext()  next()
        Iterator  it = collection2.iterator();    
        while(it.hasNext()) {
            //数据的强制转换
            //铺垫泛型
            //java.lang.IllegalStateException
            //先移除在获取发生不合法的状态异常
            //java.util.ConcurrentModificationException 并发修改异常
            collection2.add(new Goods("香烟",500,15));
            Goods  good = (Goods)it.next();
            System.out.println(good);
        }
    }

1.3 Iterator 和 Enumeration的区别

功能重复
Iterator替换了Enumeration:

  1. 添加了remove方法
  2. 方法名被简写了

2. List

List是Collection的子接口,继承来自Collection的方法,也有特有的方法,默认初始容量为10

  1. 该接口是有序的,允许存储重复元素,可以存多个null值
  2. 可以通过下标对集合内的元素进行访问
    List使用特有的ListIterator在 Iterator基础上,有元素的插入和替换的方法 指定开始的位置的迭代器,List集合特有的方法:get()获取list集合中的元素,indexOf(),lastIndexOf()。

2.1 ArrayList

底层是数组的数据结构,是长度可变的数组,存储元素是有序的,允许存储重复元素和多个null值,是线程不安全的,不同步的,效率高,读取的效率高,因为是通过下边就可以操作,但是增删的时候效率低,因为增删时所有元素的下标会被影响。

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;

public class ListDemo {

    public static void main(String[] args) {
        List list  = new ArrayList();
                list.add("Hello");
                list.add(123);
                list.add('d');
                list.add(new Object());
                list.add(99.99);
                //[Hello, 123, d, java.lang.Object@15db9742, 99.99]
            System.out.println(list);
            //通过下标获取集合中的元素
            System.out.println(list.get(0));
            //获取集合的元素个数
            System.out.println(list.size());
            //元素在集合中第一次出现的下标值
            System.out.println(list.indexOf('d'));
            //最后一次出现的下标值
            System.out.println(list.lastIndexOf(99.99));
            //截取集合片段  不包含结尾下标的集合元素
            System.out.println(list.subList(0, 3));
            System.out.println("------------集合的遍历-----------");
            //遍历
            /*
             * for(int i=0;i<list.size();i++) { System.out.println(list.get(i)); }
             */

            /*
             * for(Object i:list) { System.out.println(i); }
             */
            //通用迭代器
            /*
             * Iterator it = list.iterator(); while(it.hasNext()) { Object obj = it.next();
             * System.out.println(obj); }
             */

            //特有的listIterator
            ListIterator iterator = list.listIterator();
            //java.util.NoSuchElementException
            //返回集合中上一个元素
                //System.out.println(iterator.previous());
                iterator.add("88888");
                while(iterator.hasNext()) {
                    //iterator.add("88888");
                    System.out.println(iterator.next());
                    //iterator.set("Java");
                }
                System.out.println(list);
    }

}

2.1.1 ArrayList去除重复的三种方式

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
 
 
public class ListItertorDemo {
    public static void main(String[] args) {
 
        List list=new ArrayList();
     
        list.add("abc1");
        list.add("abc1");
        list.add("abc1");
        list.add("abc3");
        list.add("abc2");
        list.add("abc1");
        list.add("abc3");
        list.add("abc3");
        list.add("abc1");
        list.add("abc1");
        list.add("abc1");
        list.add("abc3");
         
        System.out.println(list);
        singleElement2(list);
        System.out.println(list);
 
         
    }
    /*方法二,
     * 思路:
     * 1.最后唯一性的元素也很多,可以先定义一个临时容器用于存储这些唯一性的元素
     * 2.对原有容器进行元素的获取,并到临时容器中去判断是否存在,容器本身就有这个功能,判断元素是否存在 equals
     * 3.存在就不存储,不存在就存储
     * 4.遍历完原容器后,临时容器中存储的就是唯一性的元素
     */
    public static void singleElement2(List list){
        List temp=new ArrayList();
        for (Iterator it = list.iterator(); it.hasNext();) {
            Object obj = (Object) it.next();
            if(!temp.contains(obj)){
                temp.add(obj);
            }  
        }
        list.clear();
        list.addAll(temp);
         
    }
    //方法一,思想是数组选择排序
    public static void singleElement(List list){
         
        for(int x=0;x<list.size()-1;x++){
             
            Object obj1=list.get(x);
            for(int y=x+1;y<list.size();y++){
                 
                if(obj1.equals(list.get(y))){
                    list.remove(y);
                    y--;    //可以省略 
                }
            }
        }  
        
        //方法三,面向对象的思想,继承ArrayList重写add方法
        
        @Override
    public boolean add(Object o) {
        if (this.indexOf(o) != -1){//判断Mylist集合中是否有这个元素,如果有的话,索引就肯定不等于-1
            return  false;
        }else {
            return super.add((E) o);
        }
    }
        
        
        
    }  
}

2.2 Vector

Vector 类可以实现可增长的对象数组。与数组一样,它包含可以使用整数索引进行访问的组件。但是,Vector 的大小可以根据需要增大或缩小,以适应创建 Vector 后进行添加或移除项的操作。

Vector是线程安全的,线程同步的。

Vector读取快 增删慢

从JDK1.2开始使用ArrayList替换了Vector

2.3 LinkedList

List接口的的链表实现。实现所有可选的列表操作,并且允许所有元素(包括null)。除了实现List接口外,LinkedList类还为在列表的开头及结尾getremoveinsert` 元素提供了统一的命名方法。这些操作允许将链接列表用作堆栈、队列双端队列
LinkedList是线程不安全的,线程不同步的。

数据结构为:链表结构(底层其实是数组加链表),读取慢,增删快。

  • 数组是为了查询快,让链表有序
  • 链表是为了增删快

linkedlist是双向链表加列表的实现. 因为LinkedList实现了ListQueue接口。

常用的方法:

  • addFirst():
  • addLast():
  • getFirst():
  • getLast():
  • element():
  • pop():
  • push():
  • peek():
  • offer():
  • offerFirst():
  • removeFirst():
  • removeLast():
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

public class LinkedListDemo {
    public static void main(String[] args) {
        List list=new LinkedList();
        LinkedList list1=new LinkedList();
        list.add(123);
        list.add("222");
        list.add(20.00);
        System.out.println(list.get(0));
        System.out.println("=========================");

        Iterator iterator = list.iterator();
        while (iterator.hasNext()){
            Object next = iterator.next();
            System.out.println(next);
        }
        System.out.println("========================");

        /*list.clear();
        System.out.println(list);*/

        list1.addFirst("1234");
        list1.addAll(list);
        System.out.println(list1);
        System.out.println(list1.peek());
        System.out.println(list1);
        list1.pop();
        System.out.println(list1);
        list1.push(890);
        System.out.println(list1);
        System.out.println("=========================");

        System.out.println(list1.peekLast());//获取但不移除此列表的最后一个元素;如果此列表为空,则返回 null。
        System.out.println(list1);
        Object element = list1.element();//源码中是  return getFirst();获取不移除
        System.out.println(element);
        System.out.println(list1);
        System.out.println("=========================");
    }
}

LinkedList中的add方法是如何实现的:(源码)

 public boolean add(E e) {
        linkLast(e);
        return true;
    }
 private static class Node<E> {
        E item;
        Node<E> next;
        Node<E> prev;

        Node(Node<E> prev, E element, Node<E> next) {
            this.item = element;
            this.next = next;
            this.prev = prev;
        }
    }
void linkLast(E e) {
        final Node<E> l = last;
        final Node<E> newNode = new Node<>(l, e, null);
        last = newNode;
        if (l == null)
            first = newNode;
        else
            l.next = newNode;
        size++;
        modCount++;
    }

2.4 ArrayList,Vector和LinkedList的区别

  1. ArrayList是不同步的,效率高,查询快,增删慢
  2. Vector是同步的,效率快但是相对ArrayList慢,读取快,增删慢
  3. 二者都有一个初始容量大小,采用线性连续存储空间;当Vector或ArrayList中的元素超过它的初始大小时,Vector会将它的容量翻倍,而ArrayList只增加50%的大小,这样ArrayList就有利于节约内存空间。
  4. ArrayList和LinkedList之间的区别就是数组和双向链表之间的区别。
    • 数组的特点:因为数组分配的是一块连续的内存空间,使用索引来查找元素是非常快速的。但是插入,删除数据性能比较低。增删操作会影响到所有元素的下标
    • 双向链表的特点,查询效率较低,因为查询一个元素需要从头部开始查询,挨个遍历每一个元素直到找到所需元素,插入,删除效率高。增删操作时只需要把它前一个元素的指针指向自己,自己的指针指向下一个元素就可以了。
public boolean add(E e) {
ensureCapacityInternal(size + 1); //判断当前数组的容量是否够大如果不够大则扩容
elementData[size++] = e;//将元素添加到数组尾部
return true;
}

源码中可以看出来:ArrayList执行效率取决于:ensureCapacityInternal(size+ 1)方法的执行,在该方法中会判断数组容量是否足够,如果不够则进行扩容到原来的1.5倍。在扩容的过程中会生成一个新的数组,将原数组中的元素复制到新数组中。所以在这种情况下如果数组容量足够大ArrayList的效率是非常高的,我们也可以根据实际情况给它一个合适的初始值。

查看LinkedList中add方法源码

可以看到每新增一个元素就要创建一个Node对象,进行频繁的赋值操作 “final Node newNode = new Node<>(l, e, null);”对效率有一定的影响。

  1. 在查询操作较多,在特定位置插入数据和删除数据较少的情况下一般选用ArrayList,在特定位置插入数据,删除数据操作较多,查询较少的情况下一般选用LinkedList,但是在大多数应用中都是对查询效率要求较高,所以ArrayList集合应用更广泛一些。

3. 数据结构

3.1 数组

存储相同数据类型的数据。 数组的长度是不可以变化的,在集合的数据结构中,底层是数组数据结构的集合长度是可以变化的。
可以通过下标进行元素的获取和赋值。

3.2 栈和队列

栈一般是保存我们的变量,在方法的栈内存中声明变量和赋值,如果是对象在栈中保存的是对象声明的变量,引用的堆内存中的地址值

特点:先进后出

Stack是Vector的子类:

  	* push:压栈
  	* pop:弹栈

队列和栈相反:先进先出

在这里插入图片描述

3.3 链表

特点:

  1. 存储的元素值
  2. 存储元素的地址值
  3. 链表在内存中是无序的
  • 单向链表
  • 双向链表(循环链表)
    在这里插入图片描述

3.4 树

在这里插入图片描述
二叉树的特点:

  • 第一个是根节点
  • 大的往右走
  • 小的往左走

4. 泛型

为什么有泛型机制?

就是在编程中很多时候需要数据类型的转换 ,很麻烦。所以在JDK1.5提出了泛型的机制。

泛型的分类?

  1. 集合中的泛型
  2. 接口的泛型

泛型的好处:

  1. 通过允许指定泛型类或方法操作的类型,泛型功能将类型安全的任务从编程人员转移给了编译器。不需要编写代码来测试数据类型是否正确,因为在编译时会强制使用正确的数据类型。减少了类型强制转换的需要和运行时错误的可能性。
  2. 程序变的简单起来
  3. 如果集合规定了泛型 泛型意外的数据就不会加进去 在编译就会出错。
  4. 提高代码的可读性
  5. 泛型是对 java 语言的类型系统的一种扩展,以支持创建可以按类型进行参数化的类。可以把类型参数看作是使用参数化类型时指定的类型的一个占位符,就像方法的形式参数是运行时传递的值的占位符一样。
  	ArrayList<Student> list1 = new ArrayList<Student>();//jdk1.5的使用方式
        
        ArrayList<Student> list1 = new ArrayList<>();//jdk1.8的使用方式


 		//集合元素是基本数据类型  泛型是基本数据类型的包装类
        ArrayList<Integer> list2 = new ArrayList<>();
            list2.add(23);
            list2.add(88);

4.1 泛型在接口中的使用

public interface BaseDao<T> {
    void add(T t);
}



public interface GoodsDao  extends BaseDao<Goods>{

}


//测试类
public class BaseDaoDemo {

    public static void main(String[] args) {
        //匿名内部类
        StudentDao stuDao = new StudentDao() {
            @Override
            public void add(Student t) {

            }};
            //add 方法在进行重写的时候会自动进行参数类型的填充
            GoodsDao goodsDao = new GoodsDao() {

                @Override
                public void add(Goods t) {

                }};
    }

}

4.2 泛型在方法中的使用

//方法的参数类不确定就可以使用泛型的形式
    public static <T> void method(T t ) {
        System.out.println("Hello"+t );
    }

5. Set集合

一个不包含重复元素的 collection。更确切地讲,set 不包含满足 e1.equals(e2) 的元素对 e1e2,并且最多包含一个 null 元素,可以有序也可以无序。

5.1 hashSet

此类实现 Set 接口,由哈希表(实际上是一个 HashMap 实例)支持。它不保证 set 的迭代顺序;特别是它不保证该顺序恒久不变。此类允许使用 null 元素

  • 数据结构是哈希表
  • 是无序的
  • 允许null元素
  • 不是线程同步 效率高
import java.util.HashSet;
import java.util.Iterator;
/**HashSet: 
 * 底层是哈希表的结构:
 * */
public class HashSetDemo {

    public static void main(String[] args) {
        // 所谓的无序是指  添加和获取的顺序不一致
        HashSet<String> set = new HashSet<>();
            set.add("Hello");
            set.add("Java");
            set.add("Java");
            set.add("123");
            set.add("World");
            set.add("World");
            set.add("Python");
            //[Java, Hello, World, Python]
        System.out.println(set);
            //5
        System.out.println(set.size());
           
        Iterator<String> iterator = set.iterator();
            while(iterator.hasNext()) {
                System.out.println(iterator.next());
            }
    }
}

数据结构:(哈希表)
JDK1.8之前哈希表=数组加链表

JDK1.8之后:

  • 哈希表=数组+链表(链表长度超过8后转为红黑树)
  • 哈希表=数组+红黑树(提高查询速度)

在这里插入图片描述

5.1.1 如何保证元素唯一性

底层使用的是hashMap的put方法 , add()方法说明,如果set集合中没有包含添加的元素 , 添加进去返回true,如果 通过hashCode,equals方法比较已经包含了要添加的元素,那么集合不会改变,并且返回false。

由于HashMap的put()方法添加key-value对时,当新放入HashMap的Entry中key 与集合中原有Entry的key相同(hashCode()返回值相等,通过equals比较也返回true),新添加的Entry的value会将覆盖原来Entry的value,但key不会有任何改变, 因此如果向HashSet中添加一个已经存在的元素时,新添加的集合元素将不会被放入HashMap中, 原来的元素也不会有任何改变,这也就满足了Set中元素不重复的特性。

 public boolean add(E e) {
        return map.put(e, PRESENT)==null;
    }
5.1.2 加载因子

初始的容量是16如果说是超过初始容量以后呢。他会生成新的哈希表,将原来的哈希表覆盖。

/**
     * Constructs a new set containing the elements in the specified
     * collection.  The <tt>HashMap</tt> is created with default load factor
     * (0.75) and an initial capacity sufficient to contain the elements in
     * the specified collection.
     *
     * @param c the collection whose elements are to be placed into this set
     * @throws NullPointerException if the specified collection is null
     */
    public HashSet(Collection<? extends E> c) {
        map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
        addAll(c);
    }

上面的意思大致为:

构造一个包含指定元素的新集合。
HashMap使用默认负载因子创建(0.75)和足以容纳元素的初始容量指定的集合。
@param c将其元素放置到这个集合中的集合
如果指定的集合为空,则抛出NullPointerException

5.2 TreeSet

基于 TreeMapNavigableSet实现。使用元素的自然顺序对元素进行排序,或者根据创建 set 时提供的 Comparator进行排序,具体取决于使用的构造方法,该接口不是同步的,效率高。

  • 自然排序
  • 比较器排序 Comparable
5.2.1 自然排序

实现了Comparable接口就实现了自然排序

当我们想要对装有对象的集合进行排序时,就需要让对象实现Comparable接口

public class Teacher implements Comparable<Teacher> {
    private String teaName;
    private int teaAge;
    private String teaAddress;
    public Teacher() {
        super();
    }
    public Teacher(String teaName, int teaAge, String teaAddress) {
        super();
        this.teaName = teaName;
        this.teaAge = teaAge;
        this.teaAddress = teaAddress;
    }
    ......get  set ......

    @Override
    public String toString() {
        return "Teatcher [teaName=" + teaName + ", teaAge=" + teaAge + ", teaAddress=" + teaAddress + "]";
    }
    /*我们让 自定义类对象进行自然排序。
     * 怎么排序?
     * 1. 首先实现Comparable接口  重写CompareTo方法
     * 2. 我们根据对象的哪一个属性进行比较?
     * 3. 我们要定义排序主规则: 按年龄 ----> 名字 ----> 地址
     */

    @Override
    public int compareTo(Teacher teacher) {
        if(teacher == null) {
            throw new NullPointerException("输入的参数对象不能是null!!!");
        }
        if(this==teacher) {
            return 0;
        }

        int  ageResult = this.getTeaAge() - teacher.getTeaAge();
        int nameResult = this.getTeaName().compareTo(teacher.getTeaName()); 
        int addressResult = this.getTeaAddress().compareTo(teacher.getTeaAddress());
        int result  = ageResult == 0 ? (nameResult == 0?addressResult : nameResult):ageResult;
        return result;
    }

}
 TreeSet <Teacher> tSet = new TreeSet<>();
            tSet.add(new Teacher("张三",33,"西安市"));
            tSet.add(new Teacher("王五",13,"汉中市"));
            tSet.add(new Teacher("翟柳",23,"宝鸡市"));
            tSet.add(new Teacher("张麻子",33,"西安市"));
            tSet.add(new Teacher("张麻子",33,"西安市"));
            System.out.println(tSet);
5.2.2 比较器排序

TreeSet(Comparator comparator) 比较器排序 如果comparator是null 那依然使用的是自然排序。

Comparator 接口是比较器 :

compare()方法实现两个参数的比较

Comparator接口实现类的比较器排序

package com.xdkj.javase.set;

import java.util.Comparator;

public class StringComparator implements Comparator<String> {

    @Override
    public int compare(String o1, String o2) {
        //字符串  使用自然排序的比较
        return o1.compareTo(o2);
    }

}
public static void method2() {
        //comparator ordering 比较器排序
        TreeSet<String> treeSet = new TreeSet(new StringComparator());
            treeSet.add("Hello");
            treeSet.add("小明");
            treeSet.add("张三");
            treeSet.add("World");
            //[Hello, World, 小明, 张三]
        System.out.println(treeSet);
    }

匿名内部类实现比较器排序

//匿名内部类实现 比较器排序
    public static void method3() {
        //comparator ordering 比较器排序

        TreeSet<String> treeSet = new TreeSet(new Comparator<String>() {
            @Override
            public int compare(String o1, String o2) {
                return o1.compareTo(o2);
            }
        });
            treeSet.add("Hello");
            treeSet.add("小明");
            treeSet.add("张三");
            treeSet.add("World");
            //[Hello, World, 小明, 张三]
        System.out.println(treeSet);
    }

自定义类的匿名内部类实现比较器排序:

package com.xdkj.javase.test;

public class Student {
    private String stuName;
    private int stuAge;
    private String stuNumber;
    private String stuAddress;
    public Student() {
        super();
        // TODO Auto-generated constructor stub
    }
    public Student(String stuName, int stuAge, String stuNumber, String stuAddress) {
        super();
        this.stuName = stuName;
        this.stuAge = stuAge;
        this.stuNumber = stuNumber;
        this.stuAddress = stuAddress;
    }
    .....get  set .....

    @Override
    public String toString() {
        return "Student [stuName=" + stuName + ", stuAge=" + stuAge + ", stuNumber=" + stuNumber + ", stuAddress="
                + stuAddress + "]";
    }
    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((stuAddress == null) ? 0 : stuAddress.hashCode());
        result = prime * result + stuAge;
        result = prime * result + ((stuName == null) ? 0 : stuName.hashCode());
        result = prime * result + ((stuNumber == null) ? 0 : stuNumber.hashCode());
        return result;
    }
    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        Student other = (Student) obj;
        if (stuAddress == null) {
            if (other.stuAddress != null)
                return false;
        } else if (!stuAddress.equals(other.stuAddress))
            return false;
        if (stuAge != other.stuAge)
            return false;
        if (stuName == null) {
            if (other.stuName != null)
                return false;
        } else if (!stuName.equals(other.stuName))
            return false;
        if (stuNumber == null) {
            if (other.stuNumber != null)
                return false;
        } else if (!stuNumber.equals(other.stuNumber))
            return false;
        return true;
    }

}
//自定义类的比较器排序
    public static void method4() {
        //comparator ordering 比较器排序
        TreeSet<Student> treeSet = new TreeSet(new Comparator<Student>() {
            //定义主规则和次要的规则
            @Override
            public int compare(Student stu1, Student stu2) {
                int nameResult = stu1.getStuName().compareTo(stu2.getStuName());
                return  nameResult == 0 ? (stu1.getStuAge() - stu2.getStuAge() == 0 ? (stu1.getStuNumber().compareTo(stu2.getStuNumber())):stu1.getStuAge() - stu2.getStuAge()):nameResult;
            }
        });
            treeSet.add(new Student("小明",23,"java010","西安市"));
            treeSet.add(new Student("瓯网",66,"java011","西安市"));
            treeSet.add(new Student("小明",23,"java010","西安市"));

        System.out.println(treeSet);
    }

5.3 LinkedHashSet

底层是链表 + 哈希表的数据结构

具有可预知迭代顺序的 Set 接口的哈希表和链接列表实现。此实现与 HashSet 的不同之外在于,后者维护着一个运行于所有条目的双重链接列表。此链接列表定义了迭代顺序,即按照将元素插入到 set 中的顺序(插入顺序)进行迭代.

  • 线程不同步的
  • 有序的,不允许重复的

为什么两种数据结构?

  1. 使用链表保证元素有序
  2. HashSet确定元素的唯一性
import java.util.LinkedHashSet;

public class LinkedHashSetDemo {

    public static void main(String[] args) {
        LinkedHashSet <String> set = new LinkedHashSet<>();
            set.add("Hello");
            set.add("Hello");
            set.add("小明");
            set.add("张三");
            set.add("张三");
            set.add("world");
            //[Hello, 小明, 张三, world]
        System.out.println(set);
    }
}

6. Map

map集合没有继承Collection接口,其提供的是key到value的映射,是一个键值对映射关系的集合,Map中不能包含相同的key值,每个key只能映射一个相同value,key值还决定了存储对象在映射中的存储位置,但不是key对象本身决定的,而是通过散列技术进行处理,可产生一个散列码的整数值,散列码通常用作一个偏移量,该偏移量对应分配给映射内存区域的起始位置,从而确定存储对象在映射中的存储位置将键映射到值的对象。一个映射不能包含重复的键;每个键最多只能映射到一个值

map集合的遍历的两种方式:

  1. 获取key的set视图在通过遍历key的set集合 get()方法通过键获取值
  2. 获取键值映射的Entry 视图 在通过遍历 Entry视图获取到 键值映射 在通过 getKey(),getValue()分别获取键和值
 public static void main(String[] args) {
        Map<Integer,String> map=new HashMap<>();
        map.put(1,"迪迦");
        map.put(2,"盖亚");
        map.put(3,"梦比优斯");
        map.put(4,"泰罗");
        //map.put(4,"泰罗"); 允许存储空键
        System.out.println(map.get(5));//  null
        System.out.println("=====================");
        //第一种遍历方式
        Set<Integer> set = map.keySet();
        for (Integer integer : set) {
            String s = map.get(integer);
            System.out.println(integer+"----"+s);
        }
        System.out.println("============================");
        //第二种遍历方式
        Set<Map.Entry<Integer, String>> entries = map.entrySet();
        for (Map.Entry<Integer, String> entry : entries) {
            System.out.println(entry);
        }

6.1 Hashtable

  1. 底层使用的是哈希表数据结构 还有数组的数据结构

     - 数组保证查询快
     - 哈希保证唯一性
    
  2. 线程安全的,同步的 ,效率低

  3. 不允许null值和null键

  4. 命名错误 在JDK1.2以后使用HashMap替换了Hashtable

Hashtable如何保证唯一性和迭代顺序是有序的:(源码)

public synchronized V put(K key, V value) {
        // Make sure the value is not null
        if (value == null) {
            throw new NullPointerException();
        }

        // Makes sure the key is not already in the hashtable.
        Entry<?,?> tab[] = table;
            //计算键的哈希值
        int hash = key.hashCode();
        //根据键的哈希值计算出一个数组的下标值
        int index = (hash & 0x7FFFFFFF) % tab.length;
        @SuppressWarnings("unchecked")
        Entry<K,V> entry = (Entry<K,V>)tab[index];
        for(; entry != null ; entry = entry.next) {
            //如果键的值相同使用新的值覆盖原来的值 键只有一个
            if ((entry.hash == hash) && entry.key.equals(key)) {

                V old = entry.value;
                entry.value = value;
                return old;
            }
        }

        addEntry(hash, key, value, index);
        return null;
    }
private void addEntry(int hash, K key, V value, int index) {
        modCount++;

        Entry<?,?> tab[] = table;
     //如果集合的键的数量大于 容量值
        if (count >= threshold) {
            // Rehash the table if the threshold is exceeded
            //重新刷新一个哈希表
            rehash();
            tab = table;
            //在生成新的键的哈希值
            hash = key.hashCode();
            //新的数组的下标值
            index = (hash & 0x7FFFFFFF) % tab.length;
        }

        // Creates the new entry.
        @SuppressWarnings("unchecked")
        Entry<K,V> e = (Entry<K,V>) tab[index];
         //创建键值映射关系 存储键和值  放入数组保证迭代顺序一致
        tab[index] = new Entry<>(hash, key, value, e);
        count++;
    }

6.2 HashMap

  1. 基于哈希表的 Map 接口的实现。此实现提供所有可选的映射操作
  2. 并允许使用 null 值和 null 键。
  3. 此类不保证映射的顺序,特别是它不保证该顺序恒久不变
  4. 不是同步的 线程不安全的 效率高
import java.util.HashMap;
import java.util.Map;

public class HashMapDemo {

    public static void main(String[] args) {
        Map<String,Integer> map = new HashMap<>();
            map.put("Hello", 99);
            map.put("World", 88);
            map.put("lucy", 5666);
            map.put("lilei", 123);
            map.put("hanmeiemi", 456);
            map.put("hanmeiemi", 66666);
            map.put(null, 66666);
            map.put(null, null);

            //{lilei=123, Hello=99, hanmeiemi=456, World=88, lucy=5666}
            //添加的顺序和迭代的顺序不一致
            //允许null值和null键
            System.out.println(map);
            //第一个 如何保证键的唯一
            //值新的覆盖旧的
            //自定义对象作为键 如何保证唯一
    }

}

自定义类作为键:

自定义类作为键 ,因为HashMap底层使用的是equals判断键是否相等,所以我们要进行equals和hashCode方法的重写.

package com.xdkj.javase.map;

public class Person {
    private String pName;
    private int pAge;
    private String pEmail;
    public Person() {
        super();
        // TODO Auto-generated constructor stub
    }
    public Person(String pName, int pAge, String pEmail) {
        super();
        this.pName = pName;
        this.pAge = pAge;
        this.pEmail = pEmail;
    }
    ...get set ....
    @Override
    public String toString() {
        return "Person [pName=" + pName + ", pAge=" + pAge + ", pEmail=" + pEmail + "]";
    }
    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + pAge;
        result = prime * result + ((pEmail == null) ? 0 : pEmail.hashCode());
        result = prime * result + ((pName == null) ? 0 : pName.hashCode());
        return result;
    }
    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        Person other = (Person) obj;
        if (pAge != other.pAge)
            return false;
        if (pEmail == null) {
            if (other.pEmail != null)
                return false;
        } else if (!pEmail.equals(other.pEmail))
            return false;
        if (pName == null) {
            if (other.pName != null)
                return false;
        } else if (!pName.equals(other.pName))
            return false;
        return true;
    }

}
public static void method2() {
        Map<Person,Integer> map = new HashMap<>();
        map.put(new Person("张三",25,"123@qq.com"),99);
        map.put(new Person("李四",25,"123@qq.com"),99);
        map.put(new Person("张三",25,"123@qq.com"),99);
        map.put(new Person("王麻子",26,"123@qq.com"),88);

        System.out.println(map);
    }
{Person [pName=张三, pAge=25, pEmail=123@qq.com]=99, Person [pName=王麻子, pAge=26, pEmail=123@qq.com]=88, Person [pName=李四, pAge=25, pEmail=123@qq.com]=99}

HashMap源码解读:

  • HashMap基于Hash算法实现的,通过put(key,value)存储,get(key)来获取。当传入key时,HashMap会根据key.HashCode()计算出hash值,根据hash值将value保存在bukect里。当计算出的hash值相同时,我们称之为哈希冲突,HashMap的做法是用链表和红黑树存储相同hash值的value。当hash冲突较少时,使用链表,否则使用红黑树。
 /**
     * Implements Map.put and related methods.
     *
     * @param hash hash for key
     * @param key the key
     * @param value the value to put
     * @param onlyIfAbsent if true, don't change existing value
     * @param evict if false, the table is in creation mode.
     * @return previous value, or null if none
     */
    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        //初始的哈希表底层是数组的结构 数据保证的是在查询的时候速度快 节省空间
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        else {
            Node<K,V> e; K k;
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
                for (int binCount = 0; ; ++binCount) {
                    //加入键值映射的时候 在数量小于等于7的时候 使用的是链表的结构
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            //在数量大于7的时候底层转换为了二叉树的结构 对键的进行排序  
                            //HashMap迭代的书序不一致
                            treeifyBin(tab, hash);
                        break;
                    }
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            //保证同一个节点  新的值覆盖旧的值
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        //集合的数量超出 初始的容量 进行数据表的刷新 数据结构的重建
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }

在这里插入图片描述
Hashtable和HashMap的区别:

  1. Hashtbale 也是哈希表的数据结构
  2. HashMap是 数据加链表加红黑树(二叉树)
  3. HashMap是线程不安全的,不同步的效率高
  4. Hashtable是线程安全的,同步的效率低
  5. Hashtable命名错误 被HashMap替换
  6. HashMap运行null键和Null值
  7. Hashtable不允许null键和Null值
  8. HashMap 和Hashtable都允许值重复,键唯一

6.3 TreeMap

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

import java.util.Comparator;
import java.util.TreeMap;

public class TreeMapDemo {

    public static void main(String[] args) {
        //会自然排序和比较器排序
        TreeMap<Integer,String> map = new TreeMap<>();
            map.put(1,"one");
            map.put(3,"three");
            map.put(4,"four");
            map.put(2,"two");
        System.out.println(map);
        //比较器排序
        TreeMap<Person,String> map1 = new TreeMap<>(new Comparator<Person>() {
            @Override
            public int compare(Person o1, Person o2) {
                return o1.getpName().compareTo(o2.getpName())==0?o1.getpAge()-o2.getpAge():o1.getpName().compareTo(o2.getpName());
            }
        });
            map1.put(new Person("小明",25,"123@qq.com"),"Hello");
            map1.put(new Person("小红",22,"123@qq.com"),"Hello");
            map1.put(new Person("小刚",18,"123@qq.com"),"Hello");
        System.out.println(map1);
    }

}

TreeMap源码分析:

public V put(K key, V value) {
     //创建根节点
        Entry<K,V> t = root;
        if (t == null) {
            //检查根节点的键是否为null
            compare(key, key); // type (and possibly null) check
            //挂载根节点
            root = new Entry<>(key, value, null);
            size = 1;
            modCount++;
            return null;
        }
        int cmp;
        Entry<K,V> parent;
        // split comparator and comparable paths
        Comparator<? super K> cpr = comparator;
    //比较器排序
        if (cpr != null) {
            do {
                parent = t;
                //传入的key和根节点的key比较
                cmp = cpr.compare(key, t.key);
                if (cmp < 0)
                    //将当前节点的左边变为比较的根节点
                    t = t.left;
                else if (cmp > 0)
                    //将当前节点的右边变为比较的根节点
                    t = t.right;
                else
                    //键相同 新的值覆盖旧的值
                    return t.setValue(value);
            } while (t != null);
        }
        else {
            //自然排序
            if (key == null)
                throw new NullPointerException();
            @SuppressWarnings("unchecked")
                Comparable<? super K> k = (Comparable<? super K>) key;
            do {
                parent = t;
                //自然排序比较
                cmp = k.compareTo(t.key);
                if (cmp < 0)
                    t = t.left;
                else if (cmp > 0)
                    t = t.right;
                else
                    return t.setValue(value);
            } while (t != null);
        }
     //不是自然排序也不是比较器排序 和父节点进行比较  按照大小进行左右挂载
        Entry<K,V> e = new Entry<>(key, value, parent);
        if (cmp < 0)
            parent.left = e;
        else
            parent.right = e;
        fixAfterInsertion(e);
        size++;
        modCount++;
        return null;
    }

6.4 LinkedHashMap

Map 接口的哈希表和链接列表实现,具有可预知的迭代顺序。此实现与 HashMap 的不同之处在于,后者维护着一个运行于所有条目的双重链接列表。此链接列表定义了迭代顺序,该迭代顺序通常就是将键插入到映射中的顺序(插入顺序)。

数据结构是: 双向链表+ 列表+ 哈希表

插入顺序和迭代顺序是一致的。

不同步 线程不安全

允许null值和Null键

import java.util.LinkedHashMap;

public class LinkedHashMapDemo {

    public static void main(String[] args) {
        LinkedHashMap<String,Integer> map = new LinkedHashMap<>();
                map.put("Hello",123);
                map.put("World",123);
                map.put("Java",123);
                map.put("c++",123);
            //插入顺序和迭代顺序是一致的
            System.out.println(map);

    }

}

6.5 Properties

Properties 类表示了一个持久的属性集。Properties 可保存在流中或从流中加载。属性列表中每个键及其对应值都是一个字符串。

userName=admin
password=123
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;

public class PropertiesDemo {

    public static void main(String[] args) throws IOException {
        //获取一个资源作为流
        InputStream resourceAsStream = PropertiesDemo.class.getClassLoader().getResourceAsStream("db.properties");

        Properties properties = new Properties();
        //从流中加载数据
        properties.load(resourceAsStream);
        //从properties中 通过键获取值
        System.out.println(properties.getProperty("userName"));
        System.out.println(properties.getProperty("password"));
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值