介绍
Java 的集合就像一个容器,用来存储 Java 类的对象;
- 实际上只是保存对象的引用变量,但通常习惯上认为集合里保存的是对象;
- 可以动态保存任意多个对象,使用方便(相比数组);
集合类主要负责保存、盛装其他数据,因此也被称为容器类;
java.util包 提供一个表示和操作对象集合的统一构架,包含大量集合接口,以及这些接口的实现类和操作它们的算法;
集合接口
单列集合Collection:定义存取一组对象的方法的集合
双列集合Map:保存具有映射关系“key-value对”的集合
集合的实现类
collection实现子类可以存放多个元素,每个元素可以是Object;
- 在 Java5 之前, Java 集合会丢失容器中所有对象的数据类型,把所有对象都当成 Object 类型处理;
- 从 JDK 5.0 增加了泛型以后,Java 集合可以记住容器中对象的数据类型;
有些是有序、可重复(list),有些是无序、不可重复(set);
collection接口没有直接的实现子类,是通过它的子接口Set和List来实现;
如何选用
开发中,根据业务操作特点,并结合集合实现类特征进行选择。
Collection接口
Collection 接口定义一些通用的方法,实现对集合的基本操作; 例如:添加对象、删除对象、清空容器和判断容器是否为空等。
Collection接口没有直接的实现子类,通过它的子接口Set和List来实现;
子接口List
List 集合类中元素有序(即添加顺序和取出顺序一致)、且可重复;
List 集合中的每个元素都有其对应的顺序索引,即支持索引;
- 从0开始
- list.get(3)
JDK API中List接口的常用的实现类:ArrayList、LinkedList和Vector。
List容器中元素均对应一个整数型的序号记载其在容器中的位置,可根据序号存取容器中的元素;
常用方法
遍历方式
- 1.iterator
- 2.增强for
- 3.普通for循环
//1、迭代器遍历方式
Collection col = new ArrayList();
col.add(new Book("三国演义", "罗贯中", 10.1));
col.add(new Book("小李飞刀", "古龙", 5.1));
col.add(new Book("红楼梦", "曹雪芹", 34.6));
//遍历 col 集合
//1. 先得到 col 对应的迭代器(Iterator对象)
Iterator iterator = col.iterator();
//2. 使用 while 循环遍历
while (iterator.hasNext()) {//判断是否还有数据
//返回下一个元素,类型是 Object
Object obj = iterator.next();
System.out.println("obj=" + obj);
}
//3. 当退出 while 循环后 , 这时 iterator 迭代器,指向最后的元素
//在使用next()方法前,若没有调用hasNext()方法,会发生NoSuchElementException异常
// iterator.next();
//4. 如果希望再次遍历,需要重置我们的迭代器
iterator = col.iterator();
System.out.println("===第二次遍历===");
while (iterator.hasNext()) {
Object obj = iterator.next();
System.out.println("obj=" + obj);
}
//2、增强for循环 遍历方式
// 创建一个集合
List list = new ArrayList();
list.add(new Dog("小黑", 3));
list.add(new Dog("大黄", 100));
list.add(new Dog("大壮", 8));
//先使用 for 增强
for (Object dog : list) {
System.out.println("dog=" + dog);
}
与使用 Iterator 接口迭代访问集合元素类似的是,foreach 循环中的迭代变量也不是集合元素本身,系统只是依次把集合元素的值赋给迭代变量,因此在 foreach 循环中修改迭代变量的值也没有任何实际意义;
//3.普通for遍历方式
for (int i = 0; i < list.size(); i++) {
System.out.println("对象=" + list.get(i));
}
实现类Arraylist
基于动态数组的数据结构实现数据存储;
- 对象引用的一个”变长”数组; 实现了可变数组的大小;
- 提供了快速基于索引访问元素的方式,对尾部成员的增加和删除支持较好;
- 元素必须连续存储;
可将ArrayList作为缺省选择;
基本等同于Vector,但线程不安全(执行效率高);
底层操作机制
维护一个Object类型的数组elementData;
当用无参构造器创建对象时,初始elementData容量为0;
- 第1次添加,则扩容为10;
- 如需再次扩容,则扩容为1.5倍;(10*1.5 =15)
若使用指定大小的构造器,则初始elementData容量为指定大小[例如:8];
- 若需扩容,则直接扩容为elementData容量的1.5倍。
public static void main(String[] args) {
List list = new ArrayList();
Person smith = new Person("smith", 80, 50000);
Person smith02 = new Person("smith02", 80, 50000);
Person smith03 = new Person("smith03", 80, 50000);
list.add(smith);
List list02 = new ArrayList(2);
list02.add(smith);
list02.add(smith02);
list02.add(smith03);
}
//ArrayList部分源码分析
/*
属性:
private static final int DEFAULT_CAPACITY = 10;
private static final Object[] EMPTY_ELEMENTDATA = {};
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
transient Object[] elementData;
private int size;
public ArrayList() { //无参构造器
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;//创建一个空的elementData数组{}
}
第一次扩容:
public boolean add(E e) {
ensureCapacityInternal(size + 1); // 1、先确定是否需要扩容
elementData[size++] = e;//2、再进行赋值
return true;
}
private void ensureCapacityInternal(int minCapacity) {//minCapacity为1;
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { //若elementData为空数组{}
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); //为minCapacity赋值10
}
ensureExplicitCapacity(minCapacity);//此时minCapacity为10
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++; //记录集合被修改的次数
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity); //扩容
}
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; //值为2147483639
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length; //oldCapacity值为0
int newCapacity = oldCapacity + (oldCapacity >> 1); //newCapacity=0;
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity; //minCapacity=11,表示新增的第11个元素;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);//第一次扩容 newCapacity = 10;
}
再一次扩容:
private void grow(int minCapacity) { //此时minCapacity为11
// overflow-conscious code
int oldCapacity = elementData.length; //oldCapacity值为10
int newCapacity = oldCapacity + (oldCapacity >> 1); //newCapacity=15;
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity; //newCapacity=11;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);//第二次扩容 newCapacity = 15;
}
(2)过程
1)使用无参构造器
1.创建一个空的elementData数组 = {};
2.执行list.add,先确定是否要扩容,然后执行赋值;
3.确定minCapacity,第一次扩容10;
4.modCount++记录集合被修改的次数;
5.如果elementData的大小不够,则使用grow()去扩容;
6.若扩容,则使用扩容机制;
第1次newCapacity = 10;
第2次及其以后,按照1.5倍扩容
扩容使用Arrays.copyOf();
2)使用有参构造器
1.创建一个指定大小的elementData数组;
this.elementData= new Object[capacity];
2.若需扩容,按照elementDatade 1.5被扩容,执行流程同无参构造器扩容流程;
实现类LinkedList
插入或删除元素的操作的效率高; 但随即访问元素的速度相对较慢(随机访问是指检索集合中特定索引位置的元素);
可以添加任意元素(元素可以重复),包括null;
双向链表结构
- 基于链表数据结构的实现; 占用的内存空间比较大
- 底层实现了双向链表和双端队列的特点
线程不安全,没有实现同步。
底层操作机制
LinkedList维护两个属性 first和last分别指向 首节点和尾节点;
节点(Node对象):
- 维护prev、next、item三个属性;
- 通过prev指向前一个,通过next指向后一个节点;
- 是LinkedList中保存数据的基本结构;
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;
}
}
备注:
prev变量记录前一个元素的位置
next变量记录下一个元素的位置
item:保存数据
源码分析:
/*
LinkedList linkedList = new LinkedList();
linkedList.add(2);//增加一个结点,默认添加到末尾
属性:
transient Node<E> first;
transient Node<E> last;
1. public LinkedList() {//无参构造器
}
2. 这时 linkeList 的属性 first = null last = null
3. 执行 添加
public boolean add(E e) {//e为Integer 2
linkLast(e);
return true;
}
4.将新的结点,加入到双向链表的最后
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++;
}
*/
/*
linkedList.remove(); // 这里默认删除的是第一个结点
1. 执行 removeFirst
public E remove() {
return removeFirst();
}
2. 执行
public E removeFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return unlinkFirst(f);
}
3. 执行 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;
}
*/
实现类Vector
线程安全( synchronized);
大多数操作与ArrayList相同;
底层操作机制
源码分析:
/*
属性:
protected Object[] elementData;
protected int elementCount;
protected int capacityIncrement;
1. new Vector() 底层
public Vector() {
this(10);
}
补充:如果是 Vector vector = new Vector(8),则直接走下面方法
public Vector(int initialCapacity) {
this(initialCapacity, 0);
}
2. vector.add(i)
2.1 //下面这个方法就添加数据到vector集合
public synchronized boolean add(E e) { //Vector类的操作方法是线程安全的
modCount++;
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = e;
return true;
}
2.2 //确定是否需要扩容 条件 : minCapacity - elementData.length>0
private void ensureCapacityHelper(int minCapacity) {
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
2.3 //如果 需要的数组大小 不够用,就扩容 , 扩容的算法
//newCapacity = oldCapacity + ((capacityIncrement > 0) ?
// capacityIncrement : oldCapacity);
//就是扩容两倍.
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
capacityIncrement : oldCapacity);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
elementData = Arrays.copyOf(elementData, newCapacity);
}
*/
应用
子接口Set
元素;
- 存放元素为无序(添加和取出的顺序不一致,但取出顺序是固定)、没有索引
- 不能添加重复的元素;有且仅有一个null
set接口没有提供额外的方法;
不是同步的;
遍历方式
- 1.iterator迭代器
- 2.增强for循环;不能使用索引的方式遍历
实现类HashSet
Set接口的典型实现
- 不是线程安全的;
- 实际上HashMap
底层: 实际是HashMap,数组+链表+红黑树
- HashMap.Node,记录了结点的hash(哈希值) , key , value , next(Node<K,V> next),用于存储单向链表结构中的数据
元素
- 按Hash算法来存储集合中的元素,具有很好的存取、查找、删除;
性能
- 不能保证元素的排列顺序,取决于hash后,再确定索引的结果
- 可以存放 null ,但是只能有一个 null,即元素不能重复;
public HashSet() { //无参构造器
map = new HashMap<>();
}
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next;
Node(int hash, K key, V value, Node<K,V> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
public final K getKey() { return key; }
public final V getValue() { return value; }
public final String toString() { return key + "=" + value; }
public final int hashCode() {
return Objects.hashCode(key) ^ Objects.hashCode(value);
}
public final V setValue(V newValue) {
V oldValue = value;
value = newValue;
return oldValue;
}
public final boolean equals(Object o) {
if (o == this)
return true;
if (o instanceof Map.Entry) {
Map.Entry<?,?> e = (Map.Entry<?,?>)o;
if (Objects.equals(key, e.getKey()) &&
Objects.equals(value, e.getValue()))
return true;
}
return false;
}
}
afterNodeInsertion(evict); //evict = true;
void afterNodeAccess (Node<K,V> p) { }
void afterNodeInsertion(boolean evict) { }
void afterNodeRemoval(Node<K,V> p) { }
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
Node<K,V> newNode(int hash, K key, V value, Node<K,V> next) {
return new Node<>(hash, key, value, next);
}
底层操作机制
说明:
1.HashSet底层为HashMap,添加元素时,先得到该对象的HashCode值
2.根据HashCode值,通过某个散列函数决定该对象在HashSet底层数组中的存储位置--(索引值)
在存储数据表table,查看该索引位置的元素
3.若该位置为null,则直接添加
4.若有,则调用equals()方法;
5.equals()方法返回true,则添加失败
6.如果为false,保存该元素,通过链表的方式链接
7. 在java8中,若一条链表的元素个数达到TREEIFY_THRESHOLD(8),且table表的大小>=MIN_TREEIFY_CAPACITY(64),则进行树化(红黑树)。
@SuppressWarnings({"all"})
public class HashSetSource {
public static void main(String[] args) {
HashSet hashSet = new HashSet();
hashSet.add("java");//到此位置,第 1 次 add 分析完毕.
hashSet.add("php");//到此位置,第 2 次 add 分析完毕
hashSet.add("java");
System.out.println("set=" + hashSet);
}
}
//源码分析
//HashSet类
1、执行HashSet()
属性:
private static final Object PRESENT = new Object();
public HashSet() {//无参构造器
map = new HashMap<>();
}
//HashMap类
属性:
transient Node<K,V>[] table;
int threshold;
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
static final int MAXIMUM_CAPACITY = 1 << 30;
static final float DEFAULT_LOAD_FACTOR = 0.75f;
static final int TREEIFY_THRESHOLD = 8;
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR; // DEFAULT_LOAD_FACTOR=0.75;
}
2、执行add()
//HashSet类
public boolean add(E e) { //e:java
return map.put(e, PRESENT)==null; //e:java map:{} PRESENT:Object@540,共享的一个对象
}
3.执行 put() , 该方法会执行 hash(key) 得到key对应的hash值 算法h = key.hashCode()) ^ (h >>> 16)
//HashMap类
public V put(K key, V value) {//key:java value:Object@540
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[],transient Node<K,V>[] table;
if ((tab = table) == null || (n = tab.length) == 0) //if语句表示如果当前table 是null, 或者 大小=0,就开始第一次扩容到16个空间.
n = (tab = resize()).length;//扩容后返回HashMap$Node[16]@571 ;此时table为HashMap$Node[16]@571;
//根据key得到 hash, 计算该 key 应该存放到 tab 表的哪个索引位置,并把这个位置的对象,赋给 p
if ((p = tab[i = (n - 1) & hash]) == null)//判断 p 是否为 null
tab[i] = newNode(hash, key, value, null); //如果 p 为 null, 表示还没有存放元素, 就创建一个newNode结点放入tab[i]位置;
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 循环比较
//(1) 依次和该链表的每一个元素比较后,都不相同, 则加入到该链表的最后
//注意在把元素添加到链表后,立即判断 该链表是否已经达到 8 个结点, 就调用 treeifyBin() 对当前这个链表进行树化(转成红黑树)
//注意,在转成红黑树时,要进行判断, 判断条件
//if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY(64))
// resize();
// 如果上面条件成立,先 table 扩容.
// 只有上面条件不成立时,才进行转成红黑树;
//(2) 依次和该链表的每一个元素比较过程中,如果有相同情况,就直接 break
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;
//就是我们每加入一个结点 Node(k,v,h,next), size++
if (++size > threshold)
resize();//扩容
afterNodeInsertion(evict);
return null;
}
//扩容
final Node<K,V>[] resize() {
Node<K,V>[] oldTab = table;//当前table 是null,即初始值
int oldCap = (oldTab == null) ? 0 : oldTab.length; //oldCap:0
int oldThr = threshold; //属性threshold初始值为0;
int newCap, newThr = 0;
if (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
newCap = DEFAULT_INITIAL_CAPACITY; //DEFAULT_INITIAL_CAPACITY初始值为16
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); //DEFAULT_LOAD_FACTOR初始值为0.75 ;newThr为12
}
if (newThr == 0) {
float ft = (float)newCap * loadFactor;
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
(int)ft : Integer.MAX_VALUE);
}
threshold = newThr; //threshold赋值为12
@SuppressWarnings({"rawtypes","unchecked"})
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap]; //创建newTab:HashMap$Node[16]@571
table = newTab;//table:HashMap$Node[16]@571
if (oldTab != null) {//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;//newTab:HashMap$Node[16]@571
}
//创建Node[]结点
Node<K,V> newNode(int hash, K key, V value, Node<K,V> next) {
return new Node<>(hash, key, value, next);
}
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),就会进行树化(红黑树),否则仍然采用数组扩容机制
实现类LinkedHashSet
底层LinkedHashMap【是HashMap的子类】,维护了一个 数组+双向链表【Hash表和双向链表(head和tail)】
根据元素的HashCode值决定元素的存储位置,同时用链表维护元素的次序;
LinkedHashMap.Entry
- 不仅记录了结点的hash , key , value , next
- 还维护了一个双向的数据结构(其实就是维护了两个LinkedHashMap.Entry类的两个引用before和after)
底层操作机制
Set set = New LinkedHashSet();
//源码分析:
//LinkHashSet类
1. public LinkedHashSet() {
super(16, .75f, true);
}
//HashSet类
2. HashSet(int initialCapacity, float loadFactor, boolean dummy) {//参数值16, 0.75,true
map = new LinkedHashMap<>(initialCapacity, loadFactor);
}
//LinkedHashMap类
3. public LinkedHashMap(int initialCapacity, float loadFactor) {
super(initialCapacity, loadFactor);
accessOrder = false;
}
//HashMap类
4. public HashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
this.loadFactor = loadFactor; //0.75
this.threshold = tableSizeFor(initialCapacity); //16
}
/
2. set.add(new String("AA"));
//源码分析
public String(String original) {
this.value = original.value;
this.hash = original.hash;
}
//HashSet
3. public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
步骤同HashSet
实现类TreeSet
特点
- 可以确保集合元素处于排序状态 ; 有序,查询速度比List快
- 底层使用红黑树结构存储数据
排序方式
自然排序:
- 调用集合元素的compareTo(Object obj)方法来比较元素之间的大小关系,然后将集合元素按升序(默认情况)排列;
- 该排序方式下,只能向 TreeSet 集合中添加相同数据类型的对象,否则会抛出 ClassCastException 异常;
- 针对Integer和String等基础对象类型;
定制排序
- 通过Comparator接口来实现。
- 需要重写compare(T o1,T o2)方法
API方法
相比collection接口,增加访问第一个、前一个、后一个、最后一个元素的方法和3 个从 TreeSet 中截取子 TreeSet 的方法
map接口
介绍
Map与Collection并列存在;
- map用于保存具有映射关系的数据:key-value
key和value;
- key: 不允许重复,可以为null; 常用String类作为Map的“键”;
- velue: 可以为null,允许重复;
- key和value之间存在单向一对一关系,即通过指定的key总能找到唯一的、确定的value;
- Map中的key和value都可以是任何引用类型的数据,封装到HashMap$Node对象中
Map存放数据示意图,1对k-v存放到一个HashMap$Node中,又因为HashMap.Node<k,v> 实现Map.Entry<k,v>接口;
Map map = new HashMap();
map.put("no1", "韩顺平");//k-v
map.put("no2", "张无忌");//k-v
map.put(new Car(), new Person());//k-v
//1. k-v 最后是 HashMap$Node node = newNode(hash, key, value, null)
static class Node<K,V> implements Map.Entry<K,V>{
...}
//2. k-v 为了方便程序员的遍历,还会 创建entrySet 集合 ,该集合存放的元素的类型 Map.Entry, 而一个Entry对象有k,v
entrySet<map.Entry<K,V>> // 即: transient Set<Map.Entry<K,V>> entrySet;
//3. entrySet中,定义的类型是 Map.Entry ,实际上存放的还是 HashMap $Node
//因为 static class Node<K,V> implements Map.Entry<K,V>
//4. 当把 HashMap$Node 对象 存放到 entrySet 就方便我们的遍历, 因为 Map.Entry提供了重要方法 K getKey(); V getValue();
Set set = map.entrySet();
System.out.println(set.getClass());// HashMap$EntrySet
for (Object obj : set) {
System.out.println(obj.getClass()); //HashMap$Node
//从 HashMap$Node 取出k-v
//1. 先做一个向下转型
Map.Entry entry = (Map.Entry) obj;
System.out.println(entry.getKey() + "-" + entry.getValue() ); }
Set set1 = map.keySet();
System.out.println(set1.getClass());
Collection values = map.values();
System.out.println(values.getClass())
遍历元素方式
Map map = new HashMap();
map.put("邓超", "孙俪");
map.put("王宝强", "马蓉");
map.put("宋喆", "马蓉");
map.put("刘令博", null);
map.put(null, "刘亦菲");
map.put("鹿晗", "关晓彤");
//1、entries遍历
Set entrySet = map.entrySet(); // EntrySet<Map.Entry<K,V>>
for (Object entry : entrySet) {
//将 entry 转成 Map.Entry
Map.Entry m = (Map.Entry) entry;
System.out.println(m.getKey() + "-" + m.getValue());
}
//2、values遍历
Collection values = map.values();
for (Object value : values) {
System.out.println(value);
}
//3、通过键找值遍历--从键取值是耗时的操作
//第一组: 先取出 所有的 Key , 通过 Key 取出对应的 Value
Set keyset = map.keySet();
for (Object key : keyset) {
System.out.println(key + "-" + map.get(key));
}
常用方法
实现类
//使用Properties类来读取mysql.properties文件
//1.创建Properties对象
Properties properties = new Properties();
//2.加载指定配置文件
properties.load(new FileReader("src\\mysql.properties"));
//3.把k-v显示控制台
properties.list(System.out);
//4.根据key获取对应的值
String user = properties.getProperty("user");
String pwd = properties.getProperty("pwd");
System.out.println("用户名=" + user);
System.out.println("密码是=" + pwd);
//使用Properties类来创建配置文件,修改配置文件内容
Properties properties = new Properties();
//创建
//1.如果该文件没有key就是创建
//2.如果该文件有key,就是修改
properties.setProperty("charset", "utf8");
properties.setProperty("user", "汤姆");//注意保存时,是中文的 unicode 码值
properties.setProperty("pwd", "888888");
//将k-v存储文件中即可
properties.store(new FileOutputStream("src\\mysql2.properties"), (String)null);
System.out.println("保存配置文件成功~");
Collection类
Collections 类是 Java 提供的一个操作 Set、List 和 Map 等集合的工具类;
提供了许多操作集合的静态方法,实现集合元素的排序、查找、替换和复制等操作;
方法
查找、替换操作
排序