Java类集框架
一、Java类集简介
1、类集框架简介
从JDK1.2开始Java引入了类集开发框架,类集就是一套动态对象数组的实现方案。
在实际的开发之中,没有任何一项的开发可以离开数组,但是传统的数组实现起来非常繁琐,而且长度是致命伤,所以不可能大范围使用,但是开发又不可能离开数组,所以最初只能使用一些数据结构来实现数组的动态处理,其中最重要的两个结构:链表、树,但是也需要面对一些困难:
- 数据结构的代码实现困难
- 链表与二叉树进行维护是非常麻烦的
- 尽可能的保证链表或二叉树的性能
类集的主要功能:对常见的数据接口进行完整的包装,并提供相应的接口和实现子类减少开发困难
类集之中提供有如下的几个核心接口:Collection , List , Set , Map , Iterator , Enumeratio , ListIterator
1、Collection集合接口
Collection集合是单值集合中最大的父接口,定义有所有的单值数据的数据处理
核心操作方法:
No | 方法名称 | 类型 | 描述 |
---|---|---|---|
1 | boolean add(E e) | 普通 | 向集合保存数据 |
2 | boolean addAll(Collection<? extends E> c) | 普通 | 追加一组数据 |
3 | void clear() | 普通 | 清空集合,同时执行GC处理 |
4 | boolean contains(Object o) | 普通 | 查询数据是否存在,徐亚equals()支持 |
5 | boolean remove(Object o) | 普通 | 数据删除,需要equals()支持 |
6 | int size() | 普通 | 获取数据长度 |
7 | Object[] toArray | 普通 | 将集合变为对象数组返回 |
8 | Iterator iterator() | 普通 | 将集合变为Iterator接口返回 |
进行集合操作时最常用的方法:【增加】add() , 【输出】iterator()
当前更多采用Collection的两个子接口:【允许重复】List , 【不允许重复】Set
二、List集合
1、List接口
List是Collection子接口
特点:允许保存重复元素数据
定义:public interface extends Collection
List子接口对Collection接口进行了扩充
No | 方法名称 | 类型 | 描述 |
---|---|---|---|
1 | E get(int index) | 普通 | 获取指定索引数据 |
2 | E set(int index,E element) | 普通 | 修改指定索引数据 |
3 | ListIterator listIterator() | 普通 | 返回ListIterator接口对象 |
4 | boolean contains(Object o) | 普通 | 查询数据是否存在,徐亚equals()支持 |
5 | boolean remove(Object o) | 普通 | 数据删除,需要equals()支持 |
6 | int size() | 普通 | 获取数据长度 |
7 | Object[] toArray | 普通 | 将集合变为对象数组返回 |
8 | Iterator 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 | 方法名称 | 类型 | 描述 |
---|---|---|---|
1 | boolean hasNext() | 普通 | 判断是否有数据 |
2 | E next() | 普通 | 取出当前数据 |
3 | default 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 | 方法名称 | 类型 | 描述 |
---|---|---|---|
1 | V put(K key,V value) | 普通 | 向集合中保存数据 |
2 | V get(Object key) | 普通 | 根据key查询数据 |
3 | Set<Map.Entry<K,V>> entrySet() | 普通 | 将Map集合转为Set集合 |
4 | boolean containsKey(Object key) | 普通 | 查询指定的key是否存在 |
5 | Set keySet() | 普通 | 将Map集合的key转为Set集合 |
6 | V 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集合的输出,必须按照以下步骤处理:
- 利用Map接口中提供的entrySet()方法将Map集合转为Set集合
- 利用Set接口中的Iterator()方法将Set集合转为Iterator接口实例
- 利用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 | 方法名称 | 类型 | 描述 |
---|---|---|---|
1 | public Object setProperty(String key,String value) | 普通 | 设置属性 |
2 | public String getProperty(String key) | 普通 | 取得属性,key不存在返回null |
3 | public String getProperty(String key,String defaultValue) | 普通 | 取得属性,key不存在返回默认值 |
4 | public void store(OutputStream out,String comments) throws IOException | 普通 | 输出属性内容 |
5 | public 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本身提供的支持,在实际开发中,肯定不会这样进行,因为所有的数据如果都保存在内存中,面对大数据的环境,会直接崩坏掉