Java类集框架

Java类集框架

一、Java类集简介

1、类集框架简介

从JDK1.2开始Java引入了类集开发框架,类集就是一套动态对象数组的实现方案。

在实际的开发之中,没有任何一项的开发可以离开数组,但是传统的数组实现起来非常繁琐,而且长度是致命伤,所以不可能大范围使用,但是开发又不可能离开数组,所以最初只能使用一些数据结构来实现数组的动态处理,其中最重要的两个结构:链表、树,但是也需要面对一些困难:

  • 数据结构的代码实现困难
  • 链表与二叉树进行维护是非常麻烦的
  • 尽可能的保证链表或二叉树的性能

类集的主要功能:对常见的数据接口进行完整的包装,并提供相应的接口和实现子类减少开发困难

类集之中提供有如下的几个核心接口:Collection , List , Set , Map , Iterator , Enumeratio , ListIterator

1、Collection集合接口

Collection集合是单值集合中最大的父接口,定义有所有的单值数据的数据处理

核心操作方法:

No方法名称类型描述
1boolean add(E e)普通向集合保存数据
2boolean addAll(Collection<? extends E> c)普通追加一组数据
3void clear()普通清空集合,同时执行GC处理
4boolean contains(Object o)普通查询数据是否存在,徐亚equals()支持
5boolean remove(Object o)普通数据删除,需要equals()支持
6int size()普通获取数据长度
7Object[] toArray普通将集合变为对象数组返回
8Iterator iterator()普通将集合变为Iterator接口返回

进行集合操作时最常用的方法:【增加】add() , 【输出】iterator()

当前更多采用Collection的两个子接口:【允许重复】List , 【不允许重复】Set

二、List集合

1、List接口

List是Collection子接口

特点:允许保存重复元素数据

定义:public interface extends Collection

List子接口对Collection接口进行了扩充

No方法名称类型描述
1E get(int index)普通获取指定索引数据
2E set(int index,E element)普通修改指定索引数据
3ListIterator listIterator()普通返回ListIterator接口对象
4boolean contains(Object o)普通查询数据是否存在,徐亚equals()支持
5boolean remove(Object o)普通数据删除,需要equals()支持
6int size()普通获取数据长度
7Object[] toArray普通将集合变为对象数组返回
8Iterator iterator()普通将集合变为Iterator接口返回

List中常用子类:ArrayList , Vector , LinkedList


JDK1.9后追加了一些静态方法

public class JavaDemo {
   public static void main(String[] args) throws Exception {
      List<String> list = List.of("Hello", "World", "!");
      for (Object o : list.toArray()) {
         System.out.print(o + "、");
      }
   }
}

2、ArrayList子类

定义:

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

ArrayList继承结构:

在这里插入图片描述

使用ArrayList实例化List父接口

public class JavaDemo {
   public static void main(String[] args) throws Exception {
      List<String> list = new ArrayList<>();
      list.add("Hello");
      list.add("Hello");	//重复数据
      list.add("World");
      list.add("!");
      System.out.println(list);
   }
}
//[Hello, Hello, World, !]

List存储特征:

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

以上程序虽然实现了输出,但是是利用了toString()实现的。

Iterable父接口中定义有forEach()方法,其定义:

  • 输出支持:default void forEach(Consumer<? super T> action)

利用forEach()输出(非标准输出)

public class JavaDemo {
   public static void main(String[] args) throws Exception {
      List<String> list = new ArrayList<>();
      list.add("Hello");
      list.add("Hello");
      list.add("World");
      list.add("!");
      list.forEach((str) -> {
         System.out.print(str + ",");
      });
   }
}
//Hello,Hello,World,!,

此种输出不是正常开发情况下需要考虑的操作

List集合中的其他操作

public class JavaDemo {
   public static void main(String[] args) throws Exception {
      List<String> list = new ArrayList<>();
      System.out.println("集合是否为空?" + list.isEmpty() + ",集合元素个数:" + list.size());
      list.add("Hello");
      list.add("Hello");
      list.add("World");
      list.add("!");
      list.remove("Hello");
      System.out.println("集合是否为空?" + list.isEmpty() + ",集合元素个数:" + list.size());
      list.forEach((str) -> {
         System.out.print(str + " ");
      });
   }
}
//集合是否为空?true,集合元素个数:0
//集合是否为空?false,集合元素个数:3
//Hello World ! 

ArrayList中的操作与链表形式相似,但并非使用链表实现,ArrayList封装的是数组

ArrayList构造:

public ArrayList()

public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

public ArrayList(int initialCapacity)

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);
    }
}

在进行添加时,如果数组长度不够,会开辟新的数组,并将旧数组拷贝到新数组中


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

JDK1.9之前:ArrayList默认的构造就会开辟大小为10的数组

当ArrayList之中保存的容量不足的时候会采用成倍的方式进行增长,原始长度为10,下次增长为20,以此类推

使用ArrayList子类时要估算数据量大小,若超过10个,应使用有参构造方法进行创建,避免垃圾空间的产生

3、ArrayList保存自定义对象

实现自定义类对象的保存

public class JavaDemo {
   public static void main(String[] args) throws Exception {
      List<Person> list = new ArrayList<>();
      list.add(new Person("linlin" , 18));
      list.add(new Person("xiaowu" , 19));
      list.add(new Person("axu" , 20));
      System.out.println(list.contains(new Person("linlin" , 18)));
      list.remove(new Person("linlin" , 18));
      list.forEach(System.out::println);  //方法引用代替了消费型接口
   }
   //true
   //Person{name='xiaowu', age=19}
   //Person{name='axu', age=20}
}
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 o) {
      if (this == o) return true;
      if (o == null || getClass() != o.getClass()) return false;
      Person person = (Person) o;
      return age == person.age && Objects.equals(name, person.name);
   }
   
   @Override
   public int hashCode() {
      return Objects.hash(name, age);
   }
}

在使用List保存自定义对象时,如果需要使用到contains(),remove()方法进行查询与产出处理时,必须保证类中已经重写了equals()方法

4、LinkedList子类

LinkedList类是基于链表实现的

定义:

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

LinkedList继承关系:

在这里插入图片描述

使用LinkedList实现集合操作

public class JavaDemo {
   public static void main(String[] args) throws Exception {
      List<String> list = new LinkedList<>();
      list.add("Hello");
      list.add("Hello");  //重复数据
      list.add("World");
      list.add("!");
      list.forEach(System.out::println);
   }
}

LinkList与ArrayList使用方式使完全相同的,但内部的实现是不同的

LinkedList构造方法里面并没有提供有像ArrayList那样的初始化大小的方法,而只是提供有无参的构造方法:public LinkedList()

如果观察源码,就会发现LinkedList封装的就是一个链表


面试题:请问ArrayList与LinkedList有什么区别?

  • ArrayList是数组实现的集合操作,而LinkedList是链表实现的集合操作
  • 在使用List集合中的get()方法根据索引获取数据是,ArrayList的时间复杂度为“O(1)”,而LinkedList的时间复杂度为“O(n)” (n为集合的长度)
  • ArrayList在使用的时候默认的初始化对象数组的大小长度为10,如果空间不足则会采用2倍的形式进行容量的扩充,保存大数据量的时候有可能会造成垃圾的产生以及性能的下降,但是这个时候可以使用LinkedList保存

5、Vector子类

Vector定义结构:

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

继承结构与ArrayList是相同的

Vector继承结构:

在这里插入图片描述

Vector类使用

public class JavaDemo {
   public static void main(String[] args) throws Exception {
      List<String> list = new Vector<>();
      list.add("Hello");
      list.add("Hello");  //重复数据
      list.add("World");
      list.add("!");
      list.forEach(System.out::println);
   }
}

观察Vector类实现

public Vector() {
    this(10);
}
public Vector(int initialCapacity, int capacityIncrement) {
    super();
    if (initialCapacity < 0)
        throw new IllegalArgumentException("Illegal Capacity: "+
                                           initialCapacity);
    this.elementData = new Object[initialCapacity];
    this.capacityIncrement = capacityIncrement;
}
public Vector(int initialCapacity) {
    this(initialCapacity, 0);
}

Vector类如果使用的是无参构造方法,则一定会默认开辟一个10个长度的数组,而后其余的实现操作与ArrayList是相同的

Vector类中的操作方法

public synchronized boolean add(E e) {
    modCount++;
    add(e, elementData, elementCount);
    return true;
}

通过观察源码可以发现Vector类之中操作方法采用的是synchronized同步处理,而ArrayList没有进行同步处理

Vector类之中的方法在多线程访问的时候属于线程安全的,但是性能不如ArrayList高

三、Set集合

1、Set接口

Set接口是Collection子接口

最大特点:不允许保存重复元素

在JDK1.9以前Set集合与Collection集合的定义并无差别

在JDK1.9之后扩充了一些static方法

Set集合定义:

public interface Set<E> extends Collection<E>

Set集合并没有扩充很多新方法,无法使用List集合中题提供的get()方法,不能获取指定索引数据

Set继承关系

在这里插入图片描述

Set集合特征:

public class JavaDemo {
   public static void main(String[] args) throws Exception {
      Set<String> set = Set.of("Hello", "Hello" , "World", "!");
      set.forEach(System.out::println);
   }
}

程序结果:

Exception in thread “main” java.lang.IllegalArgumentException: duplicate element: Hello

当使用of()新方法时,如果集合中有重复元素就会抛出异常

Set集合的常规使用形式是依靠子类进行实例化的.

Set接口中常用的子类:HashSet , TreeSet

2、HashSet子类

HashSet最大的特点:保存的数据是无序的

HashSet定义:

public class HashSet<E>
extends AbstractSet<E>
implements Set<E>, Cloneable, Serializable

HashSet继承结构

在这里插入图片描述

观察HashSet

public class JavaDemo {
   public static void main(String[] args) throws Exception {
      Set<String> set = new  HashSet<>();
      set.add("World");
      set.add("Hello");
      set.add("Hello");   //重复元素
      set.forEach(System.out::println);
   }
}
//Hello
//World

通过程序执行结果可知,HashSet操作特点:不允许保存重复元素(Set接口定义的),HashSet中保存的数据是无序的

3、TreeSet子类

TreeSet最大特点:保存的数据是有序的

TreeSet定义:

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

TreeSet子类继承了AbstractSet父抽象类,同时实现了NavigableSet父接口

TreeSet继承结构

在这里插入图片描述

使用TreeSet

public class JavaDemo {
   public static void main(String[] args) throws Exception {
      Set<String> set = new TreeSet<>();
      set.add("World");
      set.add("Hello");
      set.add("Hello");   //重复元素
      set.add("A");
      set.forEach(System.out::println);
   }
}
//A
//Hello
//World

当利用TreeSet保存的数据的时,所有数据都将按照数据的升序进行自动排序

4、TreeSet子类排序操作

自定义类实现排序处理

public class JavaDemo {
   public static void main(String[] args) throws Exception {
      Set<Person> set = new TreeSet<>();
      set.add(new Person("张三" , 18));
      set.add(new Person("李四" , 18)); //年龄相同
      set.add(new Person("王五" , 20));
      set.add(new Person("王五" , 20)); //名字相同
      set.add(new Person("赵六" , 19));
      set.forEach(System.out::println);
   }
}
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 +
            '}';
   }
}

执行结果:

Exception in thread “main” java.lang.ClassCastException: class com.xialuote.Person cannot be cast to class java.lang.Comparable (com.xialuote.Person is in unnamed module of loader ‘app’; java.lang.Comparable is in module java.base of loader ‘bootstrap’)

因此想要实现自定义类排序必须要实现Comparable接口

public class JavaDemo {
   public static void main(String[] args) throws Exception {
      Set<Person> set = new TreeSet<>();
      set.add(new Person("张三" , 18));
      set.add(new Person("李四" , 18)); //年龄相同
      set.add(new Person("王五" , 20));
      set.add(new Person("王五" , 20)); //名字相同
      set.add(new Person("赵六" , 19));
      set.forEach(System.out::println);
   }
}
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 "Person{" +
            "name='" + name + '\'' +
            ", age=" + age +
            '}';
   }
   
   @Override
   public int compareTo(Person person) {
      if (age < person.age){
         return -1;
      } else if (age > person.age) {
          return 1;
      } else {
         return name.compareTo(person.name);
      }
   }
}

在使用自定义类对象进行比较处理的时候一定要将该类之中的所有属性都依次进行大小关系的匹配,否则在属性相同时会认为是重复数据

所以TreeSet是利用了Comparable接口来确认重读数据的

提示:TreeSet本质上是利用TreeMap子类实现的集合数据的存储,而TreeMap需要根据Comparable确定大小关系

由于TreeSet在操作过程之中需要将类中的所有属性进行比对,实现难度太高了,因此开发中应该首选HashSet进行存储

5、重复元素消除

TreeSet利用了Comparable接口实现了重复元素的判断,而HashSet利用的是Object类汇总提供的方法实现的:

  • 对象编码:public int hashCode()
  • 对象比较:public boolean equals(Object obj)

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

实现重复元素处理

public class JavaDemo {
   public static void main(String[] args) throws Exception {
      Set<Person> set = new HashSet<>();
      set.add(new Person("张三" , 18));
      set.add(new Person("李四" , 18)); //年龄相同
      set.add(new Person("王五" , 20));
      set.add(new Person("王五" , 20)); //名字相同
      set.add(new Person("赵六" , 19));
      set.forEach(System.out::println);
   }
}
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 o) {
      if (this == o) return true;
      if (o == null || getClass() != o.getClass()) return false;
      Person person = (Person) o;
      return age == person.age && Objects.equals(name, person.name);
   }
   
   @Override
   public int hashCode() {
      return Objects.hash(name, age);
   }
}
//Person{name='赵六', age=19}
//Person{name='张三', age=18}
//Person{name='王五', age=20}
//Person{name='李四', age=18}

在Java程序之中真正的重复元素判断处理利用的是hashCode()与 equals()两个方法共同作用完成的。而在进行排序情况下(TreeSet)才会利用Comparable接口实现

四、集合输出

集合输出实际上从JDK1.8开始就在Iterable接口之中提供有一个 forEach()方法,这种输出形式并不是传统输出,也很难在开发中出现。

对于集合有四种输出形式:Iterator迭代输出 , ListIterator双向迭代输出 , Enumeration枚举输出 , foreach输出

1、Iterator迭代输出

JDK1.5之后Collection中多继承了一个Iterable父接口,其中出现了iterator()方法,可以获取Iterator接口对象(JDK1.5之前该方法在Collection接口中)

Iterator定义:

public interface Iterator<E>
  • 获取Iterator接口对象:Iterator iterator()

Iterator接口中定义方法:

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

使用Iterator输出

public class JavaDemo {
   public static void main(String[] args) throws Exception {
      Set<String> set = Set.of("Hello" , "World" , "!");
      Iterator<String> iterator = set.iterator();
      while (iterator.hasNext()) {
         System.out.println(iterator.next());
      }
   }
}

在Collection中也定义有remove()方法,如果在进行迭代输出时使用了Collection中的remove()方法,会导致迭代失败。

因此如果需要删除,只能使用Iterator中提供的remove()方法(非必须情况下,尽量不使用Iterator中的remove()方法)


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

  • 在进行迭代输出的时候如果使用了Collection.remove()则会造成并发更新异常,此时只能利用Iterator.remove()实现正常删除

2、ListIterator双向迭代输出

使用Iterator进行迭代输出有一个特点:只允许有前向后输出

如果要双向迭代处理,必须依靠Listlterator接口

Collection中没有定义获取ListIterator接口对象的方法,但是List中有,所以ListIterator接口是专门为List集合准备的

ListIterator定义如下方法:

  • 判断是否有前一个元素:boolean hasPrevious()
  • 获取当前元素:E previous()

实现双向迭代

public class JavaDemo {
   public static void main(String[] args) throws Exception {
      ArrayList<String> list= new ArrayList<>();
      list.add("Hello");
      list.add("World");
      list.add("!");
      ListIterator<String> iterator = list.listIterator();
      System.out.print("由前向后输出:");
      while (iterator.hasNext()) {
         System.out.print(iterator.next());
      }
      System.out.println();
      System.out.print("由后向前输出:");
      while (iterator.hasPrevious()) {
         System.out.print(iterator.previous());
      }
   }
}
//由前向后输出:HelloWorld!
//由后向前输出:!WorldHello

如果要实现由后向前的遍历,首先要实现由前向后遍历

3、Enumeration枚举输出

Enumeration只为Vector一个类提供服务,要想获取Enumeration接口对象,只能依靠Vector类中提供的方法

  • 获取Enumeration:public Enumeration elements()

Enumeration中定义有两个操作方法:

  • 判断是否有下一个元素:boolean hasMoreElements()
  • 获取当前元素:E nextElement()

使用Enumeration实现输出

public class JavaDemo {
   public static void main(String[] args) throws Exception {
      Vector<String> list= new Vector<>();
      list.add("Hello");
      list.add("World");
      list.add("!");
      Enumeration<String> enu = list.elements();
      while (enu.hasMoreElements()) {
         System.out.println(enu.nextElement());
      }
   }
}

4、foreach输出

使用foreach输出

public class JavaDemo {
   public static void main(String[] args) throws Exception {
      List<String> list= new ArrayList<>();
      list.add("Hello");
      list.add("World");
      list.add("!");
      for (String str : list) {
         System.out.println(str);
      }
   }
}

五、Map集合

在数据结构之中,除了进行单个对象的保存,也可以进行二元偶对象的保存(key=value),通过key获取value

Collection集合保存数据的目的是为了输出,Map集合保存数据是为了key的查找

1、Map接口

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

public interface Map<K,V>

该接口是一个独立的父接口,在进行接口对象实例化的时候需要设置Key与Value

Map接口中定义有许多方法,其中核心操作方法有:

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

Map集合集合中也提供了static方法

观察Map集合特点

public class JavaDemo {
   public static void main(String[] args) throws Exception {
      Map<String, Integer> map = Map.of("One"  , 1 , "Two" , 2);
      System.out.println(map);
   }
}

在Map集合之中数据保存就是按照“key=value”的形式存储的

使用of()方法时,里面的数据是不允许重复的,如果重复则会出现“IllegalArgumentException“异常,如果设置为null,会出现“NullPointerException”异常

of()方法并不是Map集合的标准用法,开发之中需要通过Map集合的子类进行接口对象实例化

常用子类有:HashMap , Hashtable , TreeMap , LinkedHashMap

2、HashMap子类

HashMap定义:

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

HashMap主要特点:无序存储

HashMap继承结构:

在这里插入图片描述

观察Map集合的使用

public class JavaDemo {
   public static void main(String[] args) throws Exception {
      Map<String, Integer> map = new HashMap<>();
      map.put("one" , 1);
      map.put("two" , 2);
      map.put("one" , 3); //key重复
      map.put(null , 0);  //key 为 null
      map.put("zero" , null);  //value 为 null
      System.out.println(map);
   }
}
//{null=0, zero=null, one=3, two=2}

以上的操作形式为Map集合使用的最标准的处理形式

通过HashMap实例化的Map接口可以针对key或value保存null的数据,即便保存数据的key重复,也不会出现异常,而是出现内容替换


Map接口中的put()方法本身是有返回值的,这个返回值是在重复key的情况下返回旧的value

观察put方法

public class JavaDemo {
   public static void main(String[] args) throws Exception {
      Map<String, Integer> map = new HashMap<>();
      System.out.println(map.put("one" , 1)); //key不重复,返回null
      System.out.println(map.put("one" , 3)); //key重复,返回旧数据
   }
}
//null
//1

当使用无参构造时会有一个loadFactor属性,该属性默认值为0.75

static final float DEFAULT_LOAD_FACTOR = 0.75f;
public HashMap() {
    this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}

在使用put()方法进行数据保存时会调用一个putVal()方法,同时会将key进行hash处理(生成hash码)

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

putVal()方法里面依然提供有一个Node节点类进行数据的保存

在使用putVal()方法时会调用resize()方法,此方法是进行容量的扩充


面试题:在进行HashMap的put()操作的时候,如何实现容量扩充的?

  • 在HashMap类中提供有一个“DEFAULT_INITIAL_CAPACITY”常量,作为初始化容量配置,该常量默认大小为16个元素
  • 当保存内容的容量超过了阈值(DEFAULT_LOAD_FACTOR = 0.75f),相当于“容量 * 阈值 = 12”个时就会进行容量扩充
  • 在进行扩充的时候HashMap采用的是成倍的扩充模式,即:每次扩充2倍容量

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

  • 在HashMap中进行数据存储依然利用了Node类完成,这种情况就证明可以使用的数据结构只有两种:链表(时间复杂度:“O(n)”)、二叉树(时间复杂度:“O(logn)”)
  • 从JDK 1.8开始,HashMap的实现出现了改变,因为其要适应于大数据时代,所以对于存储发生了变化,在HashMap类内部提供有一个重要的常量:“static final int TREEIFY_THRESHOLD = 8;”。在使用HashMap保存时,如果保存数据个数没有超过阈值(8),则会按照链表的形式存储;如果超过了,则将链表转为红黑树,利用左旋与右旋保证数据的查询性能

3、LinkedHashMap子类的子类

LinkedHashMap是基于链表实现的,保存数据的顺序为增加顺序

LinkedHashMap定义:

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

因为是链表保存,所以使用LinkedHashMap时数据量不要特别大,否则时间复杂度攀升

LinkedHashMap继承关系:

在这里插入图片描述

使用LinkedHashMap

public class JavaDemo {
   public static void main(String[] args) throws Exception {
      Map<String, Integer> map = new LinkedHashMap<>();
      map.put("one" , 1);
      map.put("two" , 2);
      map.put("one" , 3); //key重复
      map.put(null , 0);  //key 为 null
      map.put("zero" , null);  //value 为 null
      System.out.println(map);
   }
}
//{one=3, two=2, null=0, zero=null}

LinkedHashMap进行存储的保存数据是添加顺序

4、Hashtable子类

Hashtable定义:

public class Hashtable<K,V>
extends Dictionary<K,V>
implements Map<K,V>, Cloneable, Serializable

Hashtable继承结构:
在这里插入图片描述

观察Hashtable的使用

public class JavaDemo {
   public static void main(String[] args) throws Exception {
      Map<String, Integer> map = new Hashtable<>();
      map.put("one" , 1);
      map.put("two" , 2);
      map.put("one" , 3); //key重复
      System.out.println(map);
   }
}
//{two=2, one=3}

在使用Hashtable时,key和value都不允许为空,否则会报“NullPointerException”异常


面试题:请解释HashMap与Hashtable的区别?

  • HashMap中的方法都属于异步操作(非线程安全),HashMap允许保存null的数据
  • Hashtable中的方法都属于同步操作(线程安全),Hashtable不允许保存null的数据,否则出现“NullPointerException”异常

5、Map.Entry内部接口

List(LinkedList子类)依靠的是链表实现的数据存储,在进行存储数据时将数据保存在Node节点中,在HashMap中也可以见到Node类定义,其本身实现了Map.Entry接口

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

因此,所有的key和value的数据都被封装在了Map.Entry接口之中

Map.Entry定义:

public static interface Map.Entry<K,V>

此内部接口中提供有两个重要方法:

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

JDK1.9之后,Map接口中追加了一个新的方法:

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

创建Map.Entry对象

public class JavaDemo {
   public static void main(String[] args) throws Exception {
      Map.Entry<String, Integer> entry = Map.entry("one", 1);
      System.out.println("获取key:" + entry.getKey());
      System.out.println("获取value:" + entry.getValue());
      System.out.println(entry.getClass().getName()); //使用子类
   }
}
//获取key:one
//获取value:1
//java.util.KeyValueHolder

在整个Map集合里面,Map.Entry的主要作用就是作为Key和Value的包装类型使用,大部分情况下在进行存储时都会将key和value包装为一个Map.Entry对象进行使用

6、利用Iterator输出Map集合

Map集合中没有方法可以直接返回Iterator接口对象

在Map集合里面保存的实际上是一组Map.Entry_接口对象(里面包装的是Key与Value),因此Map依然实现的是单值的保存

在Map中有一个方法:Set<Map.Entry<K,V>> entrySet() , 将全部的Map集合转为Set集合

如果要使用Iterator实现Map集合的输出,必须按照以下步骤处理:

  1. 利用Map接口中提供的entrySet()方法将Map集合转为Set集合
  2. 利用Set接口中的Iterator()方法将Set集合转为Iterator接口实例
  3. 利用Iterator进行迭代输出获取每一组的Map.Entry对象,随后通过getKey()与getValue()获取数据

利用Iterator输出Map集合

public class JavaDemo {
   public static void main(String[] args) throws Exception {
      Map<String, Integer> map = new HashMap<>();
      map.put("one" , 1);
      map.put("two" , 2);
      Set<Map.Entry<String, Integer>> set = map.entrySet();   //将Map集合变为Set集合
      Iterator<Map.Entry<String, Integer>> iterator = set.iterator();
      while (iterator.hasNext()) {
         Map.Entry<String, Integer> next = iterator.next();
         System.out.println(next.getKey() + " = " + next.getValue());
      }
   }
}
//one = 1
//two = 2

虽然Map集合支持迭代输出,但是Map的主要用法是实现数据的key的查找操作


如果不使用Iterator,而使用foreach输出,也需要将Map集合转为Set集合

使用foreach输出Map集合

public class JavaDemo {
   public static void main(String[] args) throws Exception {
      Map<String, Integer> map = new HashMap<>();
      map.put("one" , 1);
      map.put("two" , 2);
      Set<Map.Entry<String, Integer>> set = map.entrySet();   //将Map集合变为Set集合
      for (Map.Entry<String , Integer> entry : set) {
         System.out.println(entry.getKey() + " = " + entry.getValue());
      }
   }
}
//one = 1
//two = 2

7、自定义Map的key类型

对于自定义Key类型所在的类中一定要重写hashCode()与equals()方法

使用自定义类作为Key类型

public class JavaDemo {
   public static void main(String[] args) throws Exception {
      Map<Person, String> map = new HashMap<>();
      map.put(new Person("张三" , 18) , "linlin");
      System.out.println(map.get(new Person("张三" , 18)));
      
   }
}
class Person{
   private String name;
   private int age;
   
   public Person(String name, int age) {
      this.name = name;
      this.age = age;
   }
   
   @Override
   public boolean equals(Object o) {
      if (this == o) return true;
      if (o == null || getClass() != o.getClass()) return false;
      Person person = (Person) o;
      return age == person.age && Objects.equals(name, person.name);
   }
   
   @Override
   public int hashCode() {
      return Objects.hash(name, age);
   }
}
//linlin

虽然允许使用自定义类作为Key的类型,但在开发之中,Map集合的Key常用类型就是:String , Long , Integer , 尽量使用系统类

面试题:如果在进行HashMap数据操作时出现了Hash冲突(Hash码相同),HashMap是如何解决的?

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

六、集合工具类

1、Stack栈操作

栈是一种先进后出的数据结构

在Java中使用Stack描述栈的操作

Stack类定义:

public class Stack<E> extends Vector<E>

虽然Stack是Vector的子类,但是Stack使用的并不是Vector之中的方法,而是如下的两个方法:

  • 入栈:public E push(E item)
  • 出栈:public E pop()

实现栈的操作

public class JavaDemo {
   public static void main(String[] args) throws Exception {
      Stack<String> stack = new Stack<>();
      stack.push("A");
      stack.push("B");
      stack.push("C");
      stack.push("D");
      System.out.println(stack.pop());
      System.out.println(stack.pop());
      System.out.println(stack.pop());
      System.out.println(stack.pop());
      System.out.println(stack.pop());    //无数据 EmptyStackException
   }
}

所有保存的数据将按照倒序进行弹出,当栈为空时,会抛出“EmptyStackException”异常

2、Queue队列

Queue描述的是一个队列,而队列的特点是先进先出的操作形式

如果将队列应用在多线程的“生产者与消费者”模型上,对于生产者过快的情况下就没有必要等待消费者获取数据了,可以将所有的内容保存在队列之中。队列的实现可以使用LinkedList子类完成。

队列的使用主要依靠Queue接口之中提供的方法处理,Queue提供有如下的方法:

  • 向队列之中追加数据:boolean offer(E e),可以直接使用add()方法
  • 通过队列获取数据:E poll(),弹出后删除数据

实现队列操作

public class JavaDemo {
   public static void main(String[] args) throws Exception {
      Queue<String> queue = new LinkedList<>();
      queue.offer("X");   //追加数据
      queue.offer("Y");
      queue.offer("Z");
      System.out.println(queue.poll());   //弹出数据,X
      System.out.println(queue.poll());   //弹出数据,Y
      System.out.println(queue.poll());   //弹出数据,Z
      System.out.println(queue.poll());   //超出数据,弹出null
   }
}

除了LinkedList子类之外,还有一个优先级队列的概念,可以使用PriorityQueue()实现优先级队列(比较功能)

PriorityQueue继承结构:

在这里插入图片描述

使用优先级队列

public class JavaDemo {
   public static void main(String[] args) throws Exception {
      Queue<String> queue = new PriorityQueue<>();
      queue.offer("X");   //追加数据
      queue.offer("Y");
      queue.offer("A");
      System.out.println(queue.poll());   //弹出数据,A
      System.out.println(queue.poll());   //弹出数据,X
      System.out.println(queue.poll());   //弹出数据,Y
   }
}

对于队列的选用,也是需要根据项目的环境决定的

3、Properties属性操作

*.properties文件的存储结构是按照“key=value”的形式存储的

这种存储形式与Map集合很相似,但是区别在于保存的内容只能是字符串

为了描述properties属性的定义,java.util包中提供有Properties类类型,此类是Hashtable的子类

Properties定义:

public class Properties
extends Hashtable<Object,Object>

Properties只能操作String类型

No方法名称类型描述
1public Object setProperty(String key,String value)普通设置属性
2public String getProperty(String key)普通取得属性,key不存在返回null
3public String getProperty(String key,String defaultValue)普通取得属性,key不存在返回默认值
4public void store(OutputStream out,String comments) throws IOException普通输出属性内容
5public void load(InputStream inStream) throws IOException普通通过输入流读取属性内容

观察属性的设置和取得

public class JavaDemo {
   public static void main(String[] args) throws Exception {
      Properties properties = new Properties();
      properties.setProperty("baidu" , "www.baidu.com");
      properties.setProperty("sina" , "www.sina.com");
      System.out.println(properties.getProperty("baidu"));
      System.out.println(properties.getProperty("sina"));
      System.out.println(properties.getProperty("bing"));
      System.out.println(properties.getProperty("bing"  , "Found"));
   }
}
//www.baidu.com
//www.sina.com
//null
//Found

Properties可以像Map集合一样进行内容的设置与获取,但是区别在于Properties只能操作String类型

Properties类另一个最重要的功能是可以通过输出流输出属性,也可以通过输入流读取属性

将属性内容保存在文件之中

public class JavaDemo {
   public static void main(String[] args) throws Exception {
      Properties properties = new Properties();
      properties.setProperty("baidu" , "www.baidu.com");
      properties.setProperty("sina" , "www.sina.com");
      properties.store(new FileOutputStream(new File("E:" + File.separator + "info.properties")),"zhushi");
   }
}

虽然可以实现资源文件的输入处理,但是如果输入的是中文,则会自动帮助用户转码

读取资源文件

public class JavaDemo {
   public static void main(String[] args) throws Exception {
      Properties properties = new Properties();
      properties.load(new FileInputStream(new File("E:" + File.separator + "info.properties")));
      System.out.println(properties.getProperty("baidu"));
      System.out.println(properties.getProperty("sina"));
   }
}
//www.baidu.com
//www.sina.com

使用Properties类型的最大特点是可以进行资源内容的输入与输出

在实际的开发之中,Properties往往用于读取配置资源的信息,主要在标准设计之中做程序初始化准备时使用

4、Collection工具类

Collection是java提供的一组集合数据的操作工具类,可以实心各个集合的操作

使用Collection操作List集合

public class JavaDemo {
   public static void main(String[] args) throws Exception {
      List<String> list = new ArrayList<>();
      Collections.addAll(list , "Hello " , "World ","!");
      System.out.println(list);
   }
}
//[Hello , World , !]

数据反转

public class JavaDemo {
   public static void main(String[] args) throws Exception {
      List<String> list = new ArrayList<>();
      Collections.addAll(list , "Hello " , "World ","!");
      Collections.reverse(list);  //反转
      System.out.println(list);
   }
}
//[!, World , Hello ]

使用二分查找

public class JavaDemo {
   public static void main(String[] args) throws Exception {
      List<String> list = new ArrayList<>();
      Collections.addAll(list , "Hello" , "World","!");
      Collections.reverse(list);  //反转
      Collections.sort(list); //先进行排序
      System.out.println(list);
      System.out.println(Collections.binarySearch(list , "Hello"));
   }
}
//[!, Hello, World]
//1

大部分情况下对于集合的使用可能没有这么多复杂要求,更多的情况是利用集合保存数据,要么进行输出,要么进行查询


面试题:请解释Collection与Collections的区别?

  • Collection是集合接口,允许保存单值对象
  • Collections是集合操作的工具类,二者没有本质联系

七、Stream数据流

JDK1.8时已经进入大数据的时代,所以在类集中也支持有数据的流式分析处理操作,为此提供了一个Stream的接口,同时在Collection接口中也提供有接口实例化的方法

  • 获取Stream接口对象:default Stream stream()

1、Stream基本操作

Stream主要功能是进行数据的分析处理,同时主要是针对于集合中的数据进行分析操作

Stream的基本操作

public class JavaDemo {
   public static void main(String[] args) throws Exception {
      List<String> list = new ArrayList<>();
      Collections.addAll(list , "Java" , "JavaScript","Python");
      Stream<String> stream = list.stream();  //获取Stream接口对象
      //System.out.println(stream.count()); //输出元素个数
      //将每个元素变为小写字母,并判断j是否存在
      System.out.println(stream.filter((ele) -> ele.toLowerCase().contains("j")).count());
   }
}

上面的程序只是实现了最基础的个数统计,更多情况下可能是获取里面满足条件的数据内容

实现数据的采集操作

public class JavaDemo {
   public static void main(String[] args) throws Exception {
      List<String> list = new ArrayList<>();
      Collections.addAll(list , "Java" , "JavaScript","Python");
      Stream<String> stream = list.stream();  //获取Stream接口对象
      //System.out.println(stream.count()); //输出元素个数
      //将每个元素变为小写字母,,将满足j存在条件的数据收集起来转为List集合
      List<String> result = stream.filter((ele) -> ele.toLowerCase().contains("j")).collect(Collectors.toList());
      System.out.println(result);
   }
}
//[Java, JavaScript]

在Stream数据流处理的过程中还允许进行数据的分页处理,提供有两个方法:

  • 设置取出最大数据量:Stream limit(long maxSize)
  • 跳过指定数据量:Stream skip(long n)

观察分页

public class JavaDemo {
   public static void main(String[] args) throws Exception {
      List<String> list = new ArrayList<>();
      Collections.addAll(list , "Java" , "JavaScript" , "Python" , "JSP" , "Json");
      Stream<String> stream = list.stream();  //获取Stream接口对象
      //System.out.println(stream.count()); //输出元素个数
      //将每个元素变为小写字母,,将满足j存在条件的数据收集起来转为List集合
      List<String> result = stream.filter((ele) -> ele.toLowerCase().contains("j")).skip(2).limit(2).collect(Collectors.toList());
      System.out.println(result);
   }
}
//[JSP, Json]

Stream的操作主要是利用自身的特点实现数据的分析处理操作

2、MapReduce基础模型

在进行数据分析的处理之中有一个最重要的基础模型:MapReduce模型,一共分为两个部分,Map处理部分和Reduce分析部分

在进行数据分析之前要对数据进行合理的处理,然后才可以统计分析操作

MapReduce基础模型

public class JavaDemo {
   public static void main(String[] args) throws Exception {
      //要使用Steam进行分析处理,则一定要将全部要分析的数据保存在集合中
      ArrayList<Order> list = new ArrayList<>();
      list.add(new Order("小黄鸭" , 9.9 , 10));
      list.add(new Order("电脑" , 8999.9 , 2));
      list.add(new Order("鼠标" , 99.9 , 5));
      list.add(new Order("键盘" , 299.9 , 16));
      list.add(new Order("耳机" , 200.9 , 50));
      //分析购买商品中含有“小”的信息数据,并且进行商品单价和数量的处理,然后分析汇总
      DoubleSummaryStatistics stat = list.stream().filter((ele) -> ele.getName().contains("小"))
            .mapToDouble((orderObject) -> orderObject.getPrice() * orderObject.getAmount())
            .summaryStatistics();
      System.out.println("购买数量:" + stat.getCount());
      System.out.println("购买总价:" + stat.getSum());
      System.out.println("平均花费:" + stat.getAverage());
      System.out.println("最高花费:" + stat.getMax());
      System.out.println("最低花费:" + stat.getMin());
      
   }
}

class Order{    //订单信息
   private String name;    //商品名称
   private double price;   //商品单价
   private int amount;     //商品数量
   
   public Order(String name, double price, int amount) {
      this.name = name;
      this.price = price;
      this.amount = amount;
   }
   
   public String getName() {
      return name;
   }
   
   public double getPrice() {
      return price;
   }
   
   public int getAmount() {
      return amount;
   }
}

这些分析操作只是JDK本身提供的支持,在实际开发中,肯定不会这样进行,因为所有的数据如果都保存在内存中,面对大数据的环境,会直接崩坏掉

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

MyRedScarf

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值