目录
第一章 List接口
1.1 List接口介绍
1.2 List接口中常用的方法
1.2.1 add(Object e)
1.2.2 remove(Object e)
1.2.3 set(int index, Object e)
1.2.4 get(int index)
1.2.5 List集合迭代的三种方式
1.2.6 Iterator的并发修改异常
1.3 List集合存储数据的结构
1.3.1 堆栈
1.3.2 队列
1.3.3 数组
1.3.4 链表
1.4 ArrayList集合
1.5 LinkedList集合
1.6 Vector集合(了解即可,已被ArrayList取代)
第二章 Set接口
2.1 Set接口介绍
2.2 HashSet集合介绍
2.3 HashSet集合存储数据的结构(哈希表)
2.4 HashSet存储JavaAPI中的类型元素
2.5 HashSet存储自定义类型元素
2.6 LinkedHashSet介绍
第三章 判断集合元素唯一的原理
3.1 ArrayList的contains方法判断元素是否重复原理
3.2 HashSet的add/contains等方法判断元素是否重复原理
第四章 总结
List接口:
- 它是一个元素存取有序的集合。例如,存元素的顺序是11、22、33。那么集合中,元素的存储就是按照11、22、33的顺序完成的)。(存取的位置一样)
- 它是一个带有索引的集合,通过索引就可以精确的操作集合中的元素(与数组的索引是一个道理)。
- 集合中可以有重复的元素,通过元素的equals方法,来比较是否为重复的元素。
List接口的常用子类有:
- ArrayList集合
- LinkedList集合
增加元素方法
add(Object e):向集合末尾处,添加指定的元素
add(int index, Object e):向集合指定索引处,添加指定的元素,原有元素依次后移
/*
* add(int index, E)
* 将元素插入到列表的指定索引上
* 带有索引的操作,防止越界问题
* java.lang.IndexOutOfBoundsException
* ArrayIndexOutOfBoundsException
* StringIndexOutOfBoundsException
*/
public static void function(){
List list = new ArrayList();
list.add("abc1");
list.add("abc2");
list.add("abc3");
list.add("abc4");
System.out.println(list);
list.add(1, "itcast");
System.out.println(list);
}
删除元素
remove(Object e):将指定元素对象,从集合中删除,返回值为被删除的元素
remove(int index):将指定索引处的元素,从集合中删除,返回值为被删除的元素
public static void function_1(){
List list = new ArrayList();
list.add(1.1);
list.add(1.2);
list.add(1.3);
list.add(1.4);
double d = list.remove(2);
System.out.println(d); //1.3
System.out.println(list); //[1.1, 1.2, 1.4]
替换元素方法
set(int index, Object e):将指定索引处的元素,替换成指定的元素,返回值为替换前的元素
package day19.demo1;
import java.util.ArrayList;
import java.util.List;
/*
* List接口派系, 继承Collection接口
* 下面有很多实现类
* List接口特点: 有序,索引,可以重复元素
* 实现类, ArrayList, LinkedList
*
* List接口中的抽象方法,有一部分方法和他的父接口Collection是一样
* List接口的自己特有的方法, 带有索引的功能
*/
public class ListDemo {
public static void main(String[] args) {
// TODO Auto-generated method stub
function_2();
}
/*
* E set(int index, E)
* 修改指定索引上的元素
* 返回被修改之前的元素
*/
public static void function_2(){
List list = new ArrayList();
list.add(1);
list.add(2);
list.add(3);
list.add(4);
Integer i = list.set(0, 5);
System.out.println(i);
System.out.println(list);
}
}
查询元素方法
get(int index):获取指定索引处的元素,并返回该元素
List集合迭代的三种方式:
- 迭代器(集合共有的迭代方法)
- for循环(因为List集合有索引)
- 增强for循环
//1.迭代器
Iterator it = list.iterator();
while (it.hasNext()) {
String str = it.next();
System.out.println(str);
}
//2.因为List集合有索引,可以使用for循环
for (int i = 0; i < list.size(); i++) {
String str = list.get(i);
System.out.println(str);
}
//3.增强for
for( 数据类型 变量名 : 数组或者集合 ){
System.out.println(变量);
}
1. 迭代器在使用的时候,不能改集合的长度,set方法可以,remove,add均不可。因为迭代器是将值赋值给变量,然后打印,并且next()只能后移,如果突然加入元素,无法前移,因此有安全隐患,迭代器使用时不允许修改集合长度。否则会抛出java.util.ConcurrentModificationException并发修改异常。
2. 引用类型判断等于不能使用“==”,会出现问题。普通类似可以使用“==”做判断。
例如s==“abc3”(String类型为引用类型,判断会发生错误),只能使用s.equals("abc3")判断。
package day19.demo1;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/*
* 迭代器的并发修改异常 java.util.ConcurrentModificationException
* 就是在遍历的过程中,使用了集合方法修改了集合的长度,不允许的
*/
public class ListDemo1 {
public static void main(String[] args) {
List list = new ArrayList();
list.add("abc1");
list.add("abc2");
list.add("abc3");
list.add("abc4");
//对集合使用迭代器进行获取,获取时候判断集合中是否存在 "abc3"对象
//如果有,添加一个元素 "ABC3"
Iterator it = list.iterator();
while(it.hasNext()){
String s = it.next();
//对获取出的元素s,进行判断,是不是有"abc3"
if(s.equals("abc3")){
list.add("ABC3");
}
System.out.println(s);//java.util.ConcurrentModificationException
}
}
}
List接口下有很多个集合,它们存储元素所采用的结构方式是不同的,这样就导致了这些集合有它们各自的特点,供给我们在不同的环境下进行使用。数据存储的常用结构有:堆栈、队列、数组、链表。我们分别来了解一下:
堆栈,采用该结构的集合,对元素的存取有如下的特点:
- 先进后出(即,存进去的元素,要在后它后面的元素依次取出后,才能取出该元素)。例如,子弹压进弹夹,先压进去的子弹在下面,后压进去的子弹在上面,当开枪时,先弹出上面的子弹,然后才能弹出下面的子弹。
- 栈的入口、出口的都是栈的顶端位置
- 压栈:就是存元素。即,把元素存储到栈的顶端位置,栈中已有元素依次向栈底方向移动一个位置。
- 弹栈:就是取元素。即,把栈的顶端位置元素取出,栈中已有元素依次向栈顶方向移动一个位置。
队列,采用该结构的集合,对元素的存取有如下的特点:
- 先进先出(即,存进去的元素,要在后它前面的元素依次取出后,才能取出该元素)。例如,安检。排成一列,每个人依次检查,只有前面的人全部检查完毕后,才能排到当前的人进行检查。
- 队列的入口、出口各占一侧。例如,下图中的左侧为入口,右侧为出口。
数组,采用该结构的集合,对元素的存取有如下的特点:
查找元素快:通过索引,可以快速访问指定位置的元素
增删元素慢:
- 指定索引位置增加元素:需要创建一个新数组,将指定新元素存储在指定索引位置,再把原数组元素根据索引,复制到新数组对应索引的位置。如下图
- 指定索引位置删除元素:需要创建一个新数组,把原数组元素根据索引,复制到新数组对应索引的位置,原数组中指定索引位置元素不复制到新数组中。如下图
链表,采用该结构的集合,对元素的存取有如下的特点:
1.多个节点之间,通过地址进行连接。例如,多个人手拉手,每个人使用自己的右手拉住下个人的左手,依次类推,这样多个人就连在一起了。
2.查找元素慢:想查找某个元素,需要通过连接的节点,依次向后查找指定元素
3.增删元素快:
增加元素:操作如左图,只需要修改连接下个元素的地址即可。
删除元素:操作如右图,只需要修改连接下个元素的地址即可。
ArrayList集合数据存储的结构是数组。元素增删慢,查找快,由于日常开发中使用最多的功能为查询数据、遍历数据,所以ArrayList是最常用的集合。此实现不是同步的(即线程不安全的,但运行速度快)
许多程序员开发时非常随意地使用ArrayList完成任何需求,并不严谨(增删慢),这种用法是不提倡的。
LinkedList集合数据存储的结构是链表。方便元素添加、删除的集合。实际开发中对一个集合元素的添加与删除经常涉及到首尾操作,而LinkedList提供了大量首尾操作的方法。
LinkedList是List的子类,List中的方法LinkedList都是可以使用,这里就不做详细介绍,我们只需要了解LinkedList的特有方法即可。在开发时,LinkedList集合也可以作为堆栈,队列的结构使用。此实现不是同步的(即线程不安全的,但运行速度快)
方法演示:
package day19.demo1;
import java.util.LinkedList;
/*
* LinkedList 链表集合的特有功能
* 自身特点: 链表底层实现,查询慢,增删快
*
* 子类的特有功能,不能多态调用
*/
public class LinkedListDemo {
public static void main(String[] args) {
function();
}
/*
* E removeFirst() 移除并返回链表的开头
* E removeLast() 移除并返回链表的结尾
*/
public static void function_3(){
LinkedList link = new LinkedList();
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]
}
/*
* E getFirst() 获取链表的开头
* E getLast() 获取链表的结尾
*/
public static void function_2(){
LinkedList link = new LinkedList();
link.add("1");
link.add("2");
link.add("3");
link.add("4");
if(!link.isEmpty()){
String first = link.getFirst();
String last = link.getLast();
System.out.println(first);//1
System.out.println(last);//4
System.out.println(link);//[1, 2, 3, 4]
}
}
public static void function_1(){
LinkedList link = new LinkedList();
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]
}
/*
* addFirst(E) 添加到链表的开头
* addLast(E) 添加到链表的结尾
*/
public static void function(){
LinkedList link = new LinkedList();
link.addLast("heima");
link.add("abc");
link.add("bcd");
link.addFirst("itcast");
System.out.println(link);//[itcast, heima, abc, bcd]
}
}
Vector集合数据存储的结构是数组,为JDK中最早提供的集合,其中提供了一个独特的取出方式,就是枚举Enumeration,它其实就是早期的迭代器。此接口Enumeration的功能与 Iterator 接口的功能是类似的。从Java1.2Vector改进为List接口。
由于Vector是同步的(即线程安全,但运行速度慢),现在Vector集合已被ArrayList取代,因为都为数组,因此用法相同。枚举Enumeration已被迭代器Iterator替代。
学习Collection接口时,记得Collection中可以存放重复元素,也可以不存放重复元素,那么我们知道List中是可以存放重复元素的。那么不重复元素给哪里存放呢?那就是Set接口,它里面的集合,所存储的元素就是不重复的。
Set不存储重复元素。通过元素的equals方法,来判断是否为重复元素,Set是个不包含重复元素的集合。
set 集合取出元素的方式可以采用:迭代器、增强for。(List有索引,而set无)
Set集合有多个子类,这里我们介绍其中的HashSet、LinkedHashSet 这两个集合。
set的方法与其父类Collection大致相同。
此类实现Set接口,由哈希表支持(实际上是一个 HashMap集合,学会了set就相当于学会了map)。
HashSet集合不能保证的迭代顺序与存储顺序相同(无序性)
此类允许使用null元素。
HashSet集合,采用哈希表结构存储数据,保证元素唯一性的方式依赖于:hashCode()与equals()方法。
什么是哈希表呢?
哈希表底层使用的也是数组机制,数组中也存放对象,而这些对象往数组中存放时的位置比较特殊,当需要把这些对象给数组中存放时,那么会根据这些对象的特有数据结合相应的算法,计算出这个对象在数组中的位置,然后把这个对象存放在数组中。而这样的数组就称为哈希数组,即就是哈希表。
当向哈希表中存放元素时,需要根据元素的特有数据结合相应的算法,这个算法其实就是Object类中的hashCode方法。由于任何对象都是Object类的子类,所以任何对象有拥有这个方法。即就是在给哈希表中存放对象时,会调用对象的hashCode方法,算出对象在表中的存放位置,这里需要注意,如果两个对象hashCode方法算出结果一样,这样现象称为哈希冲突,这时会调用对象的equals方法,比较这两个对象是不是同一个对象,如果equals方法返回的是true,那么就不会把第二个对象存放在哈希表中,如果返回的是false,就会把这个值存放在哈希表中。
总结:保证HashSet集合元素的唯一,其实就是根据对象的hashCode和equals方法来决定的。如果我们往集合中存放自定义的对象,那么保证其唯一,就必须复写hashCode和equals方法建立属于当前对象的比较方式。
package day19.demo1;
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 set = new HashSet();
set.add("cn");
set.add("heima");
set.add("java");
set.add("java");
set.add("itcast");
Iterator it = set.iterator();
while(it.hasNext()) {
System.out.println(it.next());
}
System.out.println("==============");
for(String s:set) {
System.out.println(s);
}
}
}
运行结果:
哈希表概念:
- 桶:一个数组下的链表长度。
- 加载因子:默认为0.75,当哈希表元素个数>(数组长度*加载因子)时,扩容数组。
- 数组长度:默认为16,后面可以扩容。
给HashSet中存储JavaAPI中提供的类型元素时,不需要重写元素的hashCode和equals方法,因为这两个方法,在JavaAPI的每个类中已经重写完毕,如String类、Integer类等。
填充哈希表的过程:
1.先计算对象的哈希值,new String("abc").hashCode()
2.集合去容器找此位置是否已有对象。
-若此位置无元素,则放入哈希表。
-若此位置有元素,则看是否为重复元素,p1.equals(p2) 。
给HashSet中存放自定义类型元素时,需要重写对象中的hashCode和equals方法,建立自己的比较方式,才能保证HashSet集合中的对象唯一。
hashCode和equals方法也可以自动生成。
package day19.demo1;
public class Person {
private String name;
private int age;
/*
* 没有做重写父类,每次运行结果都是不同整数
* 如果子类重写父类的方法,哈希值,自定义的
* 存储到HashSet集合的依据
*/
public int hashCode(){
return name.hashCode()+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;
}
}
package day19.demo1;
import java.util.HashSet;
/*
* HashSet集合的自身特点:
* 底层数据结构,哈希表
* 存储,取出都比较快
* 线程不安全,运行速度快
*/
public class HashSetDemo1 {
public static void main(String[] args) {
/*HashSet set = new HashSet();
set.add(new String("abc"));
set.add(new String("abc"));
set.add(new String("bbc"));
set.add(new String("bbc"));
System.out.println(set);*/
//将Person对象中的姓名,年龄,相同数据,看作同一个对象
//判断对象是否重复,依赖对象自己的方法 hashCode,equals
HashSet setPerson = new HashSet();
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);
//不重写时输出,[b..10, e..17, d..19, a..11, b..10, c..25]
//重写Person的hashCode,equals后,输出[c..25, b..10, d..19, e..17, a..11]
}
}
我们知道HashSet保证元素唯一,可是元素存放进去是没有顺序的,那么我们要保证有序,怎么办呢?
在HashSet下面有一个子类LinkedHashSet,它是链表和哈希表组合的一个数据存储结构。
LinkedHashSet 自身特性,有序的(存储和取出的顺序相同的),是线程不安全的集合,运行速度快。
package day19.demo1;
import java.util.LinkedHashSet;
/*
* LinkedHashSet 基于链表的哈希表实现
* 继承自HashSet
*
* LinkedHashSet 自身特性,具有顺序,存储和取出的顺序相同的
* 线程不安全的集合,运行速度块
*/
public class LinkedHashSetDemo {
public static void main(String[] args) {
LinkedHashSet link = new LinkedHashSet();
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]
}
}
ArrayList的contains方法会使用调用方法时,传入的元素的equals方法依次与集合中的旧元素所比较,从而根据返回的布尔值判断是否有重复元素。此时,当ArrayList存放自定义类型时,由于自定义类型在未重写equals方法前,判断是否重复的依据是地址值,所以如果想根据内容判断是否为重复元素,需要重写元素的equals方法。
Set集合不能存放重复元素,其添加方法在添加时会判断是否有重复元素,有重复不添加,没重复则添加。
HashSet集合由于是无序的,其判断唯一的依据是元素类型的hashCode与equals方法的返回结果。规则如下:
先判断新元素与集合内已经有的旧元素的HashCode值
- 如果不同,说明是不同元素,添加到集合。
- 如果相同,再判断equals比较结果。返回true则相同元素;返回false则不同元素,添加到集合。
所以,使用HashSet存储自定义类型,如果没有重写该类的hashCode与equals方法,则判断重复时,使用的是地址值,如果想通过内容比较元素是否相同,需要重写该元素类的hashcode与equals方法。
List与Set集合的区别?
List:
它是一个有序的集合(元素存与取的顺序相同)
它可以存储重复的元素
Set:
它是一个无序的集合(元素存与取的顺序可能不同)
它不能存储重复的元素
List集合中的特有方法
void add(int index, Object element) 将指定的元素,添加到该集合中的指定位置上
Object get(int index)返回集合中指定位置的元素。
Object remove(int index) 移除列表中指定位置的元素, 返回的是被移除的元素
Object set(int index, Object element)用指定元素替换集合中指定位置的元素,返回值的更新前的元素
ArrayList:
底层数据结构是数组,查询快,增删慢
LinkedList:
底层数据结构是链表,查询慢,增删快
HashSet:
元素唯一,不能重复
底层结构是 哈希表结构
元素的存与取的顺序不能保证一致
LinkedHashSet:
元素唯一不能重复
底层结构是 哈希表结构 + 链表结构
元素的存与取的顺序一致
如何保证元素的唯一的?
重写hashCode() 与 equals()方法