JAVA SE — Day 19

来源:互联网 发布:mac safari 1password 编辑:程序博客网 时间:2024/04/29 06:27

目 录

第1章 List接口
1.1 List接口介绍
1.2 List接口常用方法
1.2.1 List集合的遍历方式
1.2.2 Iterator的并发修改异常
1.3 List集合存储数据的结构
1.4 ArrayList集合
1.5 LinkedList集合
1.6 Vector集合
第2章 Set接口
2.1 Set接口介绍
2.2 HashSet集合介绍
2.2.1 哈希表存储数据的结构
2.2.2 哈希表的存储过程
2.3 HashSet存储JavaAPI中的类型元素
2.3.1 对象的哈希值
2.3.2 字符串对象的哈希值
2.4 HashSet存储自定义类型元素
2.5 LinkedHashSet介绍
第3章 判断集合元素唯一的原理
3.1 ArrayList的contains方法判断元素是否重复原理
3.2 HashSet的add/contains等方法判断元素是否重复原理
第4章 思考题
第5章 总结

第1章 List接口

  • 有序的 collection(也称为序列)。此接口的用户可以对列表中每个元素的插入位置进行精确地控制。用户可以根据元素的整数索引(在列表中的位置)访问元素,并搜索列表中的元素。与 set 不同,列表通常允许重复的元素。

1.1 List接口介绍

1)List接口的特点:

  • 它是一个元素存取有序的集合。存储的顺序与取出的顺序相同。
  • 它是一个带有索引的集合,通过索引就可以精确的操作集合中的元素(与数组的索引是一个道理)。
  • 集合中可以有重复的元素,通过元素的equals方法,来比较是否为重复的元素。

2) List接口的常用子类有:

  • ArrayList集合
  • LinkedList集合

1.2 List接口常用方法

这里写图片描述

  • List接口中的抽象方法,有一部分方法和他的父类接口Collection是一样的,另一部分是List特有的方法(与父类方法区分:带索引的方法都是List特有的)
  • List接口的特有方法(带索引的方法)

1)增加元素方法

  • add(Object e):向集合末尾处,添加指定的元素
  • add(int index, Object e) 向集合指定索引处,添加指定的元素,原有元素依次后移
public class ListDemo {   public static void main(String[] args) {       function();   }   /*    *  add(int index, E)    *  将元素插入到列表的指定索引上    *  带有索引的操作,都要做到防止越界问题    *  java.lang.IndexOutOfBoundsException:集合中的越界异常    *         ArrayIndexOutOfBoundsException:数组的越界异常    *         StringIndexOutOfBoundsException:字符串越界异常    */   public static void function(){     List<String> list = new ArrayList<String>();     list.add("abc1");     list.add("abc2");     list.add("abc3");     list.add("abc4");     System.out.println(list);//输出结果为:[abc1, abc2, abc3, abc4]     list.add(1, "itcast");     System.out.println(list);//输出结果为:[abc1, itcast, abc2, abc3, abc4]     list.add(6, "itcast");     //目前集合为,有五个元素,索引数1-4,所以可以添加到索引5上,而索引6则越界     System.out.println(list);     //输出结果为:java.lang.IndexOutOfBoundsException: Index: 6, Size: 5   }} 

2)删除元素删除

  • remove(Object e):将指定元素对象,从集合中删除,返回值为被删除的元素
  • remove(int index):将指定索引处的元素,从集合中删除,返回值为被删除的元素
public class ListDemo {   public static void main(String[] args) {       function_1();   }   /*    *  E remove(int index)    *  移除指定索引上的元素    *  返回被删除之前的元素    */   public static void function_1(){     List<Double> list = new ArrayList<Double>();     list.add(1.1);     list.add(1.2);     list.add(1.3);     list.add(1.4);     Double d = list.remove(0);//如果此处写的索引不存在发生越界异常,则以下代码不会执行     System.out.println(d);//输出结果为:1.1     System.out.println(list);//输出结果为:[1.2, 1.3, 1.4]   }}   

3)替换元素方法

  • set(int index, Object e):将指定索引处的元素,替换成指定的元素,返回值为替换前的元素
public class ListDemo {   public static void main(String[] args) {       function_2();   }   /*    *  E set(int index, E)    *  修改指定索引上的元素    *  返回被修改之前的元素    */   public static void function_2(){     List<Integer> list = new ArrayList<Integer>();     list.add(1);     list.add(2);     list.add(3);     list.add(4);     Integer i = list.set(0, 5);//如果此处写的索引不存在发生越界异常     System.out.println(i);//输出结果为:1     System.out.println(list);//输出结果为:[5, 2, 3, 4]   } }

4)查询元素方法

  • get(int index):获取指定索引处的元素,并返回该元素

1.2.1 List集合的遍历方式

1)迭代器遍历:
这里写图片描述
2)for循环,利用索引查找的方式
这里写图片描述
3)增强for循环,代码简单,day18学习过。

1.2.2 Iterator的并发修改异常

/* *  迭代器的并发修改异常 java.util.ConcurrentModificationException *  就是在遍历的过程中,使用了集合方法修改了集合的长度,不允许的 */public class ListDemo1 {     public static void main(String[] args) {            List<String> list = new ArrayList<String>();            list.add("abc1");            list.add("abc2");            list.add("abc3");            list.add("abc4");            //对集合使用迭代器进行获取,获取时候判断集合中是否存在 "abc3"对象            //如果有,添加一个元素 "ABC3"            Iterator<String> it = list.iterator();            while(it.hasNext()){              String s = it.next();              //对获取出的元素s,进行判断,是不是有"abc3"              if(s.equals("abc3")){//字符串的全等,不可以用==表示!                list.add("ABC3");              }              System.out.println(s);//输出结果为:abc1                                    //         abc2                                    //         abc3                                          //   Exception in thread "main" java.util.ConcurrentModificationException                                    //   遍历到abc3的时候发生修改元素长度的情况,则会报错                                    //   并发修改异常,不可以在迭代器运行的时候修改集合中的元素            }          }}
  • 在迭代过程中,使用了集合的方法对元素进行操作。导致迭代器并不知道集合中的变化,容易引发数据的不确定性。
  • 并发修改异常解决办法:在迭代时,不要使用集合的方法操作元素。
    那么想要在迭代时对元素操作怎么办?通过ListIterator迭代器操作元素是可以的,ListIterator的出现,解决了使用Iterator迭代过程中可能会发生的错误情况。

1.3 List集合存储数据的结构

  • List接口下有很多个集合,它们存储元素所采用的结构方式是不同的,这样就导致了这些集合有它们各自的特点,供给我们在不同的环境下进行使用。数据存储的常用结构有:堆栈、队列、数组、链表。
    1)堆栈,采用该结构的集合,对元素的存取有如下的特点:
  • 先进后出(即,存进去的元素,要在后它后面的元素依次取出后,才能取出该元素)。例如,子弹压进弹夹,先压进去的子弹在下面,后压进去的子弹在上面,当开枪时,先弹出上面的子弹,然后才能弹出下面的子弹。
  • 栈的入口、出口的都是栈的顶端位置
  • 压栈:就是存元素。即,把元素存储到栈的顶端位置,栈中已有元素依次向栈底方向移动一个位置。
  • 弹栈:就是取元素。即,把栈的顶端位置元素取出,栈中已有元素依次向栈顶方向移动一个位置。

2)队列,采用该结构的集合,对元素的存取有如下的特点:

  • 先进先出(即,存进去的元素,要在后它前面的元素依次取出后,才能取出该元素)。例如,安检。排成一列,每个人依次检查,只有前面的人全部检查完毕后,才能排到当前的人进行检查。
  • 队列的入口、出口各占一侧。例如,下图中的左侧为入口,右侧为出口。

3)数组,采用该结构的集合,对元素的存取有如下的特点:

  • 查找元素快:通过索引,可以快速访问指定位置的元素
  • 增删元素慢:
    • 指定索引位置增加元素:需要创建一个新数组,将指定新元素存储在指定索引位置,再把原数组元素根据索引,复制到新数组对应索引的位置。如下图
    • 指定索引位置删除元素:需要创建一个新数组,把原数组元素根据索引,复制到新数组对应索引的位置,原数组中指定索引位置元素不复制到新数组中。如下图

4)链表,采用该结构的集合,对元素的存取有如下的特点:

  • 多个节点之间,通过地址进行连接。例如,多个人手拉手,每个人使用自己的右手拉住下个人的左手,依次类推,这样多个人就连在一起了。
  • 查找元素慢:想查找某个元素,需要通过连接的节点,依次向后查找指定元素
  • 增删元素快:
  • 增加元素:操作如左图,只需要修改连接下个元素的地址即可。
  • 删除元素:操作如右图,只需要修改连接下个元素的地址即可。

1.4 ArrayList集合

这里写图片描述

  • ArrayList集合数据存储的结构是数组结构。元素增删慢,查找快,由于日常开发中使用最多的功能为查询数据、遍历数据,所以ArrayList是最常用的集合。
  • 许多程序员开发时非常随意地使用ArrayList完成任何需求,并不严谨,这种用法是不提倡的。

1.5 LinkedList集合

这里写图片描述

  • LinkedList集合数据存储的结构是链表结构。方便元素添加、删除的集合。实际开发中对一个集合元素的添加与删除经常涉及到首尾操作,而LinkedList提供了大量首尾操作的方法。

这里写图片描述

  • LinkedList是List的子类,List中的方法LinkedList都是可以使用,这里就不做详细介绍,我们只需要了解LinkedList的特有方法即可。在开发时,LinkedList集合也可以作为堆栈,队列的结构使用。
  • 代码示例如下:
import java.util.LinkedList;/* *  LinkedList 链表集合的特有功能 *    自身特点: 链表底层实现,查询慢,增删快 *   *  子类的特有功能,不能多态调用 *  所以必须写成: LinkedList<String> link = new LinkedList<String>(); *  不能写成: List<String> link = new LinkedList<String>(); 否则 下面调用方法时就会报错 */public class LinkedListDemo {  public static void main(String[] args) {      function_3();  }  /*   *  addFirst(E) 添加到链表的开头   *  addLast(E) 添加到链表的结尾   */     public static void function(){        LinkedList<String> link = new LinkedList<String>();        link.addLast("heima");        link.add("abc");        link.add("bcd");        link.addFirst("itcast");        System.out.println(link);//输出结果为:[itcast, heima, abc, bcd]     }     public static void function_1(){        LinkedList<String> link = new LinkedList<String>();        link.addLast("a");        link.addLast("b");        link.addLast("c");        link.addLast("d");        link.addFirst("1");        link.addFirst("2");        link.addFirst("3");        System.out.println(link);//输出结果为:[3, 2, 1, a, b, c, d]     }     /*      * E getFirst() 获取链表的开头      * E getLast() 获取链表的结尾      */     public static void function_2(){       LinkedList<String> link = new LinkedList<String>();       link.add("1");       link.add("2");       link.add("3");       link.add("4");       //如果加上link.clear();再执行就会报错,因为已经清空了       //为了防止上述情况,获取时进行判断,       if(link.size()!=0){           String first = link.getFirst();           String last = link.getLast();           System.out.println(first);//输出结果为:1           System.out.println(last);//输出结果为:4         }       //下列代码效果相同       if(!link.isEmpty()){         String first = link.getFirst();         String last = link.getLast();         System.out.println(first);//输出结果为:1         System.out.println(last);//输出结果为:4       }     }        /*        *  E removeFirst() 移除并返回链表的开头        *  E removeLast() 移除并返回链表的结尾        */        public static void function_3(){          LinkedList<String> link = new LinkedList<String>();          link.add("1");          link.add("2");          link.add("3");          link.add("4");          String first = link.removeFirst();          String last = link.removeLast();          System.out.println(first);//输出结果为:1          System.out.println(last);//输出结果为:4          System.out.println(link);//输出结果为:[2, 3]        }}

1.6 Vector集合

这里写图片描述

  • Vector集合数据存储的结构是数组结构,为JDK中最早提供的集合。Vector中提供了一个独特的取出方式,就是枚举Enumeration,它其实就是早期的迭代器。此接口Enumeration的功能与 Iterator 接口的功能是类似的。
  • Vector集合已被ArrayList替代。枚举Enumeration已被迭代器Iterator替代。

第2章 Set接口

2.1 Set接口介绍

  • Set接口的特点
    • 它是个不包含重复元素的集合。
    • Set集合取出元素的方式可以采用:迭代器、增强for。
    • Set集合有多个子类,这里我们介绍其中的HashSet、LinkedHashSet这两个集合。
import java.util.HashSet;import java.util.Iterator;import java.util.Set;/* *  Set接口,特点不重复元素,没索引 *   *  Set接口的实现类,HashSet (哈希表) *  特点: 无序集合,存储和取出的顺序不同,没有索引,不存储重复元素 *  代码的编写上,和ArrayList完全一致 */public class HashSetDemo {  public static void main(String[] args) {    Set<String> set = new HashSet<String>();    set.add("cn");    set.add("heima");    set.add("java");    set.add("java");    set.add("itcast");    Iterator<String> it = set.iterator();    while(it.hasNext()){      System.out.println(it.next());//输出结果为:java  不允许存储重复元素,所以只有一个                                    //         itcast                                    //         heima                                    //         cn    }    System.out.println("==============");    for(String s : set){      System.out.println(s);//输出结果为:java  不允许存储重复元素,所以只有一个                            //         itcast                            //         heima                            //         cn    }  }}

2.2 HashSet集合介绍

查阅HashSet集合的API介绍:此类实现Set接口,由哈希表支持(实际上是一个 HashMap集合)。HashSet集合不能保证的迭代顺序与元素存储顺序相同。
HashSet集合,采用哈希表结构存储数据,保证元素唯一性的方式依赖于:hashCode()与equals()方法。

2.2.1 哈希表存储数据的结构

  • 加载因子:表中填入的记录数/哈希表的长度
    例如:
    加载因子是0.75 代表:
    数组中的16个位置,其中存入16*0.75=12个元素

  • 如果在存入第十三个(>12)元素,导致存储链子过长,会降低哈希表的性能,那么此时会扩充哈希表(在哈希),底层会开辟一个长度为原长度2倍的数组,把老元素拷贝到新数组中,再把新元素添加数组中

  • 当存入元素数量>哈希表长度*加载因子,就要扩容,因此加载因子决定扩容时机

这里写图片描述

2.2.2 哈希表的存储过程

  • 哈希表的存储过程中,不允许有重复数据,具体的执行依据是哈希值hashCode()与equals()方法。
import java.util.HashSet;/* * HashSet集合的自身特点 *    底层数据结构,哈希表 *    存储和取出都比较快 *    线程不安全,运行速度快  */public class HashSetDemo1 {     public static void main(String[] args) {            HashSet<String> set = new HashSet<String>();            set.add(new String("abc"));            set.add(new String("abc"));            set.add(new String("bbc"));            set.add(new String("bbc"));            System.out.println(set); //输出结果为:[bbc, abc]        }}
  • 存储过程的图解:

这里写图片描述

  • 存取原理: 每存入一个新的元素都要走以下三步:

    1.首先调用本类的hashCode()方法算出哈希值

    2.在容器中找是否与新元素哈希值相同的老元素,
    如果没有直接存入
    如果有转到第三步

    3.新元素会与该索引位置下的老元素利用equals方法一一对比:
    一旦新元素.equals(老元素)返回true,停止对比,说明重复,不再存入(注意String类重写过equals()方法)
    如果与该索引位置下的老元素都通过equals方法对比返回false,说明没有重复,存入

2.3 HashSet存储JavaAPI中的类型元素

2.3.1 对象的哈希值

① hashCode()方法

/* *对象的哈希值,普通的十进制整数 *父类Object,方法public int hashCode()计算结果是int整数  *  */public class HashDemo {    public static void main(String[] args) {        Person p = new Person();        int i = p.hashCode();        System.out.println(i);//输出结果为不可预知的整数    }}
public class Person {}

② 重写 hashCode()方法,自定义哈希值

/* *对象的哈希值,普通的十进制整数 *父类Object,方法public int hashCode()计算结果是int整数  *  */public class HashDemo {    public static void main(String[] args) {        Person p = new Person();        int i = p.hashCode();        System.out.println(i);//输出结果为:1    }}
public class Person {   /*    * 没有重写父类hashCode方法,每次运行结果都是不同的整数    * 重写父类hashCode方法,哈希值就是自定义的数值    * 哈希值是存储到HashSet集合的依据    */    public int hashCode() {       return 1;   }}

2.3.2 字符串对象的哈希值

  • 给HashSet中存储JavaAPI中提供的类型元素时,不需要重写元素的hashCode和equals方法,因为这两个方法,在JavaAPI的每个类中已经重写完毕,如String类、Integer类等。

1)代码示例如下:

/* *String类的哈希值 *  */public class HashDemo {    public static void main(String[] args) {         String s1 = new String("abc");         String s2 = new String("abc");         System.out.println(s1.hashCode());//输出结果为:96354         System.out.println(s2.hashCode());//输出结果为:96354    }}

2)String类哈希值计算方式的图解:

这里写图片描述

2.4 HashSet存储自定义类型元素

  • 给HashSet中存放自定义类型元素时,需要重写对象中的hashCode和equals方法,建立自己的比较方式,才能保证HashSet集合中的对象唯一。
  • 代码示例如下:

1) 原版

public class Person {      private String name;      private int age;      public String getName() {        return name;      }      public void setName(String name) {        this.name = name;      }      public int getAge() {        return age;      }      public void setAge(int age) {        this.age = age;      }      public Person(String name, int age) {        super();        this.name = name;        this.age = age;      }      public Person(){}      public String toString(){        return name+".."+age;      }   }
import java.util.HashSet;public class HashSetDemo1 {     public static void main(String[] args) {            HashSet<Person> setPerson = new HashSet<Person>();            setPerson.add(new Person("a",11));            setPerson.add(new Person("b",10));            setPerson.add(new Person("b",10));            setPerson.add(new Person("c",25));            setPerson.add(new Person("d",19));            setPerson.add(new Person("e",17));            System.out.println(setPerson);            //输出结果为:[a..11, c..25, d..19, b..10, b..10, e..17]            //new出来的两个Person("b",10)不同,所以不能判断为重复     }}

2) 对比组:自定义对象重写hashCode和equals方法

public class Person {      private String name;      private int age;      public int hashCode() {          return  name.hashCode()+age*55;//name.hashCode()+age;的返回值为十进制数      }      /*       *尽可能让不同的属性值产生不同的哈希值,这样就不用再调用equals方法去比较属性       *所以age →→ age*55       */      //方法equals重写父类,保证和父类相同      //public boolean equals(Object obj){}      public boolean equals(Object obj) {           if(this==obj)                return true;           if(obj==null)                return false;              if(obj instanceof Person) {               Person p = (Person)obj;                 return name.equals(p.name) && age==p.age;           }           return false;       }      public String getName() {        return name;      }      public void setName(String name) {        this.name = name;      }      public int getAge() {        return age;      }      public void setAge(int age) {        this.age = age;      }      public Person(String name, int age) {        super();        this.name = name;        this.age = age;      }      public Person(){}      public String toString(){        return name+".."+age;      }   }
import java.util.HashSet;public class HashSetDemo1 {     public static void main(String[] args) {           //将Person对象中的姓名,年龄,相同数据,看作同一个对象            //判断对象是否重复,依赖对象自己的方法 hashCode,equals            HashSet<Person> setPerson = new HashSet<Person>();            setPerson.add(new Person("a",11));            setPerson.add(new Person("b",10));            setPerson.add(new Person("b",10));            setPerson.add(new Person("c",25));            setPerson.add(new Person("d",19));            setPerson.add(new Person("e",17));            System.out.println(setPerson);  //输出结果为:[e..17, d..19, a..11, b..10, c..25]     }}        //经过重写hashCode方法,("a",11)、("b",10)、("b",10)的哈希值是相同的,但("a",11)、("b",10)的equals方法返回值不同,所以判定不是重复元素//每个对象的地址值都不同,所以需要重写equals方法比较具体属性。("b",10)、("b",10)调用Obejct类的hashCode方法返回相同的哈希值,且equals返回true,判断为重复元素,所以结果只存一次 

1) 图解

这里写图片描述

2.5 LinkedHashSet介绍

这里写图片描述

  • 我们知道HashSet保证元素唯一,可是元素存放进去是没有顺序的,那么我们要保证有序,怎么办呢?
  • 在HashSet下面有一个子类LinkedHashSet,它是链表和哈希表组合的一个数据存储结构,LinkedHashSet集合保证元素的存入和取出的顺序一致。
  • 代码示例:
import java.util.LinkedHashSet;/* *   LinkedHashSet 基于链表的哈希表实现 *   继承自HashSet  实现Set接口 *    *   LinkedHashSet自身特性:具有顺序,存储和取出的顺序相同的 *   线程不安全的集合,运行速度块 */public class LinkedHashSetDemo {  public static void main(String[] args) {    LinkedHashSet<Integer> link = new LinkedHashSet<Integer>();    link.add(123);    link.add(44);    link.add(33);    link.add(33);    link.add(66);    link.add(11);    System.out.println(link);//输出结果为:[123, 44, 33, 66, 11]  即不重复且有序  }}

第3章 第3章 判断集合元素唯一的原理

3.1 ArrayList的contains方法判断元素是否重复原理

这里写图片描述

  • ArrayList的contains方法会使用调用方法时,传入的元素的equals方法依次与集合中的旧元素所比较,从而根据返回的布尔值判断是否有重复元素。此时,当ArrayList存放自定义类型时,由于自定义类型在未重写equals方法前,判断是否重复的依据是地址值,所以如果想根据内容判断是否为重复元素,需要重写元素的equals方法。

3.2 HashSet的add/contains等方法判断元素是否重复原理

这里写图片描述

  • Set集合不能存放重复元素,其添加方法在添加时会判断是否有重复元素,有重复不添加,没重复则添加。
  • HashSet集合由于是无序的,其判断唯一的依据是元素类型的hashCode与equals方法的返回结果。规则如下:
    先判断新元素与集合内已经有的旧元素的HashCode值

    • 如果不同,说明是不同元素,添加到集合。
    • 如果相同,再判断equals比较结果。返回true则相同元素;返回false则不同元素,添加到集合。
  • 所以,使用HashSet存储自定义类型,如果没有重写该类的hashCode与equals方法,则判断重复时,使用的是地址值,如果想通过内容比较元素是否相同,需要重写该元素类的hashcode与equals方法。

第4章 思考题

1) 问题:

① 如果两个对象的哈希值相同 p1.hashCode()==p2.hashCode()
两个对象的equals一定返回true吗 p1.equals(p2) 一定是true吗

正确答案:不一定

② 如果两个对象的equals方法返回true,p1.equals(p2)==true
两个对象的哈希值一定相同吗

正确答案: 一定

2)分析:

这里写图片描述

这里写图片描述

实际上可以通过编译,令hashCode方法return1,即固定数字,也可以令equals方法return true,或者定义静态变量static int a;hashCode中return a++;都可以推翻上述答案,虽然编译运行没有问题,但是不符合hashCode常规协定。

第5 章 总结

1)List与Set集合的区别?

  • List:
    它是一个有序的集合(元素存与取的顺序相同)
    它可以存储重复的元素
  • Set:
    它是一个无序的集合(元素存与取的顺序可能不同)
    它不能存储重复的元素

2)List集合中的特有方法

  • void add(int index, Object element) 将指定的元素,添加到该集合中的指定位置上
  • Object get(int index)返回集合中指定位置的元素。
  • Object remove(int index) 移除列表中指定位置的元素, 返回的是被移除的元素
  • Object set(int index, Object element)用指定元素替换集合中指定位置的元素,返回值的更新前的元素

3)ArrayList:

  • 底层数据结构是数组,查询快,增删慢

4)LinkedList:

  • 底层数据结构是链表,查询慢,增删快

5)HashSet:

  • 元素唯一,不能重复
  • 底层结构是哈希表结构
  • 元素的存与取的顺序不能保证一致
  • 如何保证元素的唯一的?
    • 重写hashCode() 与 equals()方法

6)LinkedHashSet:

  • 元素唯一不能重复
  • 底层结构是 哈希表结构 + 链表结构
  • 元素的存与取的顺序一致
原创粉丝点击