集合
在学完数组后,我们可以知道,数组在定义后,长度就不可变了,所以我们要存储更多的东西时,需要频繁的创建数组,所以数组是不合适的,就需要其他容器------集合
集合是长度可变的,可以存储不同类数据的容器,例如可以同时存储整型,浮点型,对象等。
1. Collection
Collection
是Collection 层次结构 中的根接口。Collection 表示一组对象,这些对象也称为 collection 的元素。一些 collection 允许有重复的元素,而另一些则不允许。一些 collection 是有序的,而另一些则是无序的。JDK 不提供此接口的任何直接 实现:它提供更具体的子接口(如 Set
和 List
)实现。此接口通常用来传递 collection,并在需要最大普遍性的地方操作这些 collection。
1.1 Collection中的方法
- add
- addAll
- clear
- contains
- containsAll
- equals
- hashCode
- isEmpty
- remove
- removeAll
- toArray
- size
import java.util.ArrayList;
import java.util.Collection;
public class CollectionDemmo {
public static void main(String[] args) {
Goods goods1 = new Goods("瓜子",100,9.9);
Goods goods2 = new Goods("花生",200,9.9);
Goods goods3 = new Goods("啤酒",20000,12.99);
Goods goods4 = new Goods("麻辣条",100,3);
// 使用多态的形式 Collection接口 List子接口的实现类
Collection collection = new ArrayList();
//集合是个容器 存储元素
//基本数据类型和对象都可以进行存储
collection.add("123");
collection.add(123);
collection.add('a');
collection.add(99.99);
collection.add(goods1);
System.out.println(collection.size());
System.out.println(collection);
//集合的长度是可以扩增的
collection.add(goods2);
collection.add(goods3);
System.out.println(collection.size());
System.out.println(collection);
//集合提供remove方法移除元素 集合的长度自动减少
collection.remove(goods3);
System.out.println(collection.size());
System.out.println(collection);
}
}
public static void method1() {
Goods goods1 = new Goods("瓜子",100,9.9);
Goods goods2 = new Goods("花生",200,9.9);
Goods goods3 = new Goods("啤酒",20000,12.99);
Goods goods4 = new Goods("麻辣条",100,3.0);
// 使用多态的形式 Collection接口 List子接口的实现类
Collection collection = new ArrayList();
//集合是个容器 存储元素
//基本数据类型和对象都可以进行存储
collection.add("123");
collection.add(123);
collection.add('a');
collection.add(99.99);
collection.add(goods1);
System.out.println(collection.size());
System.out.println(collection);
//集合的长度是可以扩增的
collection.add(goods2);
collection.add(goods3);
System.out.println(collection.size());
System.out.println(collection);
//集合提供remove方法移除元素 集合的长度自动减少
collection.remove(goods3);
System.out.println(collection.size());
System.out.println(collection);
}
//集合 的其他的方法
public static void method2() {
Collection collection1 = new ArrayList();
collection1.add("123");
collection1.add(123);
collection1.add('a');
collection1.add(99.99);
Collection collection2 = new ArrayList();
Goods goods1 = new Goods("瓜子",100,9.9);
Goods goods2 = new Goods("花生",200,9.9);
collection2.add(goods1);
collection2.add(goods2);
//集合合并
collection1.addAll(collection2);
System.out.println(collection1);
//是否包含某一个元素
System.out.println(collection2.contains(goods1));//true
//判断集合中数据是否相等
System.out.println(collection1.equals(collection2));//false
//判断集合是否为空
System.out.println(collection1.isEmpty());//false
System.out.println(collection1.removeAll(collection2));//true
System.out.println(collection1);
Object [] obj = collection1.toArray();
System.out.println(obj);
}
1.2 迭代器
提供集合的通用的遍历方式
- hasNext()判断是否有下一个可迭代的元素
- next()获取下一位迭代的元素
- remove()移除下一个迭代元素
public static void method3() {
//数组集合
Collection collection2 = new ArrayList();
Goods goods1 = new Goods("瓜子",100,9.9);
Goods goods2 = new Goods("花生",200,9.9);
collection2.add(goods1);
collection2.add(goods2);
System.out.println(collection2);
//集合的遍历
//hasNext() next()
Iterator it = collection2.iterator();
while(it.hasNext()) {
//数据的强制转换
//铺垫泛型
Goods good = (Goods)it.next();
System.out.println(good);
}
}
不合法的状态异常:
public static void method4() {
//数组集合
Collection collection2 = new ArrayList();
Goods goods1 = new Goods("瓜子",100,9.9);
Goods goods2 = new Goods("花生",200,9.9);
collection2.add(goods1);
collection2.add(goods2);
System.out.println(collection2);
//集合的遍历
//hasNext() next()
Iterator it = collection2.iterator();
while(it.hasNext()) {
//数据的强制转换
//铺垫泛型
//java.lang.IllegalStateException
//先移除在获取发生不合法的状态异常
//it.remove();
Goods good = (Goods)it.next();
//it.remove();
System.out.println(good);
}
}
ConcurrentModificationException并发修改异常:
public static void method5() {
//数组集合
Collection collection2 = new ArrayList();
Goods goods1 = new Goods("瓜子",100,9.9);
Goods goods2 = new Goods("花生",200,9.9);
collection2.add(goods1);
collection2.add(goods2);
System.out.println(collection2);
//集合的遍历
//hasNext() next()
Iterator it = collection2.iterator();
while(it.hasNext()) {
//数据的强制转换
//铺垫泛型
//java.lang.IllegalStateException
//先移除在获取发生不合法的状态异常
//java.util.ConcurrentModificationException 并发修改异常
collection2.add(new Goods("香烟",500,15));
Goods good = (Goods)it.next();
System.out.println(good);
}
}
1.3 Iterator 和 Enumeration的区别
功能重复
Iterator替换了Enumeration:
- 添加了remove方法
- 方法名被简写了
2. List
List是Collection的子接口,继承来自Collection的方法,也有特有的方法,默认初始容量为10
- 该接口是有序的,允许存储重复元素,可以存多个null值
- 可以通过下标对集合内的元素进行访问
List使用特有的ListIterator
在 Iterator基础上,有元素的插入和替换的方法 指定开始的位置的迭代器,List集合特有的方法:get()获取list集合中的元素,indexOf(),lastIndexOf()。
2.1 ArrayList
底层是数组的数据结构,是长度可变的数组,存储元素是有序的,允许存储重复元素和多个null值,是线程不安全的,不同步的,效率高,读取的效率高,因为是通过下边就可以操作,但是增删的时候效率低,因为增删时所有元素的下标会被影响。
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
public class ListDemo {
public static void main(String[] args) {
List list = new ArrayList();
list.add("Hello");
list.add(123);
list.add('d');
list.add(new Object());
list.add(99.99);
//[Hello, 123, d, java.lang.Object@15db9742, 99.99]
System.out.println(list);
//通过下标获取集合中的元素
System.out.println(list.get(0));
//获取集合的元素个数
System.out.println(list.size());
//元素在集合中第一次出现的下标值
System.out.println(list.indexOf('d'));
//最后一次出现的下标值
System.out.println(list.lastIndexOf(99.99));
//截取集合片段 不包含结尾下标的集合元素
System.out.println(list.subList(0, 3));
System.out.println("------------集合的遍历-----------");
//遍历
/*
* for(int i=0;i<list.size();i++) { System.out.println(list.get(i)); }
*/
/*
* for(Object i:list) { System.out.println(i); }
*/
//通用迭代器
/*
* Iterator it = list.iterator(); while(it.hasNext()) { Object obj = it.next();
* System.out.println(obj); }
*/
//特有的listIterator
ListIterator iterator = list.listIterator();
//java.util.NoSuchElementException
//返回集合中上一个元素
//System.out.println(iterator.previous());
iterator.add("88888");
while(iterator.hasNext()) {
//iterator.add("88888");
System.out.println(iterator.next());
//iterator.set("Java");
}
System.out.println(list);
}
}
2.1.1 ArrayList去除重复的三种方式
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
public class ListItertorDemo {
public static void main(String[] args) {
List list=new ArrayList();
list.add("abc1");
list.add("abc1");
list.add("abc1");
list.add("abc3");
list.add("abc2");
list.add("abc1");
list.add("abc3");
list.add("abc3");
list.add("abc1");
list.add("abc1");
list.add("abc1");
list.add("abc3");
System.out.println(list);
singleElement2(list);
System.out.println(list);
}
/*方法二,
* 思路:
* 1.最后唯一性的元素也很多,可以先定义一个临时容器用于存储这些唯一性的元素
* 2.对原有容器进行元素的获取,并到临时容器中去判断是否存在,容器本身就有这个功能,判断元素是否存在 equals
* 3.存在就不存储,不存在就存储
* 4.遍历完原容器后,临时容器中存储的就是唯一性的元素
*/
public static void singleElement2(List list){
List temp=new ArrayList();
for (Iterator it = list.iterator(); it.hasNext();) {
Object obj = (Object) it.next();
if(!temp.contains(obj)){
temp.add(obj);
}
}
list.clear();
list.addAll(temp);
}
//方法一,思想是数组选择排序
public static void singleElement(List list){
for(int x=0;x<list.size()-1;x++){
Object obj1=list.get(x);
for(int y=x+1;y<list.size();y++){
if(obj1.equals(list.get(y))){
list.remove(y);
y--; //可以省略
}
}
}
//方法三,面向对象的思想,继承ArrayList重写add方法
@Override
public boolean add(Object o) {
if (this.indexOf(o) != -1){//判断Mylist集合中是否有这个元素,如果有的话,索引就肯定不等于-1
return false;
}else {
return super.add((E) o);
}
}
}
}
2.2 Vector
Vector
类可以实现可增长的对象数组。与数组一样,它包含可以使用整数索引进行访问的组件。但是,Vector
的大小可以根据需要增大或缩小,以适应创建Vector
后进行添加或移除项的操作。
Vector是线程安全的,线程同步的。
Vector读取快 增删慢
从JDK1.2开始使用ArrayList替换了Vector
2.3 LinkedList
List
接口的的链表实现。实现所有可选的列表操作,并且允许所有元素(包括null)。除了实现List接口外,LinkedList类还为在列表的开头及结尾
get、
remove和
insert` 元素提供了统一的命名方法。这些操作允许将链接列表用作堆栈、队列或双端队列。
LinkedList是线程不安全的,线程不同步的。
数据结构为:链表结构(底层其实是数组加链表),读取慢,增删快。
- 数组是为了查询快,让链表有序
- 链表是为了增删快
linkedlist
是双向链表加列表的实现. 因为LinkedList实现了List
和Queue
接口。
常用的方法:
- addFirst():
- addLast():
- getFirst():
- getLast():
- element():
- pop():
- push():
- peek():
- offer():
- offerFirst():
- removeFirst():
- removeLast():
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
public class LinkedListDemo {
public static void main(String[] args) {
List list=new LinkedList();
LinkedList list1=new LinkedList();
list.add(123);
list.add("222");
list.add(20.00);
System.out.println(list.get(0));
System.out.println("=========================");
Iterator iterator = list.iterator();
while (iterator.hasNext()){
Object next = iterator.next();
System.out.println(next);
}
System.out.println("========================");
/*list.clear();
System.out.println(list);*/
list1.addFirst("1234");
list1.addAll(list);
System.out.println(list1);
System.out.println(list1.peek());
System.out.println(list1);
list1.pop();
System.out.println(list1);
list1.push(890);
System.out.println(list1);
System.out.println("=========================");
System.out.println(list1.peekLast());//获取但不移除此列表的最后一个元素;如果此列表为空,则返回 null。
System.out.println(list1);
Object element = list1.element();//源码中是 return getFirst();获取不移除
System.out.println(element);
System.out.println(list1);
System.out.println("=========================");
}
}
LinkedList中的add方法是如何实现的:(源码)
public boolean add(E e) {
linkLast(e);
return true;
}
private static class Node<E> {
E item;
Node<E> next;
Node<E> prev;
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
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++;
}
2.4 ArrayList,Vector和LinkedList的区别
- ArrayList是不同步的,效率高,查询快,增删慢
- Vector是同步的,效率快但是相对ArrayList慢,读取快,增删慢
- 二者都有一个初始容量大小,采用线性连续存储空间;当Vector或ArrayList中的元素超过它的初始大小时,Vector会将它的容量翻倍,而ArrayList只增加50%的大小,这样ArrayList就有利于节约内存空间。
- ArrayList和LinkedList之间的区别就是数组和双向链表之间的区别。
- 数组的特点:因为数组分配的是一块连续的内存空间,使用索引来查找元素是非常快速的。但是插入,删除数据性能比较低。增删操作会影响到所有元素的下标
- 双向链表的特点,查询效率较低,因为查询一个元素需要从头部开始查询,挨个遍历每一个元素直到找到所需元素,插入,删除效率高。增删操作时只需要把它前一个元素的指针指向自己,自己的指针指向下一个元素就可以了。
public boolean add(E e) {
ensureCapacityInternal(size + 1); //判断当前数组的容量是否够大如果不够大则扩容
elementData[size++] = e;//将元素添加到数组尾部
return true;
}
源码中可以看出来:ArrayList执行效率取决于:ensureCapacityInternal(size+ 1)方法的执行,在该方法中会判断数组容量是否足够,如果不够则进行扩容到原来的1.5倍。在扩容的过程中会生成一个新的数组,将原数组中的元素复制到新数组中。所以在这种情况下如果数组容量足够大ArrayList的效率是非常高的,我们也可以根据实际情况给它一个合适的初始值。
查看LinkedList中add方法源码
可以看到每新增一个元素就要创建一个Node对象,进行频繁的赋值操作 “final Node newNode = new Node<>(l, e, null);”对效率有一定的影响。
- 在查询操作较多,在特定位置插入数据和删除数据较少的情况下一般选用ArrayList,在特定位置插入数据,删除数据操作较多,查询较少的情况下一般选用LinkedList,但是在大多数应用中都是对查询效率要求较高,所以ArrayList集合应用更广泛一些。
3. 数据结构
3.1 数组
存储相同数据类型的数据。 数组的长度是不可以变化的,在集合的数据结构中,底层是数组数据结构的集合长度是可以变化的。
可以通过下标进行元素的获取和赋值。
3.2 栈和队列
栈一般是保存我们的变量,在方法的栈内存中声明变量和赋值,如果是对象在栈中保存的是对象声明的变量,引用的堆内存中的地址值
特点:先进后出
Stack是Vector的子类:
* push:压栈 * pop:弹栈
队列和栈相反:先进先出
3.3 链表
特点:
- 存储的元素值
- 存储元素的地址值
- 链表在内存中是无序的
- 单向链表
- 双向链表(循环链表)
3.4 树
二叉树的特点:
- 第一个是根节点
- 大的往右走
- 小的往左走
4. 泛型
为什么有泛型机制?
就是在编程中很多时候需要数据类型的转换 ,很麻烦。所以在JDK1.5提出了泛型的机制。
泛型的分类?
- 集合中的泛型
- 接口的泛型
泛型的好处:
- 通过允许指定泛型类或方法操作的类型,泛型功能将类型安全的任务从编程人员转移给了编译器。不需要编写代码来测试数据类型是否正确,因为在编译时会强制使用正确的数据类型。减少了类型强制转换的需要和运行时错误的可能性。
- 程序变的简单起来
- 如果集合规定了泛型 泛型意外的数据就不会加进去 在编译就会出错。
- 提高代码的可读性
- 泛型是对 java 语言的类型系统的一种扩展,以支持创建可以按类型进行参数化的类。可以把类型参数看作是使用参数化类型时指定的类型的一个占位符,就像方法的形式参数是运行时传递的值的占位符一样。
ArrayList<Student> list1 = new ArrayList<Student>();//jdk1.5的使用方式
ArrayList<Student> list1 = new ArrayList<>();//jdk1.8的使用方式
//集合元素是基本数据类型 泛型是基本数据类型的包装类
ArrayList<Integer> list2 = new ArrayList<>();
list2.add(23);
list2.add(88);
4.1 泛型在接口中的使用
public interface BaseDao<T> {
void add(T t);
}
public interface GoodsDao extends BaseDao<Goods>{
}
//测试类
public class BaseDaoDemo {
public static void main(String[] args) {
//匿名内部类
StudentDao stuDao = new StudentDao() {
@Override
public void add(Student t) {
}};
//add 方法在进行重写的时候会自动进行参数类型的填充
GoodsDao goodsDao = new GoodsDao() {
@Override
public void add(Goods t) {
}};
}
}
4.2 泛型在方法中的使用
//方法的参数类不确定就可以使用泛型的形式
public static <T> void method(T t ) {
System.out.println("Hello"+t );
}
5. Set集合
一个不包含重复元素的 collection。更确切地讲,set 不包含满足 e1.equals(e2)
的元素对 e1
和 e2
,并且最多包含一个 null 元素,可以有序也可以无序。
5.1 hashSet
此类实现 Set
接口,由哈希表(实际上是一个 HashMap
实例)支持。它不保证 set 的迭代顺序;特别是它不保证该顺序恒久不变。此类允许使用 null
元素
- 数据结构是哈希表
- 是无序的
- 允许null元素
- 不是线程同步 效率高
import java.util.HashSet;
import java.util.Iterator;
/**HashSet:
* 底层是哈希表的结构:
* */
public class HashSetDemo {
public static void main(String[] args) {
// 所谓的无序是指 添加和获取的顺序不一致
HashSet<String> set = new HashSet<>();
set.add("Hello");
set.add("Java");
set.add("Java");
set.add("123");
set.add("World");
set.add("World");
set.add("Python");
//[Java, Hello, World, Python]
System.out.println(set);
//5
System.out.println(set.size());
Iterator<String> iterator = set.iterator();
while(iterator.hasNext()) {
System.out.println(iterator.next());
}
}
}
数据结构:(哈希表)
JDK1.8之前哈希表=数组加链表
JDK1.8之后:
- 哈希表=数组+链表(链表长度超过8后转为红黑树)
- 哈希表=数组+红黑树(提高查询速度)
5.1.1 如何保证元素唯一性
底层使用的是hashMap的put方法 , add()方法说明,如果set集合中没有包含添加的元素 , 添加进去返回true,如果 通过hashCode,equals方法比较已经包含了要添加的元素,那么集合不会改变,并且返回false。
由于HashMap的put()方法添加key-value对时,当新放入HashMap的Entry中key 与集合中原有Entry的key相同(hashCode()返回值相等,通过equals比较也返回true),新添加的Entry的value会将覆盖原来Entry的value,但key不会有任何改变, 因此如果向HashSet中添加一个已经存在的元素时,新添加的集合元素将不会被放入HashMap中, 原来的元素也不会有任何改变,这也就满足了Set中元素不重复的特性。
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
5.1.2 加载因子
初始的容量是16
如果说是超过初始容量以后呢。他会生成新的哈希表,将原来的哈希表覆盖。
/**
* Constructs a new set containing the elements in the specified
* collection. The <tt>HashMap</tt> is created with default load factor
* (0.75) and an initial capacity sufficient to contain the elements in
* the specified collection.
*
* @param c the collection whose elements are to be placed into this set
* @throws NullPointerException if the specified collection is null
*/
public HashSet(Collection<? extends E> c) {
map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
addAll(c);
}
上面的意思大致为:
构造一个包含指定元素的新集合。
HashMap使用默认负载因子创建(0.75)和足以容纳元素的初始容量指定的集合。
@param c将其元素放置到这个集合中的集合
如果指定的集合为空,则抛出NullPointerException
5.2 TreeSet
基于 TreeMap
的 NavigableSet
实现。使用元素的自然顺序
对元素进行排序,或者根据创建 set 时提供的 Comparator
进行排序,具体取决于使用的构造方法,该接口不是同步的,效率高。
- 自然排序
- 比较器排序 Comparable
5.2.1 自然排序
实现了Comparable接口就实现了自然排序
当我们想要对装有对象的集合进行排序时,就需要让对象实现Comparable接口
public class Teacher implements Comparable<Teacher> {
private String teaName;
private int teaAge;
private String teaAddress;
public Teacher() {
super();
}
public Teacher(String teaName, int teaAge, String teaAddress) {
super();
this.teaName = teaName;
this.teaAge = teaAge;
this.teaAddress = teaAddress;
}
......get set ......
@Override
public String toString() {
return "Teatcher [teaName=" + teaName + ", teaAge=" + teaAge + ", teaAddress=" + teaAddress + "]";
}
/*我们让 自定义类对象进行自然排序。
* 怎么排序?
* 1. 首先实现Comparable接口 重写CompareTo方法
* 2. 我们根据对象的哪一个属性进行比较?
* 3. 我们要定义排序主规则: 按年龄 ----> 名字 ----> 地址
*/
@Override
public int compareTo(Teacher teacher) {
if(teacher == null) {
throw new NullPointerException("输入的参数对象不能是null!!!");
}
if(this==teacher) {
return 0;
}
int ageResult = this.getTeaAge() - teacher.getTeaAge();
int nameResult = this.getTeaName().compareTo(teacher.getTeaName());
int addressResult = this.getTeaAddress().compareTo(teacher.getTeaAddress());
int result = ageResult == 0 ? (nameResult == 0?addressResult : nameResult):ageResult;
return result;
}
}
TreeSet <Teacher> tSet = new TreeSet<>();
tSet.add(new Teacher("张三",33,"西安市"));
tSet.add(new Teacher("王五",13,"汉中市"));
tSet.add(new Teacher("翟柳",23,"宝鸡市"));
tSet.add(new Teacher("张麻子",33,"西安市"));
tSet.add(new Teacher("张麻子",33,"西安市"));
System.out.println(tSet);
5.2.2 比较器排序
TreeSet(Comparator comparator) 比较器排序 如果comparator是null 那依然使用的是自然排序。
Comparator 接口是比较器 :
compare()方法实现两个参数的比较
Comparator接口实现类的比较器排序
package com.xdkj.javase.set;
import java.util.Comparator;
public class StringComparator implements Comparator<String> {
@Override
public int compare(String o1, String o2) {
//字符串 使用自然排序的比较
return o1.compareTo(o2);
}
}
public static void method2() {
//comparator ordering 比较器排序
TreeSet<String> treeSet = new TreeSet(new StringComparator());
treeSet.add("Hello");
treeSet.add("小明");
treeSet.add("张三");
treeSet.add("World");
//[Hello, World, 小明, 张三]
System.out.println(treeSet);
}
匿名内部类实现比较器排序
//匿名内部类实现 比较器排序
public static void method3() {
//comparator ordering 比较器排序
TreeSet<String> treeSet = new TreeSet(new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return o1.compareTo(o2);
}
});
treeSet.add("Hello");
treeSet.add("小明");
treeSet.add("张三");
treeSet.add("World");
//[Hello, World, 小明, 张三]
System.out.println(treeSet);
}
自定义类的匿名内部类实现比较器排序:
package com.xdkj.javase.test;
public class Student {
private String stuName;
private int stuAge;
private String stuNumber;
private String stuAddress;
public Student() {
super();
// TODO Auto-generated constructor stub
}
public Student(String stuName, int stuAge, String stuNumber, String stuAddress) {
super();
this.stuName = stuName;
this.stuAge = stuAge;
this.stuNumber = stuNumber;
this.stuAddress = stuAddress;
}
.....get set .....
@Override
public String toString() {
return "Student [stuName=" + stuName + ", stuAge=" + stuAge + ", stuNumber=" + stuNumber + ", stuAddress="
+ stuAddress + "]";
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((stuAddress == null) ? 0 : stuAddress.hashCode());
result = prime * result + stuAge;
result = prime * result + ((stuName == null) ? 0 : stuName.hashCode());
result = prime * result + ((stuNumber == null) ? 0 : stuNumber.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Student other = (Student) obj;
if (stuAddress == null) {
if (other.stuAddress != null)
return false;
} else if (!stuAddress.equals(other.stuAddress))
return false;
if (stuAge != other.stuAge)
return false;
if (stuName == null) {
if (other.stuName != null)
return false;
} else if (!stuName.equals(other.stuName))
return false;
if (stuNumber == null) {
if (other.stuNumber != null)
return false;
} else if (!stuNumber.equals(other.stuNumber))
return false;
return true;
}
}
//自定义类的比较器排序
public static void method4() {
//comparator ordering 比较器排序
TreeSet<Student> treeSet = new TreeSet(new Comparator<Student>() {
//定义主规则和次要的规则
@Override
public int compare(Student stu1, Student stu2) {
int nameResult = stu1.getStuName().compareTo(stu2.getStuName());
return nameResult == 0 ? (stu1.getStuAge() - stu2.getStuAge() == 0 ? (stu1.getStuNumber().compareTo(stu2.getStuNumber())):stu1.getStuAge() - stu2.getStuAge()):nameResult;
}
});
treeSet.add(new Student("小明",23,"java010","西安市"));
treeSet.add(new Student("瓯网",66,"java011","西安市"));
treeSet.add(new Student("小明",23,"java010","西安市"));
System.out.println(treeSet);
}
5.3 LinkedHashSet
底层是链表 + 哈希表的数据结构
具有可预知迭代顺序的 Set
接口的哈希表和链接列表实现。此实现与 HashSet
的不同之外在于,后者维护着一个运行于所有条目的双重链接列表。此链接列表定义了迭代顺序,即按照将元素插入到 set 中的顺序(插入顺序)进行迭代.
- 线程不同步的
- 有序的,不允许重复的
为什么两种数据结构?
- 使用链表保证元素有序
- HashSet确定元素的唯一性
import java.util.LinkedHashSet;
public class LinkedHashSetDemo {
public static void main(String[] args) {
LinkedHashSet <String> set = new LinkedHashSet<>();
set.add("Hello");
set.add("Hello");
set.add("小明");
set.add("张三");
set.add("张三");
set.add("world");
//[Hello, 小明, 张三, world]
System.out.println(set);
}
}
6. Map
map集合没有继承Collection接口,其提供的是key到value的映射,是一个键值对映射关系的集合,Map中不能包含相同的key值,每个key只能映射一个相同value,key值还决定了存储对象在映射中的存储位置,但不是key对象本身决定的,而是通过散列技术进行处理,可产生一个散列码的整数值,散列码通常用作一个偏移量,该偏移量对应分配给映射内存区域的起始位置,从而确定存储对象在映射中的存储位置将键映射到值的对象。一个映射不能包含重复的键;每个键最多只能映射到一个值
map集合的遍历的两种方式:
- 获取key的set视图在通过遍历key的set集合 get()方法通过键获取值
- 获取键值映射的Entry 视图 在通过遍历 Entry视图获取到 键值映射 在通过 getKey(),getValue()分别获取键和值
public static void main(String[] args) {
Map<Integer,String> map=new HashMap<>();
map.put(1,"迪迦");
map.put(2,"盖亚");
map.put(3,"梦比优斯");
map.put(4,"泰罗");
//map.put(4,"泰罗"); 允许存储空键
System.out.println(map.get(5));// null
System.out.println("=====================");
//第一种遍历方式
Set<Integer> set = map.keySet();
for (Integer integer : set) {
String s = map.get(integer);
System.out.println(integer+"----"+s);
}
System.out.println("============================");
//第二种遍历方式
Set<Map.Entry<Integer, String>> entries = map.entrySet();
for (Map.Entry<Integer, String> entry : entries) {
System.out.println(entry);
}
6.1 Hashtable
-
底层使用的是哈希表数据结构 还有数组的数据结构
- 数组保证查询快 - 哈希保证唯一性
-
线程安全的,同步的 ,效率低
-
不允许null值和null键
-
命名错误 在JDK1.2以后使用HashMap替换了Hashtable
Hashtable如何保证唯一性和迭代顺序是有序的:(源码)
public synchronized V put(K key, V value) {
// Make sure the value is not null
if (value == null) {
throw new NullPointerException();
}
// Makes sure the key is not already in the hashtable.
Entry<?,?> tab[] = table;
//计算键的哈希值
int hash = key.hashCode();
//根据键的哈希值计算出一个数组的下标值
int index = (hash & 0x7FFFFFFF) % tab.length;
@SuppressWarnings("unchecked")
Entry<K,V> entry = (Entry<K,V>)tab[index];
for(; entry != null ; entry = entry.next) {
//如果键的值相同使用新的值覆盖原来的值 键只有一个
if ((entry.hash == hash) && entry.key.equals(key)) {
V old = entry.value;
entry.value = value;
return old;
}
}
addEntry(hash, key, value, index);
return null;
}
private void addEntry(int hash, K key, V value, int index) {
modCount++;
Entry<?,?> tab[] = table;
//如果集合的键的数量大于 容量值
if (count >= threshold) {
// Rehash the table if the threshold is exceeded
//重新刷新一个哈希表
rehash();
tab = table;
//在生成新的键的哈希值
hash = key.hashCode();
//新的数组的下标值
index = (hash & 0x7FFFFFFF) % tab.length;
}
// Creates the new entry.
@SuppressWarnings("unchecked")
Entry<K,V> e = (Entry<K,V>) tab[index];
//创建键值映射关系 存储键和值 放入数组保证迭代顺序一致
tab[index] = new Entry<>(hash, key, value, e);
count++;
}
6.2 HashMap
- 基于哈希表的
Map
接口的实现。此实现提供所有可选的映射操作 - 并允许使用
null
值和null
键。 - 此类不保证映射的顺序,特别是它不保证该顺序恒久不变
- 不是同步的 线程不安全的 效率高
import java.util.HashMap;
import java.util.Map;
public class HashMapDemo {
public static void main(String[] args) {
Map<String,Integer> map = new HashMap<>();
map.put("Hello", 99);
map.put("World", 88);
map.put("lucy", 5666);
map.put("lilei", 123);
map.put("hanmeiemi", 456);
map.put("hanmeiemi", 66666);
map.put(null, 66666);
map.put(null, null);
//{lilei=123, Hello=99, hanmeiemi=456, World=88, lucy=5666}
//添加的顺序和迭代的顺序不一致
//允许null值和null键
System.out.println(map);
//第一个 如何保证键的唯一
//值新的覆盖旧的
//自定义对象作为键 如何保证唯一
}
}
自定义类作为键:
自定义类作为键 ,因为HashMap底层使用的是equals判断键是否相等,所以我们要进行equals和hashCode方法的重写.
package com.xdkj.javase.map;
public class Person {
private String pName;
private int pAge;
private String pEmail;
public Person() {
super();
// TODO Auto-generated constructor stub
}
public Person(String pName, int pAge, String pEmail) {
super();
this.pName = pName;
this.pAge = pAge;
this.pEmail = pEmail;
}
...get set ....
@Override
public String toString() {
return "Person [pName=" + pName + ", pAge=" + pAge + ", pEmail=" + pEmail + "]";
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + pAge;
result = prime * result + ((pEmail == null) ? 0 : pEmail.hashCode());
result = prime * result + ((pName == null) ? 0 : pName.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Person other = (Person) obj;
if (pAge != other.pAge)
return false;
if (pEmail == null) {
if (other.pEmail != null)
return false;
} else if (!pEmail.equals(other.pEmail))
return false;
if (pName == null) {
if (other.pName != null)
return false;
} else if (!pName.equals(other.pName))
return false;
return true;
}
}
public static void method2() {
Map<Person,Integer> map = new HashMap<>();
map.put(new Person("张三",25,"123@qq.com"),99);
map.put(new Person("李四",25,"123@qq.com"),99);
map.put(new Person("张三",25,"123@qq.com"),99);
map.put(new Person("王麻子",26,"123@qq.com"),88);
System.out.println(map);
}
{Person [pName=张三, pAge=25, pEmail=123@qq.com]=99, Person [pName=王麻子, pAge=26, pEmail=123@qq.com]=88, Person [pName=李四, pAge=25, pEmail=123@qq.com]=99}
HashMap源码解读:
HashMap
基于Hash算法实现的,通过put(key,value)存储,get(key)来获取。当传入key时,HashMap
会根据key.HashCode()
计算出hash值,根据hash值将value保存在bukect
里。当计算出的hash值相同时,我们称之为哈希冲突,HashMap
的做法是用链表和红黑树存储相同hash值的value。当hash冲突较少时,使用链表,否则使用红黑树。
/**
* Implements Map.put and related methods.
*
* @param hash hash for key
* @param key the key
* @param value the value to put
* @param onlyIfAbsent if true, don't change existing value
* @param evict if false, the table is in creation mode.
* @return previous value, or null if none
*/
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
//初始的哈希表底层是数组的结构 数据保证的是在查询的时候速度快 节省空间
Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
for (int binCount = 0; ; ++binCount) {
//加入键值映射的时候 在数量小于等于7的时候 使用的是链表的结构
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
//在数量大于7的时候底层转换为了二叉树的结构 对键的进行排序
//HashMap迭代的书序不一致
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
//保证同一个节点 新的值覆盖旧的值
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
//集合的数量超出 初始的容量 进行数据表的刷新 数据结构的重建
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
Hashtable和HashMap的区别:
- Hashtbale 也是哈希表的数据结构
- HashMap是 数据加链表加红黑树(二叉树)
- HashMap是线程不安全的,不同步的效率高
- Hashtable是线程安全的,同步的效率低
- Hashtable命名错误 被HashMap替换
- HashMap运行null键和Null值
- Hashtable不允许null键和Null值
- HashMap 和Hashtable都允许值重复,键唯一
6.3 TreeMap
基于红黑树(Red-Black tree)的 NavigableMap
实现。该映射根据其键的自然顺序进行排序,或者根据创建映射时提供的 Comparator
进行排序,具体取决于使用的构造方法。
import java.util.Comparator;
import java.util.TreeMap;
public class TreeMapDemo {
public static void main(String[] args) {
//会自然排序和比较器排序
TreeMap<Integer,String> map = new TreeMap<>();
map.put(1,"one");
map.put(3,"three");
map.put(4,"four");
map.put(2,"two");
System.out.println(map);
//比较器排序
TreeMap<Person,String> map1 = new TreeMap<>(new Comparator<Person>() {
@Override
public int compare(Person o1, Person o2) {
return o1.getpName().compareTo(o2.getpName())==0?o1.getpAge()-o2.getpAge():o1.getpName().compareTo(o2.getpName());
}
});
map1.put(new Person("小明",25,"123@qq.com"),"Hello");
map1.put(new Person("小红",22,"123@qq.com"),"Hello");
map1.put(new Person("小刚",18,"123@qq.com"),"Hello");
System.out.println(map1);
}
}
TreeMap源码分析:
public V put(K key, V value) {
//创建根节点
Entry<K,V> t = root;
if (t == null) {
//检查根节点的键是否为null
compare(key, key); // type (and possibly null) check
//挂载根节点
root = new Entry<>(key, value, null);
size = 1;
modCount++;
return null;
}
int cmp;
Entry<K,V> parent;
// split comparator and comparable paths
Comparator<? super K> cpr = comparator;
//比较器排序
if (cpr != null) {
do {
parent = t;
//传入的key和根节点的key比较
cmp = cpr.compare(key, t.key);
if (cmp < 0)
//将当前节点的左边变为比较的根节点
t = t.left;
else if (cmp > 0)
//将当前节点的右边变为比较的根节点
t = t.right;
else
//键相同 新的值覆盖旧的值
return t.setValue(value);
} while (t != null);
}
else {
//自然排序
if (key == null)
throw new NullPointerException();
@SuppressWarnings("unchecked")
Comparable<? super K> k = (Comparable<? super K>) key;
do {
parent = t;
//自然排序比较
cmp = k.compareTo(t.key);
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
else
return t.setValue(value);
} while (t != null);
}
//不是自然排序也不是比较器排序 和父节点进行比较 按照大小进行左右挂载
Entry<K,V> e = new Entry<>(key, value, parent);
if (cmp < 0)
parent.left = e;
else
parent.right = e;
fixAfterInsertion(e);
size++;
modCount++;
return null;
}
6.4 LinkedHashMap
Map
接口的哈希表和链接列表实现,具有可预知的迭代顺序。此实现与 HashMap
的不同之处在于,后者维护着一个运行于所有条目的双重链接列表。此链接列表定义了迭代顺序,该迭代顺序通常就是将键插入到映射中的顺序(插入顺序)。
数据结构是: 双向链表+ 列表+ 哈希表
插入顺序和迭代顺序是一致的。
不同步 线程不安全
允许null值和Null键
import java.util.LinkedHashMap;
public class LinkedHashMapDemo {
public static void main(String[] args) {
LinkedHashMap<String,Integer> map = new LinkedHashMap<>();
map.put("Hello",123);
map.put("World",123);
map.put("Java",123);
map.put("c++",123);
//插入顺序和迭代顺序是一致的
System.out.println(map);
}
}
6.5 Properties
Properties
类表示了一个持久的属性集。Properties
可保存在流中或从流中加载。属性列表中每个键及其对应值都是一个字符串。
userName=admin
password=123
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
public class PropertiesDemo {
public static void main(String[] args) throws IOException {
//获取一个资源作为流
InputStream resourceAsStream = PropertiesDemo.class.getClassLoader().getResourceAsStream("db.properties");
Properties properties = new Properties();
//从流中加载数据
properties.load(resourceAsStream);
//从properties中 通过键获取值
System.out.println(properties.getProperty("userName"));
System.out.println(properties.getProperty("password"));
}
}