java之Collection

9 篇文章 0 订阅
5 篇文章 0 订阅

主要内容

  • Collection集合
  • 迭代器
  • 增强for
  • List集合
  • Set集合

学习目标

  • 能够说出集合与数组的区别
  • 说出Collection集合的常用功能
  • 能够使用迭代器对集合进行取元素
  • 能够说出集合的使用细节
  • 能够使用集合存储自定义类型
  • 能够使用foreach循环遍历集合
  • 能够说出List集合和Set集合的区别
  • 能够说出List集合各种实现类的区别
  • 能够说出Set集合各种实现类的区别
  • 能够说出Collection和Map集合的区别
  • 说出Map集合的常用功能
  • 能够遍历Map集合
  • 能够说出各种Map集合实现类的区别
  • 能够简单阐述HashMap的底层实现
  • 能够查询和使用集合工具类的相关方法
  • 能够说出Set与Map的关系

集合Collection

集合框架

  • 集合是JAVA提供的一种容器,可以用来存储多个数据
  • 在这里插入图片描述

集合与数组都是容器,他们有什么区别呢?

  • 数组的长度是固定的。集合的长度是可变的。
  • 数组中存储的是同一类型的元素,可以存储基本数据类型值。集合存储的都是对象。而且对象的类型可以不一致。在开发中一般当对象多的时候,使用集合进行存储。

为了可以满足用户数据更多种的逻辑关系,而设计的一系列的不同于数组的可变的聚合的抽象数据类型。这些接口和类在java.util包中,因为类型很丰富,因此我们通常称为集合框架集。

集合主要分为两大系列:Collection和Map,Collection 表示一组对象,Map表示一组映射关系或键值对。
![在这里插入图片描述](https://img-blog.csdnimg.cn/e183253fe5364a4ebaccb0bf939cfcbb.png

其中collection,list,set都是接口,其他的全部都是具体的实现类.

在这里插入图片描述
集合父接口的常用方法
在这里插入图片描述
①add方法:向集合中添加元素。

向collection集合中添加元素必定成功,因为collection集合的元素可以重复,有的集合不能重复添加元素,就会失败。

②size方法:获取集合的大小。

也就是一共多少个元素,上图例子输出:3。

③remove方法:移除对应的元素。

如果该元素在集合中存在,返回true,移除成功。

如果该元素在集合中不存在,返回false,移除失败。

上图例子输出:true。

④contains方法:判断集合是否包含该元素。

上图例子输出:true。

⑤clear方法:清空集合。

将集合中的所有元素清空,但是集合依旧还存在。

若是collection=null,表示不仅元素没了,集合也没了。

⑥toArray方法:将集合转换成数组,因为集合存储的数据类型不确定,所以返回的数组只能是Object数组。

⑦isEmpty方法:判断集合是否为空。

注意:Collection是没有索引的,所有它不能根据索引找到对应的元素,因此它才没有get方法。collection可以重复可以随便加元素,就是跟我们所学的知识一样,索引在collection里面好像并没有很大的用处.

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

  • List:有序的 collection(也称为序列)。此接口的用户可以对列表中每个元素的插入位置进行精确地控制。用户可以根据元素的整数索引(在列表中的位置)访问元素,并搜索列表中的元素。
  • Queue:队列通常(但并非一定)以 FIFO(先进先出)的方式排序各个元素。不过优先级队列和 LIFO 队列(或堆栈)例外,前者根据提供的比较器或元素的自然顺序对元素进行排序,后者按 LIFO(后进先出)的方式对元素进行排序。
  • Set:一个不包含重复元素的 collection。更确切地讲,set 不包含满足 e1.equals(e2) 的元素对 e1 和 e2,并且最多包含一个 null 元素。正如其名称所暗示的,此接口模仿了数学上的 set 抽象。
    SortedSet进一步提供关于元素的总体排序 的 Set。这些元素使用其自然顺序进行排序,或者根据通常在创建有序 set 时提供的 Comparator进行排序。该 set 的迭代器将按元素升序遍历 set。提供了一些附加的操作来利用这种排序。

Map:将键映射到值(key,value)的对象。一个映射不能包含重复的键;每个键最多只能映射到一个值。 Map 接口提供三种collection 视图,允许以键集、值集或键-值映射关系集的形式查看某个映射的内容。映射顺序 定义为迭代器在映射的 collection 视图上返回其元素的顺序。某些映射实现可明确保证其顺序,如 TreeMap 类;另一些映射实现则不保证顺序,如 HashMap 类。

  • SortedMap进一步提供关于键的总体排序 的 Map。该映射是根据其键的自然顺序进行排序的,或者根据通常在创建有序映射时提供的 Comparator 进行排序。

在这里插入图片描述
在这里插入图片描述

Iterator迭代器

Iterator接口

在程序开发中,经常需要遍历集合中的所有元素。针对这种需求,JDK专门提供了一个接口java.util.Iterator。Iterator接口也是Java集合中的一员,但它与Collection、Map接口有所不同,Collection接口与Map接口主要用于存储元素,而Iterator主要用于迭代访问(即遍历)Collection中的元素,因此Iterator对象也被称为迭代器。
想要遍历Collection集合,那么就要获取该集合迭代器完成迭代操作,下面介绍一下获取迭代器的方法:

public Iterator iterator(): 获取集合对应的迭代器,用来遍历集合中的元素的。
下面介绍一下迭代的概念:

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

Iterator接口的常用方法如下:

  • public E next():返回迭代的下一个元素。
  • public boolean hasNext():如果仍有元素可以迭代,则返回 true。
    接下来我们通过案例学习如何使用Iterator迭代集合中元素:

Java.util.Iterator迭代器中有一个方法:
​ void remove() ;
那么,既然Collection已经有remove(xx)方法了,为什么Iterator迭代器还要提供删除方法呢?
因为Collection的remove方法,无法根据条件删除。

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;

public class IteratorDemo {
    public static void main(String[] args) {
        // 使用多态方式 创建对象
        Collection<String> coll = new ArrayList<String>();

        // 添加元素到集合
        coll.add("串串星人");
        coll.add("吐槽星人");
        coll.add("汪星人");
        //遍历
        //使用迭代器 遍历   每个集合对象都有自己的迭代器
        Iterator<String> it = coll.iterator();
        //  泛型指的是 迭代出 元素的数据类型
        while(it.hasNext()){ //判断是否有迭代元素
            String s = it.next();//获取迭代出的元素
//            System.out.println(s);
//            串串星人
//            吐槽星人
//            汪星人
        }

        //使用Iterator迭代器删除元素
        Collection<String> coll01 = new ArrayList<>();
        coll01.add("陈琦");
        coll01.add("李晨");
        coll01.add("邓超");
        coll01.add("黄晓明");

        //删除名字有三个字的
//		coll.remove(o)//无法编写

        Iterator<String> iterator = coll01.iterator();
        while(iterator.hasNext()){
            String element = iterator.next();
            if(element.length()==3){
//				coll.remove(element);//错误的
                iterator.remove();
            }
        }
        System.out.println(coll01); //[陈琦, 李晨, 邓超]

    }

}

tips::在进行集合元素取出时,如果集合中已经没有元素了,还继续使用迭代器的next方法,将会发生java.util.NoSuchElementException没有集合元素的错误。

注意:不要在使用Iterator迭代器进行迭代时,调用Collection的remove(xx)方法,否则会报异常java.util.ConcurrentModificationException,或出现不确定行为。

迭代器的实现原理

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

Iterator迭代器对象在遍历集合时,内部采用指针的方式来跟踪集合中的元素,为了让初学者能更好地理解迭代器的工作原理,接下来通过一个图例来演示Iterator对象迭代元素的过程:
在这里插入图片描述
在调用Iterator的next方法之前,迭代器的索引位于第一个元素之前,不指向任何元素,当第一次调用迭代器的next方法后,迭代器的索引会向后移动一位,指向第一个元素并将该元素返回,当再次调用next方法时,迭代器的索引会指向第二个元素并将该元素返回,依此类推,直到hasNext方法返回false,表示到达了集合的末尾,终止对元素的遍历。

增强for

增强for循环(也称for each循环)是JDK1.5以后出来的一个高级for循环,专门用来遍历数组和集合的。

格式:

for(元素的数据类型  变量 : Collection集合or数组){ 
  	//写操作代码
}

它用于遍历Collection和数组。通常只进行遍历元素,不要在遍历的过程中对集合元素进行增删操作。

import org.junit.Test;

import java.util.ArrayList;
import java.util.Collection;


public  class NBFor {
    @Test
    public void TestNBFor(){

        //遍历数组
        int[] arr = {3,5,6,87};
        //使用增强for遍历数组
        for(int a : arr){//a代表数组中的每个元素
            System.out.println(a);
        }

        //遍历集合
        Collection<String> coll = new ArrayList<String>();
        coll.add("小河神");
        coll.add("老河神");
        coll.add("神婆");
        //使用增强for遍历
        for(String s :coll){//接收变量s代表 代表被遍历到的集合元素
            System.out.println(s);
        }
    }
}

tips: 新for循环必须有被遍历的目标。目标只能是Collection等或者是数组。新式for仅仅作为遍历操作出现。

java.lang.Iterable接口

ava.lang.Iterable接口,实现这个接口允许对象成为 “foreach” 语句的目标。

Java 5时Collection接口继承了java.lang.Iterable接口,因此Collection系列的集合就可以直接使用foreach循环遍历。

java.lang.Iterable接口的抽象方法:

public Iterator iterator(): 获取对应的迭代器,用来遍历数组或集合中的元素的。
代码示例:

让昨天我们自定义的动态数组支持foreach遍历

自定义动态数组:

import java.util.Arrays;
import java.util.Iterator;
import java.util.NoSuchElementException;

public class MyArrayList<E> implements Iterable<E>{
	private Object[] all;
	private int total;
	
	public MyArrayList(){
		all = new Object[5];
	}

	public void add(E e) {
		ensureCapacityEnough();
		all[total++] = e;
	}

	private void ensureCapacityEnough() {
		if(total >= all.length){
			all = Arrays.copyOf(all, all.length*2);
		}
	}

	//...省略其他代码

	@Override
	public Iterator<E> iterator() {
		return new Itr();
	}
	
	private class Itr implements Iterator<E>{
		int cursor;

		@Override
		public boolean hasNext() {
			return cursor<=total;
		}

		@SuppressWarnings("unchecked")
		@Override
		public E next() {
			return (E) all[cursor++];
		}
		
	}
}

测试类

public class TestForeach {
	public static void main(String[] args) {
		MyArrayList<String> my = new MyArrayList<String>();
		my.add("hello");
		my.add("java");
		my.add("world");
		my.add("atguigu");
		my.add("list");
		my.add("data");
		
		for (String string : my) {
			System.out.println(string);
		}
	}
}

同理,因为foreach本质上就是使用Iterator迭代器进行遍历的,所以也不要在foreach遍历的过程使用Collection的remove()方法。否则,要么报异常java.util.ConcurrentModificationException,要么行为不确定。

Java中modCount的用法,快速失败(fail-fast)机制

当使用foreach或Iterator迭代器遍历集合时,同时调用迭代器自身以外的方法修改了集合的结构,例如调用集合的add和remove方法时,就会报ConcurrentModificationException。

如果在Iterator、ListIterator迭代器创建后的任意时间从结构上修改了集合(通过迭代器自身的 remove 或 add 方法之外的任何其他方式),则迭代器将抛出 ConcurrentModificationException。因此,面对并发的修改,迭代器很快就完全失败,而不是冒着在将来不确定的时间任意发生不确定行为的风险。

这样设计是因为,迭代器代表集合中某个元素的位置,内部会存储某些能够代表该位置的信息。当集合发生改变时,该信息的含义可能会发生变化,这时操作迭代器就可能会造成不可预料的事情。因此,果断抛异常阻止,是最好的方法。这就是Iterator迭代器的快速失败(fail-fast)机制。

注意,迭代器的快速失败行为不能得到保证,一般来说,存在不同步的并发修改时,不可能作出任何坚决的保证。快速失败迭代器尽最大努力抛出 ConcurrentModificationException。因此,编写依赖于此异常的程序的方式是错误的,正确做法是:*迭代器的快速失败行为应该仅用于检测 bug。*例如:

	@Test
	public void test02() {
		ArrayList<String> list = new ArrayList<>();
		list.add("hello");
		list.add("java");
		list.add("atguigu");
		list.add("world");
		
        //以下代码没有发生ConcurrentModificationException异常
		Iterator<String> iterator = list.iterator();
		while(iterator.hasNext()){
			String str = iterator.next();
			
			if("atguigu".endsWith(str)){
				list.remove(str);
			}
		}
	}

那么如何实现快速失败(fail-fast)机制的呢?

在ArrayList等集合类中都有一个modCount变量。它用来记录集合的结构被修改的次数。

当我们给集合添加和删除操作时,会导致modCount++。

然后当我们用Iterator迭代器遍历集合时,创建集合迭代器的对象时,用一个变量记录当前集合的modCount。例如:int expectedModCount = modCount;,并且在迭代器每次next()迭代元素时,都要检查 expectedModCount != modCount,如果不相等了,那么说明你调用了Iterator迭代器以外的Collection的add,remove等方法,修改了集合的结构,使得modCount++,值变了,就会抛出ConcurrentModificationException。

下面以AbstractList和ArrayList.Itr迭代器为例进行源码分析:

AbstractList类中声明了modCount变量:

    /**
     * The number of times this list has been <i>structurally modified</i>.
     * Structural modifications are those that change the size of the
     * list, or otherwise perturb it in such a fashion that iterations in
     * progress may yield incorrect results.
     *
     * <p>This field is used by the iterator and list iterator implementation
     * returned by the {@code iterator} and {@code listIterator} methods.
     * If the value of this field changes unexpectedly, the iterator (or list
     * iterator) will throw a {@code ConcurrentModificationException} in
     * response to the {@code next}, {@code remove}, {@code previous},
     * {@code set} or {@code add} operations.  This provides
     * <i>fail-fast</i> behavior, rather than non-deterministic behavior in
     * the face of concurrent modification during iteration.
     *
     * <p><b>Use of this field by subclasses is optional.</b> If a subclass
     * wishes to provide fail-fast iterators (and list iterators), then it
     * merely has to increment this field in its {@code add(int, E)} and
     * {@code remove(int)} methods (and any other methods that it overrides
     * that result in structural modifications to the list).  A single call to
     * {@code add(int, E)} or {@code remove(int)} must add no more than
     * one to this field, or the iterators (and list iterators) will throw
     * bogus {@code ConcurrentModificationExceptions}.  If an implementation
     * does not wish to provide fail-fast iterators, this field may be
     * ignored.
     */
    protected transient int modCount = 0;

modCount是这个list被结构性修改的次数。结构性修改是指:改变list的size大小,或者,以其他方式改变他导致正在进行迭代时出现错误的结果。

这个字段用于迭代器和列表迭代器的实现类中,由迭代器和列表迭代器方法返回。如果这个值被意外改变,这个迭代器将会抛出 ConcurrentModificationException的异常来响应:next,remove,previous,set,add 这些操作。在迭代过程中,他提供了fail-fast行为而不是不确定行为来处理并发修改。

子类使用这个字段是可选的,如果子类希望提供fail-fast迭代器,它仅仅需要在add(int, E),remove(int)方法(或者它重写的其他任何会结构性修改这个列表的方法)中添加这个字段。调用一次add(int,E)或者remove(int)方法时必须且仅仅给这个字段加1,否则迭代器会抛出伪装的ConcurrentModificationExceptions错误。如果一个实现类不希望提供fail-fast迭代器,则可以忽略这个字段。

Arraylist的Itr迭代器

   private class Itr implements Iterator<E> {
        int cursor;      
        int lastRet = -1; 
        int expectedModCount = modCount;//在创建迭代器时,expectedModCount初始化为当前集合的modCount的值

        public boolean hasNext() {
            return cursor != size;
        }

        @SuppressWarnings("unchecked")
        public E next() {
            checkForComodification();//校验expectedModCount与modCount是否相等
            int i = cursor;
            if (i >= size)
                throw new NoSuchElementException();
            Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length)
                throw new ConcurrentModificationException();
            cursor = i + 1;
            return (E) elementData[lastRet = i];
        }
       	final void checkForComodification() {
            if (modCount != expectedModCount)//校验expectedModCount与modCount是否相等
                throw new ConcurrentModificationException();//不相等,抛异常
        }
}

List集合

我们掌握了Collection接口的使用后,再来看看Collection接口中的子类,他们都具备那些特性呢?

List接口介绍

java.util.List接口继承自Collection接口,是单列集合的一个重要分支,习惯性地会将实现了List接口的对象称为List集合。
List接口特点:

  1. List集合所有的元素是以一种线性方式进行存储的,例如,存元素的顺序是11、22、33。那么集合中,元素的存储就是按照11、22、33的顺序完成的)
  2. 它是一个元素存取有序的集合。即元素的存入顺序和取出顺序一致。
  3. 它是一个带有索引的集合,通过索引就可以精确的操作集合中的元素(与数组的索引是一个道理)。
  4. 集合中可以有重复的元素,通过元素的equals方法,来比较是否为重复的元素。

List集合类中元素有序、且可重复。这就像银行门口客服,给每一个来办理业务的客户分配序号:第一个来的是“张三”,客服给他分配的是0;第二个来的是“李四”,客服给他分配的1;以此类推,最后一个序号应该是“总人数-1”。

注意:List集合关心元素是否有序,而不关心是否重复,请大家记住这个原则。例如“张三”可以领取两个号。

List集合的常用方法

List作为Collection集合的子接口,不但继承了Collection接口中的全部方法,而且还增加了一些根据元素索引来操作集合的特有方法,如下:

List除了从Collection集合继承的方法外,List 集合里添加了一些根据索引来操作集合元素的方法。

1、添加元素

void add(int index, E ele)
boolean addAll(int index, Collection<? extends E> eles)
2、获取元素

E get(int index)
List subList(int fromIndex, int toIndex)
3、获取元素索引

int indexOf(Object obj)
int lastIndexOf(Object obj)
4、删除和替换元素

E remove(int index)
E set(int index, E ele)
List集合特有的方法都是跟索引相关

public class ListDemo {
    public static void main(String[] args) {
		// 创建List集合对象
    	List<String> list = new ArrayList<String>();
    	
    	// 往 尾部添加 指定元素
    	list.add("图图");
    	list.add("小美");
    	list.add("不高兴");
    	
    	System.out.println(list);
    	// add(int index,String s) 往指定位置添加
    	list.add(1,"没头脑");
    	
    	System.out.println(list);
    	// String remove(int index) 删除指定位置元素  返回被删除元素
    	// 删除索引位置为2的元素 
    	System.out.println("删除索引位置为2的元素");
    	System.out.println(list.remove(2));
    	
    	System.out.println(list);
    	
    	// String set(int index,String s)
    	// 在指定位置 进行 元素替代(改) 
    	// 修改指定位置元素
    	list.set(0, "三毛");
    	System.out.println(list);
    	
    	// String get(int index)  获取指定位置元素
    	
    	// 跟size() 方法一起用  来 遍历的 
    	for(int i = 0;i<list.size();i++){
    		System.out.println(list.get(i));
    	}
    	//还可以使用增强for
    	for (String string : list) {
			System.out.println(string);
		}  	
	}
}

在JavaSE中List名称的类型有两个,一个是java.util.List集合接口,一个是java.awt.List图形界面的组件,别导错包了。

List的实现类

ArrayList集合
java.util.ArrayList集合数据存储的结构是数组结构。元素增删慢,查找快,由于日常开发中使用最多的功能为查询数据、遍历数据,所以ArrayList是最常用的集合。

许多程序员开发时非常随意地使用ArrayList完成任何需求,并不严谨,这种用法是不提倡的。

Vector集合
ArrayList与Vector的区别?

它们的底层物理结构都是数组,我们称为动态数组。

  • ArrayList是新版的动态数组,线程不安全,效率高,Vector是旧版的动态数组,线程安全,效率低。
  • 动态数组的扩容机制不同,ArrayList扩容为原来的1.5倍,Vector扩容增加为原来的2倍。
  • 数组的初始化容量,如果在构建ArrayList与Vector的集合对象时,没有显式指定初始化容量,那么Vector的内部数组的初始容量默认为10,而ArrayList在JDK1.6及之前的版本也是10,而JDK1.7之后的版本ArrayList初始化为长度为0的空数组,之后在添加第一个元素时,再创建长度为10的数组。
  • Vector因为版本古老,支持Enumeration迭代器。但是该迭代器不支持快速失败。而Iterator和ListIterator迭代器支持快速失败。如果在迭代器创建后的任意时间从结构上修改了向量(通过迭代器自身的 remove 或 add 方法之外的任何其他方式),则迭代器将抛出ConcurrentModificationException。因此,面对并发的修改,迭代器很快就完全失败,而不是冒着在将来不确定的时间任意发生不确定行为的风险。

LinkedList集合
java.util.LinkedList集合数据存储的结构是链表结构。方便元素添加、删除的集合。
LinkedList源码中add()和remove()方法 链接:https://blog.csdn.net/weixin_44847885/article/details/121076118

除了实现 List 接口外,LinkedList 类还为在列表的开头及结尾 get、remove 和 insert 元素提供了统一的命名方法。这些操作允许将链接列表用作堆栈、队列或双端队列。

LinkedList是一个双向链表
在这里插入图片描述

JDK1.6之后LinkedList实现了Deque接口。双端队列也可用作 LIFO(后进先出)堆栈。如果要使用堆栈结构的集合,可以考虑使用LinkedList,而不是Stack。

堆栈方法	等效Deque方法
push(e)	    addFirst(e)
pop()	    removeFirst()
peek()	    peekFirst()

Stack与LinkedList都能作为栈结构,对外表现的功能效果是一样,但是它们的物理结构不同,Stack的物理结构是顺序结构的数组,而LinkedList的物理结构是链式结构的双向链表。我们推荐使用LinkedList。

用作队列时,将得到 FIFO(先进先出)行为。将元素添加到双端队列的末尾,从双端队列的开头移除元素。

Queue 方法	等效 Deque 方法
add(e)	     addLast(e)
offer(e)	 offerLast(e)
remove()	 removeFirst()
poll()	     pollFirst()
element()	 getFirst()
peek()	     peekFirst()
package List;

import org.junit.jupiter.api.Test;

import java.util.LinkedList;

public class LinkList {

    @Test
    public void Stack(){
        LinkedList<Integer> list = new LinkedList<>();
        //入栈
        list.addFirst(1);
        list.addFirst(2);
        list.addFirst(3);

        //出栈: LIFO(后进先出)
        System.out.println(list.removeFirst());//3
        System.out.println(list.removeFirst());//2
        System.out.println(list.removeFirst());//1
        //栈空了,会报异常java.util.NoSuchElementException
//        System.out.println(list.removeFirst());

    }
//    堆栈方法	等效Deque方法
//    push(e)	addFirst(e)
//    pop()	    removeFirst()
//    peek()	peekFirst()

    @Test
    public void queue(){
        LinkedList<Integer> list = new LinkedList<>();
        //入队
        list.addLast(1);
        list.addLast(2);
        list.addLast(3);

        //出队, FIFO(先进先出)
        System.out.println(list.pollFirst());//1
        System.out.println(list.pollFirst());//2
        System.out.println(list.pollFirst());//3
        //队空了,返回null
        System.out.println(list.pollFirst());//null
    }
//    Queue方法	  Deque方法
//    add(e)	 addLast(e)
//    offer(e)	 offerLast(e)
//    remove()	 removeFirst()
//    poll()	 pollFirst()
//    element()	 getFirst()
//    peek()	 peekFirst()
}

remove()默认删除首节点

@Test
    public void Test_LinkedList(){
        LinkedList<Integer> list = new LinkedList<>();
        list.add(1);
        list.add(2);
        list.add(3);
        System.out.println(list);
        ArrayList<Integer> tmp = new ArrayList<Integer>();
        for(int i=0;i<list.size();++i) {
            Integer t = list.remove();//默认先进先出
            tmp.add(t);
            System.out.println(tmp);
        }
    }
//            3
//            2
//            1
//            1
//            2
//            3

(1)这里用无参的remove()进行测试

 LinkedList<Object> objects = new LinkedList<>();
        objects.add(1);
        objects.add(8);
        
        objects.remove();

(2)remove()调了removeFirst(),让对象f指向first首节点,所以remove()无参方法删除的是首节点

public E remove() {
        return removeFirst();
    }
public E removeFirst() {
        final Node<E> f = first;
        if (f == null)
            throw new NoSuchElementException();
        return unlinkFirst(f);
    }

(3)unlinkFirst(Node f)方法是remove()删除首节点的核心方法,先取出首节点存储的数据item,然后f.next指向下一个节点,把数据置空,如果没有下一个节点就直接回收,first = next(也就是first = first.next)让first首节点指向下一个节点,将下一个节点的prev置空,最后返回之前取出的item,这套流程简单理解为让frist指向下一个节点,然后把原首节点全部置空,移除

/**
     * Unlinks non-null first node f.
     */
    private E unlinkFirst(Node<E> f) {
        // assert f == first && f != null;
        final E element = f.item;
        final Node<E> next = f.next;
        f.item = null;
        f.next = null; // help GC
        first = next;
        if (next == null)
            last = null;
        else
            next.prev = null;
        size--;
        modCount++;
        return element;
    }

ListIterator

List 集合额外提供了一个 listIterator() 方法,该方法返回一个 ListIterator 对象, ListIterator 接口继承了 Iterator 接口,提供了专门操作 List 的方法:

void add():通过迭代器添加元素到对应集合
void set(Object obj):通过迭代器替换正迭代的元素
void remove():通过迭代器删除刚迭代的元素
boolean hasPrevious():如果以逆向遍历列表,往前是否还有元素。
Object previous():返回列表中的前一个元素。
int previousIndex():返回列表中的前一个元素的索引
boolean hasNext()
Object next()
int nextIndex()

学生类

package List;

public class Student {
    private int id;
    private String name;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Student(int id, String name) {
        this.id = id;
        this.name = name;
    }

}

测试类

@Test
    public void test_ListIterator(){
        List<Student> c = new ArrayList<>();
        c.add(new Student(1,"张三"));
        c.add(new Student(2,"李四"));
        c.add(new Student(3,"王五"));
        c.add(new Student(4,"赵六"));
        c.add(new Student(5,"钱七"));

        //从指定位置往前遍历
        ListIterator<Student> listIterator = c.listIterator(c.size());
        while(listIterator.hasPrevious()){
            Student previous = listIterator.previous();
            System.out.println(previous.getName());
        }
    }

Set集合

Set接口是Collection的子接口,set接口没有提供额外的方法。但是比Collection接口更加严格了。

Set 集合不允许包含相同的元素,如果试把两个相同的元素加入同一个 Set 集合中,则添加操作失败。

Set集合支持的遍历方式和Collection集合一样:foreach和Iterator。

Set的常用实现类有:HashSet、TreeSet、LinkedHashSet。

HashSet

HashSet 是 Set 接口的典型实现,大多数时候使用 Set 集合时都使用这个实现类。

java.util.HashSet底层的实现其实是一个java.util.HashMap支持,然后HashMap的底层物理实现是一个Hash表。(什么是哈希表,下一节在HashMap小节在细讲,这里先不展开)

HashSet 按 Hash 算法来存储集合中的元素,因此具有很好的存取和查找性能。HashSet 集合判断两个元素相等的标准:两个对象通过 hashCode() 方法比较相等,并且两个对象的 equals() 方法返回值也相等。因此,存储到HashSet的元素要重写hashCode和equals方法。

示例代码:定义一个Employee类,该类包含属性:name, birthday,其中 birthday 为 MyDate类的对象;MyDate为自定义类型,包含年、月、日属性。要求 name和birthday一样的视为同一个员工。

public class MyDate {
	private int year;
	private int month;
	private int day;
	public MyDate(int year, int month, int day) {
		super();
		this.year = year;
		this.month = month;
		this.day = day;
	}
	public MyDate() {
		super();
	}
	public int getYear() {
		return year;
	}
	public void setYear(int year) {
		this.year = year;
	}
	public int getMonth() {
		return month;
	}
	public void setMonth(int month) {
		this.month = month;
	}
	public int getDay() {
		return day;
	}
	public void setDay(int day) {
		this.day = day;
	}
	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + day;
		result = prime * result + month;
		result = prime * result + year;
		return result;
	}
	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		MyDate other = (MyDate) obj;
		if (day != other.day)
			return false;
		if (month != other.month)
			return false;
		if (year != other.year)
			return false;
		return true;
	}
	@Override
	public String toString() {
		return year + "-" + month + "-" + day;
	}
}

public class Employee {
	private String name;
	private MyDate birthday;
	public Employee(String name, MyDate birthday) {
		super();
		this.name = name;
		this.birthday = birthday;
	}
	public Employee() {
		super();
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public MyDate getBirthday() {
		return birthday;
	}
	public void setBirthday(MyDate birthday) {
		this.birthday = birthday;
	}
	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((birthday == null) ? 0 : birthday.hashCode());
		result = prime * result + ((name == null) ? 0 : name.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;
		Employee other = (Employee) obj;
		if (birthday == null) {
			if (other.birthday != null)
				return false;
		} else if (!birthday.equals(other.birthday))
			return false;
		if (name == null) {
			if (other.name != null)
				return false;
		} else if (!name.equals(other.name))
			return false;
		return true;
	}
	@Override
	public String toString() {
		return "姓名:" + name + ", 生日:" + birthday;
	}
}

import java.util.HashSet;

public class TestHashSet {
	@SuppressWarnings("all")
	public static void main(String[] args) {
		HashSet<Employee> set = new HashSet<>();
		set.add(new Employee("张三", new MyDate(1990,1,1)));
		//重复元素无法添加,因为MyDate和Employee重写了hashCode和equals方法
		set.add(new Employee("张三", new MyDate(1990,1,1)));
		set.add(new Employee("李四", new MyDate(1992,2,2)));
		
		for (Employee object : set) {
			System.out.println(object);
		}
	}
}

LinkedHashSet

LinkedHashSet是HashSet的子类,它在HashSet的基础上,在结点中增加两个属性before和after维护了结点的前后添加顺序。java.util.LinkedHashSet,它是链表和哈希表组合的一个数据存储结构。LinkedHashSet插入性能略低于 HashSet,但在迭代访问 Set 里的全部元素时有很好的性能。

LinkedHashSet<String> set = new LinkedHashSet<>();
set.add("张三");
set.add("李四");
set.add("王五");
set.add("张三");
		
System.out.println("元素个数:" + set.size());
for (String name : set) {
	System.out.println(name);
}
运行结果:
元素个数:3
张三
李四
王五

TreeSet

底层结构:里面维护了一个TreeMap,都是基于红黑树实现的!

特点:
1、不允许重复
2、实现排序
自然排序或定制排序

如何实现去重的?

如果使用的是自然排序,则通过调用实现的compareTo方法
如果使用的是定制排序,则通过调用比较器的compare方法

如何排序?

方式一:自然排序
让待添加的元素类型实现Comparable接口,并重写compareTo方法

方式二:定制排序
创建Set对象时,指定Comparator比较器接口,并实现compare方法

自然顺序
如果试图把一个对象添加到 TreeSet 时,则该对象的类必须实现 Comparable 接口。实现 Comparable 的类必须实现 compareTo(Object obj) 方法,两个对象即通过 compareTo(Object obj) 方法的返回值来比较大小。对于 TreeSet 集合而言,它判断两个对象是否相等的唯一标准是:两个对象通过 compareTo(Object obj) 方法比较返回值为0。

定制排序
如果放到TreeSet中的元素的自然排序(Comparable)规则不符合当前排序需求时,或者元素的类型没有实现Comparable接口。那么在创建TreeSet时,可以单独指定一个Comparator的对象。使用定制排序判断两个元素相等的标准是:通过Comparator比较两个元素返回了0。

package set;

import org.junit.Test;

import java.util.Comparator;
import java.util.TreeSet;

public class TestTreeSet {
    @Test
    //自然排序
    public void test1(){
        TreeSet<String> set = new TreeSet<>();
        set.add("zhangsan");  //String它实现了java.lang.Comparable接口
        set.add("lisi");
        set.add("wangwu");
        set.add("zhangsan");

        System.out.println("元素个数:" + set.size());
        for (String str : set) {
            System.out.println(str);
        }
    }

    @Test
    //定制排序
    public void test3(){
        TreeSet<Student> set = new TreeSet(new Comparator<Student>(){

            @Override
            public int compare(Student o1, Student o2) {
                return o1.getId() - o2.getId();
            }

        });
        set.add(new Student(3,"张三"));
        set.add(new Student(1,"李四"));
        set.add(new Student(2,"王五"));
        set.add(new Student(3,"张三风"));

        System.out.println("元素个数:" + set.size());
        for (Student stu : set) {
            System.out.println(stu);
        }
    }


}

package set;

public class Student{
    private int id;
    private String name;
    public Student(int id, String name) {
        super();
        this.id = id;
        this.name = name;
    }
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    //......这里省略了name属性的get/set
    @Override
    public String toString() {
        return "Student [id=" + id + ", name=" + name + "]";
    }
}

Map集合

概述

现实生活中,我们常会看到这样的一种集合:IP地址与主机名,身份证号与个人,系统用户名与系统用户对象等,这种一一对应的关系,就叫做映射。Java提供了专门的集合类用来存放这种对象关系的对象,即java.util.Map<K,V>接口。

我们通过查看Map接口描述,发现Map<K,V>接口下的集合与Collection接口下的集合,它们存储数据的形式不同。

  • Collection中的集合,元素是孤立存在的(理解为单身),向集合中存储元素采用一个个元素的方式存储。

  • Map中的集合,元素是成对存在的(理解为夫妻)。每个元素由键与值两部分组成,通过键可以找对所对应的值。

  • Collection中的集合称为单列集合,Map中的集合称为双列集合。

  • 需要注意的是,Map中的集合不能包含重复的键,值可以重复;每个键只能对应一个值(这个值可以是单个值,也可以是个数组或集合值)。

在这里插入图片描述

Map常用方法

1、添加操作

V put(K key,V value)
void putAll(Map<? extends K,? extends V> m)

2、删除
void clear()
V remove(Object key)

3、元素查询的操作
V get(Object key)
boolean containsKey(Object key)
boolean containsValue(Object value)
boolean isEmpty()

4、元视图操作的方法:
Set keySet()
Collection values()
Set<Map.Entry<K,V>> entrySet()

5、其他方法
int size()

@Test
    public void Test01() {
        //创建 map对象
        HashMap<String, String> map = new HashMap<String, String>();

        //添加元素到集合
        map.put("黄晓明", "杨颖");
        map.put("文章", "马伊琍");
        map.put("邓超", "孙俪");
        System.out.println(map);

        //String remove(String key)
        System.out.println(map.remove("邓超"));
        System.out.println(map);

        // 想要查看 黄晓明的媳妇 是谁
        System.out.println(map.get("黄晓明"));
        System.out.println(map.get("邓超"));
    }

tips:使用put方法时,若指定的键(key)在集合中没有,则没有这个键对应的值,返回null,并把指定的键值添加到集合中;
若指定的键(key)在集合中存在,则返回值为集合中键对应的值(该值为替换前的值),并把指定键所对应的值,替换成指定的新值。

Map集合的遍历

Collection集合的遍历:(1)foreach(2)通过Iterator对象遍历

Map的遍历,不能支持foreach,因为Map接口没有继承java.lang.Iterable接口,也没有实现Iterator iterator()方法。只能用如下方式遍历:

(1)分开遍历:
单独遍历所有key
单独遍历所有value
(2)成对遍历:
遍历的是映射关系Map.Entry类型的对象,Map.Entry是Map接口的内部接口。每一种Map内部有自己的Map.Entry的实现类。在Map中存储数据,实际上是将Key---->value的数据存储在Map.Entry接口的实例中,再在Map集合中插入Map.Entry的实例化对象,如图示:
在这里插入图片描述

@Test
    public void Test_Iterate() {
        HashMap<String,String> map = new HashMap<>();
        map.put("许仙", "白娘子");
        map.put("董永", "七仙女");
        map.put("牛郎", "织女");
        map.put("许仙", "小青");

        System.out.println("所有的key:");
        Set<String> keySet = map.keySet();
        for (String key : keySet) {
            System.out.println(key);
        }

        System.out.println("所有的value:");
        Collection<String> values = map.values();
        for (String value : values) {
            System.out.println(value);
        }

        System.out.println("所有的映射关系");
        Set<Map.Entry<String,String>> entrySet = map.entrySet();
        for (Map.Entry<String,String> entry : entrySet) {
//			System.out.println(entry);
            System.out.println(entry.getKey()+"->"+entry.getValue());
        }

    }

Map的实现类

Map接口的常用实现类:HashMap、TreeMap、LinkedHashMap和Properties。其中HashMap是 Map 接口使用频率最高的实现类。
1、HashMap和Hashtable的区别与联系
HashMap和Hashtable都是哈希表。

HashMap和Hashtable判断两个 key 相等的标准是:两个 key 的hashCode 值相等,并且 equals() 方法也返回 true。因此,为了成功地在哈希表中存储和获取对象,用作键的对象必须实现 hashCode 方法和 equals 方法。

Hashtable是线程安全的,任何非 null 对象都可以用作键或值。

HashMap是线程不安全的,并允许使用 null 值和 null 键。

示例代码:添加员工姓名为key,薪资为value

    @Test
    public void Test_HashMap(){
        HashMap<String,Double> map = new HashMap<>();
        map.put("张三", 10000.0);
        //key相同,新的value会覆盖原来的value
        //因为String重写了hashCode和equals方法
        map.put("张三", 12000.0);
        map.put("李四", 14000.0);
        //HashMap支持key和value为null值
        String name = null;
        Double salary = null;
        map.put(name, salary);

        Set<Map.Entry<String, Double>> entrySet = map.entrySet();
        for (Map.Entry<String, Double> entry : entrySet) {
            System.out.println(entry);
        }
//        null=null
//        李四=14000.0
//        张三=12000.0

2、LinkedHashMap
LinkedHashMap 是 HashMap 的子类。此实现与 HashMap 的不同之处在于,后者维护着一个运行于所有条目的双重链接列表。此链接列表定义了迭代顺序,该迭代顺序通常就是将键插入到映射中的顺序(插入顺序)。

示例代码:添加员工姓名为key,薪资为value

    @Test
    public void Test_LickedHashMap(){
        LinkedHashMap<String,Double> map = new LinkedHashMap<>();
        map.put("张三", 10000.0);
        //key相同,新的value会覆盖原来的value
        //因为String重写了hashCode和equals方法
        map.put("张三", 12000.0);
        map.put("李四", 14000.0);
        //HashMap支持key和value为null值
        String name = null;
        Double salary = null;
        map.put(name, salary);

        Set<Map.Entry<String, Double>> entrySet = map.entrySet();
        for (Map.Entry<String, Double> entry : entrySet) {
            System.out.println(entry);
        }
    }
//    张三=12000.0
//    李四=14000.0
//    null=null

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

代码示例:添加员工姓名为key,薪资为value

package Map;

import java.util.Comparator;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeMap;

import org.junit.Test;

public class TestTreeMap {
    @Test
    public void test1() {
        TreeMap<String,Integer> map = new TreeMap<>();
        map.put("Jack", 11000);
        map.put("Alice", 12000);
        map.put("zhangsan", 13000);
        map.put("baitao", 14000);
        map.put("Lucy", 15000);

        //String实现了Comparable接口,默认按照Unicode编码值排序
        Set<Entry<String, Integer>> entrySet = map.entrySet();
        for (Entry<String, Integer> entry : entrySet) {
            System.out.println(entry);
        }
//        Alice=12000
//        Jack=11000
//        Lucy=15000
//        baitao=14000
//        zhangsan=13000
    }
    @Test
    public void test2() {
        //指定定制比较器Comparator,按照Unicode编码值排序,但是忽略大小写
        TreeMap<String,Integer> map = new TreeMap<>(new Comparator<String>() {

            @Override
            public int compare(String o1, String o2) {
                return o1.compareToIgnoreCase(o2);
            }
        });
        map.put("Jack", 11000);
        map.put("Alice", 12000);
        map.put("zhangsan", 13000);
        map.put("baitao", 14000);
        map.put("Lucy", 15000);

        Set<Entry<String, Integer>> entrySet = map.entrySet();
        for (Entry<String, Integer> entry : entrySet) {
            System.out.println(entry);
        }
//        Alice=12000
//        baitao=14000
//        Jack=11000
//        Lucy=15000
//        zhangsan=13000
    }
}


4、Properties
Properties 类是 Hashtable 的子类,Properties 可保存在流中或从流中加载。属性列表中每个键及其对应值都是一个字符串。

存取数据时,建议使用setProperty(String key,String value)方法和getProperty(String key)方法。

代码示例:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值