集合

集合

1. 集合的概述:

  1. 什么是集合?有什么用?

    集合是一种容器。可以来容纳其他类型的数据。

  2. 集合为什么说在开发中用的比较多?

    集合是一个容器,是一个载体,可以一次容纳多个对象,在实际开发中,假设连接数据库,数据库当中有10条记录,那么假设把这10条记录查询出来,在java程序中会将10条数据封装成10个java对象,然后将10个java对象放到某一个集合当中,将集合传到前端,然后遍历集合,将一个数据一个数据展现出来。

  3. 集合不能直接存储基本数据类型,另外集合也不能直接存储java对象,集合当中存储的都是java对象的内存地址。(或者说集合中存储的是引用)。

    list.add(100)//自动装箱Integer
    

    注意:

    ​ 集合在java中本身是一个容器,是一个对象,也有内存地址。

    ​ 集合在任何时候存储的都是"引用"。

  4. 在java中每一个不同的集合,底层会对应不同的数据结构。往不同的集合中存储元素,等于将数据放到了不同的数据结构当中。

  5. 什么是数据结构:

    数据存储的结构就是数据结构,不同的数据结构,数据存储的方式不同。比如:数组,二叉树,链表,哈希表等。

    如:

    new ArrayList(); //创建一个集合对象,底层是数组
    new LinkedList();//创建一个集合对象,底层是链表
    new TreeSet();//创建一个集合对象,底层是二叉树
    
  6. 集合在java JDK中哪个包下?

    java.util.*;

    所有的集合类和集合接口都在java.util包下。

  7. 在java中集合元素分为两大类:

    一类是单个方式存储元素:这一类集合中的超级父接口:java util.Collection;

    一类是以键值对的方式存储元素:这一类集合的超级父接口是:java.util.map;

  8. list和set集合的区别:

    list:有序可重复,存储的元素有下标。有序:存进去是这个顺序,出来还是这个顺序。

    set:无序不可重复。存储的元素没有下标。

  9. list主要分为ArrayList,LinkedList,Vector,他们的区别:

    • ArrayList:底层采用了数组这种数据结构,初始化容量是10,ArrayList是非线程安全的。
    • LinkedList:底层采用了双向链表数据结构。
    • Vector: 底层采用了数组这种数据结构,Vector集合是线程安全的,效率低,现在保证线程安全有别的方法,故用的少。
  10. Set主要分为HashSet,SortedSet(接口),TreeSet等

    • HashSet:实际上HashSet集合在new的时候,底层实际上new了一个HashMap集合。向HashSet集合中存储元素,实际上是存储到HashMap集合中去了。HashMap集合是一个哈希表数据结构。
    • SortedSet:由于继承Set集合,所以它的特点也是无序不可重复的。但放在SortedSet中的元素可以自动按照大小顺序排序,故我们称SortedSet集合为可排序集合。
    • TreeSet: 底层实际上是TreeMap new一个TreeSet集合的时候,底层实际上new一个TreeMap集合。往TreeSet集合中放数据的时候,实际上是将数据放到TreeMap集合中了。TreeMap集合底层采用了二叉树数据结构。
  11. Map集合的特点:

    • Map集合和Collection集合没关系。
    • Map集合以key和value这种键值对的方式存储元素。
    • key和value都是存储java对象的内存地址。
    • 所有Map集合中的key是无序,不可重复的。
    • Map集合中的key和Set集合存储元素特点相同。
  12. Map主要分为HashMap,Hashtable(Properties),SortedMap(TreeMap)等。

    • HashMap:底层是哈希表数据结构,是非线程安全的。
    • Hashtable:底层也是哈希表数据结构,是线程安全的,其中所有方法都带有synchronized关键字,效率较低,现在使用较少,因为控制线程安全有其他更好的方案。
    • SortedMap:首先是无序不可重复的,另外放在SortedMap集合Key部分的元素会自动按照大小顺序排序,称为可排序集合。
    • TreeMap:底层数据结构是一个二叉树,key可以按照大小顺序排序。
    • Properties:是线程安全的,因为继承Hashtable,另外Properties存储元素的时候也是采用key和value的形式存储,并且key和value只支持String类型,不支持其他类型。Properties被称为属性类。

2.Collection接口:

概述:

  1. Collection中能存放什么元素?
    没有使用"泛型"之前,Collection中可以存储Object的所有子类型, 使用"泛型"之后,Collection中只能存储某个具体的类型。集合中不能直接存储基本数据类型,也不能存java对象,只能存储java对象的内存地址。

Collection中的常用方法:

/*
* 关于 java.util.Collection接口中的常用方法
*     1. Collection中能存放什么元素?
*        没有使用"泛型"之前,Collection中可以存储Object的所有子类型
*        使用"泛型"之后,Collection中只能存储某个具体的类型。
*     2. Collection常用方法:
*        boolean add(Obeject e): 向集合中添加元素
*        int size():获取集合元素的个数
*        void clear(): 清空集合
*        boolean contains(Object o):判断当前集合中是否包含元素o,包含返回true
*        boolean remove(Object o): 删除集合中某个元素
*        boolean isEmpty():  判断集合是否为空
*        Object[] toArray():  把集合转化为数组


 * */
public class CollectionTest01 {
    public static void main(String[] args) {
        //创建一个集合对象
        //Collection c=new Collections();   //接口是抽象的,无法实例化
        //多态
        Collection  c=new ArrayList();

        //测试Collection接口中的常用方法
        c.add(100); //自动装箱,实际上是放进去了一个对象的内存地址。
        c.add(3.14); //自动装箱
        c.add(new Object());//放一个Object对象
        c.add(true); //放一个boolean类型

        //获取集合中元素的个数
        System.out.println("c集合中元素的个数为:"+c.size()); //4

        //清空集合
        c.clear();
        System.out.println("c集合中元素的个数为:"+c.size());  //0

        // 再向集合中添加元素:
        c.add("hello"); //hello对象的内存地址放到了集合当中。
        c.add("world");
        c.add("小马");

        //判断集合中是否包含"小马"
        boolean flag=c.contains("小马");
        System.out.println(flag);//true

        //删除集合中某个元素:
        c.remove("小马");

        //判断集合是否为空
        System.out.println(c.isEmpty()); //false

        //把集合转换成数组
        Object[]obj=c.toArray();
        System.out.println(Arrays.toString(obj));
    }
}
  1. Collection中contains()方法:

    1. boolean contains(Object o):判断集合中是否包含某个对象o ,如果包含返回true,不包含返回false

    2. contains方法底层是怎么判断集合中是否包含某个元素的呢?

      调用了equals方法进行比对,equals方法返回true,就表示包含这个元素。

      ​ 故放在集合中的元素要重写equals方法

      /*
      * 深入Collection集合中的contains方法
      *  boolean contains(Object o)  判断集合是否包含某个对象o 如果包含返回true
      
       * */
      public class CollectionTest04 {
          public static void main(String[] args) {
              //创建集对象
              Collection c =new ArrayList();
              c.add(new String("abc"));
              c.add(new String("def"));
      
              //集合中元素的个数
              System.out.println("元素的个数是:"+c.size());
      
              String x=new String("abc");
              System.out.println(c.contains(x)); //true  判断集合中是否存在abc,比较的是内容
                                                 //        contains()方法底层调用了equals(),Sting中重写了equals方法
          }
      
      
      }
      
      
    3. 同样remove()方法底层也调用了equals方法。

      public class CollectionTest05 {
          public static void main(String[] args) {
           
              Collection c1=new ArrayList();
              String s1=new String("hello");
              c1.add(s1);
              String s2=new String("hello");
              c1.remove(s2);                  //s1.equals(s2) java认为s1和s2是一样的,删除s1就是删除s2.
              System.out.println(c1.size()); //0
      
          }
      }
      

集合的遍历或迭代:

  1. 注意:集合结构只要发生改变,迭代器必须重新获取。如果还用原来老的迭代器,会发生异常

    ​ 在迭代集合元素的过程中,不能调用集合对象的remove方法,删除元素 c.remove(o);会出现异常。

    ​ 在迭代元素的过程中,一定要使用迭代器Iterator的remove方法,删除元素。不要使用集合自带的remove方法。

public class CollectionTest02 {
    public static void main(String[] args) {
        //注意:以下的遍历或迭代方式,是所有Collection通用的一种方式
        //在Map集合中不能使用。在所有Collection以及子类中使用

        //创建集合对象
        Collection c=new ArrayList();

        //添加元素
        c.add("abc");
        c.add("小马");
        c.add(true);
        c.add(new Object());

        //对集合Collection进行遍历/迭代
        //1. 获取集合对象的迭代器对象:Iterator
        Iterator it=c.iterator();
        //2.  通过以上获取的迭代器对象,开始迭代/遍历集合
        //    注意:迭代器i最初并没有指向第一个元素
        /*
        以下两个方法是迭代器对象Iterator中的方法
         boolean hasNext() :如果仍有元素可以迭代,则返回 true。
                            false表示没有更多元素可以迭代了。
          Object next() : 返回迭代的下一个元素。让迭代器前进一位,并且将指向的元素返回(拿到)
        * */

/*     for (int i=0;i<c.size();i++){
         if(it.hasNext()){
             System.out.println(it.next());
         }
     }*/
     while (it.hasNext()){
         System.out.println(it.next());
     }

    }
}

  1. 关于remove():

    public class CollectionTest06 {
        public static void main(String[] args) {
            Collection c=new ArrayList();
            c.add(1);
            c.add(2);
            c.add(3);
            c.add(4);
            //注意:集合结构只要发生改变,迭代器必须重新获取
            Iterator it=c.iterator();
            while (it.hasNext()){
                //编写代码时next()方法返回值类型必须是Object
                //Object o=it.next();
                System.out.println(it.next());
            }
    
                Collection c2=new ArrayList();
                c2.add("abc");
                c2.add("edf");
                c2.add("xyz");
    
                Iterator it1=c2.iterator();
                while (it1.hasNext()){
                    Object o=it1.next();
                    //删除元素
                    //删除元素之后,集合的结构发生了变化,应该重新去获取迭代器
                    //但是,循环下一次的时候并没有重新获取迭代器,所以会出现异常。
                    //出异常的根本原因:集合中元素删除了,但没有更新迭代器(迭代器不知道集合发生变化)
                    //c2.remove(o);//java.util.ConcurrentModificationException(导致迭代器的快照和原集合状态不同)
    
                    //使用迭代器来删除:会自动更新迭代器,并且更新集合
                    it1.remove(); //删除的一定是迭代器指向的当前元素
                    System.out.println(o);
                }
            System.out.println(c2.size());//0
        }
    }
    
    

3. List:

概述:

  1. List集合存储元素的特点:有序可重复
    有序:List集合中的元素有下标 从0开始,以1递增
    可重复: 存储一个1,还存储1

特有的常用方法

  1. List既然是Collection接口的子接口,那么肯定List接口有自己特色的方法。
    以下只列出List集合特有的常用方法:
void add(int index,String item); //在列表指定位置插入指定元素(第一个参数是下标) 方法使用不多,效率较低
Object set(int index,Object element)//修改指定下标位置的元素
Object get(int index);//根据下标获取元素 故有自己比较特殊的遍历方式,如下
int  indexOf(Object o) //获取指定对象第一次出现的索引
int  lastIndexOf(Object o)//获取指定对象最后一次出现处的索引
Object remove(int index)   //删除指定下标位置的元素


  1. List集合特有的遍历方式:

    public class ListTest {
        public static void main(String[] args) {
            //创建List类型的集合
             List myList=new ArrayList();
             //添加元素
            myList.add("A");  //默认都是向集合末尾添加元素
            myList.add("B");
            myList.add("C");
            myList.add("D");
            //因为有下标,所以list集合有自己比较特殊的遍历方式
            //通过下标遍历
            for (int i=0;i<myList.size();i++){
                Object obj1=myList.get(i);
                System.out.println(obj1);
            }
    
        }
    }
    
    
  2. 方法测试如下:

public class ListTest01 {
    public static void main(String[] args) {
        //创建List类型的集合
         List myList=new ArrayList();

         //添加元素
        myList.add("A");  //默认都是向集合末尾添加元素
        myList.add("B");
        myList.add("C");
        myList.add("C");
        myList.add("D");

        myList.add(1,"king"); //在列表指定位置插入指定元素

        //迭代
        Iterator it=myList.iterator();
        while (it.hasNext()){
            System.out.println(it.next());
        }

        //根据下标获取元素
        Object obj=myList.get(1);
        System.out.println(obj);
        System.out.println("===========----");

        //因为有下标,所以list集合有自己比较特殊的遍历方式
        //通过下标遍历
        for (int i=0;i<myList.size();i++){
            Object obj1=myList.get(i);
            System.out.println(obj1);
        }

        //获取指定对象第一次出现的索引
        System.out.println(myList.indexOf("C")); //3


        //获取指定对象最后一次出现处的索引
        System.out.println(myList.lastIndexOf("C"));//4

        //删除指定下标位置的元素
        //删除下标为0 的元素
        myList.remove(0);
        System.out.println(myList.size());//5

        //修改指定下标位置的元素
        myList.set(0,"hamburg");

        //遍历集合
        for (int i=0;i<myList.size();i++){
            System.out.println(myList.get(i));
        }

    }
}

ArrayList:

  1. ArrayList集合初始化容量是10(底层先创建了一个长度为0的数组,当添加第一个元素的时候,初始化容量10 )

  2. 底层是Object类型的数组

    可在集合创建的时候,给定集合容量

     List list1=new ArrayList(20);
    
  3. ArrayList集合的扩容:

    扩容到原容量的1.5倍

  4. ArrayList集合底层是数组,怎么优化?

    尽可能少的扩容。因为数组扩容效率比较低,建议在使用ArrayList集合的时候,预估计元素的个数,给定一个初始化容量。

  5. ArrayList的优缺点:

    检索效率较高:每个元素占用空间大小相同,内存地址是连续的,知道首元素内存地址,然后知道下标,通过数学表达式计算出元素的内存地址,故检索效率高。

    随机增删元素效率比较低,无法存储大数据量(很难找到一块非常巨大的连续内存空间),但是向数组末尾添加元素,效率还是比较高的。

  6. 面试官经常会问的一个问题:

    这么多集合中,你用哪个集合比较多? ArrayList

  7. ArrayList是非线程安全的集合。

  8. ArrayList的三种构造方法:

    /*
    * 集合ArrayList的构造方法
    * */
    public class ArrayListTest02 {
        public static void main(String[] args) {
            //默认初始化容量10
            List myList=new  ArrayList();
            //指定初始化容量100
            List myList1=new  ArrayList(100);
    
            //创建一个hashSet集合
            Collection c=new HashSet();
            //添加元素
            c.add(100);
            c.add(10);
            c.add(90);
            c.add(200);
            c.add(500);
    
            //通过这个构造方法可以将hashSet集合转化为List集合
            List myList2=new  ArrayList(c);
            for (int i=0;i<myList2.size();i++){
                System.out.println(myList2.get(i));
            }
    
        }
    }
    
    

LinkedList(双向链表):

  1. 单链表的数据结构:

    对于链表数据结构来说,基本的单元是节点(Node)。

    对于单向链表来说,任何一个节点Node中都有两个属性:第一:存储数据。第二:下一个节点的内存地址

  2. 链表的优缺点:

    优点:随机增删元素效率较高。(因为链表上的元素在空间存储上内存地址不连续,故增删元素不涉及到大量元素位移的问题)。

    缺点:查询效率比较低。每一次查找某个元素的时候,都需要从头节点开始往下遍历。

  3. 双向链表的内部结构:

    在这里插入图片描述

  4. LinkedList集合没有初始化容量,最初这个链表中没有任何元素,first和last引用都是null。

Vector:

  1. 底层也是一个数组

  2. 初始化容量:10

  3. 怎么扩容的:扩容之后是原容量的2倍。

  4. Vector中所有的方法都是线程同步的,都带有synchronized关键字,是线程安全的。效率比较低,使用的较少。

  5. 怎么将一个线程不安全的ArrayList集合转化成线程安全的:使用集合工具类:java.util.Collections

  6. 如:

    public class VectorTest {
        public static void main(String[] args) {
            //创建一个Vector集合
            Vector v=new Vector();
    
            //添加元素
            // 默认容量10
            v.add(1);
            v.add(2);
            v.add(3);
            v.add(4);
            v.add(5);
            v.add(6);
            v.add(7);
            v.add(8);
            v.add(9);
            v.add(10);
    
            //满了之后扩容(扩容之后的容量是20)
            v.add(11);
    
            Iterator it=v.iterator();
            while (it.hasNext()){
                System.out.println(it.next());
            }
    
            //将一个线程不安全的ArrayList集合转化成线程安全的
            List myList=new ArrayList();//非线程安全的
            //变成线程 安全的
            Collections.synchronizedList(myList);
    
            //myList就是线程安全的了
            myList.add("111");
            myList.add("222");
            myList.add("333");
        }
    }
    
    

4. 泛型:

JDK5.0 推出的新特性:泛型

  1. 泛型这种语法机制,只在程序编译阶段起作用,只是给编译器参考的。

  2. 使用泛型的好处:

    • 集合中存储的元素类型统一了。
    • 从集合中取出的元素类型是泛型指定的类型,不需要进行大量的"向下转型"!
  3. 泛型的缺点:导致集合中存储的元素缺乏多样性!

  4. 大多数业务中,集合中元素的类型还是统一的,所以这种泛型特性被大家所认可。

    代码:

    public class GenericTest01 {
        public static void main(String[] args) {
         /*   //不使用泛型机制 分析程序存在的缺点
            List mylist=new ArrayList();
    
            //准备对象
            Cat c=new Cat();
            Bird b=new Bird();
    
            //将对象添加到集合当中
            mylist.add(c);
            mylist.add(b);
    
            //遍历集合 取Cat让它抓老鼠,取Bird让它飞
            Iterator it=mylist.iterator();
            while (it.hasNext()){
                Object obj=it.next();
                if(obj instanceof Cat){
                    ((Cat) obj).catchMouse();
                }
                if (obj instanceof Bird){
                    ((Bird) obj).fly();
                }
            } */
    
    
            //使用JDK5之后的泛型机制
            //使用泛型List<Animal>之后,表示List集合中只允许存储Animal类型的数据
            //用泛型来指定集合中存储的数据类型,集合中的数据类型更加统一了。
            List<Animal> myList =new ArrayList<Animal>();
    
            //指定List集合中只能存储Animal,若存储String 就会编译报错
            //myList.add("abc");
    
            Cat c=new Cat();
            Bird b=new Bird();
            myList.add(c);
            myList.add(b);
    
            //获取迭代器
            //表示迭代器迭代的是Animal类型
            Iterator<Animal> it=myList.iterator();
            while (it.hasNext()){
                //使用泛型之后每次迭代返回的数据都是Animal类型
                Animal a=it.next();
                //这里不需要进行强制类型转化了,直接调用
               // a.move();
                //调用子类型特有的方法还是需要向下转化的!
                if(a instanceof Cat){
                    ((Cat) a).catchMouse();
                }
                if (a instanceof Bird){
                    ((Bird) a).fly();
                }
            }
    
        }
    }
    
    
    class Animal{
        public void move(){
            System.out.println("动物在移动");
        }
    }
    
    class Cat extends Animal{
        public void catchMouse(){
            System.out.println("猫抓老鼠");
        }
    }
    
    
    class Bird extends Animal{
        public void fly(){
            System.out.println("鸟儿在飞翔");
        }
    
    }
    
    
  5. JDK8之后引入了:自动类型推断机制。(又称为钻石表达式)

    public class GenericTest02 {
        public static void main(String[] args) {
            //ArrayList<这里的类型会自动推断>(),前提是JDK8之后才允许
            //自动推断 ,钻石表达式
            List<Animal> mylist=new ArrayList<>();
            mylist.add(new Animal());
            mylist.add(new Cat());
            mylist.add(new Bird());
    
            //遍历
            Iterator<Animal> it=mylist.iterator();
            while (it.hasNext()){
                System.out.println(it.next());
                Animal a=it.next();
                a.move();
            }
    
            List<String>stringList=new ArrayList<>();
    
            //类型不匹配
           // stringList.add(new Cat());
    
            //类型不匹配
           // stringList.add(123);
    
            stringList.add("abc");
    
    
        }
    }
    
  6. 自定义泛型:

    1. 自定义泛型的时候 <> 尖括号中的是一个标识符,随便写
    2. java源代码经常出现的是:和
    *  E是Element单词首字母
    *  T是Type单词首字母
    

5. foreach:

  1. JDK5.0之后推出的新特性:叫做增强for循环,或者叫做foreach。

  2. 语法格式:

    for(元素类型 变量名:数组或集合){
        System.out.println(变量名);
    }
    
  3. 代码演示:

    public class ForEacchTest01 {
        public static void main(String[] args) {
            //int类型数组
            int[]arr={422,12,434,52442};
    
            //遍历数组(普通for循环):
            for(int i=0;i<arr.length;i++){
                System.out.println(arr[i]);
            }
    
            //foreach
            //语法格式
          /*  for(元素类型 变量名:数组或集合){
                System.out.println(变量名);
            }*/
            System.out.println("========================");
            //foreach有一个缺点:没有下标。在需要使用下标的循环中,不建议使用for循环
            for (int a:arr
                 ) {
                System.out.println(a);
            }
    
    
        }
    }
    
    
  4. 结合的三种遍历方式:

    public class ForEachTest02 {
        public static void main(String[] args) {
            List<String> strList=new ArrayList<>();
            strList.add("A");
            strList.add("B");
            strList.add("C");
            strList.add("D");
    
            //遍历:使用迭代器方式
            Iterator<String>it=strList.iterator();
            while (it.hasNext()){
                System.out.println(it.next());
            }
    
            System.out.println("===========================");
    
            //使用下标方式(只针对有下标的集合)
            for (int i=0;i<strList.size();i++){
                System.out.println(strList.get(i));
            }
    
            System.out.println("============================");
    
            //使用foreach
            for (String a:
                 strList) {
                System.out.println(a);
            }
        }
    }
    
    

6. Set:

1. HashSet:

  1. 存储时顺序和取出的顺序不同。

  2. 不可重复。

  3. 放到HashSet集合的元素实际上是放在HashMap集合Key部分。

  4. 初始化容量:16,初始化容量建议是2的倍数,扩容后是原容量的2倍。

  5. 代码演示:

    public class HashSetTest01 {
        public static void main(String[] args) {
            Set<String>strs=new HashSet<>();
    
            //添加元素:
            strs.add("c");
            strs.add("A");
            strs.add("C");
            strs.add("B");
            strs.add("A");
    
            //遍历
            for (String s:strs
                 ) {
                System.out.println(s);
            }
            //
            /*
            * A
            * B
            * C
            * */
    
        }
    }
    
    

2.TreeSet:

  1. TreeSet集合存储元素的特点:

    无序不可重复的,但是存储的元素可以自动按照大小顺序排序!称为可排序集合

  2. 无序:这里的无序指的是存进去的顺序和取出来的顺序不同。并且没有下标。

  3. TreeSet集合实际上是一个TreeMap,TreeMap底层是一个二叉树。

  4. 放到TreeSet集合中的元素,等同于放到TreeMap集合key部分。

  5. 代码演示:

    public class TreeSetTest01 {
        public static void main(String[] args) {
            //创建集合对象
            Set<String>strt=new TreeSet<>();
            strt.add("A");
            strt.add("B");
            strt.add("Z");
            strt.add("Y");
            strt.add("Z");
            strt.add("K");
            strt.add("M");
            for (String s:
                strt ) {
                System.out.println(s);
            }
            //遍历结果,从小到大自动排序
            /*
            * A
              B
              K
              M
              Y
              Z
            * */
    
        }
    }
    
  6. TreeSet对自定义类型的排序:

    1. 需要指定自定义类型的排序规则

    2. 第一种方式:

      • 自定义的类实现:java.lang.Comparable接口。

      • 在compareTo()方法中编写比较逻辑,比较规则,或按照什么进行比较。

    3. 代码如下:

      /*
      * 对自定义的类型来说,TreeSet可以排序嘛?
      *    以下程序中对于Person类型来说,无法排序。因为没有指定Person对象之间的比较规则。
      * */
      public class TreeSetTest03 {
          public static void main(String[] args) {
              Person p1=new Person(10);
              Person p2=new Person(20);
              Person p3=new Person(9);
              Person p4=new Person(25);
      
              TreeSet<Person> ts=new TreeSet<>();
              ts.add(p1);
              ts.add(p2);
              ts.add(p3);
              ts.add(p4);
      
              for (Person p:
                 ts  ) {
                  System.out.println(p);
              }
      
          }
      }
      
      
      //放在TreeSet集合中的元素,需要实现java.lang.Comparable接口
      //并且重写compareTo方法。
      class Person implements Comparable<Person>{
          int age;
          public Person(int age){
              this.age=age;
          }
      
          public String toString(){
              return "Person[age="+age+"]";
          }
      
          //需要在这个方法中编写比较逻辑 或者比较规则,按照什么进行比较。
          //比较规则最终还是由程序员指定的,比如按照年龄升序,或按照年龄降序
          @Override
          public int compareTo(Person p) {  // p1.compareTo(p2);
              //this是p1
              //p是p2
              //p1和p2比较,就是this和p比较。
         /*     int age1=this.age;
              int age2=p.age;
              if (age1==age2){
                  return 0;
              }
              else if(age1>age2){
                  return 1;
              }
              else{
                  return -1;
              }*/
          //以上代码可以简化为:
         //return p.age-this.age; //降序
         return this.age-p.age;//升序
      
          }
      }
      
      
    4. 再如: 先按照年龄升序,如果年龄一样再按照姓名升序:

      /*
      * 先按照年龄升序,如果年龄一样再按照姓名升序
      * */
      public class TreeSetTest05 {
          public static void main(String[] args) {
              TreeSet<Vip>ts=new TreeSet<>();
              ts.add(new Vip("zhangsan",21));
              ts.add(new Vip("lisi",20));
              ts.add(new Vip("wangwu",20));
              ts.add(new Vip("zhaoliu",10));
      
              for (Vip v:
                   ts) {
                  System.out.println(v);
              }
          }
      }
      
      class Vip implements Comparable<Vip>{
          String name;
          int age;
      
          public Vip(String name, int age) {
              this.name = name;
              this.age = age;
          }
      
          @Override
          public String toString() {
              return "Vip{" +
                      "name='" + name + '\'' +
                      ", age=" + age +
                      '}';
          }
      
      
          /*
          * compareTo方法返回值很重要:
          * 返回0,表示相同,value会覆盖
          * 返回>0 会继续在右子树上找
          * 返回<0 会继续在左子树上找
          * */
          @Override
          public int compareTo(Vip v) {
          if (this.age==v.age){
              //如果年龄相同,按名字排序
              //姓名是String类型,可以直接调用compareTo进行比较
              return   this.name.compareTo(v.name);
          }
          //年龄不一样
          return this.age-v.age;
      }
      }
      
    5. TreeSet集合中元素可排序的第二种方式:使用比较器方式

      代码如下(创建一个比较器类):

      public class TreeSetTest06 {
          public static void main(String[] args) {
               //创建TreeSet集合的时候,需要使用这个比较器
              //这样是不行的,没有通过构造方法传递一个比较器过去
              //TreeSet<WuGui> wuGuis=new TreeSet<>();
              //给构造方法传递一个比较器
              TreeSet<WuGui>wuGuis=new TreeSet<>(new WuGuiComparator());
      
              wuGuis.add(new WuGui(1000));
              wuGuis.add(new WuGui(1100));
              wuGuis.add(new WuGui(900));
              wuGuis.add(new WuGui(800));
              wuGuis.add(new WuGui(1200));
      
              //遍历
              for (WuGui w:
                   wuGuis) {
                  System.out.println(w);
              }
      
          }
      }
      
      
      //乌龟
      class WuGui {
          int age;
      
          public WuGui(int age) {
              this.age = age;
          }
      
          @Override
          public String toString() {
              return "小乌龟[" +
                      "age=" + age +
                      ']';
          }
      
      }
      
      //单独在这里编写一个比较器
      // 比较器实现java.util.Comparator接口。
      // (Comparable是java.lang包下的,Comparator是java.util包下的)
      
      class WuGuiComparator implements Comparator<WuGui>{
      
          @Override
          public int compare(WuGui o1, WuGui o2) {
              //指定比较规则
              //按照年龄排序
              return o1.age-o2.age;
          }
      }
      
    6. 使用匿名内部类的方式:

      代码:

      public class TreeSetTest07 {
          public static void main(String[] args) {
               //创建TreeSet集合的时候,需要使用这个比较器
              //这样是不行的,没有通过构造方法传递一个比较器过去
              //TreeSet<WuGui> wuGuis=new TreeSet<>();
              //给构造方法传递一个比较器(这个类没有名字,直接new接口Comparator)
              TreeSet<WuGui>wuGuis=new TreeSet<>(new Comparator<WuGui>() {
                  @Override
                  public int compare(WuGui o1, WuGui o2) {
                      return o1.age-o2.age;
                  }
              });
      
              wuGuis.add(new WuGui(1000));
              wuGuis.add(new WuGui(1100));
              wuGuis.add(new WuGui(900));
              wuGuis.add(new WuGui(800));
              wuGuis.add(new WuGui(1200));
      
              //遍历
              for (WuGui w:
                   wuGuis) {
                  System.out.println(w);
              }
      
          }
      }
      
      
      //乌龟
      class WuGui {
          int age;
      
          public WuGui(int age) {
              this.age = age;
          }
      
          @Override
          public String toString() {
              return "小乌龟[" +
                      "age=" + age +
                      ']';
          }
      
      }
      
1. 自定义类型排序总结(代码见上):

总结:

  1. 放到TreeSet集合或TreeMap集合key部分的元素要想做到排序,包括两种方式:

    • 第一种:放在集合中的元素实现java.lang.Comparable接口,重写compareTo()方法。

    • 第二种:在构造TreeSet或者TreeMap集合的时候给它传一个比较器对象(new Comparator(){})。重写compare()方法。

  2. Comparable和Comparator怎么选择:

    当比较规则不会发生改变的时候,或者说当比较规则只有1个的时候,建议实现Comparable接口。

    如果比较规则有多个,并且需要多个比较规则直接频繁切换的时候,建议使用Comparator接口。

    Comparator接口的设计符合OCP原则(开闭原则)

2. 自平衡二叉树数据结构:
  1. TreeSet/TreeMap是自平衡二叉树,遵循左小右大原则存放,

  2. 遍历二叉树的时候有三种方式:(前中后说的是根的位置)

    • 前序遍历:根左右
    • 中序遍历:左根右:40 50 55 60 80 100 120 130 135 140 180 666
    • 后序遍历:左右根
  3. TreeSet集合/TreeMap集合采用的是:中序遍历方式。Iterator迭代器也采用中序遍历方式。1

  4. 如: 100 200 50 60 80 120 140 130 135 180 666 40 55的二叉树结构图:

5.在这里插入图片描述

7. Map:

1. 介绍:

  1. 注意:Map和Collection没有继承关系。
  2. Map集合以key和value的方式存储数据:键值对。
    • key和value都是引用数据类型。
    • key和value都是存储对象的内存地址。
    • key起到主导的地位,value是key的一个附属品。

2. 常用方法:

*   V put(K key, V value):向Map集合中添加键值对
*   V get(Object key): 通过key获取value
*   void clear():清空Map集合
*   boolean containsKey(Object key) 判断Map中是否包含某个key
*   boolean containsValue(Object value) 判断Map中是否包含某个value
*   boolean isEmpty(): 判断Map集合中元素个数是否为0
*   Set<K> keySet():  获取Map集合所有的Key  返回一个set集合
*   V remove(Object key): 通过key删除键值对
*   int size(): 获取Map集合中键值对的数量
*   Collection<V> values() 获取Map集合中所有的value,返回一个Collection
*   Set<Map.Entry<K,V>> entrySet()  将Map集合转化为Set集合
*   注意:Map集合通过entrySet()方法转化成的这个Set集合,Set集合中元素的类型是Map.Entry<K,V>
         Map.Entry和String一样,都是一种类型的名字,只不过:Map.Entry是静态内部类。
  1. 代码演示:

    public class MapTest01 {
        public static void main(String[] args) {
            //创建Map集合对象
            Map<Integer,String> m=new HashMap<>();
    
            //向Map集合中添加键值对
            m.put(1,"xiaoma"); //1在这里进行了自动装箱
            m.put(2,"xiaozhao");
            m.put(3,"xiaochen");
            m.put(4,"xiaohuang");
    
            //通过key获取value
            String value=m.get(1);
            System.out.println(value); //xiaoma
    
            //获取键值对的数量
            System.out.println("键值对的数量为: "+m.size()); //4
    
            //通过key删除key-value
            m.remove(4);
            System.out.println("键值对的数量为: "+m.size());
    
            //判断是否包含某个key
            //contains方法底层都是调用的equals进行比对的,所有自定义的类型需要重写equals方法。
            System.out.println(m.containsKey(4)); //false
    
            //判断是否包含某个value
            System.out.println(m.containsValue("xiaozhao")); //true
    
            //获取所有的value
           Collection<String> values= m.values();
            for (String a:
                values ) {
                System.out.println(a);
            }
    
    
            //清空集合
            m.clear();
            System.out.println("键值对的数量为: "+m.size()); //0
    
            //判断是否为空
            System.out.println(m.isEmpty()); //true
    
        }
    }
    
    

3. Map遍历(重点):

  1. 有两种方式:

    • 通过 **Set keySet()**获取所有的key ,再通过遍历key,来遍历value。

    • 通过**Set<Map.Entry<K,V>> entrySet()**方法,把Map集合直接全部转换成set集合,再去遍历,效率高。

      注意:Set集合中元素的类型是:Map.Entry


/*
二种方式:
*/
public class MapTest02 {
    public static void main(String[] args) {
        //第一种方式:获取所有的key 通过遍历key,来遍历value
        Map<Integer,String>m=new HashMap<>();
        //向Map集合中添加键值对
        m.put(1,"xiaoma");
        m.put(2,"xiaozhao");
        m.put(3,"xiaochen");
        m.put(4,"xiaohuang");

        //遍历Map集合
        //获取所有的key 所有的key是一个set集合
        Set<Integer> keys=m.keySet();
        //遍历key 通过key获取value
        //1:迭代器
        Iterator<Integer>it=keys.iterator();
        while (it.hasNext()){
            //取出其中一个key
            Integer key=it.next();
            //通过key获取value
            String value=m.get(key);
            System.out.println(key+"="+value);
        }

        System.out.println("============================");
        //2:foreach
        for (Integer key:keys
             ) {
            System.out.println(key+"="+m.get(key));

        }
        
         System.out.println("============================");
        //第二种方式:Set<Map.Entry<K,V>> entrySet()
        //以上这个方法是把Map集合直接全部转换成set集合
        //Set集合中元素的类型是:Map.Entry
        Set<Map.Entry<Integer,String>>set=m.entrySet();

        //迭代器:
        Iterator<Map.Entry<Integer,String>>it1=set.iterator();
        while (it1.hasNext()){
            Map.Entry<Integer,String>entry=it1.next();
            Integer key=entry.getKey();
            String  value=entry.getValue();
            System.out.println(key+"="+value);
        }

        System.out.println("============================");
        //foreach
        for (Map.Entry<Integer,String> a:
             set) {
            System.out.println(a.getKey()+"="+a.getValue());
        }
    }
}

4. HashMap:

1.介绍:
  1. HashMap集合底层是哈希表/散列表的数据结构。

  2. 哈希表是一个怎样的数据结构:

    • ​ 哈希表是一个数组和单向链表的结合体
    • ​ 数组:在查询方面效率很高,随机增删方面效率很低
    • ​ 单向链表:在随机增删方面效率较高,在查询方法效率很低。
    • ​ 哈希表将以上的两种数据结构融合在一起,充分发挥它们各自的优点。
  3. HashMap集合的key特点:

    • 无序:因为不一定挂到哪个单向链表上。

    • 不可重复:equals方法保证HashMap集合的key不可重复。如果key重复,value会被覆盖。

  4. 注意:放在HashMap集合key部分的元素其实就是放到HashSet集合中了。故HashSet集合中的元素也需要同时重写HashCode()和equals()方法。

2. 哈希表的数据结构:
  • 最主要掌握的是:

    map.put(k,v)
    v=map.get(k)
    以上这两个方法的实现原理,是必须掌握的  
    
  • 如图:

在这里插入图片描述

  • 哈希表HashMap使用不当时,无法发挥性能!

    假设将所有hashCode()方法返回值固定为某个值,那么会导致底层哈希表变成了纯单向链表。这种情况我们称为:散列表分布不均匀。

  • 那什么是散列表分布均匀?
    假设有100个元素,10个单链表,那么每个单链表上有10 个节点,这是最好的。是散列表分布均匀的。

  • 假设将所有的hashCode()方法返回值都设定为不一样的值,可以吗,有什么问题?

    不行,因为这样的话导致底层哈希表就成为一维数组了,没有链表的概念了。也是散列分布不均匀。

    散列分布均匀,需要你重写HashCode()时有一定的技巧。

  • HashMap集合的默认初始化容量是16默认加载因子是0.75。扩容之后的容量是原容量的二倍

    这个默认加载因子是指:当HashMap集合底层数组的容量达到**75%**的时候,数组开始扩容。

  • 重点:HashMap集合初始化容量必须是2的倍数,这也是官方推荐的。

    这是因为达到散列表均匀,为了提高HashMap集合的存取效率,所必须的。

  • 向HashMap集合中存,或者向HashMap集合中取,都是先调用hashCode方法,然后再调用equals方法。

3.重写hashCode和equals
  1. 如果一个类的equals方法重写了,那么hashCode()方法必须重写。并且equals方法返回如果是true ,那么hashCode()方法返回的值需要一样。

    因为equals方法返回true表示两个对象相同,在同一个单向链表上比较。那么对于同一个单向链表的节点来说,他们的哈希值转换后的数组下标都是相同的,所以hashCode()方法的返回值也应该相同。

  2. 使用idea工具同时生成。

  3. 对于哈希表数据结构来说:

    如果o1和o2的hash值相同,一定是放到同一个单链表上的。

    当然如果o1和o2的hash值不同,但由于哈希算法执行结束之后转换的数组下标可能相同,此时会发生“哈希碰撞”。

4.HashMap集合的改进:
  1. 在JDK8之后,如果哈希表单向链表中元素超过8个,单向链表这种数据结构会变成红黑树数据结构。当红黑树上的节点数量小于6时,会重新把红黑树变成单向链表数据结构。这种方式也是为了提高检索效率。
  2. HashMap允许key和value为null
5. HashMap和Hashtable的区别:
  1. Hashtable的key和value都是不能为null的
  2. HashMap的key和value都是可以为null的
  3. Hashtable方法都带有synchronized:线程安全的。线程安全有其他的方案,这个Hashtable对线程的处理导致效率较低,使用较少。
  4. Hashtable和HashMap一样,底层都是哈希表数据结构。
  5. Hashtable的初始化容量是11,默认加载因子是:0.75
  6. Hashtable的扩容:原容量 * 2+1

5.Properties属性类:

  1. 介绍:

    Properties是一个Map集合,继承Hashtable,Properties的key和value都是String类型, Properties称为属性类对象。是线程安全的。

  2. 需要掌握的两个方法:

     Object setProperty(String key, String value)  //添加数据
     String getProperty(String key) //通过key获取value
     
     
    
  3. 代码演示:

    public class PropertiesTest01 {
        public static void main(String[] args) {
            //创建一个Properties对象
            Properties pro=new Properties();
            //需要掌握Properties的两个方法,一个存一个取
            pro.setProperty("username","root");
            pro.setProperty("password","123");
    
            //通过key 获取value
            System.out.println(pro.getProperty("username")); //root
        }
    }
    

6.集合工具类Collections:

public class CollectionsTest01 {
    public static void main(String[] args) {
        //ArrayList集合不是线程安全的
        List<String>list=new ArrayList<>();

        //编程线程安全的
        Collections.synchronizedList(list);

        //排序
        list.add("adf");
        list.add("adz");
        list.add("adc");
        list.add("ade");
        Collections.sort(list);

        for (String s:
             list) {
            System.out.println(s);
        }

        //对Set集合如何排序
        Set<String> set=new HashSet<>();
        set.add("king");
        set.add("king3");
        set.add("king1");
        set.add("king2");

        //将Set集合转化成List集合
        List<String>list1=new ArrayList<>(set);
        Collections.sort(list1);
        for (String s1:
             list1) {
            System.out.println(s1);
        }


    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值