Java类集框架

56 阅读11分钟

Colletion接口

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

重要的操作方法

NO方法名称类型方法
1public boolean add(E e)重要向集合追加数据, 每次保存的肯定是单个的数据
2public boolean addAll(Collection <? extends E> e)普通向集合追加一组数据
3public void clear()普通清空集合, 让根节点为空, 同时执行gc处理
4public boolean contains(Object o)普通查询数据是否存在, 需要equals的支持
5public boolean remove(Object o)普通数据删除, 需要equals的支持
6public Object [] toArray()普通将集合转成对象数组返回
7public Iterator<E> iterator重要将集合变成iterator返回
8public int size()普通获取集合长度

List

ListCollection的子接口, 其最大的特点是允许保存有大量重复元素数据

接口定义

public interface List<E> extends Collection<E>

重要的方法操作

NO方法名称类型方法
1public E get(int index)重要获取指定索引上的数据
2public E set(int index, E element)普通修改指定索引数据
3public ListIterator<E> listIterator重要返回ListIterator接口对象

List是一个接口, 如果要使用接口则要使用子类来完成定义, 在List中有三个常用的子类:

  • ArrayList
  • Vector
  • LinkedList

ArrayList

ArrayListList子接口使用最多的一个子类.

类的定义

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

ArrayList的继承结构

通过ArrayList实例化父接口List

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

输出结果

[Hello, Hello, World, !!!!]

通过以上代码可以发现List的存储特征:

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

JDK1.8以后Iterable父接口之中定义了有一个forEach()方法的输出支持(不是标准输出)

方法定义

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

利用forEach()方法输出

 List<String> list = new ArrayList<String>();
        list.add("Hello");
        list.add("Hello");
        list.add("World");
        list.add("!!!!");
        list.forEach(str->{
            System.out.print(str + ",");
        });

输出结果

Hello,Hello,World,!!!!,

LinkedList

基于链表的定义

LinkedList继承关系

通过LinkedList实例化父接口List

public class JavaAPIDemo {
    public static void main(String[] args) {
        List<String> list = new LinkedList<String>();
        list.add("Hello");
        list.add("Hello");
        list.add("World");
        list.add("!!!!");
        System.out.println(list);
    }
}

ArrayList和LinkedList的区别

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

Vector

Vector的定义

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

从定义的结构来看和ArrayList的定义完全相同.

Vector的继承结构

Vector的使用

public class JavaAPIDemo {
    public static void main(String[] args) {
        List<String> list = new Vector<String>();
        list.add("Hello");
        list.add("Hello");
        list.add("World");
        list.add("!!!!");
        System.out.println(list);
    }
}

Vector类如果使用的是无参构造方法, 则一定默认开辟一个长度为10个长度的数组, 而后的实现和ArrayList基本相同, 通过源代码可以分析得出Vector类之中的操作方法都是采用synchronized同步处理, 而ArrayList并没有做同步处理, 所以Vector类之中的方法在多线程访问的时候是线程安全的, 但是性能不如ArrayList高.

Set

Set集合不允许保存重复元素. 是Collection的子接口 在JDK1.9以前Set集合和Collection集合的定义并没有明显的差别,

Set的定义

public interface Set<E> extends Collection<E>

Set无法像List那样使用get()方法, 也就是说Set无法实现指定索引的数据获取.

Set接口的继承关系JDK1.9以后, Set也提供了类似Listof方法. Set使用of

Set<String> all = Set.of("Hello", "World", "World");
all.forEach(System.out::println);

代码将会跑出异常, 因为保存了两个相同的元素.

Set之中有2个常用的之类, 分别是HashSetTreeSet

HashMap

HashMapSet的子类中用的最多的一个子类, 其最大的特点就是保存的数据时无序的.

HshMap的类定义

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

HashSet的继承机构

HashSet应用

Set<String> all = new HashSet<all.add("Nihao");
all.add("Wrold");
all.add("Code");
all.add("Code");
all.add("Write");
System.out.println(all);

输出结果

[Write, Nihao, Code, Wrold]
  • HashSet是无序的
  • HashSet无法保存相同的数据

TreeSet

HashSet相比, TreeSet内部保存的数据是有序的.

TreeSet类定义

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

TreeSet继承结构

TreeSet的使用

public static void main(String[] args) throws Exception {
        Set<String> all = new TreeSet<String>();
        all.add("Nihao");
        all.add("Wrold");
        all.add("Code");
        all.add("Code");
        all.add("Write");
        System.out.println(all);
}

输出

[Code, Nihao, Write, Wrold]
  • 没有重复
  • 按照首字母的升序进行排序

TreeSet排序的本质

  • TreeSet本质是利用TreeMap子类实现的集合数据的存储, 而TreeMap就需要根据Comparable来确定大小关系, 重复元素的消除和排序都是利用Comparable
  • 如果存储的是自定义类, 那么需要手动实现comparable, 且要讲类中所有的属性进行大小关系的匹配, 否则某一个或者某个属性相同的时候, 也会认为是相同属性.
  • 如果类中有较多的属性, 那么需要实现的比较多, 所以在日常开发中还是首选HashSet

HashSet进行重复元素消除的实现

HashSet判断重复元素的方式并不是利用Comparable接口完成的, 它是利用Object内部提供的方法

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

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

Iterator迭代输出

Iterator的类定义

public Iterator<T> iterator();

Iterator的接口方法

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

Iterator的继承结构

Iterator的使用

public static void main(String[] args) throws Exception {
        Set<String> all = new TreeSet<String>();
        all.add("Nihao");
        all.add("Wrold");
        all.add("Code");
        all.add("Write");
        Iterator iter = all.iterator();
        while (iter.hasNext()) {
            String str = (String) iter.next();
            System.out.println(str);
        }
}

注意: 在迭代的过程中, 不能够使用Collection中的remove(), 会导致迭代失败. 使用Iteratorremove()可以在迭代中删除原始的数据

Collection.remove和Iterator.remove()的区别

  • 在迭代输出的时候如果使用了Collection.remove()则会造成并发更新异常, Iterator.remove()方法实现正常的删除处理.

ListIterator双向迭代

ListIterator继承结构 ListIterator的操作方法

  • 判断是否有前一个元素
public boolean hasPrevious();

获取当前元素

public E previous();

实现双向迭代

public static void main(String[] args) throws Exception {
        List<String> all = new ArrayList<String>();
        all.add("Nihao");
        all.add("Wrold");
        all.add("Code");
        all.add("Write");
        ListIterator iter = all.listIterator();
        System.out.println("从前往后");
        while (iter.hasNext()) {
            String str = (String) iter.next();
            System.out.print(str + ";");
        }
        System.out.println("从后往前" + ";");
        while (iter.hasPrevious()) {
            System.out.print(iter.previous() + ";");
        }
}

Map

接口定义

public interface Map<K, V>

核心操作方法

NO方法名称类型方法
1public V put(key, value)重要向集合之中保存数据
2public V get(Object key)重要根据key查询数据
3public Set<Map, Entry<K, V>> entrySet()重要Map集合转成Set集合
4public boolean containsKey(Object key)普通查询指定的Key是否存在
5public Set<K> keySet()普通Map集合中的Key转成Set集合
6public V remove(Object key)普通根据key删掉指定的数据

HashMap

HashMap是常用的Map子类, 是无序存储.

类的定义

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

HashMap的继承关系

HashMap的使用

public static void main(String[] args) {
        Map<String, Integer> map = new HashMap<String, Integer>();
        map.put("one", 1);
        map.put("two", 1);
        map.put("three", 1);
        map.put("one", 101);
        map.put(null, 1012);
        map.put("five", null);
        System.out.println(map.get("one"));
        System.out.println(map.get("ten"));
        System.out.println(map.get(null));
}

输出结果

101
null
1012

通过代码可以发现, HashMap可以通过key或者value保存空的数据, 而且保存相同的数据也不会报错, 而是新的数据替换旧的数据, 而put()方法将旧数据的value返回.

在进行 HashMap的put()操作的收, 如何实现容量扩充的?

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

HashMap的工作原理(JDK1.8以后)

  • HashMap中进行数据存储的依然是利用了Node类完成的, 那么在这种情况下就可以证明可以使用的数据结构只有2种: 链表(O(n))和二叉树(O(logn))
  • 从JDK1.8开始HashMap的实现发生了改变, 因为要适应于大数据时代的海量数据, 所以对其存储发生了改变, 并且HashMap内部存在一个常量static final int TREEIFY_THRESHOLD = 8. 在使用HashMap进行数据保存的时候, 如果保存的数据个数没有超过阈值8, 会按照链表的形式进行存储, 如果超过了阈值, 那么会将链表转为红黑树以实现树的平衡, 并且利用左旋和右旋保证数据的查询性能

如果在进行HashMap的数据操作的时候, 出现了Hash冲突(Hash码相同), HashMap是怎么解决的

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

LinkedHashMap

LinkedHashMapHashMap的区别在于LinkedHashMap数据保存的顺序就是数据添加的顺序

LinkedHashMap的定义

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

既然是链表存储, 所以一般在使用LinkedHashMap类的时候数据量不会太大, 不然会造成时间复杂度的攀升. LinkedHashMapHashMap的子类

LinkedHashMap继承关系

LinkedHashMap的使用

public static void main(String[] args) {
        Map<String, Integer> map = new LinkedHashMap<>();
        map.put("one", 1);
        map.put("two", 1);
        map.put("three", 1);
        map.put("one", 101);
        map.put(null, 1012);
        map.put("five", null);
        System.out.println(map);
    }

输出结果

{one=101, two=1, three=1, null=1012, five=null}

验证LinkedHashMap数据保存的顺序就是数据添加的顺序

Queue队列

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

队列

如果将队列应用在多线程的"生产者与消费者"的模型处理上, 那么对于生产者生产过快的情况下就不需要等待消费者获取数据了, 可以将所有的内容直接保存在队列之中. 队列的实现可以使用LinkedList来完成.

Queue的继承结构

Queue的重要操作方法

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

使用LinkedList实现一个队列

public static void main(String[] args) {
        Queue<String> queue = new LinkedList<String>();
        queue.offer("A");
        queue.offer("B");
        queue.offer("C");
        queue.offer("D");
        System.out.println(queue.poll());
        System.out.println(queue.poll());
        System.out.println(queue.poll());
        System.out.println(queue.poll());
}

输出结果

A
B
C
D

除了LinkedList以外, 可以使用PriorityQueue实现优先级队列, PriorityQueue的使用和LinkedList一样

Collections

Collections的继承结构

数据的全部追加

public static void main(String[] args) {
        List<String> all = new ArrayList<String>();
        Collections.addAll(all, "Hello", "World", "Thks");
        System.out.println(all);
}

数据的翻转

public static void main(String[] args) {
        List<String> all = new ArrayList<String>();
        Collections.addAll(all, "Hello", "World", "Thks");
        Collections.reverse(all);
        System.out.println(all);
}

数据的二分查找

public static void main(String[] args) {
        List<String> all = new ArrayList<String>();
        Collections.addAll(all, "Hello", "World", "Thks");
        // 二分查找之前必须排序
        Collections.sort(all);
        System.out.println(Collections.binarySearch(all, "Hello"));
}

Collection和Collections的区别

  • Collection是集合接口, 允许保存单值对象
  • Collections是集合操作的工具类

Stream基本操作

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

获取Stream接口对象

public default Stream<E> stream();

Stream基础操作

  • 获取元素的个数
public static void main(String[] args) {
        List<String> all = new ArrayList<String>();
        Collections.addAll(all, "Java", "JS", "JSP", "OC", "Swift", "GO", "Ruby");
        Stream<String> stream = all.stream(); // 获取Stream接口对象
        System.out.println(stream.count());
}

输出

7
  • 判断包含字母j(不区分大小写)的元素的个数
public static void main(String[] args) {
        List<String> all = new ArrayList<String>();
        Collections.addAll(all, "Java", "JS", "JSP", "OC", "Swift", "GO", "Ruby");
        Stream<String> stream = all.stream(); // 获取Stream接口对象
        System.out.println(stream.filter(ele->ele.toLowerCase().contains("j")).count());
}

输出结果

3
  • 判断包含字母j(不区分大小写)的元素
public static void main(String[] args) {
        List<String> all = new ArrayList<String>();
        Collections.addAll(all, "Java", "JS", "JSP", "OC", "Swift", "GO", "Ruby");
        Stream<String> stream = all.stream(); // 获取Stream接口对象
        List<String> list = stream.filter(ele->ele.toLowerCase().contains("j")).collect(Collectors.toList());
        System.out.println(list);
}

输出

[Java, JS, JSP]

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

  • 设置取出的最大数据量:
public Stream<T> limit(long maxSize);
  • 跳过指定的数据量:
public Stream<T> skip(long n)

观察分页

public static void main(String[] args) {
        List<String> all = new ArrayList<String>();
        Collections.addAll(all, "Java", "JS", "JSP", "OC", "Swift", "GO", "Ruby", "JSON", "JavaScript");
        Stream<String> stream = all.stream(); // 获取Stream接口对象
        List<String> list = stream.filter(ele->ele.toLowerCase().contains("j")).skip(2).limit(2).collect(Collectors.toList());
        System.out.println(list);
    }

输出结果

[JSP, JSON]