Java学习笔记-集合

这篇博客详细介绍了Java集合框架,包括数组、链表的优缺点,链表与二叉树的关系,以及Collection和Map接口下的ArrayList、LinkedList、Vector、HashSet、HashMap、TreeMap等数据结构的特性与操作。同时,讨论了equals、hashCode方法与内存泄漏的相关问题。
摘要由CSDN通过智能技术生成

集合

类集概述

    对象数组有那些问题?普通的对象数组的最大问题在于数组中的元素个数是固定的,不能动态的扩充大小,所以最 早的时候可以通过链表实现一个动态对象数组。但是这样做毕竟太复杂了,所以在 Java 中为了方便用户操作各个数据结构, 所以引入了类集的概念,有时候就可以把类集称为 java 对数据结构的实现。 
    在整个类集中的,这个概念是从 JDK 1.2(Java 2)之后才正式引入的,最早也提供了很多的操作类,但是并没有完 整的提出类集的完整概念。 
    类集中最大的几个操作接口:Collection、Map、Iterator,这三个接口为以后要使用的最重点的接口。 
    所有的类集操作的接口或类都在 java.util 包中。

数组和链表的优缺点

数组是一种连续存储线性结构,元素类型相同,大小相同
    数组的优点:
        存取速度快
    数组的缺点:
        实现必须知道数组的长度
        插入删除元素很慢
        空间通常是有限的
        需要大块连续的内存块
        插入删除元素的效率很低
    
链表表示离散存储线性结构
    n个节点离散分配,彼此通过指针相连,每个节点只有一个前驱节点,每个节点只有一个后续节点,首节点没有前驱节点,尾节点没有后续节点
    链表的优点:
        空间没有限制
        插入删除元素很快
    链表缺点:
        存取速度很慢
    

链表与二叉树思路

链表和二叉树:基于对象的数据结构

链表

单向链表

单链表:
    单链表 [Linked List]:由各个内存结构通过一个 Next 指针链接在一起组成,每一个内存结构都存在后继内存结    构(链尾除外),内存结构由数据域和 Next 指针域组成。
解析:
    Data 数据 + Next 指针,组成一个单链表的内存结构 ;
    第一个内存结构称为 链头,最后一个内存结构称为 链尾;
    链尾的 Next 指针设置为 NULL [指向空];
    单链表的遍历方向单一(只能从链头一直遍历到链尾)

双向链表

双链表:
    双向链表 [Double Linked List]:由各个内存结构通过指针 Next 和指针 Prev 链接在一起组成,每一个内存结构都存在前驱内存结构和后继内存结构(链头没有前驱,    链尾没有后继),内存结构由数据域、Prev 指针域和Next 指针域组成。
解析:Data 数据 + Next 指针 + Prev 指针,组成一个双向链表的内存结构;
    第一个内存结构称为 链头,最后一个内存结构称为 链尾;
    链头的 Prev 指针设置为 NULL, 链尾的 Next 指针设置为 NULL;
    Prev 指向的内存结构称为 前驱, Next 指向的内存结构称为 后继;
    双向链表的遍历是双向的,即如果把从链头的 Next 一直到链尾的[NULL] 遍历方向定义为正向,那么从链尾的 Prev 一直到    链头 [NULL ]遍历方向就是反向;

循环链表

循环列表:循环链表没有链头和链尾的说法,因为是闭环的,所以每一个内存结构都可以充当链头和链尾;
    单向循环列表:
        单向循环链表 [Circular Linked List] : 由各个内存结构通过一个指针 Next 链接在一起
        组成,每一个内存结构都存在后继内存结构,内存结构由数据域和 Next 指针域组成。
    解析:
        单向的实现就是在单链表的基础上,把链尾的 Next 指针直接指向链头,形成一个闭环;
    
    双向循环列表:
        双向循环链表 [Double Circular Linked List] : 由各个内存结构通过指针 Next 和指针
        Prev 链接在一起组成,每一个内存结构都存在前驱内存结构和后继内存结构,内存结构由
        数据域、Prev 指针域和 Next 指针域组成。
    解析:
        双向的实现就是在双向链表的基础上,把链尾的 Next 指针指向链头,再把链头的 Prev 指针指向链尾,形成一个闭环;

常见数据结构

数据存储的常用结构有:栈、队列、数组、链表和红黑树

栈:stack,又称为堆栈,栈是限定仅在表尾进行插入和删除的线性表。我们吧允许插入和删除的一段称为栈顶,另一端称为栈底,不含任何数据元素的栈称为空栈。栈又称为先进后出的线性表。
简单地说:采用该结构的集合,对元素的存取有如下的特点
1.先进后出(即,存进去的元素,要在后它后面的元素依次取出后,才能取出该元素)
2.栈的出入口都是栈的顶端位置
有两个名词需要注意:
    1.压栈:存元素
    2.弹栈:取元素

队列

队列:queue,简称队,队列是一种特殊的线性表,是运算受到限制的一种线性表,只允许在表的一端进行插入,而在另一端进行删除元素的线性表。队尾(rear)是允许插入的一端。队头(front)是允许删除的一端。空队列是不含元素的空表。
简单地说,采用该结构的集合,对元素的存取有如下的特点:
1.先进先出(即,存进去的元素,要在后它前面的元素依次取出后,才能取出该元素)。就像排队一样
2.队列的入口、出口各站一侧。

数组

数组:Array,是有序的元素序列,数组是在内存中开辟一段连续的空间,并在此空间存放元素。就像是一排出租屋,有100个房间,从001到100都是有固定编号,通过编号就可以快速找到租房子的人。
简单地说,采用该结构的集合,对元素的存取有如下的特点:
1.指定索引位置增加元素:需要创建一个新数组,将指定新元素存储在指定索引位置,再把原数组元素根据索引,复制到新数组对应索引的位置。
2.指定索引位置删除元素:需要创建一个新数组,把原数组元素根据索引,复制到新数组对应索引的位置,原数组中指定索引位置元素不复制到新数组中。

链表

链表:linked list,由一系列节点node(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。我们常说的链表结构有单向链表和双向链表	这里说的是单向列表
简单地说,采用该结构的集合,对元素的存取有如下的特点:

单向链表的特点:

  • 多个节点之间,通过地址进行连接。

  • 查找元素慢:想查找某个元素,需要通过连接的结点,依次向后查找指定元素

  • 增删元素块:

    • 增加元素:只需要修改连接下个元素的地址即可。

    • 删除元素:只需要修改下个元素的地址即可

                

 

红黑树

  • 二叉树:binary tree ,是每个结点不超过2的有序树(tree) 。 简单的理解,就是一种类似于我们生活中树的结构,只不过每个结点上都最多只能有两个子结点。二叉树是每个节点最多有两个子树的树结构。顶上的叫根结点,两边被称作“左子树”和“右子树”。

  • 如图:

红黑树--二叉树的一种,红黑树本身就是一颗二叉查找树,将节点插入后,该树仍然是一颗二叉树。也就意味着,树的键值仍然是有序的。
红黑树的约束:
	1.节点可以是红色的或者是黑色的
	2.根节点是黑色的
	3.叶子结点(特指空节点)是黑色的
	4.每个红色节点的子节点都是黑色的
	5.任何一个节点到其每一个叶子结点的所有路径上黑色节点数相同
红黑树的特点:
	速度特别快,趋近平衡树,查找叶子元素最少和最多次数不多于两倍。

Collection集合

集合概述

集合:集合是java中提供的一种容器,可以用来存储多个数据。
集合和数据既然都是容器,它们有啥区别呢?
	1.数组的长度是固定的,集合的长度是可变的
	2.数组中存储的是同一类型的元素,可以存储基本数据类型值。集合存储的都是对象。而且对象的类型可以不一致。在开发中一般当对象多的时候,使用集合进行存储

集合框架

JAVASE提供了满足各种需求的API,在使用这些API之前,先了解其继承与接口操作框架,才能了解何时采用哪个类,以及类之间如何彼此合作,从而达到灵活应用。
集合按照其存储结构可以分为两大类,分别是单列集合java.util.Collection和双列集合java.util.Map,今天主要学习Collection集合,在后会补充Map集合
	Collection:单列集合类的根接口,用于存储一系列符合某种规则的元素,它有两个重要的子接口,分别是java.util.List和java.util.Set。其中,List的特点是元素有序、元素可重复。Set的特点是元素无序,而且不可重复。List接口的主要实现类有java.util.ArratList和java.util.LinkedList,Set接口的主要实现类有java.util.HashSet和java.util.TreeSet。从上面的描述可以看出JDK中提供了丰富的集合类库,为了便于初学者进行系统的学习,接下来通过一张图来描述整个集合类的继承体系

Collection接口

Collection接口是在整个Java类集中保存单值的最大操作父接口,里面每次操作的时候都只能保存一个对象的数据。此接口定义在java.util包中。
接口定义如下:
	public interface Colection<E> extends Iterable<E>
常用方法如下:一共定义了15个方法,那么此接口的全部子类或者子接口就将全部继承以上接口中的方法
		   但是在开发中不会直接使用Collection接口。而使用其操作的子接口:List、Set
NO.方法名称类型描述
1public boolean add(E e)普通向集合中插入一个元素
2public boolean addAll(Collection<? extends E> c)普通向集合中插入一组元素
3public void clear()普通清空集合中的元素
4public boolean contains(Object o)普通查找一个元素是否存在
5public boolean containsAll(Collection<?> c)普通查找一组元素是否存在
6public boolean isEmpty()普通判断集合是否为空
7public Iterator<E> iterator()普通 Iterator 接口实例化
8public boolean remove(Object o)普通从集合中删除一个对象
9boolean removeAll(Collection<?> c)普通从集合中删除一组对象
10boolean retainAll(Collection<?> c)普通判断是否没有指定的集合
11public int size()普通求出集合中元素的个数
12public Object[] toArray()普通以对象数组的形式返回集合中的全部内容
13<T> T[] toArray(T[] a)普通指定操作的泛型类型,并把内容返回
14public boolean equals(Object o)普通从 Object 类中覆写而来
15public int hashCode()普通从 Object 类中覆写而来

List接口

List子接口的定义:  public interface List<E> extends Collection<E>
java.util.List 接口继承自 Collection 接口,是单列集合的一个重要分支,习惯性地会将实现了List 接口的对象称为List集合。在List集合中允许出现重复的元素,所有的元素是以一种线性方式进行存储的,在程序中可以通过索引来访问集合中的指定元素。另外,List集合还有一个特点就是元素有序,即元素的存入顺序和取出顺序一致。
Lst接口特点:
1.它是一个元素存取有序的集合。例如,存元素的顺序是11、22、33.那么集合中,元素的存储就是按照11、22、33的顺序完成的
2.它是一个带有索引的集合,通过索引就可以精确地操作集合中的元素(与数组的索引是一个道理)
3.集合中可以有重复的元素,通过元素的equals方法,来比较是否为重复的元素

此接口上依然使用了泛型技术。此接口对于Collection接口来讲有如下的扩充方法:
NO.方法名称类型描述
1public void add(int index,E element)普通在指定位置处增加元素
2boolean addAll(int index,Collection<? extends E> c)普通在指定位置处增加一组元素
3public E get(int index)普通根据索引位置取出每一个元素
4public int indexOf(Object o)普通根据对象查找指定的位置,找不到返回-1
5public int lastIndexOf(Object o)普通从后面向前查找位置,找不到返回-1
6public ListIterator<E> listIterator()普通返回 ListIterator 接口的实例
7public ListIterator<E> listIterator(int index)普通返回从指定位置的 ListIterator 接口的实例
8public E remove(int index)普通删除指定位置的内容
9public E set(int index,E element)普通修改指定位置的内容
10List<E> subList(int fromIndex,int toIndex)普通返回子集合
在List接口中有以上10个方法是对已有的Collection接口进行的扩充。
所以,证明,List接口拥有比Collection接口更多的操作方法。
了解了List接口之后,那么该如何使用该接口呢?需要找到此接口的实现类,常用的实现类有如下几个:
	ArrayList(95%)、Vector(4%)、LinkedList(1%)

ArrayList集合

java.util.ArrayList 集合数据存储的结构是数组结构。元素增删慢、查找快(数组结构的特点),由于日常开发中使用最多的功能为查询数据、遍历数据,所以 ArrayList 是最常用的集合。
ArrayList是List接口的子类,此类的定义如下
public class ArrayList<E> extends AbstractList<E> 
implements List<E>, RandomAccess, Cloneable, Serializable
此类继承了 AbstractList 类。AbstractList 是 List 接口的子类。AbstractList 是个抽象类,适配器设计模式。

许多程序员开发时非常随意地使用ArrayList完成任何需求,并不严谨,这种用法是不提倡的。
如果开始时需要存储大量的数据,而且使用ArrayList存储的话,在创建对象时使用一参构造方法,给定初始容量
使用默认无参的构造方法,创建对象,创建出来的数组是一个空数组,长度为0,并不是API中所说的长度为10,但API说的并没有错

注意:add方法虽然是boolean型的,但它只会返回true

Vector

与ArrayList一样,Vector本身也属于List接口的子类,此类的定义如下:
public class Vector<E> extends AbstractList<E> 
implements List<E>, RandomAccess, Cloneable, Serializable
此类与 ArrayList 类一样,都是 AbstractList 的子类。所以,此时的操作只要是List接口的子类就都按照 List 进行操作。

因为Vector操作的时候是以接口为操作的标准,所以操作结果与使用ArrayList本身并没有任何的区别
只是为了照顾习惯于使用Vector的老用户,在JDK1.2之后将Vector类进行了升级,让其多实现了一个List接口,这样才将这个类继续保留了下来

Vector类和ArrayList类的区别

NO.区别点ArrayListVector
1时间是新的类,在JDK1.2之后推出的是旧的类在JDK1.0的时候就定义了
2性能性能较高,是采用了异步处理性能较低,是采用了同步处理
3输出支持Iterator、ListIterator输出除了支持Iterator、ListIterator输出还支持Enumeration输出
4安全性线程不安全线程安全

链表操作LinkedList

java.util.LinkedList 集合数据存储的结构是链表结构。方便元素添加、删除的集合。
LinkedList是一个双向链表

此类的使用几率是非常低的,但是此类的定义如下:
public class LinkedList<E> extends AbstractSequentialList<E> 
implements List<E>, Deque<E>, Cloneable, Serializable

实际开发中对一个集合元素的添加与删除经常涉及到首尾操作,而LinkedList提供了大量首尾操作的方法。这些方法我们作为了解即可:
public void addFirst(E e) :将指定元素插入此列表的开头。
public void addLast(E e) :将指定元素添加到此列表的结尾。
public E getFirst() :返回此列表的第一个元素。
public E getLast() :返回此列表的最后一个元素。
public E removeFirst() :移除并返回此列表的第一个元素。
public E removeLast() :移除并返回此列表的最后一个元素。
public E pop() :从此列表所表示的堆栈处弹出一个元素。
public void push(E e) :将元素推入此列表所表示的堆栈。
public boolean isEmpty() :如果列表不包含元素,则返回true。

LinkedList是List的子类,List中的方法LinkedList都是可以使用,这里就不做详细介绍,我们只需要了解LinkedList的特有方法即可。在开发时,LinkedList集合也可以作为堆栈,队列的结构使用。(了解即可)

注意:在使用LinkedList创建对象 需要遍历时 要先把链表的大小取出来 不能再for循环里面取 否则每循环一次就移除一个元素,并不能循环链表大小次

LinkedList删除重复元素

LinkedList<String> list = new LinkedList<>();
添加到list里的操作
HashSet<String> set = new HashSet<>();
ListIterator<String> iterator = list.listIterator();
//让list里的数 一个个跟新的set集合比较
while (iterator.hasNext()){
    String next = iterator.next();
    if (!set.contains(next)){
        set.add(next);
    }else {
        iterator.remove();
    }
}
for (String s : list) {
    System.out.println(s);
}

Iterator

在程序开发中,经常需要遍历集合中的所有元素。针对这种需求,JDK专门提供了一个接口java.util.Iterator 。 Iterator 接口也是Java集合中的一员,但它与 Collection 、 Map 接口有所不同, Collection 接口与 Map 接口主要用于存储元素,而 Iterator 主要用于迭代访问(即遍历)Collection 中的元素,因此 Iterator 对象也被称为迭代器。
此接口的定义如下:
	public interface Iterator<E>
要想使用此接口,则必须使用 Collection 接口,在 Collection 接口中规定了一个 iterator()方法,可以用于为 Iterator 接口进行实例化操作。
通过Collection接口为其进行实例化之后,一定要记住,Iterator中的操作指针是在第一条元素之上,当调用next()方法的时候,获取当前指针指向的值并向下移动,使用hasNext()可以检查序列中是否还有元素

但是在讲解输出的时候一定要记住以下的原则:“只要是碰到了集合,则输出的时候想都不想就使用 Iterator 进行输出。”

此接口规定了以下三个方法:
No.方法名称类型描述
1boolean hasNext()普通 (常用)是否有下一个元素
2E next()普通 (常用)取出内容
3void remove()普通 (集合中很少有删除元素的操作)删除当前内容

迭代的概念

迭代的概念:
	迭代:即Collection集合元素的通用获取方式,在取元素之前先要判断集合中有没有元素,如果有,就把这个元素取出来,继续再判断,如果还有就再取出来。一直把集合中的所有元素全部取出。这种取出方式专业术语称为迭代

Iterator遍历集合

想要遍历Collection集合,那么就要获取该集合迭代器完成迭代操作,下面介绍一下获取迭代器的方法:
	public Iterator iterator() : 获取集合对应的迭代器,用来遍历集合中的元素的。
	Collection<String> all = new ArrayList<String>();
	all.add("A");
	.....
	Iterator<String> iter = all.iterator();
	while (iter.hasNext()) {// 判断是否有下一个元素 
		String str = iter.next(); // 取出当前元素 
		System.out.print(str + "、"); 
	}
	以上的操作是 Iterator 接口使用最多的形式,也是一个标准的输出形式。
	but!在进行迭代输出的时候如果要想删除当前元素,则只能使用 Iterator 接口中的 remove()方法,而不能使用集合中的remove()方法。否则将出现未知的错误。
	在使用Iterator 接口中的 remove()方法时也要注意:不能直接用remove(),要先获取数据在调用remove(),因为不进行获取的话Iterator的指针指的是第一个元素的上面,是空的,会出现异常


	while (iter.hasNext()) {// 判断是否有下一个元素 
		String str = iter.next(); // 取出当前元素 
		if (str.equals("C")) { 
			all.remove(str); // 错误的,调用了集合中的删除 
		} else { 
			System.out.print(str + "、"); 
		} 
	}
	会出现错误,因为原本需要输出的集合的内容被删掉了
	
	while (iter.hasNext()) {// 判断是否有下一个元素 
		String str = iter.next(); // 取出当前元素 
		if (str.equals("C")) { 
			iter.remove(); // 正确的做法
		} else { 
			System.out.print(str + "、"); 
		} 
	}
	
	但是,从实际的开发角度看,元素的删除操作出现的几率是很小的,基本上可以忽略,即:集合中很少有删除元素 的操作。
	
	Iterator 接口本身可以完成输出的功能,但是此接口只能进行由前向后的单向输出。如果要想进行双向输出,则必须使用其子接口 —— ListIterator。

迭代器的实现原理

我们在之前案例已经完成了Iterator遍历集合的整个过程。当遍历集合时,首先通过调用t集合的iterator()方法获得迭代器对象,然后使用hashNext()方法判断集合中是否存在下一个元素,如果存在,则调用next()方法将元素取出,否则说明已到达了集合末尾,停止遍历元素。

ListIterator

ListIterator 是可以进行双向输出的迭代接口,此接口定义如下:
	public interface ListIterator<E> 
	extends Iterator<E>
但是如果要想使用 ListIterator 接口,则必须依靠 List 接口进行实例化。
此接口是 Iterator 的子接口,此接口中定义了以下的操作方法:
No.方法名称类型描述
1void add(E e)普通增加元素
2boolean hasPrevious()普通判断是否有前一个元素
3E previous()普通取出前一个元素
4void set(E e)普通修改元素的内容
5int previousIndex()普通前一个索引位置
6int nextIndex()普通下一个索引位置

ListIterator的使用

List<String> all = new ArrayList<String>();
all.add("A");
.....
ListIterator<String> iter = all.listIterator(); 
System.out.print("从前向后输出:"); 
while (iter.hasNext()) { 
    System.out.print(iter.next() + "、"); 
}
System.out.print("\n从后向前输出:"); 
while (iter.hasPrevious()) { 
    System.out.print(iter.previous() + "、"); 
}
在使用ListIterator接口中的add()方法时需注意:新添加的元素是从上方添加进去的
但是,此处有一点需要注意的是,如果要想进行由后向前的输出,则首先必须先进行由前向后的输出。 
但是,此接口一般使用较少。

Set接口

Set 接口也是 Collection 的子接口,与 List 接口最大的不同在于,Set 接口里面的内容是不允许重复的。 
Set 接口并没有对 Collection 接口进行扩充,基本上还是与 Collection 接口保持一致。因为此接口没有 List 接口中定义 的 get(int index)方法,所以无法使用循环进行输出。 
	那么在此接口中有两个常用的子类:HashSet、TreeSet

HashSet(散列存放)

java.util.HashSet 底层的实现其实是一个 java.util.HashMap 支持

HashSet 是根据对象的哈希值来确定元素在集合中的存储位置,因此具有良好的存取和查找性能。保证元素唯一性的方式依赖于: hashCode 与 equals 方法。

既然Set接口并没有扩充任何的Collection接口中的内容,所以使用的方法全部都是Collection接口定义而来的。

HashSet属于散列的存放类集,里面的内容是无序存放的。使用HashSet实例化的Set接口实例,本身属于无序的存放

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

think:能不能通过循环的方式将Set接口中的内容输出呢?

是可以实现的,因为在Collection接口中定义了将集合变成对象数组进行输出
	Set<String> all = new HashSet<String>(); // 实例化Set接口对象
	all.add("A");
	......
	Object obj[] = all.toArray(); // 将集合变为对象数组
	for (int x = 0; x < obj.length; x++) { 
	System.out.print(obj[x] + "、"); 
	}
	but!以上的操作不好,因为在操作的时候已经制定了操作的泛型类型,那么现在最好的做法是由泛型所指定的类型变为指定的数组
	所以只能使用以下的方法:<T> T[] toArray(T[] a)
	String[] str = all.toArray(new String[] {});// 变为指定的泛型类型数组
	再进行遍历输出

不可重复例子

//添加元素 

set.add(new String("123")); 
set.add("123"); 
set.add("123"); 
set.add("321"); 
//遍历 
for (String name : set) { 
System.out.println(name); 
}
输出结果为:
123
321

HashSet集合存储数据的结构

什么是哈希表?
	在JDK1.8之前,哈希表底层采用数组+链表实现,及时用链表处理冲突,同一hash值的链表都存储在一个链表里。但是当位于一个桶中的元素较多,即hash值相等的元素较多时,通过key值一次查找的效率较低。而JDK1.8中,哈希表存储采用数组+链表+红黑树实现,当链表长度超过阈值(8)时,将链表转换成红黑树,这样大大减少了查找时间,当链表长度减少到6时从红黑树又转换为链表
	简单的来说,哈希表是有数组+链表+红黑树(JDK1.8增加了红黑树部分)实现的,如下图

                

存储流程图

                

总而言之,JDK1.8引入红黑树大程度优化了HashMap的性能,那么对于我们来讲保证HashSet集合元素的唯一,其实就是根据对象的hashCode和equals方法来决定的。如果我们往集合中存放自定义的对象,那么保证其唯一,就必须覆写hashCode和equals方法建立属于当前对象的比较方法

LinkedHashSet

我们知道HashSet保证元素唯一,可是元素存放进去是没有顺序的,那么我们要保证有序,怎么办呢?在HashSet下面有一个子类 java.util.LinkedHashSet ,它是链表和哈希表组合的一个数据存储结构

排序的子类:TreeSet

与HashSet不同的是,TreeSet本身属于排序的子类,此类的定义如下:
public class TreeSet<E> extends AbstractSet<E> 
implements NavigableSet<E>, Cloneable, Serializable

如果TreeSet集合里面是相同类型的基本数据类型,直接输出就可以排序
但是集合里时对象的话,需要该对象的类实现Comparable接口实现compareTo方法后才能进行排序

排序的说明

要想对一个自定义类的对象进行排序,该类要先实现Comparable接口
如果只比较对象的一个属性的话,比较的属性相同即使其他属性不相同也会被认为是同一个对象
所以在重写compareTo方法时,加多一个属性的比较

例子

定义了一个Person类实现了Comparable接口
public int compareTo(Person per) { 
    if (this.age > per.age) { 
        return 1; 
    } else if (this.age < per.age) { 
        return -1; 
    } else { 
        return 0; 
    } 
}

public static void main(String[] args) { 
    Set<Person> all = new TreeSet<Person>(); 
    all.add(new Person("张三", 10)); 
    all.add(new Person("李四", 10)); 
    all.add(new Person("李四", 10)); 
    all.add(new Person("王五", 11)); 
    all.add(new Person("赵六", 12)); 
    all.add(new Person("孙七", 13)); 
    System.out.println(all); 
}
执行结果:[姓名:张三,年龄:10, 姓名:王五,年龄:11, 姓名:赵六,年龄:12, 姓名:孙七,年龄:13]
    从以上的结果中可以发现,李四没有了。因为李四的年龄和张三的年龄是一样的,所以会被认为是同一个对象。则 此时必须修改 Person 类,如果假设年龄相等的话,按字符串进行排序。
public int compareTo(Person per) { 
    if (this.age > per.age) { 
        return 1; 
    } else if (this.age < per.age) { 
        return -1; 
    } else { 
        return this.name.compareTo(per.name); 
    } 
}

关于重复元素的说明

之前在使用Comparable完成的对于重复元素的判断,那么Set接口定义的时候本身就是不允许重复元素的,那么证明如果现在整的是有重复元素的话,使用HashSet也同样可以进行区分
public static void main(String[] args) { 
    Set<Person> all = new HashSet<Person>(); 
    all.add(new Person("张三", 10)); 
    all.add(new Person("李四", 10));
    System.out.println(all); 
}
	此时发现,并没有去掉所谓的重复元素,也就是说之前的操作并不是真正的重复元素的判断,而是通过Comparable接口间接完成的。
     如果要想判断两个对象是否相等,则必须使用Object类中的equals()方法完成。
     从最正规的来讲,如果想要判断两个对象是否相等,需要通过equals()完成。
     	第一种判断两个对象的编码是否一致,这个方法需要通过hashCode()完成,即:每个对象有唯一的编码
     	还需要进一步验证对象中的每个属性是否相等,需要通过equals()完成。
     所有此时需要覆写Object类中的hashCode()方法,此方法表示一个唯一的编码,一般是通过公式计算出来的

重写方法

public boolean equals(Object obj) { 
    if (this == obj) { 
        return true;
    }
    if (!(obj instanceof Person)) { 
        return false; 
    }
    Person per = (Person) obj; 
    if (per.name.equals(this.name) && per.age == this.age) { 
        return true; 
    } else { 
        return false; 
    }
}
public int hashCode() { 
    return this.name.hashCode() * this.age; 
}
发现,此时已经不存在重复元素了,所以如果想要去掉重复元素需要一开hashCode()和equals()方法共同完成

Collections

常用功能

java.utils.Collections 是集合工具类,用来对集合进行操作。部分方法如下:
public static <T> boolean addAll(Collection<T> c, T... elements) :往集合中添加一些元素。
public static void shuffle(List<?> list) 打乱顺序 :打乱集合顺序。
public static <T> void sort(List<T> list) :将集合中元素按照默认规则排序。
public static <T> void sort(List<T> list,Comparator<? super T> ) :将集合中元素按照指定规则排序。

Comparator比较器

public static <T> void sort(List<T> list) :将集合中元素按照默认规则排序。
说到排序,简单地说即使两个对象之间比较大小,那么在JAVA中提供了两种比较现实的方法,一种是比较死板的采用java.lang.Comparable接口去实现,一种是灵活的当我需要做排序的时候再去选择的java.util.Comparator接口完成
那么我们采用 public static <T> void sort(List<T> list)这个方法完成的排序,实际上要求了被排序的类型需要实现Comparable接口完成比较的功能,在String类型上如下:
public final class String implements java.io.Serializable, Comparable<String>, CharSequence {
String类实现了这个接口,并完成了比较规则的定义,但是这样就把这种规则写死了,那比如我想要字符串按照第一个字符降序排列,那么这样就要修改String的源代码,这是不可能的,那么这个时候我们可以使用
public static <T> void sort(List<T> list,Comparator<? super T> )
方法灵活的完成,这个里面就涉及到了Comparator这个接口,排序是comparator能实现的功能之一,改接口代表一个比较器,比较器具有可比性!顾名思义就是做排序的,通俗地讲需要比较两个对象谁排在前谁排在后,那么比较的方法就是
public int compare(String o1, String o2) :比较其两个参数的顺序。
    两个对象比较的结果有三种:大于,等于,小于。
    如果要按照升序排序,
    则o1 小于o2,返回(负数),相等返回0,01大于02返回(正数)
    如果要按照降序排序
    则o1 小于o2,返回(正数),相等返回0,01大于02返回(负数)

Comparator比较器操作

public static void main(String[] args) { 
    ArrayList<String> list = new ArrayList<String>(); 
    list.add("cba"); 
    list.add("aba"); 
    list.add("sba"); 
    list.add("nba"); 
    //排序方法 按照第一个单词的降序 
    Collections.sort(list, new Comparator<String>() { 
        @Override 
        public int compare(String o1, String o2) { 
            return o2.charAt(0) - o1.charAt(0); 
        } 
    }); 
    System.out.println(list); 
}
结果如下:[sba, nba, cba, aba]
排序: 01 - 02 升序
      02 - 01 降序

简述Comparable和Comparator两个接口的区别。

Comparable:强行对实现它的每个类的对象进行整体排序。这种排序被称为类的自然排序,类的comparaTo方法被称为它的自然比较方法。只能在类中实现comparaTo()一次,不能经常修改类的代码实现自己想要的排序,实现此接口的对象列表(和数组)可以通过Collections.sort(和Arrays.sort)进行自动排序,对象可以用作有序映射中的键或有序集合中的元素,无序指定比较器

Comparator:强行对某个对象进行整体排序,可以将Comparator传递给sort方法(如Collections.sort或Arrays.sort),从而允许在排序顺序上实现精确控制。还可以使用Comparator来控制某些数据结构(如有序set或有序映射)的顺序,或者为那些没有自然顺序的对象collection提供排序

Map接口

以上的Collection中,每次操作的都是一个对象,如果现在假设要操作一对对象,则必须使用Map了,类似于以下一种情况:
	张三		123456
	李四		234567
那么保存以上的信息的时候使用Collection就不那么方便,所以要使用Map接口。里面的所有内容都按照key->value的键值对形式保存,也称为二元偶对象
Map本身是一个接口,所以一般会使用以下的几个子类:HashMap、TreeMap、Hashtable
	此接口的定义如下:
		public interface Map<K,V>
	此接口与Collection接口没有任何关系,是第二大的集合操作接口。此接口常用方法如下
No.方法名称类型描述
1void clear()普通清空 Map 集合中的内容
2boolean containsKey(Object key)普通判断集合中是否存在指定的 key
3boolean containsValue(Object value)普通判断集合中是否存在指定的 value
4Set<Map.Entry<K,V>> entrySet()普通 Map 接口变为 Set 集合
5V get(Object key)普通根据 key 找到其对应的 value
6boolean isEmpty()普通判断是否为空
7Set<K> keySet()普通将全部的 key 变为 Set 集合
8Collection<V> values()普通将全部的value变为Collection集合
9V put(K key,V value)普通向集合中增加内容
10void putAll(Map<? extends K,? extends V> m)普通增加一组集合
11V remove(Object key)普通根据key删除内容

新的子类:HashMap

HashMap是Map的子类,HashMap本身是属于无序存放的,此类的定义如下:
	public class HashMap<K,V> extends AbstractMap<K,V> 
	implements Map<K,V>, Cloneable, Serializable
此类继承了AbstractMap类,同样可以被克隆,可以被序列化下来

HashMap中得到所有的key 和 value

Map<Integer, String> map = new HashMap<Integer, String>(); //新建一个HashMap集合
map.put();
.........
Set<Integer> set = map.keySet(); // 得到全部的key  keySet()| 将全部的key变为Set集合
Collection<String> value = map.values(); // 得到全部的value  map.values() 将全部的value变为Collection集合
Iterator<Integer> iter1 = set.iterator(); 
Iterator<String> iter2 = value.iterator();
while (iter1.hasNext()) { 
    System.out.print(iter1.next() + "、");  // 输出的是Set集合中存储的map集合的key
}
while (iter2.hasNext()) { 
    System.out.print(iter2.next() + "、");  // 输出的是Collection集合中存储的map集合的value
}

循环输出Map中所有的内容

Set<String> set = map.keySet(); // 得到全部的key
key Iterator<String> iter = set.iterator(); 
while (iter.hasNext()) { 
    String i = iter.next(); // 得到key 
    System.out.println(i + " --:> " + map.get(i)); // get() 根据key得到value
}

旧的子类:Hashtable

Hashtable是一个最早的key->value的操作类,本身是在JDK1.0的时候推出的。其基本操作与HashMap是类似的
操作的时候可以发现和HashMap基本上没有什么区别,而且本身都是以Map为操作标准的,所以操作的结果形式都一样。但是Hashtable中是不能向集合中插入null值的

HashMap与Hashtable的区别

在整个集合中出了ArrayList和Vector的区别之外,另一个最重要的区别就是HashMap与Hashtable的区别
No.区别点HashMapHashtable
1推出时间JDK1.2之后推出的,新的操作类JDK1.0时推出的,旧的操作类
2性能异步处理,性能较高同步处理,性能较低
3null允许设置为null不允许设置为null

TreeMap

TreeMap 子类是允许 key 进行排序的操作子类,其本身在操作的时候将按照 key 进行排序,另外,key 中的内容可以 为任意的对象,但是要求对象所在的类必须实现 Comparable 接口。
    
TreeMap在输出基本数据类型的时候会先经过排序,且若有相同的key,输出的value只会有一个而且是靠后的一个
但是,但是从一般的开发角度来看,在使用 Map 接口的时候并不关心其是否排序,所以此类 只需要知道其特点即可。

关于Map集合的输出

	在Collection接口中,可以使用iterator()方法为Iterator接口实例化,并进行输出操作,但是在Map接口中并没有此方法的定义,所以Map接口本身是不能直接使用Iterator进行输出的。
	因为Map接口中存放的每一个内容都是一对值,而使用Iterator接口输出的时候,每次取出的都实际上是一个完整的对象,。如果此时非要使用Iterator进行输出的话,则可以按照如下的步骤进行:
	1.使用Map接口中的entrySet()方法将Map接口的全部内容变成Set集合
	2.可以使用Set接口中定义的iterator()方法为Iterator接口进行实例化
	3.之后使用Iterator接口进行迭代输出,每一次的迭代都可以取得一个Map.Entry的实例
	4.通过Map.Entry进行key和value的分离

Map.Entry

那么,到底什么是Map.Entry呢
	Map.Entry本身是一个接口。此接口是定义在Map接口内部的,是Map的内部接口。此内部接口使用static进行定义,所以此接口将成为外部接口。
	实际上来讲,对于每一个存放到Map集合中的key和value都是将其变为了Map.Entry保存在了Map集合之中

​​​​​​​

在Map.Entry接口中以下的方法最为常用:
No.方法名称类型描述
1K getKey()普通得到key
2V getValue()普通得到value

使用Iterator输出Map接口

Map<String, String> map = new HashMap<String, String>(); 
map.put("ZS", "张三"); 
map.put("LS", "李四"); 
map.put("WW", "王五"); 
map.put("ZL", "赵六"); 
map.put("SQ", "孙七"); 
Set<Map.Entry<String, String>> set = map.entrySet();// 变为Set实例 
Iterator<Map.Entry<String, String>> iter = set.iterator(); 
while (iter.hasNext()) { 
    Map.Entry<String, String> me = iter.next();
    System.out.println(me.getKey() + " --> " + me.getValue()); 
}

	以上的代码一定要记住,Map集合中每一个元素都是Map.Entry的实例,只有通过Map.Entry才能进行key和value的分离操作
    除了以上的做法之外,在JDK1.5之后也可以使用foreach完成同样的输出,只是这样的操作基本上不使用

使用foreach输出Map接口

Map<String, String> map = new HashMap<String, String>(); 
map.put("ZS", "张三"); 
map.put("LS", "李四"); 
map.put("WW", "王五"); 
map.put("ZL", "赵六"); 
map.put("SQ", "孙七"); 
for (Map.Entry<String, String> me : map.entrySet()) { 
    System.out.println(me.getKey() + " --> " + me.getValue()); 
}

两种关系

使用类集,除了可以清楚地表输出动态数组的概念及各个数据结构的操作之外,也可以表示出以下的两种关系

第一种:一对多关系

一个学校有多个学生,典型的一对多
    定义学生类:
public class Student{
    private String name;
    private int age;
    private School school;
}
	定义学校类:
public class School{
    private String schoolName;
    private List<Student> allStudents = null;
}
	main方法建立两者的关系
Student stu1 = new Student("张三", 10); 
Student stu2 = new Student("李四", 11); 
School sch = new School("LAMP JAVA"); 
sch.getAllStudents().add(stu1); // 一个学校有多个学生 
stu1.setSchool(sch);// 一个学生属于一个学校 
sch.getAllStudents().add(stu2); // 一个学校有多个学生 
stu2.setSchool(sch);// 一个学生属于一个学校 
System.out.println(sch); 
Iterator<Student> iter = sch.getAllStudents().iterator(); 
while (iter.hasNext()) { 
    System.out.println(iter.next()); 
}
System.out.println(stu1.getSchool());

此时就完成了一对多的关系。

第二种关系:多对多

一个学生可以选择多门课程,一门课程允许有多个学生参加
	定义学生类
public class Student { 
    private String name; 
    private int age; 
    private List<Course> allCourses;
}
	定义课程类
public class Course { 
    private String name; 
    private int credit;
    private List<Student> allStudents = null;
}
	main方法建立两者关系,注意这时候设置的关系也应该是双向操作的
Student stu1 = new Student("张三", 10); 
Student stu2 = new Student("李四", 11);
Course c1 = new Course("Oracle", 5); 
Course c2 = new Course("Java SE基础课程", 10);
c1.getAllStudents().add(stu1); // 参加第一门课程 
c1.getAllStudents().add(stu2); // 参加第一门课程 
stu1.getAllCourses().add(c1); // 学生选择课程 
stu2.getAllCourses().add(c1); // 学生选择课程
c2.getAllStudents().add(stu1); // 参加第二门课程 
c2.getAllStudents().add(stu2); // 参加第二门课程
stu1.getAllCourses().add(c2); // 学生选择课程 
stu2.getAllCourses().add(c2); // 学生选择课程
使用Iterator输出

Collections类

Collections实际上是一个集合的操作类,此类的定义如下:
	public class Collections extends Object
这个类与Collection接口没有任何的关系。是一个单独存在的类。
	此类是一个集合的操作类,但是从实际考虑,使用此类操作并不是很方便,最好的做法就是使用各个接口的直接操作的方法完成

分析equals、hashCode与内存泄漏

equals

equals的作用:比较两个对象的地址值是否相等

但是我们必须清楚,当Sting、Math还有Integer、Double.....等等这些封装类在使用equals()方法时,已经覆盖了object类的equals()方法,不再是地址的比较而是内容的比较。

还应注意:Java语言对equals()的要求如下,这些要求是必须遵循的:
1. 对称性:如果 x.equals(y)返回是“true”,那么 y.equals(x)也应该返回是“true”。 
2. 反射性:x.equals(x)必须返回是“true”。 
3. 类推性:如果 x.equals(y)返回是“true”,而且 y.equals(z)返回是“true”,那么 z.equals(x)也应该返回是“true”。 4. 还有一致性:如果 x.equals(y)返回是“true”,只要 x 和 y 内容一直不变,不管你重复 x.equals(y)多少次,返回都是 “true”。 
5. 任何情况下,x.equals(null),永远返回是“false”;x.equals(和 x 不同类型的对象)永远返回是“false”。
以上这五点是重写 equals()方法时,必须遵守的准则,如果违反会出现意想不到的结果,请大家一定要遵守。

hashCode

定义如下:
	public native int hashCode();
说明它是一个本地方法,它的实现是根据本地机器相关的。当然我们可以在自己写的类中覆盖hashCode方法,比如String、Integer、Double...等等这些类都是覆盖了hashCode方法的

java.lang.Object中对hashCode的约定

1.在一个应用程序执行期间,如果一个对象的equals方法做比较所用到的信息没有被修改的话,则对该对象调用hashCode方法多次,让它必须始终如一地返回同一个整数
2.如果两个对象根据equals(Object o)方法是相等的,则调用这两个对象中任一对象的hashCode方法必须产生相同的整数结果
3.如果两个对象根据equals(Object o)方法是不相等的,则调用这两个对象中任一个对象的hashCode方法,不要求产生不同的整数结果。但如果能不同,则可能提高散列表的性能

在java集合中,判断两个对象是否相等的规则

1.判断两个对象的hashCode是否相等
	如果不等,认为两个对象也不等,完毕
	如果相等进行2
	(这一点只是为了提高存储效率而要求的,其实理论上没有也可以,但如果没有,实际使用时效率会大大降低,所以我们 这里将其做为必需的。后面会重点讲到这个问题。)

2.判断两个对象用equals运算是否相等
	如果不相等,认为两个对象也不相等
	如果相等,认为两个对象相等(equals()是判断两个对象是否相等的关键)

提示贴:

当一个对象被存进HashSet集合后,就不能修改这个对象中的那些参与计算的哈希值的字段了,否则,对象被修改后的哈希值与最初存储进HashSet集合中时的哈希值就不相同了,在这种情况下,及时在contains方法使用该对象的当前引用作为参数去HashSet集合中检索对象,也将返回找不到对象的结果,这也会导致无法从HashSet集合中删除该对象,从而造成内存泄漏

总结

1.类集就是一个动态的对象数组,可以向集合中加入任意多的内容。
2.List接口中是允许有重复元素的,Set接口中是不允许有重复元素的
3.所有的重复元素依靠hashCode()和equals进行区分
4.List接口的常用子类:ArrayList、Vector
5.Set接口的常用子类:HashSet、TreeSet
6.TreeSet是可以排序的,一个类的对象依靠Comparable接口排序
7.Map接口中允许存放一对内容,key->value
8.Map接口的子类:HashMap、Hashtable、TreeMap
9.Map使用Iterator输出的详细步骤
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值