JAVA集合
集合用来保存多个数据
在前面我们使用数组来保存多个数据,但是数组有一定的不足。
数组的长度必须指定,而且一旦指定,不能更改
保存的必须为同一类型的元素
使用数组进行增加/删除元素的示意代码比较麻烦
集合可以动态保存任意多个对象,使用比较方便
提供了一系列方便的操作对象的方法:add,remove,set,get等
使用集合添加,删除新元素的示意代码-简洁
集合的框架体系
Collection接口有两个重要子接口List Set ,他们实现的子类都是单列集合(就是单个值)
Map接口的实现子类 是双列集合,存放的K-V(也就是键值对)
Collection接口方法
public static void main(String[] args) {
List list = new ArrayList();
//增add
list.add("jack");
list.add(10);//list.add(new Integer(10))
list.add(true);
System.out.println("list="+list);
//删remove
list.remove("jack");//删除指定元素
list.remove(1);//删除下表为1的元素
System.out.println("list="+list);
//contains查找元素是否存在
System.out.println(list.contains("jack"));
System.out.println(list.contains(10));
//size 获取元素个数
System.out.println(list.size());
//isEmpty 判断是否为空
System.out.println(list.isEmpty());
//clear 清空
list.clear();
System.out.println("list="+list);
//addAll 添加多个元素
ArrayList list1 = new ArrayList();
list1.add("红楼梦");
list1.add("三国演义");
list.addAll(list1);
System.out.println("list="+list);
//containsAll 查找多个元素是否都存在
System.out.println(list.containsAll(list1));
//removeAll删除多个元素
list.add("聊斋");
list.removeAll(list1);
System.out.println("list="+list);
遍历元素
Collection接口遍历元素方式 1-使用Iterator迭代器
public static void main(String[] args) {
Collection col = new ArrayList();
col.add(new Book("三国演义","罗贯中",10.1));
col.add(new Book("小李飞刀","古龙",5.1));
col.add(new Book("红楼梦","曹雪芹",34.6));
System.out.println("col="+col);
//遍历col集合
//1.先得到col对应的迭代器
Iterator iterator = col.iterator();
//2.使用while循环遍历
// while (iterator.hasNext()){
返回下一个元素,类型是Object
// Object object = iterator.next();
// System.out.println("obj="+object);
// }
//快捷键 while=> itit
while (iterator.hasNext()) {
Object next = iterator.next();
System.out.println(next);
}
//3.退出while循环后,这时iterator迭代器指向最后的元素
//iterator.next();//NoSuchElementException
//4.如果想要再次遍历,需要重置我们的迭代器
iterator = col.iterator();
System.out.println("====第二次遍历====");
while (iterator.hasNext()){
Object obj = iterator.next();
System.out.println("obj="+obj);
}
}
}
class Book {
private String name;
private String author;
private double price;
public Book(String name, String author, double price) {
this.name = name;
this.author = author;
this.price = price;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
@Override
public String toString() {
return "Book{" +
"name='" + name + '\'' +
", author='" + author + '\'' +
", price=" + price +
'}';
}
Collection接口遍历元素方式 2-使用增强for循环
for(元素类型 元素名:集合或数组名){
访问元素
}
System.out.println("====第三次遍历====");
for (Object object:col){//col是被遍历的
System.out.println(object);
}
List 接口和常用方法
list接口是Collection接口的子
- List集合类的元素有序(即添加顺序和取出顺序一致)且可重复
- List集合中的每个元素都有其对应的顺序索引,即支持索引
- List容器中的元素都对应一个整数型的序号记载其在容器中的位置,可以根据序号存取容器中的元素
- JDK API中 List接口的实现类有
//3.List容器中的元素都对应一个整数型的序号记载其在容器中的位置
// 可以根据序号存取容器中的元素
List list = new ArrayList();
list.add("张三丰");
list.add("贾宝玉");
//void add(int index,Object ele);在index的位置插入ele元素
list.add(1,"吴彦祖");
System.out.println("list="+list);
List list1 = new ArrayList();
list1.add("jack");
list1.add("tom");
list.addAll(1,list1);
System.out.println("list="+list);
//Object get(int index)
//int indexOf(int index) 返回obj在当前集合中首次出现的位置
System.out.println(list.indexOf("tom"));
//int lastindexOf(Object obj) 返回obj在当前集合中末次出现的位置
list.add("tom");
System.out.println(list.lastIndexOf("tom"));
//Object remove(int index)移除指定index位置的元素,并返回此元素
list.remove(0);
System.out.println("list="+list);
//Object set(int index,Object ele):指定index位置的元素为ele,相当于替换
list.set(1, "玛丽");
System.out.println("list="+list);
//List subList(int fromIndex,int toIndex)返回从fromIndex到toIndex位置的子集合
//返回为左闭右开[)
List returnlist = list.subList(0,2);
System.out.println("returnlist="+returnlist);
}
List的三种遍历方式(ArrayList,LinkedList,Vector)
public static void main(String[] args) {
List list = new LinkedList();
list.add("jack");
list.add("tom");
list.add("鱼香肉丝");
list.add("北京烤鸭");
//1.迭代器
Iterator iterator = list.iterator();
while (iterator.hasNext()) {
Object next = iterator.next();
System.out.println(next);
}
//2.增强for
System.out.println("========增强for=========");
for (Object o:list){
System.out.println("o="+o);
}
//3.普通for
System.out.println("========普通for=========");
for (int i=0;i<list.size();i++){
System.out.println("list="+list.get(i));
}
}
//List list = new LinkedList();
// List list = new ArrayList();
List list = new Vector();
list.add(new Book("红楼梦","曹雪芹",100));
list.add(new Book("西游记","吴承恩",10));
list.add(new Book("水浒传","施耐庵",19));
list.add(new Book("三国演义","罗贯中",80));
list.add(new Book("西游记","吴承恩",10));
//对集合进行排序
for (Object object:list){
System.out.println(object);
}
sort(list);
System.out.println("========排序后==========");
for (Object object:list){
System.out.println(object);
}
}
//冒泡排序
//价格从小到大进行排序
private static void sort( List list) {
int listSize = list.size();
for (int i=0;i<listSize-1;i++){
for (int j =0;j<listSize-1-i;j++){
//取出对象Book
Book book1 = (Book) list.get(j);
Book book2 = (Book) list.get(j+1);
if (book1.getPrice()>book2.getPrice()){//交换
list.set(j, book2);
list.set(j+1, book1);
}
}
}
}
ArrayList的源码分析
- ArrayList中维护了一个Object类型的数组elementData
transient Object[] elementData;
- 当创建ArrayList对象时,如果使用的是无参构造器,则初始elementData容量为0,第一次添加则扩容elementData为10,如需再次扩容,则扩容elementData为1.5倍
- 如果使用的是指定大小得到构造器,则初始elementData容量为指定大小,如需扩容,则直接扩容elememtData为1.5倍
有参构造与无参构造类似后面遇到elementData数组不够用时再进行扩容
//1.先调用一个有参构造器创建一个指定大小的elementData数组
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);
}
}
Vector底层结构和源码剖析
1.继承了AbstractList,实现了List 接口
public class Vector<E>
extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
2.Vector底层也是一个对象数组,protected Object[] elementData;
3.Vector是线程同步的,即线程安全,Vector类操作方法基本都有synchronized
public synchronized E get(int index) {
if (index >= elementCount)
throw new ArrayIndexOutOfBoundsException(index);
return elementData(index);
}
4.在开发中需要线程同步安全是,考虑使用Vector
//查看vector源码的小案例
public static void main(String[] args) {
//无参构造器
//有参构造器
Vector vector = new Vector();
//在vector里增加0-9
for (int i = 0; i < 10; i++) {
vector.add(i);
}
vector.add(100);
System.out.println("vector="+vector);
}
//1.无参构造时先分配10个大小空间的数组
public Vector() {
this(10);
}
//如果是有参构造则分配 initialCapacity个大小空间的数组
public Vector(int initialCapacity) {
this(initialCapacity, 0);
}
//2.vector.add()
//2.1调用该方法添加数据到vector集合
public synchronized boolean add(E e) {
modCount++;//判断添加元素的次数
ensureCapacityHelper(elementCount + 1);//确认数组的大小是否足够
elementData[elementCount++] = e;
return true;
}
//上面的add调用该ensureCapacityHelper(int minCapacity)方法
private void ensureCapacityHelper(int minCapacity) {
// overflow-conscious code
if (minCapacity - elementData.length > 0)//当数组容量不足时需要扩容
grow(minCapacity);
}
//grow()扩容机制
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
capacityIncrement : oldCapacity);
//当创建 Vector vector = new Vector(8,3);时可以设置capacityIncrement为3既增加时可不按默认的2倍增加
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
elementData = Arrays.copyOf(elementData, newCapacity);
}
- Vector和ArrayList的比较
底层结构 | 版本 | 线程安全(同步)效率 | 扩容倍数 | |
---|---|---|---|---|
ArrayList | 可变数组 Object[] | jdk1.2 | 不安全,效率高 | 如果有参构造1.5倍, 如果是无参 第一次是10,以后是1.5倍 |
Vector | 可变数组 Object[] | jdk1.0 | 安全,效率不高 | 如果是无参,就默认10 满后就按2倍扩容 如果指定大小,则每次按2倍扩容 |
LinkedList底层结构和源码剖析
- LinkedList底层实现了双向链表和双端队列的特点
- 可以添加任意元素(元素可以重复),包括null
- 线程不安全,没有实现同步
- LinkedList的底层操作机制
- LinkedList底层维护了一个双向链表
- LinkedList中维护了两个属性first和last分别指向首节点和尾结点
- 每个节点(Node对象),里面又维护了prev、next、item三个属性,其中通过prev指向前一个结点,通过next指向后一个结点。最终实现双向链表
- 所以LinkedList的元素的添加和删除,不是通过数组完成的,相对来说效率较高
我们先看一个小案例,了解链表进行增删的方便
public static void main(String[] args) {
//模拟一个简单的双向链表
Node jack = new Node("jack");
Node tom = new Node("tom");
Node johny = new Node("johny");
//连接三个结点,形成双向链表
jack.next = tom;
tom.next = johny;
johny.pre = tom;
tom.pre = jack;
Node first = jack;//让first指向jack,就是双向链表的头结点
Node last = johny;//让last指向johny,就是双向链表的尾结点
//从头到尾进行遍历
while (true){
if (first==null){
break;
}
System.out.println(first);
first=first.next;
}
//从前往后遍历
System.out.println("==============");
while (true){
if(last==null){
break;
}
System.out.println(last);
last=last.pre;
}
//链表添加一个数据
//1.先创建一个Node结点,name是smith
Node smith = new Node("smith");
//把smith加到双向链表
smith.next = johny;
smith.pre = tom;
johny.pre = smith;
tom.next = smith;
//让first再次指向jack
first = jack;
last = johny;
//从头到尾进行遍历
System.out.println("================");
while (true){
if (first==null){
break;
}
System.out.println(first);
first=first.next;
}
//从后往前遍历
System.out.println("==============");
while (true){
if(last==null){
break;
}
System.out.println(last);
last=last.pre;
}
}
}
class Node{
public Object item;
public Node next;
public Node pre;
public Node (Object name){
this.item = name;
}
public String toString(){
return "Node name="+item;
}
使用linkedlist实现crud
public static void main(String[] args) {
LinkedList linkedList = new LinkedList();
//添加add()
linkedList.add(1);
linkedList.add(2);
linkedList.add(3);
System.out.println("linkedlist="+linkedList);
//删除 remove()
//linkedList.remove();//默认删除第一个结点
linkedList.remove(2);//删除索引为2的结点
System.out.println("linkedlist="+linkedList);
//修改set()
linkedList.set(1, 9999);
System.out.println("linkedlist="+linkedList);
//得到某个节点的对象get()
Object o = linkedList.get(1);
System.out.println("o="+o);
//因为LinkedList是实现了list接口,遍历方式
System.out.println("LinkedList遍历迭代器");
Iterator iterator = linkedList.iterator();
while (iterator.hasNext()){
Object next = iterator.next();
System.out.println("next="+next);
}
System.out.println("LinkedList遍历增强for");
for (Object o1:linkedList){
System.out.println("o1="+o1);
}
System.out.println("LinkedList遍历普通for");
for (int i = 0; i < linkedList.size(); i++) {
System.out.println(linkedList.get(i));
}
}
源码解读:
//1.LinkedList linkedList = new LinkedList()进入 public LinkedList()
//Constructs an empty list.构造一个空列表
public LinkedList() {
}
//这时LinkedList的属性first=null last=null
//2.执行添加
public boolean add(E e) {
linkLast(e);
return true;
}
//将新节点放到双向链表最后
void linkLast(E e) {
final Node<E> l = last;
final Node<E> newNode = new Node<>(l, e, null);//三个参数分别表示pre,item和next
last = newNode;
if (l == null)
first = newNode;
else
l.next = newNode;
size++;
modCount++;
}
//3.执行删除 linkedList.remove();
//无参数时
public E remove() {
return removeFirst();
}
public E removeFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return unlinkFirst(f);
}
//执行unlinkFirst,将f指向的双向链表的第一个结点拿掉
private E unlinkFirst(Node<E> f) {
// assert f == first && f != null;
final E element = f.item;
final Node<E> next = f.next;
f.item = null;
f.next = null; // help GC
first = next;
if (next == null)
last = null;
else
next.prev = null;
size--;
modCount++;
return element;
}
底层结构 | 增删的效率 | 改查的效率 | |
---|---|---|---|
ArrayList | 可变数组 | 较低 涉及到数组扩容 |
较高 |
LinkedList | 双向链表 | 较高,通过链表追加 | 较低 |
那么我们该如何选择ArrayList和LinkedList 呢 ❓
- 如果我们改查操作多,就选择ArrayList
- 如果我们增删操作多,选择LinkedList
- 一般来说,在程序中,80%~90%都是查询,因此大部分情况下会选择ArrayList
- 在一个项目中,根据业务灵活选择,也可能是一个模块使用ArrayList,另一个模块使用LinkedList
Set接口和常用方法
- Set接口是无序的(添加和取出的顺序不一致),没有索引
- 不允许重复元素,所以最多包含一个null
- JDK API中set接口的实现类有
Set接口的常用方法:
和List接口一样,set接口也是Collection的子接口,因此,常用方法和Collection接口一样
Set接口的遍历方式:
- 可以使用迭代器
- 增强for
- 不能使用索引的方式来获取
Set接口的常用方法举例
@SuppressWarnings({"all"})
public static void main(String[] args) {
Set set = new HashSet();
set.add("jack");
set.add("mike");
set.add("jack");//重复
set.add("john");
set.add("mary");
set.add(null);
set.add(null);
for (int i = 0;i<10;i++){
System.out.println("set="+set);
}
//遍历
//1.使用迭代器
System.out.println("=======使用迭代器==========");
Iterator iterator = set.iterator();
while (iterator.hasNext()){
Object next = iterator.next();
System.out.println("next="+next);
}
set.remove(null);
//2.使用增强for
System.out.println("==========使用增强for============");
for (Object obj:set){
System.out.println("obj="+obj);
}
//set不能通过索引来访问所以不能通过普通for来遍历
}
public static void main(String[] args) {
Set set = new HashSet();
//add()会返回TRUE 或FALSE
System.out.println(set.add("jackson"));
System.out.println(set.add("mike"));
System.out.println(set.add("jackson"));
System.out.println(set.add("Rose"));
set.remove("mike");
System.out.println("set="+set);
set = new HashSet();
System.out.println("set="+set);
//HashSet不能添加相同的元素
set.add("lucky");
set.add("lucky");//不能添加上
set.add(new Dog("tom"));
set.add(new Dog("tom"));//可以添加上
System.out.println("set="+set);
//经典面试题
set.add(new String("jony"));
set.add(new String("jony"));//加入不了
System.out.println("set="+set);
}
HashSet底层机制说明
HashSet的底层是HashMap,HashMap的底层是(数组+链表+红黑树)
源码分析:
//1.Constructs a new, empty set;
public HashSet() {
map = new HashMap<>();
}
//2.执行add()
public boolean add(E e) {//e="java"
return map.put(e, PRESENT)==null; //private static final Object PRESENT = new Object(); value的值不会变且共享
}
//3.执行put(),该方法会执行 hash(key)得到key对应的hash值 算法
/*static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); >>>表示不带符号右移 前面补0即可
}*/
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
//4.执行putval
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;//定义了辅助变量
//table就是HashMap的一个数组,类型是Node[]
//下面表示如果当前的table是null或者大小为0则
//进行第一次扩容,扩到16个空间
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
//1.根据key得到hash去计算该key应存放到table的哪个索引位置
//并且把该位置赋值给p
//2.判断p是否为null
//如果p为null,表示还没有创建元素,就创建一个
//放在该位置 tab[i] = newNode(hash, key, value, null);
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
//一个开发小技巧
//在需要局部变量(辅助变量)的时候,在创建下面的局部变量时
Node<K,V> e; K k;
//如果当前索引位置对应的链表的第一个元素和准备添加key的hash值一样
//并且满足下面两个条件之一:
//1.准备加入的key和 p指向的Node节点的key 是同一个对象
//2.p指向的Node节点的key的equals()和准备加入的key比较后相同
//就不能加入
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
//再判断p是不是一颗红黑树
//如果是一颗红黑树就调用putTreeVal,来进行添加
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
//如果table对应的索引位置已经是一个链表,就用for循环来进行比较
//依次和链表的每一个元素比较后,都不相同,则加入到链表的最后
//在把元素添加到链表后应该立即判断,该链表是否已经达到8个结点
//如果达到8个结点,就调用treeifyBin(tab, hash);对当前这个链表进行树化(转化成红黑树)
//注意:!!!!!!在转化成红黑树之前要进行判断,判断条件
//if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY(64))
// resize();
//如果上面条件成立,先对table扩容
//只有上述条件不成立,才转化成红黑树
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
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;
//size就是我们加入的结点数
//如果当前结点数大于缓冲数量(12),则进行扩容
if (++size > threshold)
// 上面的resize()里面
// newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); threshold = newThr;
resize();//扩容
afterNodeInsertion(evict);//HashMap留给它的子类比如LinkedHashMap去实现对于HashMap来说是一个空方法。
return null;
}
HashSet的扩容和转化成红黑树的机制
- HashSet的底层是HashMap,第一次添加时,table数组扩容到16,临界值(threshold)是16*加载因子(loadFactor)是0.75=12
- 如果table数组里面所有的元素(包括不同的链表)达到了临界值12就会扩容到16×2=32,新的临界值就是32×0.75=24
- 在JAVA8中,如果一条链表的元素个数到达TREEIFY_THRESHOLD(默认是8),并且table的大小>=MIN_TREEIFY_CAPACITY(默认是64),就会对该链表进行树化(红黑树),==否则仍然采用数组扩容机制(2倍扩容)==直到数组容量到达64
public static void main(String[] args) {
HashSet hashSet = new HashSet();
// for (int i = 0; i <=100; i++) {
// hashSet.add(i);//1,2,3...,100
// }
for (int i = 0; i <=6; i++) {
hashSet.add(new A(i));
}
for (int i = 0; i <=11; i++) {
hashSet.add(new A(i));
}
for (int i = 0; i <=6; i++) {
hashSet.add(new B(i));
}
System.out.println("hashSet="+hashSet);
}
}
class A{
private int n;
public A(int n) {
this.n = n;
}
@Override
public int hashCode(){
return 100;
}
@Override
public String toString() {
return "A{" +
"n=" + n +
'}';
}
}
class B{
private int n;
public B(int n){
this.n=n;
}
@Override
public int hashCode(){
return 200;
}
@Override
public String toString() {
return "B{" +
"n=" + n +
'}';
}
}
可以通过重写equals和hashCode方法来判断两个对象是否相同来判断是否可以添加到集合中
public static void main(String[] args) {
HashSet hashSet = new HashSet();
hashSet.add(new Employee("xiaoming",18));
hashSet.add(new Employee("xiaogang",18));
hashSet.add(new Employee("xiaoming",18));
System.out.println("HashSet="+hashSet);
}
}
class Employee{
private String name;
private int age;
public Employee(String name,int age){
this.name=name;
this.age=age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Employee{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o==null||getClass()!=o.getClass()) return false;
Employee employee = (Employee) o;
return age == employee.age && Objects.equals(name, employee.name);
}
@Override
public int hashCode() {
return Objects.hash(name,age);
}
//Employe和Mydate的euqals和hashcode都需要重写
public class HashSetExercise02 {
@SuppressWarnings({"all"})
public static void main(String[] args) {
HashSet hashSet = new HashSet();
hashSet.add(new Employe("xiaoming", 12345, new Mydate(2002,01,02)));
hashSet.add(new Employe("xiaoming", 12345, new Mydate(2002,01,02)));
// hashSet.add(new Employe("xiaoming", 12345, new Mydate(2002,01,03)));
// hashSet.add(new Employe("xiaobing", 12345, new Mydate(2002,01,02)));
System.out.println("hashset="+hashSet);
}
}
class Employe{
private String name;
private int sal;
private Mydate birthday;
public Employe(String name,int sal,Mydate birthday){
this.name=name;
this.sal=sal;
this.birthday=birthday;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getSal() {
return sal;
}
public void setSal(int sal) {
this.sal = sal;
}
public Mydate getBirthday() {
return birthday;
}
public void setBirthday(Mydate birthday) {
this.birthday = birthday;
}
@Override
public String toString() {
return "Employe{" +
"name='" + name + '\'' +
", sal=" + sal +
", birthday=" + birthday +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Employe)) return false;
Employe employe = (Employe) o;
return Objects.equals(name, employe.name) && Objects.equals(birthday, employe.birthday);
}
@Override
public int hashCode() {
return Objects.hash(name, birthday);
}
}
class Mydate{
private int year;
private int month;
private int day;
public Mydate(int year, int month, int day) {
this.year = year;
this.month = month;
this.day = day;
}
public int getYear() {
return year;
}
public void setYear(int year) {
this.year = year;
}
public int getMonth() {
return month;
}
public void setMonth(int month) {
this.month = month;
}
public int getDay() {
return day;
}
public void setDay(int day) {
this.day = day;
}
@Override
public String toString() {
return "Mydate{" +
year +
"-" + month +
"-" + day +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Mydate)) return false;
Mydate mydate = (Mydate) o;
return year == mydate.year && month == mydate.month && day == mydate.day;
}
@Override
public int hashCode() {
return Objects.hash(year, month, day);
}
}
Set接口实现类LinkedHashSet
- LinkedHashSet是HashSet的子类
- LinkedHashSet的底层是一个LinkedHashMap,底层维护了一个数组+双向链表
- LinkedHashSet根据元素的hashCode值来确定元素的存储位置,同时使用链表维护元素的次序,使元素看起来是以插入顺序保存的
- LinkedHashSet不允许添加重复元素
public static void main(String[] args) {
Set set = new java.util.LinkedHashSet();
set.add(new String("AA"));
set.add(456);
set.add(456);
set.add(new Custom("刘",1001));
set.add(123);
set.add("HSP");
System.out.println("set="+set);
}
//1.LinkedHashSet 添加第一次时直接将数组table扩容到16,存放的结点类型是LinkedHashMap&Entry
//2.数组是 HashMap$Node[] 存放的元素/数据是LinkedHashMap&Entry类型 (继承关系,才能存放,见下方源码)
/*static class Entry<K,V> extends HashMap.Node<K,V> {
Entry<K,V> before, after;
Entry(int hash, K key, V value, Node<K,V> next) {
super(hash, key, value, next);
}
}*/
}
class Custom{
private String name;
private int num;
public Custom(String name,int num){
this.name=name;
this.num=num;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getNum() {
return num;
}
public void setNum(int num) {
this.num = num;
}
@Override
public String toString() {
return "Custom{" +
"name='" + name + '\'' +
", num=" + num +
'}';
}
}
Map接口和常用方法
Map接口实现类的特点
- Map与Collection并列存在,用于保存具有映射关系的数据 Key-Value
- Map中的key和value可以是任何引用类型的数据,会封装到HashMap$Node对象中
- Map中的key不允许重复,原因和HashSet一样
- Map中的value值可以重复
- Map中的key可以为null,value也可以为nulll,key为null只可以有一个,而value为null可以有多个
- 常用String类作为Map的key
- key和value之间存在单向一对一的关系,即通过指定的key总能找到对应的value
public static void main(String[] args) {
Map map = new HashMap();
map.put("no1", "小明");
map.put("no1", "小明");
map.put("no2", "小红");
map.put("no3", "小刚");
map.put("no1", "小强");//具有相同的value就相当于替换
map.put("no4", "小明");
map.put(null, null);
map.put(null,"小绿" );
map.put("no5", "小兰");
map.put(1, "小紫");
//通过get方法传入key可以返回对应的value
System.out.println(map.get("no2"));
System.out.println("map"+map);
Set set = map.entrySet();
System.out.println(set.getClass());
}
8.Map存放数据的key-value示意图,一对k-v是放在一个HashMap$Node中的因为Node实现了Entry接口,有些书上也说一对k-v就是一个Entry
public class MapMethod {
@SuppressWarnings({"all"})
public static void main(String[] args) {
//演示map接口常用方法
Map map = new HashMap();
map.put("邓超",new Book("",100));
map.put("邓超","孙俪");
map.put("王宝强","马蓉");
map.put("宋喆","马蓉");
map.put("彭于晏",null);
map.put(null,"刘亦菲");
map.put("鹿晗","关晓彤");
map.put("hsp","hsp老婆");
System.out.println("map="+map);
//remove根据键删除映射关系
map.remove(null);
System.out.println("map="+map);
//get根据键获取值
Object val = map.get("鹿晗");
System.out.println("val="+val);
//size获取元素个数
System.out.println("k-v"+map.size());
//isEmpty判断是否为空
System.out.println(map.isEmpty());
//clear:清除k-v
map.clear();
//containsKey 查找键是否存在
System.out.println("结果="+map.containsKey("hsp"));
}
}
class Book{
private String name;
private int num;
public Book(String name, int num) {
this.name = name;
this.num = num;
}
}
Map接口的遍历方法
1.containsKey:查找键是否存在
2.keySet:获取所有的键
3.entrySet:获取所有的k-v
4.values:获取所有的值
public class MapFor {
@SuppressWarnings({"all"})
public static void main(String[] args) {
Map map = new HashMap();
map.put("邓超","孙俪");
map.put("王宝强","马蓉");
map.put("宋喆","马蓉");
map.put("彭于晏",null);
map.put(null,"刘亦菲");
map.put("鹿晗","关晓彤");
map.put("hsp","hsp老婆");
//第一组 先取出所有的key ,通过key取出对应的value
Set keyset = map.keySet();
//1.增强for
System.out.println("=========第一种方式===========");
for (Object key:keyset){
System.out.println(key+"-"+map.get(key));
}
//2.迭代器
System.out.println("=========第二种方式===========");
Iterator iterator = keyset.iterator();
while (iterator.hasNext()) {
Object next = iterator.next();
System.out.println(next+"-"+map.get(next));
}
//第二组 把所有的values取出
Collection values = map.values();
//这里可以使用Collections使用的遍历方法
//1.增强for
System.out.println("======取出所有value的增强for========");
for (Object value:values){
System.out.println(value);
}
//2.迭代器
System.out.println("======取出所有value的迭代器========");
Iterator iterator1 =values.iterator();
while (iterator1.hasNext()) {
Object next = iterator1.next();
System.out.println(next+"-"+map.get(next));
}
//第三组:通过EntrySet来获取k-v
Set entryset=map.entrySet();// Set<Map.Entry<K, V>> entrySet();
//1.增强for
System.out.println("====使用entryset的增强for===");
for (Object entry:entryset){
//将entry转成Map.Entry
Map.Entry m = (Map.Entry) entry;
System.out.println(m.getKey()+"-"+m.getValue());
}
//2.迭代器
System.out.println("====使用entryset的迭代器===");
Iterator iterator2 = entryset.iterator();
while (iterator2.hasNext()) {
Object entry = iterator2.next();
//System.out.println(entry.getClass());class java.util.HashMap$Node
//向下转型Map.Entry
Map.Entry m = (Map.Entry) entry;
System.out.println(m.getKey()+"-"+m.getValue());
}
}
}
小案例:
使用HashMap添加3个员工对象
要求:键:员工id 值:员工对象
遍历显示工资>18000的员工(遍历方式最少两种)
public class MapExercise01 {
@SuppressWarnings({"all"})
public static void main(String[] args) {
Map map = new HashMap();
map.put(1,new Emp("小明",12323,1));
map.put(2,new Emp("小红",18001,2));
map.put(3,new Emp("小兰",156354,3));
//通过值 使用增强for
Set keyset = map.keySet();
System.out.println("=====增强for=======");
for (Object key:keyset){
Emp emp = (Emp)map.get(key);
if (emp.getSalary()>18000){
System.out.println(emp);
}
}
//通过值 使用迭代器
Set keySet = map.keySet();
System.out.println("===========迭代器=========");
Iterator iterator = keySet.iterator();
while (iterator.hasNext()){
// Map.Entry entry = (Map.Entry)iterator.next();
//通过entry和value得到key和value
Object next = iterator.next();
Emp emp = (Emp) map.get(next);
if (emp.getSalary()>18000){
System.out.println(emp);
}
}
//通过entrySet 使用迭代器
Set entrySet = map.entrySet();
System.out.println("===========迭代器=========");
Iterator iterator = entrySet.iterator();
while (iterator.hasNext()){
Map.Entry entry = (Map.Entry)iterator.next();
//通过entry和value得到key和value
Emp emp = (Emp) entry.getValue();
if (emp.getSalary()>18000){
System.out.println(emp);
}
}
}
}
class Emp{
private String name;
private int salary;
public int id;
public Emp(String name, int salary, int id) {
this.name = name;
this.salary = salary;
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getSalary() {
return salary;
}
public void setSalary(int salary) {
this.salary = salary;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
@Override
public String toString() {
return "Emp{" +
"name='" + name + '\'' +
", salary=" + salary +
", id=" + id +
'}';
}
}
HashMap底层机制和源码剖析
- k-v是一个Node 实现了Map.Entry<K,V>,查看HashMap源码可以看到
- jdk7.0的hashmap底层实现是[数组+链表],jdk8.0底层实现是[数组+链表+红黑树]
- 扩容机制(与HashMap相同)
HashMap底层维护了Node类型的数组table,默认为null
当创建对象时,将加载因子(loadfactor)初始化为0.75
当添加key-va时,通过key的哈希值得到在table的索引,然后判断该所索引处是否有元素
如果没有元素则直接添加,如果该所引处有元素,继续判断该元素的key和准备加入的key是否相等
如果相等则直接替换val;如果不相等需要判断是树结构还是链表结构做出相应处理,如果添加时发现容量不足需要扩容
第一次添加,需要扩容table容量为16,临界值(threshold)为12(16*0.75)
以后在扩容,则需扩容table容量为原来的2倍,临界值为原来的2倍,依次类推
在java8中,如果一条链表的元素个数超过TREEIFY_THRESHOLD(默认是8),并且table的大小>=MIN_TREEIFY_CAPICITY(默认是64),就会树化(红黑树)
public class HashMapSource {
@SuppressWarnings({"all"})
public static void main(String[] args) {
HashMap hashMap = new HashMap();
hashMap.put("java",10);
hashMap.put("php",10);
hashMap.put("java",20);
System.out.println("map="+hashMap);
}
//hashmap的源码解读:
//1.先进入hashmap的构造器,初始化加载因子的的值DEFAULT_LOAD_FACTOR=0.75
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
//2.调用put函数
//调用hash函数:
/* static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}*/
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
//3.调用putval函数
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)//当tab为null或tab的长度为0时,即初始情况
n = (tab = resize()).length;//调用resize函数(见下方)n初值被赋为16
if ((p = tab[i = (n - 1) & hash]) == null)//如果此下标处没有node结点则将该结点放在此处并赋给p
tab[i] = newNode(hash, key, value, null);
else {//当下标处有node节点时
Node<K,V> e; K k;
//当哈希值相等 并且 满足现有的结点的key和准备添加的key是同一个对象 或 equals为真
//就认为不能加入新的value
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
//如果p是TreeNode类型,就调用puttreeval方法插入到红黑树中
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
//如果是链表则挂到链表后面
else {
for (int binCount = 0; ; ++binCount) {
//如果p的next为空就直接将其插到p后面
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
//如果binCount大于8个则进行树化
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
//如果e不为空则将新的value直接赋值给旧的value
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;//每次增加一个node结点都++这个表示对table进行操作的次数
//size就是我们加入的结点数
//如果当前结点数大于缓冲数量(12),则进行扩容
if (++size > threshold)
resize();
// 上面的resize()里面
// newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); threshold = newThr;
afterNodeInsertion(evict);
return null;
}
}
//
final Node<K,V>[] resize() {
Node<K,V>[] oldTab = table;//将table赋值给oldtab
int oldCap = (oldTab == null) ? 0 : oldTab.length;//如果oldtab为空则oldcap为0
int oldThr = threshold;//将threshold(临界值)赋值给oldthr
int newCap, newThr = 0;
if (oldCap > 0) {//如果oldcap>0
if (oldCap >= MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return oldTab;
}
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
newThr = oldThr << 1; // double threshold
}
else if (oldThr > 0) // initial capacity was placed in threshold
newCap = oldThr;
else { // zero initial threshold signifies using defaults
//如果oldcap=0就信生成一个newcap大小为16,newthr大小为12的数组
newCap = DEFAULT_INITIAL_CAPACITY;
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
if (newThr == 0) {
float ft = (float)newCap * loadFactor;
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
(int)ft : Integer.MAX_VALUE);
}
threshold = newThr;//将newthr(12)赋值给threshold
@SuppressWarnings({"rawtypes","unchecked"})
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];//new一个Node容量为16
table = newTab;
if (oldTab != null) {
for (int j = 0; j < oldCap; ++j) {
Node<K,V> e;
if ((e = oldTab[j]) != null) {
oldTab[j] = null;
if (e.next == null)
newTab[e.hash & (newCap - 1)] = e;
else if (e instanceof TreeNode)
((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
else { // preserve order
Node<K,V> loHead = null, loTail = null;
Node<K,V> hiHead = null, hiTail = null;
Node<K,V> next;
do {
next = e.next;
if ((e.hash & oldCap) == 0) {
if (loTail == null)
loHead = e;
else
loTail.next = e;
loTail = e;
}
else {
if (hiTail == null)
hiHead = e;
else
hiTail.next = e;
hiTail = e;
}
} while ((e = next) != null);
if (loTail != null) {
loTail.next = null;
newTab[j] = loHead;
}
if (hiTail != null) {
hiTail.next = null;
newTab[j + oldCap] = hiHead;
}
}
}
}
}
return newTab;
}
Map接口的实现类HashTable
- 存放的元素是键值对:k-v
- hashtable的键和值都不能为null,否则会抛出NullPointerException
- hashtable使用方法基本上和HashMap一样
- hashTable是线程安全的(synchronize),hashMap是线程不安全的
HashTable扩容机制为 <<1+1即*2+1
HashTable源码分析
public static void main(String[] args) {
Hashtable hashtable = new Hashtable();
hashtable.put("hello1",1);
hashtable.put("hello2",1);
hashtable.put("hello3",1);
hashtable.put("hello4",1);
hashtable.put("hello5",1);
hashtable.put("hello6",1);
hashtable.put("hello7",1);
hashtable.put("hello8",1);
hashtable.put("hello9",1);
hashtable.put("hello10",1);
hashtable.put("hello11",1);
hashtable.put("hello12",1);
hashtable.put("hello13",1);
}
//1.initialCapacity赋值为11,loadFactor为0.75
//threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1);临界因子为8
public Hashtable() {
this(11, 0.75f);
}
//进入put方法
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];
//如果在下标为index处已有元素并且equals为true则新值替代旧值
for(; entry != null ; entry = entry.next) {
if ((entry.hash == hash) && entry.key.equals(key)) {
V old = entry.value;
entry.value = value;
return old;
}
}
//如果下标处为空或hash值不相等则调用addEntry方法将其添加到该处
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
//当count的值大于threshold的值时调用rehash方法进行扩容
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++;
}
protected void rehash() {
int oldCapacity = table.length;
Entry<?,?>[] oldMap = table;
// overflow-conscious code
//新的容量为旧的*2+1
int newCapacity = (oldCapacity << 1) + 1;
if (newCapacity - MAX_ARRAY_SIZE > 0) {
if (oldCapacity == MAX_ARRAY_SIZE)
// Keep running with MAX_ARRAY_SIZE buckets
return;
newCapacity = MAX_ARRAY_SIZE;
}
Entry<?,?>[] newMap = new Entry<?,?>[newCapacity];
modCount++;
//threshold在newCapacity * loadFactor, MAX_ARRAY_SIZE + 1选取最小值
threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
table = newMap;
for (int i = oldCapacity ; i-- > 0 ;) {
for (Entry<K,V> old = (Entry<K,V>)oldMap[i] ; old != null ; ) {
Entry<K,V> e = old;
old = old.next;
int index = (e.hash & 0x7FFFFFFF) % newCapacity;
e.next = (Entry<K,V>)newMap[index];
newMap[index] = e;
}
}
}
版本 | 线程安全(同步) | 效率 | 允许null键null值 | |
---|---|---|---|---|
HashMap | 1.2 | 不安全 | 高 | 可以 |
Hashtable | 1.0 | 安全 | 较低 | 不可以 |
Map的接口实现类-properties
- properties类继承自Hashtable并且实现了Map接口,也是一种键值对的形式来保存数据
- 他的使用特点和Hashtable类似
- Properties还可以用于xxx.properties类对象并进行读取和修改
- 说明:工作后 xxx.properties文件通常作为配置文件,这个知识点在io流有举例
public class Properties_ {
@SuppressWarnings({"all"})
public static void main(String[] args) {
Properties properties = new Properties();
//1.Properties 继承hashtable
//2.通过 k-v存放数据 k和v也不能为空
//增加
properties.put("john",100);
properties.put("lucy",100);
properties.put("lic",100);
properties.put("lic",88);//值相同,替换
System.out.println("properties"+properties);
//通过key获取对应的值
System.out.println(properties.get("lic"));
//删除
properties.remove("lic");
System.out.println("properties"+properties);
//修改:键相同,值不相同
}
小结 开发中如何选择集合实现类
在开发中,选择什么集合实现类,主要取决于业务操作特点,然后根据集合实现类特性进行选择,分析如下:
先判断存储的类型(一组对象[单列]或一组键值对[双列])
一组对象[单列]:collection接口
允许重复:List
增删多:LinkedList[底层维护了一个双向链表]
改查多:ArrayList[底层维护了一个Object类型的可变数组]
不允许重复:Set
无序:HashSet[底层是HashMap,维护了一个哈希表 即(数组+链表+红黑树)]
排序:TreeSet
插入顺序和取出顺序一致:LinkedHashSet,维护数组+双向链表
一组键值对[双列]:Map
键无序:HashMap[底层是:哈希表 jdk7:数组+链表 ,jdk8:数组+链表+红黑树]
键排序:TreeMap
键插入和取出顺序一致:LinkedHashMap
读取文件 Properties
TreeSet和TreeMap的常用方法
TreeSet的使用
- 当使用无参构造器时,创建TreeSet时采用默认排序从小到大
- 当使用TreeSet(Comparator comparator):即根据指定的比较器进行排序
public class TreeSet_ {
@SuppressWarnings({"all"})
public static void main(String[] args) {
// TreeSet treeSet = new TreeSet();
TreeSet treeSet =new TreeSet<>(new Comparator<Object>() {
@Override
public int compare(Object o1, Object o2) {
return ((String)o2).compareTo((String)o1);
}
});
treeSet.add("jack");
treeSet.add("tom");
treeSet.add("sp");
treeSet.add("a");
System.out.println("treeset="+treeSet);
}
}
//默认 treeset=[a, jack, sp, tom]
treeset=[tom, sp, jack, a]
自然排序Comaprable的使用
- 存储学生对象并遍历,创建TreeSet集合使用无参构造方法
- 要求:按照年龄从小到大排序,年龄相同时,按照姓名的字母顺序排序
学生类 实现Comparable接口,改写compareTo()方法
public class TreeSet_ {
@SuppressWarnings({"all"})
public static void main(String[] args) {
TreeSet<Student> ts = new TreeSet<Student>();
Student s1 = new Student("tom",20);
Student s2 = new Student("lucy",21);
Student s3 = new Student("luna",22);
Student s4 = new Student("som",20);
ts.add(s1);
ts.add(s2);
ts.add(s3);
ts.add(s4);
System.out.println("ts="+ts);
}
}
public class Student implements Comparable<Student> {
private String name;
private int age;
public Student(){
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public int compareTo(Student s) {
//return 0 每次比较返回0只添加第一个元素,其余元素会被认为是重复元素不添加
//return 1 返回正数按照升序存储
//return -1 返回负数按照降序存储
//按照年龄从小到大排序
//当前年龄和传入对象年龄比较
int num = this.age-s.age;
//若年龄相同比较姓名
int res=num==0?this.name.compareTo(s.name):num;
return res;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
ts=[Student{name='som', age=20}, Student{name='tom', age=20}, Student{name='lucy', age=21}, Student{name='luna', age=22}]
比较器(匿名内部类)排序Comparator的使用
- 存储学生对象并遍历,创建TreeSet集合使用带参构造方法
- 要求:按照年龄从小到大排序,年龄相同时,按照姓名的字母顺序排序
学生类-没有实现Comparable接口
public class TreeSet_ {
@SuppressWarnings({"all"})
public static void main(String[] args) {
TreeSet<Student> ts = new TreeSet<Student>(new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
int num = o1.getAge()-o2.getAge();
//若年龄相同比较姓名
int res=num==0?o1.getName().compareTo(o2.getName()):num;
return res;
}
});
Student s1 = new Student("tom",20);
Student s2 = new Student("lucy",21);
Student s3 = new Student("luna",22);
Student s4 = new Student("som",20);
ts.add(s1);
ts.add(s2);
ts.add(s3);
ts.add(s4);
System.out.println("ts="+ts);
}
}
public class Student {
private String name;
private int age;
public Student(){
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
TreeSet源码
public class TreeSet_ {
@SuppressWarnings({"all"})
public static void main(String[] args) {
// TreeSet treeSet = new TreeSet();
TreeSet treeSet =new TreeSet<>(new Comparator<Object>() {
@Override
public int compare(Object o1, Object o2) {
return ((String)o2).compareTo((String)o1);
}
});
treeSet.add("jack");
treeSet.add("tom");
treeSet.add("sp");
treeSet.add("a");
System.out.println("treeset="+treeSet);
}
}
//1.构造器吧传入的比较器对象赋给了 TreeSet的底层的TreeMap的属性this.comparator
public TreeSet(Comparator<? super E> comparator) {
this(new TreeMap<>(comparator));
}
//2.调用add方法(在TreeSet中)
public boolean add(E e) {
return m.put(e, PRESENT)==null;
}
//3.调用put方法
public V put(K key, V value) {
Entry<K,V> t = root;
if (t == 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;//将我们的匿名内部类(对象)传给cpr
if (cpr != null) {
do {
parent = t;
cmp = cpr.compare(key, t.key);//动态绑定到匿名内部类
if (cmp < 0)//这里是二叉搜索树小于在左结点大于在右节点
t = t.left;
else if (cmp > 0)
t = t.right;
else
return t.setValue(value);//这里如果cmp为0会改变值但他的值为 static final类型即不改变
} 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;
}
TreeMap源码
public class TreeMap_ {
@SuppressWarnings({"all"})
public static void main(String[] args) {
// TreeMap treeMap = new TreeMap();
TreeMap treeMap = new TreeMap(new Comparator() {
@Override
public int compare(Object o1, Object o2) {
return ((String)o2).compareTo((String)o1);
}
});
treeMap.put("jack","杰克");
treeMap.put("tom","汤姆");
treeMap.put("kristina","克里斯提那");
treeMap.put("smith","史密斯");
System.out.println("treemap="+treeMap);
}
}
//1.构造器把实现了Comparator接口的匿名内部类(对象)传给了TreeMap的comparator
public TreeMap(Comparator<? super K> comparator) {
this.comparator = comparator;
}
//2.调用put方法
public V put(K key, V value) {
Entry<K,V> t = root;
if (t == null) {//第一次添加t为空,把k-v封装到Entry对象,放入root
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 {//遍历所有的key
parent = t;
cmp = cpr.compare(key, t.key);//动态绑定到匿名内部类的位置
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
else//如果遍历过程中发现准备添加的key和当前已有的key相等则将值替换
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;
}
Collections工具类
- Collections工具类介绍
- Collections是一个操作Set、List和Map等集合的工具类
- Collections中提供了一系列静态的方法对集合元素进行排序、查询和修改等操作
- 排序操作:(均为static方法)
- reverse(List):反转List中元素的顺序
- shuffle(List):对List集合元素进行随机排序
- sort(List):根据元素的自然顺序对指定的List集合按升序排列
- sort(List,Comparator):根据指定的Comparator产生顺序对List集合元素进行排序
- swap(List,int,int):将指定list集合中的i处元素和j处元素进行交换
public class Collections_ {
@SuppressWarnings({"all"})
public static void main(String[] args) {
List list = new ArrayList();
list.add("tom");
list.add("smith");
list.add("king");
list.add("milan");
//reverse(List):反转List中元素的顺序
Collections.reverse(list);
System.out.println("list="+list);
//shuffle(List):对List集合元素进行随机排序
Collections.shuffle(list);
System.out.println("list="+list);
//sort(List):根据元素的自然顺序对指定的List集合按升序排列
Collections.sort(list);
System.out.println("自然排序后="+list);
//sort(List,Comparator):根据指定的Comparator产生顺序对List集合元素进行排序
Collections.sort(list, new Comparator() {
@Override
public int compare(Object o1, Object o2) {
return ((String)o1).length()-((String)o2).length();
}
});
System.out.println("按照字符串长度大小排序="+list);
//swap(List,int,int):将指定list集合中的i处元素和j处元素进行交换
Collections.swap(list,0,1);
System.out.println("交换后的情况="+list);
}
}
查找、替换
- Object max(Collextion):根据元素的自然顺序,返回给定集合中的最大元素
- Object max(Collection,Comparator):根据Comparator指定的顺序,返回集合中的最大元素
- Object min(Collection)
- Object min(Collection,Comparator)
- int frequency(Collection,Object):返回指定集合中指定元素的出现次数
- void copy(List dest,List src):将src中的内容复制到dest
- bollean replaceAll(List list,Object oldVal,Object newVal):使用新值替换List对象的所有旧值
//1.Object max(Collextion):根据元素的自然顺序,返回给定集合中的最大元素 System.out.println("自然顺序的最大元素="+Collections.max(list)); //2.Object max(Collection,Comparator):根据Comparator指定的顺序,返回集合中的最大元素 //比如返回长度最大的元素 Object maxObject=Collections.max(list, new Comparator() { @Override public int compare(Object o1, Object o2) { return ((String)o1).length()-((String)o2).length()>=0?1:-1; } }); System.out.println("长度最大的元素="+maxObject); //3.int frequency(Collection,Object):返回指定集合中指定元素的出现次数 System.out.println("tom出现的次数="+Collections.frequency(list,"tom")); //4.void copy(List dest,List src):将src中的内容复制到dest for (int i=0;i<list.size();i++){ dest.add(""); } //拷贝 Collections.copy(dest,list); System.out.println("dest="+dest); //5.bollean replaceAll(List list,Object oldVal,Object newVal):使用新值替换List对象的所有旧值 //如果list中,有tom就替换成汤姆 Collections.replaceAll(list,"tom","汤姆"); System.out.println("list替换后="+list);
元素进行随机排序
Collections.shuffle(list);
System.out.println(“list=”+list);
//sort(List):根据元素的自然顺序对指定的List集合按升序排列
Collections.sort(list);
System.out.println(“自然排序后=”+list);
//sort(List,Comparator):根据指定的Comparator产生顺序对List集合元素进行排序
Collections.sort(list, new Comparator() {
@Override
public int compare(Object o1, Object o2) {
return ((String)o1).length()-((String)o2).length();
}
});
System.out.println(“按照字符串长度大小排序=”+list);
//swap(List,int,int):将指定list集合中的i处元素和j处元素进行交换
Collections.swap(list,0,1);
System.out.println(“交换后的情况=”+list);
}
}
- 查找、替换
1. Object max(Collextion):根据元素的自然顺序,返回给定集合中的最大元素
2. Object max(Collection,Comparator):根据Comparator指定的顺序,返回集合中的最大元素
3. Object min(Collection)
4. Object min(Collection,Comparator)
5. int frequency(Collection,Object):返回指定集合中指定元素的出现次数
6. void copy(List dest,List src):将src中的内容复制到dest
7. bollean replaceAll(List list,Object oldVal,Object newVal):使用新值替换List对象的所有旧值
```java
//1.Object max(Collextion):根据元素的自然顺序,返回给定集合中的最大元素
System.out.println("自然顺序的最大元素="+Collections.max(list));
//2.Object max(Collection,Comparator):根据Comparator指定的顺序,返回集合中的最大元素
//比如返回长度最大的元素
Object maxObject=Collections.max(list, new Comparator() {
@Override
public int compare(Object o1, Object o2) {
return ((String)o1).length()-((String)o2).length()>=0?1:-1;
}
});
System.out.println("长度最大的元素="+maxObject);
//3.int frequency(Collection,Object):返回指定集合中指定元素的出现次数
System.out.println("tom出现的次数="+Collections.frequency(list,"tom"));
//4.void copy(List dest,List src):将src中的内容复制到dest
for (int i=0;i<list.size();i++){
dest.add("");
}
//拷贝
Collections.copy(dest,list);
System.out.println("dest="+dest);
//5.bollean replaceAll(List list,Object oldVal,Object newVal):使用新值替换List对象的所有旧值
//如果list中,有tom就替换成汤姆
Collections.replaceAll(list,"tom","汤姆");
System.out.println("list替换后="+list);