工作&&学习的热情不应季节的变化而改变
1.collection 简介
单值集合操作的最大父接口,每一次指向集合之中保存一个对象,是一种工具类,可以存储任意数量、任意类型的对象;模块在java.base ,包在java.util。
类图如下,部分:
List(允许重复),Set(不允许重复,按照顺序保存),SortedSet (不允许重复且排序),Queue(队列)
Collection接口:
// 向集合中追加数据public boolean add (E e) // 向集合中追加一组数组public boolean addAll (Collection extends E> c) // 清空集合public void clear() // 数据查询,需要equals()方法支持 public boolean contains (Object o) // 获取iterator 接口实例 public Iterator iterator() // 集合中删除指定元素 public boolean remove (Object o)// 返回集合中的元素个数public int size() // 将对象集合以数组的形式返回 public Object[] toArray()
List接口
// 将指定的元素插入此列表中的指定位置public void add (int index, E element) // 获取此列表中指定位置的元素。public E get (int index) // 用指定的元素替换此列表中指定位置的元素public E set (int index, E element) // 返回指定元素在此列表中首次出现的索引;如果此列表不包含该元素,则返回-1public int indexOf (Object o)// 获取 ListIterator 接口实例public ListIterator listIterator() // 通过指定的内容创建List集合public static List of (E e1) // 实现List集合排序public default void sort (Comparator super E> c) // 返回包含任意数量元素的不可修改列表,jdk9+public static Listof(E... elements)
package cn.fz.cls;import java.util.List;public class DemoTest1 { public static void main(String[] args) { List all = List.of("111","222","333"); //创建不能修改的list集合 }}
List 接口子类 ArrayList
// 使用默认容量public ArrayList() // 设置一个初始化容量public ArrayList(int initialCapacity)
当ArrayList 中保存的容量不足时,每一次扩充”50%”(jdk11);在每一次使用ArrayList的时候一定要考虑好长度存储问题。如果你保存的数据长度在10或以内使用无参构造即可。
ArrayList 自定义存储对象
package cn.fz.cls;import java.util.ArrayList;import java.util.List;class Ball2 { private String brand; private double price; public Ball2(String brand, double price) { this.brand = brand; this.price = price; } @Override public String toString() { return "Ball2{" + "brand='" + brand + '\'' + ", price=" + price + '}'; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null) return false; if (!(o instanceof Ball2)) { return false; } Ball2 ball2 = (Ball2) o; return this.brand.equals(ball2.brand) && this.price == ball2.price; }}public class DemoTest1 { public static void main(String[] args) { List list = new ArrayList(); list.add(new Ball2("wn",12)); list.add(new Ball2("ss",123)); list.add(new Ball2("sbn",1234)); list.remove(new Ball2("wn",12)); System.out.println(list); } }
List集合经常会用于进行数据的存储,对于需要动态查询或者删除自定义类对象里面就一定要提供equals()方法,但是如果此时只是进行存储而不进行contains()和remove()操作的时候,就不需要使用equals()方法了。
List 接口子类 LinkedList
基于链表的方案实现的集合存储。
// 链表实现基本代码,单向链表private class Node<T> { private T data; private Node next ; public Node(T data) { this.data = data; } public void addNode(Node newNode){ if (this.next == null) { this.next = newNode; } else { this.next.addNode(newNode); } } public void toArrayNode(){ LinkImpl.this.returnData[LinkImpl.this.foot ++] = this.data; if (this.next != null) { this.next.toArrayNode(); } } public T getNode(int index){ if (LinkImpl.this.foot ++ == index) { return this.data; } else { if (this.next != null) { return this.next.getNode(index); } else { return null; } } } public T setNode(int index, T newData){ if (LinkImpl.this.foot ++ == index) { T temp = this.data; this.data = newData; return temp; } else { if (this.next != null) { return this.next.setNode(index,newData); } else { return null; } } } public boolean containsNode(T data){ if (this.data.equals(data)) { return true; } else { if (this.next != null) { return this.next.containsNode(data); } else { return false; } } } public void removeNode(Node pre, T data) { if (this.data.equals(data)) { pre.next = this.next; //空出当前节点 } else { if (this.next != null) { //还有后续节点 this.next.removeNode(this,data); } } }}
在LinkedList 里面提供有节点处理类,这个类定义如下:
// 这个类现在不需要外部进行访问,属于双向链表private static class Node { E item; 存放数据 Node next; 存放下一个节点 Node prev; 存放上一个节点}
List 接口子类vector
整体定义和继承结构和ArrayList是一样的,这两者最大区别是Vector类中的方法采用的是synchronized同步处理机制,而ArrayList 没有采用同步。
ArrayList与LinkedList 区别:
ArrayList 是基于数组实现的集合类,而LinkedList 是基于链表实现的集合类。
ArrayList类 根据索引查询的时候时间复杂度O(1),而LinkedList 类 时间复杂度 O(n)
ArrayList与Vector区别:
ArrayList 是在jdk1.2 提出集合框架的时候定义的,其内部的方法未使用同步处理,属于非线程安全的操作;
Vector 是在jdk1.0的时候提供的一个类,在JDK1.2之后增加到集合框架之中,所有的方法使用Synchronized同步处理机制,线程安全处理操作;
ArrayList 和Vector 一样都是基于数组实现的动态存储
Set接口
在JDK1.9以前,Set接口并没有对Collection接口方法进行任何扩充,即:两个接口的方法是完全相同的(Set接口没有List接口中的get()方法),只不过jdk1.9开始,为其追加了一些of()方法。
package cn.fz.cls;import java.util.Set;public class DemoTest1 { public static void main(String[] args) { //自动排序 ,重复的抛出异常 ”IllegalArgumentException“ Set<String> all = Set.of("11111","33333","22222"); System.out.println(all); }}
Set接口子类HashSet(散列存储)
HashSet里面包含有一个HashMap的结构(key=value)的偶对象,所以每当使用HashSet 无参构造的时候,HashSet 之中默认大小为16,而且每当增长到75%的时候会自动进行扩容。
Set接口子类 TreeSet(有序存储)
属于排序的一种操作结构,所以里面保存的内容全部都是要求有序存储。
// 使用的是Comparable排序接口public TreeSet() // 设置额外使用Comparable排序接口。 public TreeSet(Comparator super E> comparator)
一旦使用了TreeSet类保存了自定义类对象(常见的是String、包装类、系统类为主) 这个时候类中的CompareTo()方法就要将全部的属性都拿来进行比较,如果只比较了部分,并且储存的数据这部分相同的时候会认为它是重复数据。
class Ball2 implements Comparable<Ball2> { private String brand; private double price; public Ball2(String brand, double price) { this.brand = brand; this.price = price; } @Override public String toString() { return "Ball2{" + "brand='" + brand + '\'' + ", price=" + price + '}'; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null) return false; if (!(o instanceof Ball2)) { return false; } Ball2 ball2 = (Ball2) o; return this.brand.equals(ball2.brand) && this.price == ball2.price; } @Override public int compareTo(Ball2 o) { if (this.price > o.price) { return 1; } else if (this.price < o.price) { return -1; } else { return this.brand.compareTo(o.brand); } }}public class DemoTest1 { public static void main(String[] args) { Set all = new TreeSet(); all.add(new Ball2("wn",12)); //重复 all.add(new Ball2("ss",12)); all.add(new Ball2("sbn",12)); System.out.println(all); }}
消除重复元素
Set集合最大的特点是不会进行重复元素存储,通过之前的代码可以发现TreeSet 子类依据的是Comparable 接口实现了重复元素判断,这个重复元素并不是针对所有集合类。
真正的重复元素判断实际上需要两个操作的支持:
// 进行对象编码获取public int hashCode();// 进行对象内容比较publicboolean equals(Object obj);
# 对于hashCode() 需要有一个计算公式,通过属性计算出来一个不会重复的编码,利用开发工具可以自动生成,在idea里面使用 @Overridepublic boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Ball2 ball2 = (Ball2) o; return Double.compare(ball2.price, price) == 0 && brand.equals(ball2.brand);}@Overridepublic int hashCode() { return Objects.hash(brand, price);}
Iterator 迭代输出
只要是Collection接口的子接口或者是子类都可以直接利用iterator()方法获取Iterator接口实例
接口部分方法如下:
// 判断是有下一个内容public boolean hasNext() // 获取当前内容 public E next() // 删除当前元素public default void remove()
在iterator接口里面有一个remove()方法,那么它与Collection接口中定义的remove()相比有哪些区别呢?
List all = new ArrayList();all.add("1");all.add("1");all.add("3");Iterator iterator = all.iterator();while(iterator.hasNext()) { String val = iterator.next(); if ("1".equals(val)) { iterator.remove(); } else { System.out.println(val); }}
使用Iterator接口中的remove()方法可以成功实现集合内容的删除,如果此时要使用的是Collection接口中的remove()方法。
List all = new ArrayList();all.add("1");all.add("1");all.add("3");Iterator iterator = all.iterator();while(iterator.hasNext()) { String val = iterator.next(); if ("1".equals(val)) { all.remove("1"); } else { System.out.println(val); }}
抛异常 ConcurrentModificationException,实际上使用集合中的删除操作,在迭代的时候无法提供正常支持,所以才在iterator 接口中追加有一个remove()方法。
从jdk1.8开始由于追加了Lambda表达式,以及方法引用和功能性接口,所以如果现在想进行内容的输出,比较简单。
package cn.fz.cls;import java.util.ArrayList;import java.util.List;public class DemoTest1 { public static void main(String[] args) { List<String> all = new ArrayList<String>(); all.add("1"); all.add("1"); all.add("3"); // 实际开发之中不能使用 all.forEach(System.out :: println); }}
Iterator接口子类ListIterator(双向迭代)
public boolean hasPrevious() public E previous()
package cn.fz.cls;import java.util.ArrayList;import java.util.List;import java.util.ListIterator;public class DemoTest1 { public static void main(String[] args) { List all = new ArrayList(); all.add("1"); all.add("2"); all.add("3"); all.add("4"); all.add("5"); ListIterator listIterator = all.listIterator(); while(listIterator.hasNext()){ //正向 System.out.println(listIterator.next()); } while(listIterator.hasPrevious()) { //反向 System.out.println(listIterator.previous()); } }}
Enumeration枚举输出
在jdk1.0 为了方便Vector 集合输出,在Enumeration 接口最初的时候只定义有两个操作方法。
// 判断是否有下一个元素public boolean hasMoreElements() // 获取当前元素public E nextElement()
Iterator 接口与Enumeration 接口的核心功能非常相似,但是优点在于:Iterator是一个标准,并且方法名称简短,但是需要注意的是,并没有任何一个集合的接口拥有获取Enumeration对象方法,只有Vector这个古老的类中才有对应的方法。
package cn.fz.cls;import java.util.*;public class DemoTest1 { public static void main(String[] args) { Vector all = new Vector(); all.add("1"); all.add("2"); all.add("3"); all.add("4"); Enumeration enumeration = all.elements(); while (enumeration.hasMoreElements()) { System.out.println(enumeration.nextElement()); } }}
foreach输出自定义类
package cn.fz.cls;import java.util.*;class Child implements Iterable<String> { private String content[] = new String[]{"1","2","3","4"}; private int foot; @Override public Iteratoriterator() { return new ChildIter(); } private class ChildIter implements Iterator<String> { @Override public boolean hasNext() { return Child.this.foot < Child.this.content.length; } @Override public String next() { return Child.this.content[Child.this.foot++]; } }}public class DemoTest1 { public static void main(String[] args) { Child child = new Child(); for (String tmp : child) { //自定义对象 System.out.println(tmp); } }}
Map接口简介
集合根据数据存储的不同分为两种形式:单值集合、二元偶对象集合,在之前使用的Collection都属于单值集合,map属于二元偶对象,所谓的二元偶对象指的是存储的数据为”key=>value” 结构对,在使用的时候可以根据key查询出相应的value的内容。
所以Collection和map存储数据的目的分别为:Collection 是为了数据的输出而存储,而Map是为了查询而存储。
java.util.Map 是进行二元偶对象数据存储的最大父接口,在里面所有存放的内容会按照”key=value” 的形式,Map接口的常用方法如下:
public V put (K key, V value) 向集合中保存数据,如果key存在则发生替换,同时返回旧的内容,如果key不存在,返回内容为空.public V get (Object key) 通过key查询对应的内容public V remove (Object key) 根据key删除对应的内容public int size() 获取集合长度public Collection values() 返回所有的内容public Set keySet() 获取所有的key (key不能重复)public Set<Map.Entry> entrySet() 将所有的内容以Map.entry集合返回
Map接口依赖Collection和Set接口
package cn.fz.cls;import java.util.Map;public class DemoTest1 { public static void main(String[] args) { //出现key重复,会出现异常 //Exception in thread "main" java.lang.IllegalArgumentException: duplicate key: k3 Mapmap = Map.of("k1",11,"k2",22,"k3",33,"k3",44); System.out.println(map); }}
Map接口子类HashMap
存储都是无序的,定义结构如下:
public class HashMapV> extends AbstractMapV>
Jdk1.8之后 HashMap 算法进行了重大的变更,下边进行源码分析:
1. public HashMap() { 无参构造 this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted } 2. staticfinal float DEFAULT_LOAD_FACTOR = 0.75f; (默认扩充阈值为”75%”)final float loadFactor;3.publicV put(K key, V value) { return putVal(hash(key), key, value, false, true); putVal 实现了节点的相关创建以及扩容的调用(resize())}4.resize() 默认容量大小保存16个 staticfinal int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16transient Node[] table; 默认是根据一个数组的形式存储的int threshold; 默认的大小static final int MAXIMUM_CAPACITY = 1 << 30; 最大的存储oldThr << 1 每次扩充一倍 比如 32<<1 : 64 64 << 1 1285.性能保证 staticfinal int TREEIFY_THRESHOLD = 8; 树状阈值if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st treeifyBin(tab, hash); //进行树状结构转换break;//每一次对于hashMap 来讲,如果要进行扩容,则表示当前的存储容量为“75%” 才会选择扩容 16*0.75 ,在jdk1.8之后如果hashMap的存储容量达到了8位,那么为了保证数据的查询性能,HashMap会将原始的链表存放结构转为红黑树结构保存。可以利用红黑树自旋的处理实现树的平衡修复.
Map接口子类LinkedHashMap
HashMap 之中进行数据存储的时候并不会进行顺序的定义,就可以利用LinkedHashMap 的子类来完成。
Map接口子类TreeMap
Java.util.TreeMap 实现的是一个排序的树结构,可以依据key的自然顺序实现排序处理。
Map接口子类HashTable
Jdk1.0 提出最早的偶对象存储
public class HashtableV> extends DictionaryV>
HashMap与HashTable区别:
HashTable在进行数据存储的时候是不允许存放null数据的,不管是key还是value,如果发现有null ,会出现”java.lang.NullPointerException” ,而相比较HashMap 不管是在key 还是value上都没有关于null的限制.
HashMap(相当于ArrayList) 在进行存储的时候默认大小为16,在存储量达到8位之后 为了保证数据的查询性能使用红黑树存储,都使用了异步处理,属于非线程安全
HashTable(相当于Vector) 在进行存储时默认大小为 11 ,HashTable 方法使用同步处理,属于线程安全的处理操作。
Map.Entry
存储的是二元偶对象,在Map接口里面定义有一个Map.Entry 的内部接口,此接口主要是key和value的封装的,而且在Map接口里面也可以发现Map.Entry 的子类:
Map.Entry 里面可以包装key和value ,那么也可以通过Map.Entry 获取对应的key和value,此接口定义如下:
public static interface Map.Entry 内部接口 public K getKey()public V getValue() // jdk1.9 之后Map接口方法:public static Map.Entry entry (K k, V v)
package cn.fz.cls;import java.util.Map;public class DemoTest1 { public static void main(String[] args) { Map.Entry<String,String> entry = Map.entry("k1","v1"); System.out.println(entry.getKey() + "--" + entry.getValue()); }}
Map.Entry 实质上定义了一个Map偶对象存储的标准,所有的Map接口的子类都依据此标准实现相应的节点数据的存储,也可以直接利用此实例实现key与value的分离。
Iterator输出Map集合
面对集合数据输出,肯定要考虑使用Iterator 接口完成,但是在Map接口中并没有任何一个方法可以直接获取到Iterator对象,所以此时必须经过一系列的转换得来。原因就在于它的存储不是一个普通的数据,而是一个偶对象,而Iterator 每一个都是可以输出的全部都是单个实例,为此基本的输出流程如下:
1.通过Map接口中的entrySet()方法,将Map实例转为Set接口实例public Set<Map.Entry> entrySet()2.获取了Set集合实例之后就可以调用iterator()方法获取Iterator 接口实例,泛型类型为”May.Entry”3.通过Iterator 进行迭代操作,获取每一组的”Map.Entry”实例,进行key与value的分离
package cn.fz.cls;import java.util.HashMap;import java.util.Iterator;import java.util.Map;import java.util.Set;public class DemoTest1 { public static void main(String[] args) { Map<String,String> map = new HashMap<>(); map.put("k1","v1"); map.put("k2","v2"); map.put("k3","v3"); Set<Map.Entry<String, String>> set = map.entrySet(); Iterator<Map.Entry<String,String>> iterator = set.iterator(); while(iterator.hasNext()){ Map.Entry<String,String> entry = iterator.next(); System.out.println(entry.getKey() + "--" + entry.getValue()); } }}
从jdk1.5之后Map集合也可以使用foreach 进行输出,因为其内部实现了Iterator 接口:
package cn.fz.cls;import java.util.HashMap;import java.util.Map;public class DemoTest1 { public static void main(String[] args) { Mapmap = new HashMap<>(); map.put("k1","v1"); map.put("k2","v2"); map.put("k3","v3"); for (Map.Entry entry : map.entrySet() ) { System.out.println(entry.getKey() + "--" + entry.getValue()); } }}
就是通过Iterator ,Map很少输出,因为主要是查询操作。
自定义Key
对于Map集合可以发现,设置的k和v两个泛型类型只要是引用数据类型就可以了,那么这也包括了自定义的类,也就是说自定义的类也可以称为Map中的key类型。
Map集合根据key获取数据时的流程:
1.利用hashCode ()方法生成结果进行比较,因为这只是一个数字,它的比较速度会更加的快;
2.如果发现哈希码相同的时候才会进行内容的比较。
package cn.fz.cls;import java.util.HashMap;import java.util.Map;import java.util.Objects;class Member3 { private String name; private int age; public Member3(String name, int age) { this.name = name; this.age = age; } @Override public String toString() { return "Member3{" + "name='" + name + '\'' + ", age=" + age + '}'; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Member3 member3 = (Member3) o; return age == member3.age && name.equals(member3.name); } @Override public int hashCode() { return Objects.hash(name, age); }}public class DemoTest1 { public static void main(String[] args) { Map map = new HashMap<>(); map.put(new Member3("来历",12),new String("来历")); System.out.println(map.get(new Member3("来历",12))); }}
经过现在的分析可以得出一个结论:
哈希码实际上是进行对象比较的关键所在,而进行Map存储的时候实际上也是依靠哈希码得到的存储空间,但是在很多情况下依然有可能会出现哈希冲突的问题。
四种解决方案:
开放定址法,链地址法,再哈希法,建立公共的溢出区,而java利用链地址法形式解决,把所有重复的内容放到一个链表之中保存(链表结构,上边有代码)。
Queue队列简介
队列本质指的是先进先出(First In First Out、FIFO) ,如果说现在有许多的操作者等待进行处理的时候,那么这种情况下就会将许多的操作者变为一队进行存储。
//增加队列数据public boolean add (E e) //如果可以在不违反容量限制的情况下立即将指定的元素插入此队列public boolean offer (E e) // 检索并删除此队列的头。public E remove()// 检索并删除此队列的头部,null如果此队列为空,则返回。public E poll()
package cn.fz.cls;import java.util.LinkedList;import java.util.Queue;public class DemoTest1 { public static void main(String[] args) { Queuequeue = new LinkedList<>(); queue.offer("A"); queue.offer("M"); queue.offer("C"); while (!queue.isEmpty()) { System.out.println(queue.poll()); } }}
linkedList ()是基于链表的实现,所以不会改变存储数据顺序。
package cn.fz.cls;import java.util.PriorityQueue;import java.util.Queue;public class DemoTest1 { public static void main(String[] args) { Queuequeue = new PriorityQueue<>(); queue.offer("A"); queue.offer("M"); queue.offer("C"); while (!queue.isEmpty()) { System.out.println(queue.poll()); } }}
将排序后的内容依次输出,先进先出
Deque接口
双向的操作能力,可以通过队首实现数据的增加与弹出,也可以在队尾上实现数据的增加与弹出。
package cn.fz.cls;import java.util.ArrayDeque;import java.util.Deque;public class DemoTest1 { public static void main(String[] args) { Dequedeque = new ArrayDeque(); deque.offerFirst("1"); deque.offerLast("3"); //从队首取数据 System.out.println(deque.poll()); }}
【完】
欲知更多精彩,请扫码关注,下期再见!