大家好呀!我是小笙!我学习了韩顺平老师的类和对象的知识,收获颇丰!现在来和大家分享笔记!
集合类
集合框架体系
Collection接口框架
补充框架
Map接口框架
补充框架
遍历方式
遍历方式-迭代器
Iterator接口又称为迭代器,主要用于遍历Collcection集合中的元素
所有实现了Collection接口的集合类都有一个Iterator()方法,用来返回一个迭代器
迭代器方法
default void | forEachRemaining(Consumer<? super E> action) | 对每个剩余元素执行给定操作,直到处理完所有元素或操作引发异常。 |
boolean | hasNext() | 如果迭代具有更多元素,则返回 true 。 |
E | next() | 返回迭代中的下一个元素。 |
default void | remove() | 从底层集合中移除此迭代器返回的最后一个元素(可选操作)。 |
// Iterator方法的使用实例
public class Iterator01 {
public static void main(String[] args) {
List list = new ArrayList();
list.add(new Book('三国演义',10));
list.add(new Book('水浒传',20));
list.add(new Book('西游记',15));
Iterator iterator = list.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next());
}
// 退出循环之后,迭代器指向最后一个元素
// iterator.next(); // 抛出异常 NoSuchElementException
// 需要重置迭代器 iterator = list.iterator();
}
static class Book{
private String name;
private int price;
public Book(String name, int price) {
this.name = name;
this.price = price;
}
@Override
public String toString() {
return 'Book{' +
'name='' + name + ''' +
', price=' + price +
'}';
}
}
}
遍历方式-for增强
特点:只能用于遍历集合和数组
基本语法:
for(元素类型 元素名:集合名或数组名){
// 访问元素
}
实例
public class ForS {
public static void main(String[] args) {
List list = new ArrayList();
list.add(new Toy('猛虎王',10));
list.add(new Toy('霹雳火',20));
list.add(new Toy('洛洛',15));
// 增强for 本质仍然是迭代器
// 集合
for (Object b:list) {
System.out.println(b.toString());
}
// 数组
int[] num = {2,4,5,6};
for (int n:num
) {
System.out.println(n+' ');
}
}
static class Toy{
private String name;
private int price;
public Toy(String name, int price) {
this.name = name;
this.price = price;
}
@Override
public String toString() {
return 'Toy{' +
'name='' + name + ''' +
', price=' + price +
'}';
}
}
}
// debug 跳转 底层也是迭代器
// 跳转1
public Iterator iterator() {
return new Itr();
}
// 跳转2
public boolean hasNext() {
return cursor != size;
}
// 跳转3
public E next() ......等等
遍历方式-普通for循环
public static void main(String[] args) {
List list = new ArrayList();
list.add('张三丰');
list.add('秦天柱');
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
}
Collection接口特征
可以实现存放多个元素,每个元素可以是Object类没有直接实现的子类,都是通过它的子接口Set和List来实现的
Collection的常用方法
List list = new ArrayList();
// add 添加单个元素
list.add('lns');
list.add(520); // 自动装箱 new Integer(520)
list.add('zlr');
System.out.println(list.toString()); // [lns, 520, zlr]
/*
* 父类AbstractCollection: toString方法
* public String toString() {
* Iterator it = iterator(); // 创建迭代器进行遍历该集合
* if (! it.hasNext())
* return '[]';
*
* StringBuilder sb = new StringBuilder();
* sb.append('[');
* for (;;) {
* E e = it.next();
* sb.append(e == this ? '(this Collection)' : e);
* if (! it.hasNext())
* return sb.append(']').toString();
* sb.append(',').append(' ');
* }
* }
*/
// remove 删除指定元素
list.remove(1); // 等价于 list.remove(520);
System.out.println(list); // [lns, zlr]
// contains 查找元素是否存在
System.out.println(list.contains('lns')); // true
// size 获取元素个数
System.out.println(list.size()); // 2
// isEmpty 判读是否为空
System.out.println(list.isEmpty()); // false
// clear 清空
list.clear();
System.out.println(list); // []
// addAll 添加多个元素
List list2 = new ArrayList();
list2.add('lns');
list2.add('love');
list2.add('zlr');
list.addAll(list2);
System.out.println(list); // [lns, love, zlr]
// containsAll 查找多个元素是否存在
System.out.println(list.containsAll(list2)); // true
// removeAll 删除多个元素
list.add('!');
list.removeAll(list2);
System.out.println(list); // [!]
List接口
Collection接口的子接口
特点
List集合类元素顺序有序,且可以重复List集合类中的每个元素都有其对应的顺序索引
// List集合类元素顺序有序,且可以重复
List list = new ArrayList();
list.add('das');
list.add('tom');
list.add('tom');
System.out.println(list); // [das, tom, tom]
// List集合类中的每个元素都有其对应的顺序索引
System.out.println(list.get(0)); // das
List常用方法
public class ListMethod {
public static void main(String[] args) {
List list = new ArrayList();
list.add('张三丰');
list.add('秦天柱');
// add 插入一个对象
list.add(1,'lns');
System.out.println(list); // [张三丰, lns, 秦天柱]
List list2 = new ArrayList();
list2.add('风火轮');
list2.add('大黄蜂');
// addAll 插入所有元素
list.addAll(1,list2);
System.out.println(list); // [张三丰, 风火轮, 大黄蜂, lns, 秦天柱]
// indexOf 返回该对象首次出现的索引位置
System.out.println(list.indexOf('lns')); // 3
// lastIndexOf 返回该对象最后一次出现的索引位置
list.add(1,'张三丰');
System.out.println(list ); // [张三丰, 张三丰, 风火轮, 大黄蜂, lns, 秦天柱]
System.out.println(list.lastIndexOf('张三丰')); // 1
// set 替换对象数据
list.set(1,'妞妞');
System.out.println(list); // [张三丰, 妞妞, 风火轮, 大黄蜂, lns, 秦天柱]
// subList 返回范围为[fromIndex,toIndex)位置的子集合 该方法返回的是子串集合的地址索引
list = list.subList(0, 3);
System.out.println(list); // [张三丰, 妞妞, 风火轮]
}
}
是由数组实现数据存储
特点:
元素可以是null,并且可以有多个ArrayList是线程不安全的,不能在多线程的情况下使用//对比这两种源码区别在于是否线程安全synchronized//ArrayList源码publicbooleanadd(E{modCount++;add(e,elementData,siz;returntrue;}//Vector源码publicsynchronizedbooleanadd(E{modCount++;add(e,elementData,elementCount);returntrue;}
ArrayList源码分析
ArrayList类数据存储在Object类数组中transientObject[]elementData;//transient表示该属性不会被序列化当使用无参构造方法创建该对象,初始elementData容量为0,第一次添加数据,扩容到10容量,以后每次扩容,则会扩大当前容量的5倍如果使用指定大小的有参构造器,则初始elementData容量为指定大小,如果需要扩容,也是直接扩容5倍
扩容机制
无参构造器
设置断点
debug跳转
// 跳转1
public boolean add(E e) {
modCount++; // 记录集合被修改的次数 如果madCount的值因为线程原因意外改变,则抛出异常
add(e, elementData, size); // e:传入的数据 elementDate:Object数组 size:数组元素数量
return true;
}
// 跳转2
private void add(E e, Object[] elementData, int s) {
if (s == elementData.length) // s:数组元素数量 elementDate.length:数组容量
elementData = grow(); // 只有当容量够用,不会调用该方法
elementData[s] = e; // 数组添加数据
size = s + 1;
}
// 跳转3
private Object[] grow() {
return grow(size + 1);
}
// 跳转4
private Object[] grow(int minCapacity) { // minCapacity:当前元素个数+1
int oldCapacity = elementData.length; // 记录原容量
if (oldCapacity > 0 || elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA){ //判断容量是否为0
// 传入newLength方法的参数:原容量,超出容量,0.5倍容量大小
int newCapacity = ArraysSupport.newLength(oldCapacity,
minCapacity - oldCapacity, oldCapacity >> 1 ); // >> 1 相当于乘0.5
return elementData = Arrays.copyOf(elementData, newCapacity); // 扩容,保留原数据
} else {
return elementData = new Object[Math.max(DEFAULT_CAPACITY, minCapacity)];
} // DEFAULT_CAPACITY 10 如果是容量为0,第一次扩容默认为10
}
// 当传入第11个数据时候跳转5
public static int newLength(int oldLength, int minGrowth, int prefGrowth) {
// assert oldLength >= 0
// assert minGrowth > 0
int newLength = Math.max( minGrowth,prefGrowth ) + oldLength; // 当超出容量,则扩容1.5倍
if (newLength - MAX_ARRAY_LENGTH <= 0) {
return newLength;
}
return hugeLength(oldLength, minGrowth);
}
有参构造器
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的基本介绍
// Vector的类定义
public class Vectorextends AbstractList
implements List, RandomAccess, Cloneable, java.io.Serializable
// Vector底层本质也是对象数组
protected Object[] elementData;
// Vector是线程安全
// 通常方法被 synchronized 关键字修饰
Vector源码分析
/*
* The amount by which the capacity of the vector is automatically
* incremented when its size becomes greater than its capacity. If
* the capacity increment is less than or equal to zero, the capacity
* of the vector is doubled each time it needs to grow.
* 当向量的大小变得大于其容量时,向量的容量自动增加的量。如果容量增量小于或等于零,
* 则每次需要增长时,向量的容量都会增加一倍
* @serial
*/
protected int capacityIncrement;
private Object[] grow(int minCapacity) {
int oldCapacity = elementData.length;
int newCapacity = ArraysSupport.newLength(oldCapacity,minCapacity - oldCapacity, capacityIncrement > 0 ? capacityIncrement : oldCapacity);
return elementData = Arrays.copyOf(elementData, newCapacity);
}
public static int newLength(int oldLength, int minGrowth, int prefGrowth) {
// assert oldLength >= 0
// assert minGrowth > 0
int newLength = Math.max(minGrowth, prefGrowth) + oldLength; // 2倍
if (newLength - MAX_ARRAY_LENGTH <= 0) {
return newLength;
}
return hugeLength(oldLength, minGrowth);
}
Vector和ArrayList的比较
ArrayList | 可变数组 | jdk1.2 | 不安全,但效率高 | 如果有参构造 扩容1.5倍;如果是无参1.第一次默认10,第二次扩容1.5倍 |
Vector | 可变数组 | jdk1.0 | 安全,但效率不高 | 如果有参构造 扩容2倍;如果是无参1.第一次默认10,第二次扩容2倍 |
LinkedList类基本介绍
LinkedList类底层实现了双向链表和双端队列特点可以添加任意元素包括null,并且可以重复线程不安全没有实现同步
LinkedList类的底层结构
该类底层是一个双向链表其中含有两个属性:first和last分别指向首节点和尾节点每个节点里面含有prev,next,item三个属性,其中通过prev指向前一个节点,通过next指向后一个节点,用item来存储数据进行添加和删除操作,效率比数组高
添加数据源码分析
// 添加第一个数据
// 跳转1
public boolean add(E e) { // 增加数据
linkLast(e);
return true;
}
// 跳转2
void linkLast(E e) {
final Node l = last; // last:null 第一次添加last为null
final Node newNode = new Node<>(l, e, null); //l:null e:'lns' 说明prev和next指向null
last = newNode; // last 指向尾节点
if (l == null) // 添加第一个节点
first = newNode; // first和last都指向同一个节点
else
l.next = newNode;
size++;
modCount++;
}
// 添加第二个数据: 省略部分不重要的
void linkLast(E e) {
// 总结: l这个变量可以当成连接器,连接新节点和原来最后一个节点
final Node l = last; // l:链表的最后一个节点
final Node newNode = new Node<>(l, e, null); // 创建连接上一个节点的新节点,e:'zlr'
last = newNode; // last指向新的节点(该节点就是新的最后一个节点)
if (l == null)
first = newNode;
else
l.next = newNode; // l指向新节点:节点变成l的下一个节点
size++;
modCount++;
}
// 删除数据还是更改数据...等等看源码
常用方法
// add 增加节点
LinkedList linkedList = new LinkedList();
linkedList.add('lns');
linkedList.add('zlr');
System.out.println(linkedList.toString()); // [lns, zlr]
// remove 删除节点
linkedList.remove(); // 默认删除第一个节点
System.out.println(linkedList); // [zlr]
// set 修改节点
linkedList.set(0,'奥里给');
System.out.println(linkedList); // [奥里给]
// get 根据索引获得某个节点数据
System.out.println(linkedList.get(0)); // 奥里给
ArrayList | 可变数组 | 低,数组扩容 | 高 |
LinkedList | 双向链表 | 高,动态扩容 | 低 |
Set接口
Set接口基本介绍
无序,没有索引,因此该接口不能再使用普通for循环索引的方式遍历不允许重复数据,因此可以有null但是只能有一个nullSet接口实现了Collection接口,所以可以使用该接口的所有方法
Set set = new HashSet();
set.add('lns');
set.add('null');
set.add('null'); // 只会存入一个数据
set.add('zlr');
System.out.println(set); // [lns, zlr, null] 无序
HashSet的底层实际上就是HashMap
/**
* Constructs a new, empty set; the backing {@code HashMap} instance has
* default initial capacity (16) and load factor (0.75).
*/
public HashSet() {
map = new HashMap<>();
}
HashSet框架
特点:
可以存放null值,但是只能存放一个不能存放重复元素publicclassHashSet01{publicstaticvoidmain(String[]args){Setset=newHashSet();Systeout.println(set.add('lns'));//trueSysteout.println(set.add('lns'));//falseSysteout.println(set.add(newString('lns')));//falseSysteout.println('lns'.hashCode()==newString('lns').hashCode());//trueSysteout.println(set.add(newperson('zlr')));//trueSysteout.println(set.add(newperson('zlr')));//true//newString('lns')的hashCode和'lns'相同}}classperson{privateStringname;publicperson(Stringnam{this.name=name;}}元素是无序的,创建后顺序是固定的
HashSet底层机制
先介绍散列表:数组+链表
实例代码
public class HashSetStructure {
public static void main(String[] args) {
// 数组 + 链表
Node[] table = new Node[16];
// 创建节点
Node node21 = new Node('lns', null);
table[2] = node21;
Node node22 = new Node('zlr', null);
node21.next = node22;
Node node23 = new Node('lp', null);
node22.next = node23;
Node node31 = new Node('cyj', null);
table[3] = node31;
System.out.println(Arrays.toString(table));
}
}
class Node{ // 节点
Object item; // 数据
Node next; // 指向下个节点
public Node(Object item, Node next) {
this.item = item;
this.next = next;
}
}
解
用该分析HashSet底层过程
添加一个元素时,先得到hash值=>索引值找到存储数据表table,看这个索引上是否有存放数据如果没有找到,就直接加入;如果有元素并且hashCode值相同,则调用equals方法进行比较,如果相同则不添加,反之添加
// 实例说明:如果有元素并且hashCode值相同,则调用equals方法进行比较,如果相同则不添加,反之添加
// 这就是为什么new String('lns')不会被添加以及为什么new person('zlr')会被添加2次
// 核心就在于equals方法
// 但是为什么'lns'的hashCode会和 new String('lns')相同呢(不是不同的地址吗)
// 关键就在String类重写了hashCode方法,字符串的hashCode是根据字符算出来的
/*
String类计算hashCode的算法
public static int hashCode(byte[] value) {
int h = 0;
for (byte v : value) { // value:传入的字符串
h = 31 * h + (v & 0xff);
}
return h;
}
*/
public class HashSet01 {
public static void main(String[] args) {
Set set = new HashSet();
System.out.println(set.add('lns')); // true
System.out.println(set.add('lns')); // false
System.out.println(set.add(new String('lns'))); // false
System.out.println(set.add(new person('zlr'))); // true
System.out.println(set.add(new person('zlr'))); // true
}
}
class person{
private String name;
public person(String name) {
this.name = name;
}
}
在jdk8中,一个链表的元素达到8个以及table数据表长度达到6则将数组+链表=>红黑树
public class HashSet02 {
public static void main(String[] args) {
HashSet hashSet = new HashSet();
hashSet.add('罗念笙');
hashSet.add('张洛融');
hashSet.add('罗念笙');
System.out.println(hashSet); // [张洛融, 罗念笙]
/*
* 分析源码
* 假设传入'罗念笙'
* 跳转1:add方法
* public boolean add(E e) {
* return map.put(e, PRESENT)==null; // PRESENT:占位 new Object()
* }
*
* 跳转2:put方法
* public V put(K key, V value) {
* return putVal(hash(key), key, value, false, true);
* }
*
* 跳转3:hash方法 计算hash值并返回给put方法中hash(key)
* static final int hash(Object key) {
* int h;
* return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
* }
*
* 跳转4:pubVal方法 传入形参:hash值, '罗念笙', PRESENT, false, true
* final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict) {
* Node[] tab; Node p; int n, i; // 定义辅助变量
* // transient Node[] table; table:数组+链表形式
* if ((tab = table) == null || (n = tab.length) == 0) // 没有分配数组空间
* // 分配数组空间 newCap = DEFAULT_INITIAL_CAPACITY 默认16
* n = (tab = resize()).length;
* if ((p = tab[i = (n - 1) & hash]) == null) // i = (n - 1) & hash 通过hash值计算索引
* tab[i] = newNode(hash, key, value, null); // 该索引下数组值为null,就直接添加节点
* else {
* Node e; K k; // 定义辅助变量
* // 比较索引处首节点的 hash值 以及 指向地址是否相同 或者 比较值是否相同(重写的情况)
* if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k))))
* e = p;
* // 判断 索引p 指向的是否是红黑树
* else if (p instanceof TreeNode)
* e = ((TreeNode)p).putTreeVal(this, tab, hash, key, value);
* // 遍历该索引的所有节点,依次比较;如果相同则不添加
* else {
* 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;
* // threshold 阈值,用来提前给数组扩容 :threshold = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
* // size 大小值得是加入的节点个数达到了阈值
* if (++size > threshold)
* resize();
* afterNodeInsertion(evict); // HashMap类留给子类继承使用的方法
* return null;
* }
*
* // treeifyBin方法 一个链表的元素达到8个以及table数据表长度达到64.则将 数组+链表 => 红黑树
* final void treeifyBin(Node[] tab, int hash) {
* int n, index; Node e;
* // 如果表的长度(数组长度)小于64,则扩容
* // MIN_TREEIFY_CAPACITY:64
* if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
* resize();
* // 如果表的长度(数组长度)大于等于64,则数组+链表 => 红黑树
* else if ((e = tab[index = (n - 1) & hash]) != null) {
* TreeNode hd = null, tl = null;
* do {
* TreeNode p = replacementTreeNode(e, null);
* if (tl == null)
* hd = p;
* else {
* p.prev = tl;
* tl.next = p;
* }
* tl = p;
* } while ((e = e.next) != null);
* if ((tab[index] = hd) != null)
* hd.treeify(tab);
* }
* }
*
* // 补充信息 非debug内容
* 节点Node代码
* static class Node implements Map.Entry {
* final int hash;
* final K key;
* V value;
* Node next;
*
* Node(int hash, K key, V value, Node 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;
* }
* }
*/
}
}
LinkedHashSet类底层是一个LinkedHashMap,是一个数组+双向链表的结构
// 初始化LinkedHashSet
// LinkedHashSet类构造器
public LinkedHashSet() {
super(16, .75f, true); // 初始化容量16;负载因子0.75
}
// 调用父类HashSet构造器,初始化LinkedHashMap
HashSet(int initialCapacity, float loadFactor, boolean dummy) {
map = new LinkedHashMap<>(initialCapacity, loadFactor);
}
// LinkedHashMap调用父类的HashMap的构造器
public LinkedHashMap(int initialCapacity, float loadFactor) {
super(initialCapacity, loadFactor);
accessOrder = false;
}
// 初始化HashMap
public HashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)
throw new IllegalArgumentException('Illegal initial capacity: ' +
initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY) // MAXIMUM_CAPACITY = 1 << 30
initialCapacity = MAXIMUM_CAPACITY;
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException('Illegal load factor: ' +
loadFactor);
this.loadFactor = loadFactor;
this.threshold = tableSizeFor(initialCapacity);
}
特点:
根据hashCode值来决定元素的存储位置,同时使用双向链表来维护元素的次序,所以在一定程度上是有序的不允许重复添加元素
LinkedHashSet类的底层分析
// LinkedHashSet节点源码
// 继承Node节点(HashMap的静态内部类)
static class Entry extends HashMap.Node {
Entry before, after; // 增加前驱节点和后继节点 => 双向链表
// 构造器
Entry(int hash, K key, V value, Node next) {
// Node节点构造器
super(hash, key, value, next);
}
}
// 实现Map接口里的Entry接口
static class Node implements Map.Entry {
final int hash;
final K key;
V value;
Node next;
Node(int hash, K key, V value, Node 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;
}
}
基本介绍
TreeSet类本质就是调用TreeMap,源码,比较机制等会在TreeMap中详细述说
public TreeSet() {
this(new TreeMap<>());
}
Map接口
publicinterfaceMap
Map接口特点
保存具有映射关系的数据:Key-ValueMap中的Key-Value数据可以是任何的引用数据类型,会封装到HashMap$Node对象中Map中的Key不允许重复,但是Value可以重复Key和Value存在一对一的关系,总是能通过Key找到对应得Value
深入理解map接口的Node节点
// Node节点属于HashMap内部类,实现了Map.Entry接口 ; 一个Node节点对象含有一个key和value
// Map.Entry 为Map接口的内部接口
static class Node implements Map.Entry
// key-value值是存储在Node节点对象中,Map.Entry中key-value指向Node节点对象的key-value值的引用(类似对象名对对象的引用)
final int hash;
final K key;
V value;
Node next;
// 那么EntrySet又是什么呢
// Set接口都可理解就是单列集合,其实EntrySet就是存放Map.Entry数据类型的集合
transient Set> entrySet;
// 代码示例
Map map = new HashMap();
map.put('name','罗念笙'); // key: name value: 罗念笙
map.put('name','张洛融'); // key值不能重复,如果重复会覆盖之前相同key值的value值
map.put('person','张洛融'); // value值能重复
System.out.println(map); // {person=张洛融, name=张洛融}
System.out.println(map.get('name')); // 张洛融 ; 能通过Key找到对应得Value
// 着重点看运行类型
Set set = map.entrySet();
System.out.println(set.getClass()); // class java.util.HashMap$EntrySet
for (Object obj: set
) {
System.out.println(obj.getClass()); // class java.util.HashMap$Node
}
Map常用方法
// 常用方法
// put方法 添加key-value
Map map = new HashMap();
map.put('name','lns');
map.put('age',18);
map.put('grade',99);
map.put('grade',60); // 重复key,覆盖之前的value
System.out.println(map); // {grade=60, name=lns, age=18}
// remove方法 根据key值删除映射关系
map.remove('grade');
System.out.println(map); // {name=lns, age=18}
// get方法 根据key获得value值
System.out.println(map.get('age')); // 18
// size方法 获取元素个数
System.out.println(map.size()); // 2
// isEmpty方法 判断元素是否为空
System.out.println(map.isEmpty()); // false
// containsKey 查找该键是否存在
System.out.println(map.containsKey('name')); // true
// clear 清空键值对]
map.clear();
System.out.println(map); // {}
Map接口遍历方式
// Map接口遍历方式
Map map = new HashMap();
map.put('name','lns');
map.put('age',18);
map.put('grade',99);
// 第一组: 先取出key(keySet方法,通过key取出value (get方法)
Set set = map.keySet();
// 方式1: 增强for
for (Object key:set
) {
// key: grade value: 99 key: name value: lns key: age value: 18
System.out.print('key: '+key+' value: '+map.get(key)+' ');
}
System.out.println();
// 方式2: 迭代器
Iterator iterator = set.iterator();
while(iterator.hasNext()){
Object key = iterator.next();
// key: grade value: 99 key: name value: lns key: age value: 18
System.out.print('key: '+key+' value: '+map.get(key)+' ');
}
System.out.println();
// 第二组: 通过EntrySet来获取key-value
Set entrySet = map.entrySet();
// 方式3: 用getKey方法 和 getValue方法
for (Object obj: entrySet
) {
Map.Entry entry = (Map.Entry)obj;
// key: grade value: 99 key: name value: lns key: age value: 18
System.out.print('key: '+entry.getKey()+' value: '+entry.getValue()+' ');
}
System.out.println();
// 方式4: 迭代器
Iterator iterator1 = entrySet.iterator();
while(iterator1.hasNext()){
Map.Entry entry = (Map.Entry)iterator1.next();
// key: grade value: 99 key: name value: lns key: age value: 18
System.out.print('key: '+entry.getKey()+' value: '+entry.getValue()+' ');
}
HashMap底层是数组+链表+红黑树
HashMap类特点
保存具有映射关系的数据:Key-ValueKey不允许重复,但是Value可以重复;如果重复,将会替换掉value值Key和Value存在一对一的关系,总是能通过Key找到对应得ValueHashMap没有实现线程同步,是线程不安全的
HashMap框架
注意HashMap扩容机制等价于HashSet扩容机制,如上述
Hashtable类特点
保存具有映射关系的数据:Key-ValueHashtable的key和value都不允许是null,如果是,将会抛出空指针异常Hashtable是线程安全的,与HashMap不同//Hashtable的put方法publicsynchronizedVput(Kkey,Vvalu
Hashtable框架
Hashtable扩容机制
//Hashtable构造器初始化容量11
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.
Hashtable.Entry,?> tab[] = table;
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length; // 索引值的计算方式: 散列码(hash)% 散列表的长度(tab.length)
@SuppressWarnings('unchecked') // 抑制警告
Hashtable.Entry entry = (Hashtable.Entry)tab[index]; // 创建entry节点
// 判断是否有相同的key的entry节点,如果有,就替换掉原来的value值;反之则添加entry节点
for(; entry != null ; entry = entry.next) {
if ((entry.hash == hash) && entry.key.equals(key)) {
V old = entry.value;
entry.value = value;
return old;
}
}
// 添加entry节点
addEntry(hash, key, value, index);
return null;
}
// addEntry方法
private void addEntry(int hash, K key, V value, int index) {
Entry,?> tab[] = table; // 成员变量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 e = (Entry) tab[index];
tab[index] = new Entry<>(hash, key, value, e);
count++;
modCount++;
}
// rehash方法 用于扩容
protected void rehash() {
int oldCapacity = table.length; // 记录原来的散列表的长度(table.length)
Entry,?>[] oldMap = table; // 记录原来的散列表(table)
// overflow-conscious code
int newCapacity = (oldCapacity << 1) + 1; // 新容量 = 旧容量 * 2 + 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 = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
table = newMap;
// 添加原来的数组数据
for (int i = oldCapacity ; i-- > 0 ;) {
for (Entry old = (Entry)oldMap[i] ; old != null ; ) {
Entry e = old;
old = old.next;
int index = (e.hash & 0x7FFFFFFF) % newCapacity;
e.next = (Entry)newMap[index];
newMap[index] = e;
}
}
}
细节说明
// 源码说明
// addEntry方法
private void addEntry(int hash, K key, V value, int index) {
Entry,?> tab[] = table; // 成员变量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 e = (Entry) tab[index]; // 该索引该的引用节点的引用赋值给e
tab[index] = new Entry<>(hash, key, value, e); // 加入当前的节点,并且指向下一个节点e
count++;
modCount++;
}
// Entry内部类的构造器 说明e 为当前节点的下一个节点
protected Entry(int hash, K key, V value, Entry next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
Hashtable类和HashMap类的区别
HashMap | 1.2 | 不安全 | 高 | 允许 |
Hashtable | 1.0 | 安全 | 较低 | 不允许 |
基本介绍
使用TreeMap时,如果是调用无参构造器,则放入的Key对象必须实现Comparable接口。String、Integer这些类已经实现了Comparable接口,因此可以直接作为Key使用。作为Value的对象则没有任何要求。如果作为Key的class没有实现Comparable接口,必须在创建TreeMap时同时指定一个自定义排序算法
TreeMap类的有序是按一定规则的有序,而非LinkedHashSet的插入和取出顺序一致
注意:红黑树的具体结构,我会放在数据结构里详细介绍
TreeMap源码分析
无参构造器
有参构造器
public class TreeMap02 {
public static void main(String[] args) {
// 通过key中的字符大小进行排序 降序: name > height > age
TreeMap treeMap1 = new TreeMap(new Comparator() {
@Override
public int compare(Object o1, Object o2) {
return ((car)o2).compareName((car)o1);
}
});
treeMap1.put(new car('e'),'LNS');
treeMap1.put(new car('da'),18);
treeMap1.put(new car('dwes'),185);
// {com.Al_tair.map_.treeMap_.car@27d6c5e0=185,
// com.Al_tair.map_.treeMap_.car@4f3f5b24=18,
// com.Al_tair.map_.treeMap_.car@15aeb7ab=LNS}
System.out.println(treeMap1);
}
}
class car{
String name;
public car(String name) {
this.name = name;
}
public String getName() {
return name;
}
public int compareName(Object o) {
return this.getName().length()-((car)o).getName().length();
}
}
基本介绍
Properties类特点
保存具有映射关系的数据:Key-ValueHashtable的key和value都不允许是null,如果是,将会抛出空指针异常
常用方法
// 常用方法
Properties properties = new Properties();
// put方法 添加数据,修改数据
properties.put('name','罗念笙');
properties.put('age',18);
properties.put('grade',99);
properties.put('grade',98); //修改数据
System.out.println(properties); // {grade=98, name=罗念笙, age=18}
// 通过key获取value
System.out.println(properties.get('name')); // 罗念笙
// remove方法 删除数据
properties.remove('grade');
System.out.println(properties); // {name=罗念笙, age=18}
如何选择集合实现类
先判断存储的数据类型一组对象【单列集合】:Collection接口允许重复并且有序:List接口线程安全:Vector【底层是一个Object类型的可变数组】线程不安全:增删多:LinkedList【底层是一个双向链表】改查多:ArrayLIst【底层是一个Object类型的可变数组】不允许重复:Set接口无序并且线程不安全:HashSet【底层就是HashMap,数组+链表+红黑数】定制排序并且线程不安全:TreeSet插入和取出顺序一致,并且线程不安全:LinkedHashSet【数组+双向链表】一组键值对:Map接口线程不安全:键无序:HashMap类【底层就是:数组+链表+红黑数】定制排序:TreeMap类键插入和取出顺序一致:LinkedHashMap类线程安全:读取配置文件:Properties类
Colleactions工具类
Collections是一个操作Set,List,Map等集合的工具类,提供了一系列静态的方法对集合元素进行排序,查找和修改等操作
// Collections工具类中常用方法
// 创建测试类
ArrayList arrayList = new ArrayList();
arrayList.add('cyj');
arrayList.add('lns');
arrayList.add('zlr');
System.out.println(arrayList); // [cyj, lns, zlr]
// reverse(List集合) 反转List中元素的顺序
Collections.reverse(arrayList);
System.out.println(arrayList); // [zlr, lns, cyj]
// shuffle(List集合) 对List集合元素进行随机排序
for (int i = 0; i < 3; i++) {
Collections.shuffle(arrayList);
System.out.println(arrayList); // 随机出现
}
// shuffle方法的源码
public static void shuffle(List> list) {
Random rnd = r;
if (rnd == null)
r = rnd = new Random(); // harmless race.
shuffle(list, rnd);
}
// sort(List集合) 根据元素的自然排序对List集合进行升序 比如:字符串比较的是字符大小
arrayList.add('aaa');
Collections.sort(arrayList);
System.out.println(arrayList); // [aaa, cyj, lns, zlr]
// 用比较器Comparator自定义规则进行排序 sort(arrayList,new Comparator(){});
Collections.sort(arrayList,new Comparator(){
@Override
public int compare(Object o1, Object o2) {
return ((String)o2).compareTo((String)o1);
}
});
System.out.println(arrayList); // [zlr, lns, cyj, aaa]
// swap(List集合,int i,int j) 交换集合索引为i和j的位置
Collections.swap(arrayList,1,2);
System.out.println(arrayList); // [zlr, cyj, lns, aaa]
// max(Collection集合) 返回给定集合中自然排序最大的元素
System.out.println(Collections.max(arrayList)); // zlr
// max(Collection集合,new Comparator(){}) 返回给定集合中自治排序最大的元素
System.out.println(Collections.max(arrayList,new Comparator(){
@Override
public int compare(Object o1, Object o2) {
return ((String)o2).compareTo((String)o1);
}
})); // aaa
// frequency(Collection集合,集合中的元素) 返回该元素在集合中出现的频率
System.out.println(Collections.frequency(arrayList,'aaa')); // 1
// copy(List dest,List src) 复制src的元素到dest集合中
// 注意: 目标集合元素个数必须大于等于原来集合元素的个数
ArrayList dest = new ArrayList();
for (int i = 0; i < 6; i++) {
dest.add(i);
}
Collections.copy(dest,arrayList);
System.out.println(dest); // [zlr, cyj, lns, aaa, 4, 5]
相关面试题
// 补充测试题
// 注意remove方法 是否能删除
HashSet hashSet = new HashSet();
Person person = new Person(1, 'lns');
Person person2 = new Person(1, 'zlr');
hashSet.add(person);
hashSet.add(person2);
System.out.println(hashSet); // [Person{id=1, name='lns'}, Person{id=1, name='zlr'}]
person.setName('4'); // 当重新设置名字,改变了hash值间接改变了索引,会导致接下来的删除不成功
hashSet.remove(person);
System.out.println(hashSet); // [Person{id=1, name='4'}, Person{id=1, name='zlr'}]
Java中有哪些容器?Java中的集合类主要由Collection和Map这两个接口派生而出,其中Collection接口又派生出三个子接口,分别是Set、List、Queue。所有的Java集合类,都是Set、List、Queue、Map这四个接口的实现类,这四个接口将集合分成了四大类,其中
Set代表无序的,元素不可重复的集合;
List代表有序的,元素可以重复的集合;
Queue代表先进先出的队列;
Map代表具有映射关系的集合。
这些接口拥有众多的实现类,其中最常用的实现类有HashSet、TreeSet、ArrayList、LinkedList、ArrayDeque、HashMap、TreeMap等。
Java中的容器,线程安全和线程不安全的分别有哪些?
javutil包下的集合类大部分都是线程不安全的,例如我们常用的HashSet、TreeSet、ArrayList、LinkedList、ArrayDeque、HashMap、TreeMap,这些都是线程不安全的集合类,但是它们的优点是性能好。如果需要使用线程安全的集合类,则可以使用Collections工具类提供的synchronizedXxx()方法,将这些集合类包装成线程安全的集合类。
javutil包下也有线程安全的集合类,例如Vector、Hashtable。这些集合类都是比较古老的API,虽然实现了线程安全,但是性能很差。所以即便是需要使用线程安全的集合类,也建议将线程不安全的集合类包装成线程安全集合类的方式,而不是直接使用这些古老的API。
从Java5开始,Java在javuticoncurrent包下提供了大量支持高效并发访问的集合类,它们既能包装良好的访问性能,有能包装线程安全。这些集合类可以分为两部分,它们的特征如下:
以Concurrent开头的集合类:以Concurrent开头的集合类代表了支持并发访问的集合,它们可以支持多个线程并发写入访问,这些写入线程的所有操作都是线程安全的,但读取操作不必锁定。以Concurrent开头的集合类采用了更复杂的算法来保证永远不会锁住整个集合,因此在并发写入时有较好的性能。以CopyOnWrite开头的集合类:以CopyOnWrite开头的集合类采用复制底层数组的方式来实现写操作。当线程对此类集合执行读取操作时,线程将会直接读取集合本身,无须加锁与阻塞。当线程对此类集合执行写入操作时,集合会在底层复制一份新的数组,接下来对新的数组执行写入操作。由于对集合的写入操作都是对数组的副本执行操作,因此它是线程安全的。
描述一下Mapput的过程
如上述我的HashMap和Hashtable的与添加源码分析
如何得到一个线程安全的Map?
使用Collections工具类,将线程不安全的Map包装成线程安全的Map;使用javuticoncurrent包下的Map,如ConcurrentHashMap;不建议使用Hashtable,虽然Hashtable是线程安全的,但是性能较差。
说一说你对LinkedHashMap的理解
LinkedHashMap使用双向链表来维护key-value对的顺序,该链表负责维护Map的迭代顺序,迭代顺序与key-value对的插入顺序保持一致。
LinkedHashMap可以避免对HashMap、Hashtable里的key-value对进行排序,同时又可避免使用TreeMap所增加的成本。
LinkedHashMap需要维护元素的插入顺序,因此性能略低于HashMap的性能。但因为它以链表来维护内部顺序,所以在迭代访问Map里的全部元素时将有较好的性能。
请介绍TreeMap的底层原理
TreeMap基于红黑树实现。映射根据其键的自然顺序进行排序,或者根据创建映射时提供的Comparator进行排序,具体取决于使用的构造方法。TreeMap的基本操作containsKey、get、put、remove方法,它的时间复杂度是log(。
TreeMap包含几个重要的成员变量:root、size、comparator。其中root是红黑树的根节点。它是Entry类型,Entry是红黑树的节点,它包含了红黑树的6个基本组成:key、value、left、right、parent和color。Entry节点根据根据Key排序,包含的内容是value。Entry中key比较大小是根据比较器comparator来进行判断的。size是红黑树的节点个数。
文章为作者独立观点,不代表 股票程序化软件自动交易接口观点