Java高级编程之类集框架(集合类)

类集框架简介

    在实际的开发中没有任何一项开发可以离开数组,但是传统的数组使用起来非常的繁琐,而且长度是其致命伤,正式因为长度问题,所以不可能大范围的使用,但是开发之中又离不开数组,所以就只能依靠一些数据结构来实现动态数组,而其中最为重要的两个结构:链表、树。但是面对这些数据结构的实现又不得不面对如下问题:

  • 数据结构的代码实现困难,对于一般的开发者是无法进行使用的
  • 对于链表或二叉树当进行更新处理的时候的维护是非常麻烦的
  • 对于链表或者二叉树还需要尽可能保证其操作的性能

​     因此JDK1.2开始Java引入了类集,主要是对常见的数据结构进行完整的实现包装并且提供一系列的接口与实现子类来帮助用户减少数据结构所带来的开发困难,但是最初的类集实现由于Java本身的技术对于数据的控制并不严格,全部采用了Object类型进行数据接收。而在JDK1.5之后由于泛型技术的推广,类集本身也得到了来个良好的改进,可以直接利用泛型来保存相同类型的数据,并且随着数据量的不断增加,从JDK1.8开始类集中的实现算法也得到良好的性能提升。

Collection接口简介

    java.util.Collection是单值集合操作的最大的父接口,在该接口中定义有所有的单值数据的处理操作,在这个接口中定义有如下的操作方法:

No 方法名称 类型
01 public boolean add(E e) 普通 向集合保存数据
02 public boolean addAll(Collection c) 普通 追加一组数据
03 public void clear() 普通 清空集合,让根节点为空,同时执行GC处理
04 public boolean contains(Object 0) 普通 查询数据是否存在,需要equals方法支持
05 pbulic boolean remove(Object 0) 普通 数据删除,需要equals方法
06 public int size() 普通 获取数据长度
07 public Object[] toArray() 普通 将集合变为对象数组返回
08 public Iterator iterator() 普通 将集合变成Iterator接口

在进行集合操作时有两个方法最常用:add()、iterator()

​    在JDK1.5之前,Collection只是一个独立的接口,JDK1.5之后提供了Iterable父接口,并且在JDK1.8的之后针对于Iterable接口也得到了一些扩充。另外,在jDK1.2~JDK1.4的时代里面如果想要进行集合的使用往往会直接操作Collection。但是从JDK1.5开始,更多情况下都是Collection的两个子接口:

  • 允许重复的List子接口
  • 不允许重复的Set子接口

List集合

List接口简介

​    List是Collection的子接口,其最大特点是允许保存有重复元素数据,该接口的定义如下:

public interface List<E>
extends Collection<E>

​ List接口对Collection接口进行了方法的扩充:

NO 方法名称 类型
1 public E set(int index, E element) 普通 获取指定索引上的数据
2 public E set(int index, E element) 普通 修改指定索引数据
3 public ListIterator listIterator() 普通 返回ListIlterator迭代器

​ List接口有三个常用的子类:

  • ArrayList
  • Vector
  • LinkedList

​    从JDK1.9之后List子接口中追加了静态方法,以方便用户处理。例如List.of()、Map.of()、Set.of()用于简单的不可变的元素的集合。

ArrayList子类

​    ArrayList是List子接口中使用最多的一个子类。对ArrayLIst相关定义以及源代码进行分析。

​ Java中ArrayList的定义如下:

public class ArrayList<E>
extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, Serializable

继承关系
Java高级编程之类集框架(集合类)_第1张图片

import java.util.ArrayList;
import java.util.List;
public class JavaDemo{
    public static void main(String[] args) {
        List<String> all = new ArrayList<>();
        all.add("Hello");
        all.add("Hello");
        all.add("world");
        System.out.println(all);
    }
}

通过本程序可以发现List存储的特征:

  • 保存的顺序就是其存储顺序
  • List集合里面允许存在有重复数据

​     以上的程序虽然实现了集合的输出,但是这种输出的操作是直接使用了每一个类提供的toString 方法,为了方便进行输出处理在JDK1.8之后Iterable父接口之中定义了一个forEach方法。

default void forEach(Consumer<? super T> action)

eg:利用forEach()方法输出(不是标准输出)

import java.util.ArrayList;
import java.util.List;
public class JavaDemo{
    public static void main(String[] args) {
        List<String> all = new ArrayList<>();
        all.add("Hello");
        all.add("Hello");
        all.add("world");
        all.forEach((str)->{
            System.out.println(str);
        });
    }

    }

​ 以方法的功能为例,ArrayList里面操作支持与之前编写的链表的形式是非常相似的,但是他并不是使用链表来是实现的。ArrayList封装的是一个数组。

​ ArrayList的构造方法:

  • 无参构造public ArrayList()

    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
    public ArrayList() {
            this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
        }
    
  • 有参构造public ArrayList(int initialCapacity)

    transient Object[] elementData  //transient不能被序列化
    public ArrayList(int initialCapacity) {
            if (initialCapacity > 0) {
                this.elementData = new Object[initialCapacity];
            } else if (initialCapacity == 0) {
                this.elementData = EMPTY_ELEMENTDATA;
            } else {
                throw new IllegalArgumentException("Illegal Capacity: "+
                                                   initialCapacity);
            }
        }
    

​     通过有参构造方法可以发现,在ArrayList里面包含的实际上是一个对象数组,如果进行数据追加的时候发现ArrayList集合里面保存的对象数组的长度不够的时候那么就会进行新的数组开辟,同时将原始的旧数组内容拷贝到新数组。

//jdk1.8
private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // minCapacity is usually close to size, so this is a win:
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

​    在实例化ArrayList类对象的时候并没有传递初始化的长度,则默认情况下会使用一个空数,但是在使用数据增加的时候发现数组容量不够了,则会判断当前增长的容量和默认的容量大小,使用较大的一个数值进行数组开辟,就可以得出一个结论:
在实例化ArrayList类对象的时候并没有传递初始化的长度,则默认情况下会使用一个空数,但是在使用数据增加的时候发现数组容量不够了,则会判断当前增长的容量和默认的容量大小,使用较大的一个数值进行数组开辟,就可以得出一个结论:

  1. 在JDK1.9之后:ArrayList默认构造只会使用默认的空数组,使用的时候才会开辟数组、默认开辟的长度为10。

  2. 在JDK1.9之前:ArrayList默认的构造实际上就会默认开辟大小为10的数组。

  3. 当ArrayList之中保存的容量不同的时候就会采用成倍的方式进行增长,原始长度为10,那么下次的增长就是20。以此类推估计出数据量有多少,如果超过10个,那么使用有参构造方法进行创建,以避免垃圾数组的产生

ArrayList保存自定义类对象

​​    在使用List保存自定义对象的时候如果要使用contains()和remove()方法,一定要保证类中已经覆写了equals()方法。

class Person{
    private String name;
    private int age;
    public Person(String name,int age){
        this.name=name;
        this.age=age;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (obj == null) return false;
        if(!(obj instanceof Person)) return false;
        Person person = (Person) obj;
        return this.name.equals(person.name) &&
               this.age==person.age;
    }

    
}

LinkedList子类

​ LinkList是基于链表实现的。

​ LinkList子类的定义:

public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, Serializable

继承关系

Java高级编程之类集框架(集合类)_第2张图片

​​    如果只是观察程序的功能LinkeList和ArrayList的使用是完全一样的,但是内部的实现机制是完全不一样的,LinkedList构造方法里没有提供像ArrayList那样的初始化大小的方法,而只是提供有无参构造方法“public LinkedList()”。

观察add方法的具体实现:

public boolean add(E e) {
        linkLast(e);
        return true;
    }

​​    在之前编写自定义链表的时候,是判断了传入数据是非为空的,如果为空则不进行判断。但是在LinkedList中并没有做这样的处理,而是所有数据都可以保存,而后调用linkLast()方法(在最后一个节点进行追加)。

void linkLast(E e) {
        final Node<E> l = last;
        final Node<E> newNode = new Node<>(l, e, null);
        last = newNode;
        if (l == null)
            first = newNode;
        else
            l.next = newNode;
        size++;
        modCount++;
    }

​​    在LinkedList类保存的数据都是利用Node节点进行封装处理,同时为了提高程序执行性能,每一次都会保存上一次追加的节点(最后一个节点),就可以在增加数据的时候避免递归处理。在增加数据的时候要进行数据保存个数的追加。

【面试题】ArrayList和LinkedList有什么区别:

  • ArrayList是数组实现的集合操作,而LinkedList是链表实现的集合操作。
  • 在使用List集合中的get()方法根据索引获取数据时,ArrayList的时间复杂度是O(1),而LinkedList的时间复杂度是O(n)。
  • ArrayList在使用时默认的 初始化对象数组长度是10

Vector子类

​​    Vector是一个原始古老的程序类,在JDK1.0j就已经提供,而后在JDK1.2由于很多开发这已经习惯使用Vector,而且很多系统也是基于Vector实现的,考虑到了其广泛型,所以类集框架就将其保存了下来。并且让其多实现了一个List接口。

public class Vector<E>
extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, Serializable

继承结构和ArrayList是相同的。

  public Vector() {
        this(10);
    }


 public Vector(Collection<? extends E> c) {
        elementData = c.toArray();
        elementCount = elementData.length;
        // c.toArray might (incorrectly) not return Object[] (see 6260652)
        if (elementData.getClass() != Object[].class)
            elementData = Arrays.copyOf(elementData, elementCount, Object[].class);
    }



​​    Vector类如果使用的是无参构造方法则一定会开辟一个10个长度的数组,而后面其余的实现操作和ArrayList是相同的。通过源代码的分析可以可以发现,Vector类中的操作方法都是synchronized同步处理,而ArrayList并没有进行同步处理。所以Vector类中方法在多线程中的访问是安全的,但是性能不如ArrayList高。

Set集合

Set接口简介

​​    Set集合也是Collection子接口,其最大的特点是不允许保存重复元素。

​ 在JDK1.9之前Set集合和Collection集合的定义并没有差别,Set集合继续使用了Collection接口种的方法进行操作,但是从JDK1.9之后,Set集合也像List集合一样扩充了一些static方法。

Set集合的定义

public interface Set<E>
extends Collection<E>

需要注意的是Set集合并没有像List集合一样扩充许多新的方法,不能使用get方法获得指定索引的数据
Java高级编程之类集框架(集合类)_第3张图片

​​    当使用of新方法时,若集合之中操作存在有重复元素,则会直接直接抛出异常。这与传统的Set方法不保存重复方法一致,只不过自己抛出了异常而已。

​ Set接口有两个常用的接口:

  • HashSet
  • TreeSet

HashSet子类

​​    HashSet是Set接口种使用最多的一个子类,其最大的特点是保存的数据是无序的。

HashSet子类继承关系如下:

ublic class HashSet<E>

extends AbstractSet<E>

implements Set<E>Cloneable.Serializable

这种继承的形式和之前的 ArrayList 是非常相似的,那么现在来观察一下类的继承结构:
HashSet子类的操作特点:

  • 不允许保存重复元素
  • HashSet种保存的元素是无序的

TreeSet子类

​​    与HashSet子类最大的不同是,TreeSet集合里所保存的数据是有序的。

public class TreeSet<E>
extends AbstractSet<E>
implements NavigableSet<E>, Cloneable, Serializable

Java高级编程之类集框架(集合类)_第4张图片

当使用TreeSet保存数据的时候,所有数据都将按照数据的升序进行自动排序处理。

TreeSet排序说明

​​    经过分析之后TreeSet子类中保存的数据是允许排序的,但是这个类必须要实现Comparable接口,只有实现了该接口,才能确认对象的大小关系。这是因为TreeSet子类是通过TreeMap子类实现的集合数据的存储,而TreeMap需要根据Comparable来确定大小关系。

​ 使用一个自定义的类来实现排序的处理操作:

import java.util.HashSet;
import java.util.Set;
import java.util.TreeSet;
class Person implements Comparable<Person>{
    private String name;
    private int age;
    public Person(String name,int age){
        this.name=name;
        this.age=age;
    }

    @Override
    public String toString() {
        return "姓名="+this.name+"、年龄="+this.age;
    }
    @Override
    //若不覆写该方法则会出现异常Exception in thread "main" java.lang.ClassCastException: Person cannot be cast to java.lang.Comparable
    public int compareTo(Person person){
        if(this.age<person.age){
            return -1;
        }else if(this.age>person.age){
            return 1;
        }else{
            return this.name.compareTo(person.name);
        }


    }
}

public class JavaDemo{

    public static void main(String[] args) {
        TreeSet<Person> strings = new TreeSet<Person>();
        strings.add(new Person("小王",20));
        strings.add(new Person("小王",20));
        strings.add(new Person("小张",20));
        strings.add(new Person("小李",20));
        strings.forEach(System.out::println);
    }
}

​ 在使用自定义对象进行比较处理的时候一定要将该类中的所有属性都依次进行大小关系的匹配,否则一个或某几个属性相同的时候它也会认为是重复的数据,所以TreeSet是利用了Comparable接口来重复重复确认数据的。

​ 由于TreeSet在操作过程中需要将类中的所有属性进行比较,这样实现难度太大,在实际的开发中首选HashSet子类进行存储。

关于重复元素的说明

​​    TreeSet子类是利用了Comparable接口来实现重复元素的判断。

​​    而HashSet子类则是利用Object类中提供的方法实现的。

对象编码:

public int hascode();//Onbject中的方法

对象比较:

public boolean equals(Object obj)

​​    在进行重复元素判断的时候首先利用hashCode()进行编码的匹配,如果该编码不存在则表示数据不存在,证明没有重复,如果该编码存在了,则进一步进行对象比较处理,如果发现重复了,则此数据不能够保存。

package cn.mldn.demo;

import java.util.Set;

class Person //比较器

private String name·3

private int age;

public Person(String name,int age)f

this.name = name;

this.age = age ;

@Override

public int hashCode()

final int prime=31;

int result =1;

result=prime* result + age;

result = prime*result +((name== nul1)? e: name.hashCode()) 

return result;

@Override

public boolean equals(Object obj) [

if (this == obj)

return true;

if (obj == null)

if (getClass()!= obj.getClass())

return false;

Person other =(Person) obj;

if (age l= other.age)

return false;

if (name== null)

if (other.name!= null)

return false;

] else if(!name.equals(other.name))

return false;

return true;

public String toString()

return "姓名:” + this.name +"、年龄:”+ this.age

public class JavaAPIDemo 

public static void main(String[] args) throws Exception 

[Setall= new HashSet()//为List父接口进行实例化

all.add(new Person("张三"19));

all.add(new Person("李四"19))//年龄相同,但是姓名不同

all.add(new Person("王五”,20))//数据重复

all.add(new Person("王五”,2e))//数据重复

all.add(new Person("小强”,78));

all.forEach(System.out::println)

集合输出

​    集合输出从JDK1.8开始就在Iterable接口中提供了一个forEach()方法,但是这种方法的迭代输出并不是传统意义上的集合输出形式,也很难在实际的开发中出现。对于集合类而言,一共有四种迭代输出形式:

  • Iterator迭代输出
  • ListIterator双向迭代输出
  • Enumeration枚举输出
  • foreach输出

Iterator迭代输出

​​    通过Collection接口的继承关系可以发现,从JDK1.5开始多继承了一个Iterable父接口,并且在这个接口中定义了iterator()操作方法,通过此方法可以获得Iterator接口对象(JDK1.5之前,该方法是直接定义在Collection中的)。

public IteratorT>iterator();

在Iterator接口中定义有如下的方法:

No 方法名称 类型
1 public boolean hasNext() 普通 判断是否有数据
2 public E next() 普通 取出当前数据
3 public default void remove() 普通 删除

Java高级编程之类集框架(集合类)_第5张图片

HashSet<String> strings = new HashSet<>();
        strings.add("hello");
        strings.add("hello1");
        strings.add("hello2");
        Iterator<String> iterator = strings.iterator();
        while(iterator.hasNext()){
            System.out.println(iterator.next());
        }

​ ​ ​ ​ 实际上在Collection的接口中,定义有书记处删除的操作方法,但是如果在迭代输出的时候如果使用了Collect中的remove()方法会导致迭代失败。

 Set<String> strings = new HashSet<>();
        strings.add("hello");
        strings.add("hello1");
        strings.add("hello2");
        System.out.println(strings);
        Iterator<String> iterator = strings.iterator();
        while(iterator.hasNext()){
            String str=iterator.next();
            if("hello".equals(str)){
                    iterator.remove();
            }else{
                System.out.println(str);
            }
        }
        System.out.println(strings);

【面试题】请解释Collection.remove()与Iterator.remove()的区别?

​ ​ ​ ​ 在进行迭代输出的时候如果使用了Collection.remove()则会造成并发更新异,导致程序出错,而此时只能够利用Iterator接口中的remove()方法来实现正常的删除处理。

ListIterator双向迭代输出

​ ​ ​ ​ 使用Iterator接口迭代输出时有一个特点:只允许从前向后输出。‘想要实现双向迭代输出则要通过Iterator的子接口:ListIterator接口来实现。

 ​ ​ ​ 需要注意的是想要获得ListIterator接口对象Collection接口中并没有定义相关的处理方法,但是List接口有,也就是说这个输出接口是专门为List集合准备的。

在ListIterator中定义有如下的操作方法:

public boolean hasPrevious();
public E previous();

 List<String> strings = new ArrayList<String>();
        strings.add("hello");
        strings.add("hello1");
        strings.add("hello2");
        System.out.println(strings);
        ListIterator<String> iterator = strings.listIterator();
        //正向
        while(iterator.hasNext()){
            System.out.println(iterator.next());
        }
        //反向
        while(iterator.hasPrevious()){
            System.out.println(iterator.previous());
        }

想要实现由后向前遍历,那么首先要实现的是有后向前遍历。

Enumeration枚举输出

​ ​ ​ ​ Enumeration是在JDK1.0的时候就使用的输出接口,这个接口主要是为Vector类提供输出服务的,一致到后续JDK的发展,Enumeration 依然只为Vector一个类服务,如果想要获取Enumeration接口对象,就必须使用Vector类提供的方法:

public Enumeration <E> element();

在Enumeration 中有定义有操作方法:

public boolean hasMoreElements();
public E nextElement();

forEach输出

​ ​ ​ ​ 除了使用迭代接口实现输出外,从JDK1.5开始,加强型for循环也可以实现集合的输出了。这种输出形式和数组的操作形式类似。

Map集合

​ ​ ​ ​ Collection接口及其对应的子接口中保存的数据都是单个对象,在数据结构中除了可以进行单个数据的保存之外,还可以进行二元偶对象的保存(key=value)的形式保存,而存储对象的核心意义在于需要通过key获取指定的value。

​ 在开发中Collection集合保存数据是为了输出,Map集合保存数据的目的是为了进行key的查找。

Map接口简介

​ ​ ​ ​ Map接口是进行二元偶对象保存的最大父接口,该接口的定义如下:

public interface Map<K,V>

该接口的核心操作方法:

No 方法名称 类型 描述
1 public V put(K key,V value) 普通 向集合之中保存数据
2 public get (Object key) 普通 根据key查询数据
3 public Set entrySet() 普通 将Map集合转为Set
4 public boolean containsKey(Object key) 普通 查询指定的key是否存在
5 public Set keySet() 普通 将Map集合中的key转换Set集合
6 public V remove(Object key) 普通 根据key删除掉指定的数据

​ ​ ​ ​ 在Map集合中数据的保存就是按照”key=value“的形式存储的,并且使用of()方法操作的时候是不允许有重复的,否则会出现”illeaglArgumentException“异常,如果设置的内容为空,则会出现NullPointerException。

HashMap子类

​ ​ ​ ​ HashMap子类是Map接口中最常见的一个子类,该类的主要特点是无序存储。

public class HashMap<K,V>
extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable

Java高级编程之类集框架(集合类)_第6张图片

​ ​ ​ ​ 该类的定义形式符合之前的集合定义形式,依然提供有抽象类并且需要重复实现Map接口。

观察HashMap接口的使用

import java.util.HashMap;
import java.util.Map;

public class JavaDemo{
    public static void main(String[] args)  {
        Map<String, Integer> map = new HashMap<>();
        map.put("one",1);
        map.put("two",2);
        map.put("one",111);//1.key重复
        map.put(null,1);//2.key为空
        System.out.println(map.get("one"));//111
        System.out.println(map.get(null));//1
        System.out.println(map.get("three"));//3.键值不存在null
    }
}

​ ​ ​ ​ 以上的操作形式为Map集合使用的最标准的形式,由以上代码可以发现通过HashMap实例化的Map接口可以针对key或value保存null数据。同时也可以发现保存数据的key值相同也不会出现错误,而是出现内容的替换。

​ ​ ​ ​ 对于Map接口中的put方法本身是提供有返回值的,返回值指的是在重复key的情况下返回旧的Value。

观察HashMap中的相关源代码:

 public HashMap() {
        this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
    }

static final float DEFAULT_LOAD_FACTOR = 0.75f;

​ ​ ​ ​ 当使用构造方法的时候会出现也给loadFactor属性,该属性的默认指为0.75。

public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }

​ ​ ​ ​ 在使用put方法进行数据保存的时候会调用一个putVal()方法,同时会将key进行hash处理(生成一个hash码),而对于putVal()方法里面依然会提供一个Node节点类进行数据的保存,而在使用putVal()方法操作的过程中会调用一个resize方法可以进行容量的扩充。

【面试题】在进行HashMap进行put操作的时候,是如何实现扩容的。

static final int Default_INITIAL_CAPACITY=1<<4;//aka16

​ ​ ​ ​ 在HashMap中提供有一个"Default_INITIAL_CAPACITY"常量,作为初始化的容量配置,而后这个元素的大小为16个元素,也就是数默认可以存的最大内容是16。

​ ​ ​ ​ 当保存的内容的容量超过了某个阈值(DEFAULT_LOAD_FACTOR=0.75f) ,相当于”容量*阈值=12“保存12个元素的时候就会进行容量的扩充。在进行扩充的时候HashMap使用的是成倍的扩充模式,即每一次都扩充两倍容量。

【面试题】解释HashMap的工作原理(JDK1.8之后开始的)

​ ​ ​ ​ 在HashMap中进行数据存储依然是利用Node类完成的,那么在这种情况下就证明可以使用的数据结构只有两种:链表(时间复杂度O(n))、二叉树(时间复杂度O(logn))。

​ ​ ​ ​ 从jDK1.8开始HashMap的实现出现了改变,因为其要适应大数据时代的海量数据问题,所以对齐存储发生过了变化,并且在HashMap的内部提供了一个非常重要的常量 static final TREEIFY_THRESHOLD=8 在使用HashMap进行数据保存的时候,如果保存的数据个数没有超过阈值数值8(),那么会按照链表的形式进行存储,而如果超过了,则会将链表转换为红黑树以实现树的平衡,并且利用左旋和右旋来保证数据的查询性能。

LinkedHashMap子类

​ ​ ​ ​ HashMap保存的数据都是无序的(实际上是否有序对map的影响不大),如果想要Map中的数据为其增加数据那么就可以使用LinkedHashMap(基于链表实现)。

public class LinkedHashMap<K,V>
extends HashMap<K,V>
implements Map<K,V>

​ ​ ​ ​ 既然是链表保存,那么在使用LinkedListMap保存的数据量不能太大,因为会造成时间复杂度的攀升。

HashTable子类

​ ​ ​ ​ HashTable是从JDK1.0的时候提供的,与Vector、Enumeration属于最早一批动态数组实现类,后来为了让其保存下来,所以让其多实现了一个Map接口。

public class Hashtable<K.V> extends Dictionary <K.V>

implements Map <K.V>, Cloneable,Serializable.

​ 在HashMap中进行数据存储的时候,设置的key和value都不允许为空,否则会出现NullPointerException。

Map.Entry内部接口

​ ​ ​ ​ Map集合里是如何进行数据存储的?HashMap中可以见到Node类型的定义,通过源代码定义可以发现,HashMap中的Node内部类内部实现了Map.Entry接口。

static class Node<K,V> implements Map.Entry<K,V> 

​ ​ ​ ​ 所以可以的出结论所有的key和value数据都被封装在Map.Entry接口中,而此接口的定义如下:

public static interface Map.Entry

​ 在这个内部解空中提供有两个重要的方法:

  • 获取key: public K getKey()
  • 获取value: public V getValue()

Java高级编程之类集框架(集合类)_第7张图片

​ ​ ​ ​ 在JDK1.9之前的开发版本中,使用者基本上不会去考虑创建Map.Entry的对象,实际上在正常的开发过程中使用者也不关心Map.Entry对象创建,可是从JDK1.9之后,Map接口里追加了一个新的方法:

//创建Map对象
public static <K,V> Map.Entry<K,V> entry(K k,V v)

​ 在整个Map的集合里,Map.Entry的主要作用是作为一个Key和Value的包装类型使用,而大部分情况下进行数据存储的时候,都会讲key和value包装成一个Map.Entry对象进行使用。

使用Iterator输出Map对象

​ ​ ​ ​ 对于集合输出而言,最标准的做法就是利用Iterator接口来完成,但是Map集合中并没有一个方法可以直接返回Iterator接口对象。

​ ​ ​ ​ 在Map集合中保存的实际上是一组Map.Entry接口对象,所以整个来讲Map实现的依然是单值的保存,这样在Map集合中提供了一个方法,将全部的Map集合转换为Set集合。

public Set Map.Entry<Keym,Value> entrySet()

​ ​ ​ ​ 经过分析可知,如果想要通过Iterator实现Map集合的输出,则必须按照以下步骤处理:

  • 利用Map接口中提供的entrySet方法将Map集合转换为Set集合。

  • 利用Set接口中的iterator方法将Set集合转换为Iterator接口实例。

  • 利用Iterator进行迭代输出每一组的Map.Entry对象,随后通过getKey()和getValue()获取数据。

     ​ ​ ​ 虽然Map集合有迭代输出的支持,但是如果从实际的开发来讲,Map集合最主要的用法在在于实现数据的key查找,另外需要提醒的是,如果不使用Iterator而使用foreach语法输出也需要将Map集合转换为Set。

自定义Map的key类型

​ ​ ​ ​ 对于自定义key类型所在的类一定要覆写hashCode和equals()方法,否则无法查到。

public V put(K key,V value){
    return putVal(hash(key),key,value,false,true);
}

​ ​ ​ ​ 在进行数据保存的时候会自动使用传入的key的数据生成一个hash码。

public V get(Object key){
    Node<K,V> e;
    return (e=getNode(hash(key),key))==null?null:e.value;
}

​ ​ ​ ​ 根据key值查询数据的时候将传入的key值通过hash()方法来获取其对应的hash码,那也就证明,查询过程之中首先要利用hashCode()来进行数据查询的时候还需要使用到equals()方法。

【面试题】HashMap是如何解决Hash冲突的。

​ ​ ​ ​ 当出现了hash冲突之后为保证程序的正常执行,会在冲突的位置上将所有的Hash冲突的内容转为链表保存。

集合工具类

stack

public class Stack extends Vector

​ ​ ​ ​ Stack是Vector的也给子类,但是它使用的并不是Vector类之中提供的方法,而是采用如下的两个方法:

//入栈
public E push(E item);
//出栈
public E pop()

Queue

​ ​ ​ ​ 队列可以使用LinkedList的子类来完成。

Queue<String> queue=new LinkedList<String>();
//向对类中追加数据
public boolean offer(E e);
//通过队列获取数据
public E poll()

PriortyQueue优先队列的实现:
Java高级编程之类集框架(集合类)_第8张图片

​ ​ ​ ​ 可以使用PriortyQueue实现优先级队列(比较功能),有排序操作。一定和Comparable接口有着直接的对应关系。

Properties属性操作

​ ​ ​ ​ java.util包里提供有Properties类型,此类是Hashtable的子类。

public class Properties extends Hashtable<Object,Object>

可以发现在继承Hashtable时候为Hashtable中定义的泛型为Object,Properties是不需要操作泛型的,因为它能实现的类型只是String类型。

​ ​ ​ ​ 在Properties中如果想要实现属性的操作可以采用如下的方法:

//1.设置属性
setProperty(String key,String value)
//2.取得属性   如果没有指定值则返回Null
getProperty(String key)
//3.取得属性   如果没有指定key则返回默认值
getProperty(String key,String defaultValue)
//4.获得属性列表
list(PrintStream out)

Properties往往用读取配置资源的信息,其最大的特点是:

​ ​ ​ ​ 可以进行资源内容的输入和输出处理操作,但是在实际的开发中,Properties往往用于读取配置资源的信息,这一点主要是在标准设计中做程序初始化准备的使用使用。

Collections工具类

​ ​ ​ ​ Collections是java提供的一组集合数据的操作工具类,利用它可以实现各个集合的操作。注意和Collection的区别,Collection是集合接口,允许保存单值对象,而Collections是集合操作的工具类。

//使用Collections操作List集合
List<String> all=new ArrayList<String>();
Collections.addAll(all,"Hello","Byebye");
//数据的反转
Collections.reverse(all)
//使用二分法查找
Collections.sort(all);//先进行排序处理
System.out.println((Collections.binarySearch(all,"Hello"));

你可能感兴趣的:(Java基础学习笔记,java,数据结构,链表)