Java基础
集合
分类
1.Collection
2.Map
Collection集合
常用方法
public class TestJava {
public static void main(String[] args) {
Map map = new HashMap<>();
Map map1 = new ConcurrentHashMap<>();
Map map2 = new Hashtable();
List list = new ArrayList<>();
// add():往集合里添加元素
list.add("zhangSan");
list.add(true);
list.add(1);
list.add(1.1);
List list1 = new ArrayList<>();
list1.add("加1");
list1.add("加2");
list1.add("加3");
list1.add("加1");
System.out.println(list);
// remove():删除集合里的元素
list.remove("zhangSan");
System.out.println(list);
// 集合是否包含元素
System.out.println(list.contains("zhangSan"));
// size():集合的元素个数
System.out.println(list.size());
// 添加多个元素
list.addAll(list1);
System.out.println(list);
// 是否存在多个
System.out.println(list.containsAll(list1));
// 删除多个元素
list.removeAll(list1);
System.out.println(list);
// clear():清空集合
list.clear();
}
}
迭代器
// 迭代器:collection.iterator()
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
List集合
特点:
List集合中的元素有序可重复
常用方法
// 通过下标返回数据
System.out.println("返回的数据:"+dogs.get(1));
// 更改指定下标的值
dogs.set(1,new Dog("d",4));
System.out.println("更改后的数据:"+dogs.get(1));
System.out.println(dogs);
// 指定下标插入元素
dogs.add(2,new Dog("e",5));
System.out.println(dogs);
// 返回对象在集合中首次出现的位置
System.out.println(dogs.indexOf(dog2));
// 返回对象在集合中最后一次出现的位置
int i = dogs.lastIndexOf(dog2);
// 返回指定区间的集合元素
ArrayList
ArrayList可以存放多个空值,ArrayList底层基于数组,线程不安全。
当使用无参构造器初始化时,ArrayList的elementData的容量为0,第一次添加,扩容到10,再扩容时以1.5倍扩容。
当使用有参构造器扩容时,elementData容量以给定的容量为默认,扩容以1.5倍扩容。
elementData被transient修饰,表示瞬间的,不可被序列化。
无参构造扩容
先进行初始化,默认值为0
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
调用add方法,先判断容量是否充足
当ArrayList集合需要的容量大于当前集合长度时,调用copyOf()进行扩容
有参构造扩容
第一次初始化时,创建指定大小的Object[capacity],第一次扩容时,直接扩容1.5倍。
Vector
特点:
Vector的底层也是通过数组实现的,默认大小也是10。主要特点:查询快,增删慢 , 涉及写的方法中都加入了,synchronized同步锁,保证线程安全,但是效率低
原理:创建对象与ArrayList类似,但有一点不同,它可以设置扩容是容量增长大小。
List vector = new Vector(10,20);
LinkedList
特点:LinkedList底层是维护双向链表,没有指定size的逻辑,只有pre节点和next节点,增删快,查询慢,线程不安全。
add():
void linkLast(E e) {
//取出双线链表的尾节点
final Node<E> l = last;
//将新添加的对象装进新的节点中,并将上个节点属性指向L
final Node<E> newNode = new Node<>(l, e, null);
//再将LinkedList的尾节点指向新的节点
last = newNode;
//如果last为空,说明LinkedList为空,则将头节点也指向新节点
if (l == null)
first = newNode;
else
l.next = newNode;
//LinkedList的长度加1
size++;
modCount++;
}
modCount字段:
在单线程时可能会出现冗余,但在多线程时,当迭代器进行迭代时,当对数据进行修改时,迭代器抛ConcurrentModificationException()
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
Set集合
特点:
元素唯一,不保存重复元素,
加入顺序不一致,
无索引,继承Conllection,可用迭代器遍历,
加入Set的元素必须定义equals()方法以确保对象的唯一性。
HashSet
特点:
元素唯一,无序无索引,底层是哈希表(HashMap),允许有元素为null。
注意:
如果元素要存储到HashSet集合中,必须覆盖hashCode方法和equals方法。
创建HashSet(),默认加载因子为0.75,可以指定
临界阈值(扩容因子):只要哈希表中的所欲节点个数 >= 临界阈值,就会进行树化判断并扩容
源码:
源码介绍: 哈希表的创建,添加元素
当调用无参构造函数创建哈希表时,会将默认加载因子赋值给HashMap()加载阈值
当HashSet() 的 add()方法被调用时,底层会调用HashMap的**put(key,value)**方法,并将new Object()作为key放入k位置。
再将需要添加的元素进行hash,hash是调用元素的hashCode() ^ hashCode >>> 16,
在**putVal()**方法里进行操作
首先对表判断,是否为空 || 长度是否大于零,为空则调用resize()进行扩容。
扩容判断结束后,再判断当前元素哈希结果位置索引是否为空,为空则直接插入。
当索引位置不为空时,**与索引位置元素进行哈希值和equals()**判断,为同一元素则返回当前元素,在最外层add()方法进行判断。
判断结果为不同元素,再判断是否为树类型,为树类型时,则调用树算法进行插入。
不为树类型时,对节点进行死循环遍历,当p.next == null 时,进行插入。
如果节点的元素个数 >= 7 时,进行树化判断,
树化判断若哈希表长度小于64,则调用resize()进行 <<1 扩容。
//底层new HashMap() 哈希表
public HashSet() {
map = new HashMap<>();
}
//进入HashMap()方法
//默认大小为1 << 4
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
//指定参数时
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
添加元素 add()
//1.底层调用HashMap()的put()方法,为了保证put(key,value)方法参数一致,PRESENT参数为一个static final new Object()对象
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
//进入put方法里,其中hash(key)方法源码是对key进行判断,若(key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16)
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
//进入putVal()方法
final V putVal(/*hash(key)*/int hash,/*key值*/ K key,/*PERSENT*/ V value,
boolean onlyIfAbsent,boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
//判断当前HashSet是否为空,或者长度为零,是则调用resize()进行初始化
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
//判断当前哈希表的哈希值索引为空,则直接插入
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
//若不为空
else {
Node<K,V> e; K k;
/*判断当前哈希表元素的哈希值和待插入元素的哈希值是否相同,并且值是否相同,
相同则返回待插入元素值*/
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
//如果不相同,判断当前索引位置的数据结构类型是否为树节点
else if (p instanceof TreeNode)
//是则通过红黑树的算法插入元素
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
//否则对索引位置节点进行遍历
for (int binCount = 0; ; ++binCount) {
//当节点的下一个节点为空时,将待插入节点插入
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
//当节点的长度 >=7 时,则进行树化判断
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
//treeifyBin(tab, hash)树化判断
//当表长度大于64时,将此节点树化
//当表长度小于64时,调用resize()对哈希表进行进行 <<1 扩容
treeifyBin(tab, hash);
break;
}
//如果遍历过程中,节点元素和待插入元素为同一元素,相同则返回待插入元素值
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
LinkedHashSet
特点:元素唯一,有序无索引,底层是双向链表哈希表(LinkedHashMap),允许有元素为null,继承HashSet,实现Set接口
HashMap
特点:
HashMap 是一个散列表,它存储的内容是键值对(key-value)映射。
HashMap 实现了 Map 接口,根据键的 HashCode 值存储数据,具有很快的访问速度,最多允许一条记录的键为 null,不支持线程同步。
HashMap 是无序的,即不会记录插入的顺序。
HashMap底层扩容机制
HashMap当add(key,value)中key值相同时,会在putVal(hash(key),k,v,boolean)方法中替换,HashMap是可以指定初始容量的,不指定的话默认初始容量是16,如果传入一个值用来指定初始容量,并不是直接使用这个值来定义map大小,而是用大于等于这个值的最小2的幂次方来作为初始容量,之所以要采用2的幂次方来作为HashMap底层数组的长度,是因为在对数据的key值进行哈希函数运算得到的哈希值范围很大,无法直接作为数据存放位置的数组下标,因此需要对得到的哈希值作进一步处理,一般来说是对数组长度进行取模运算,在数组长度为2的幂次方的时候,用哈希值与数组长度-1的值做逻辑与运算就相当于对数组长度取余,逻辑与运算因为运算符优先级的关系会比直接的取余运算效率更高,这就是HashMap底层数组长度一般为2的幂次方的原因。
/**
*
* @param hash 由key计算出来的 hash值
* @param key 要存储的key
* @param value 要存储的value
* @param onlyIfAbsent 如果当前位置已存在一个值,是否替换,false是替换,true是不替换
* @param evict 表是否在创建模式,如果为false,则表是在创建模式。
*/
final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict)
遍历:
三类方式:
Map map = new HashMap<>(); map.put(1,"a"); map.put(2,"b"); map.put(3,"c"); map.put(4,"d"); map.put(5,"e");
// 遍历HashMap()
// Set<K> keySet();
Iterator iterator = map.keySet().iterator();
while(iterator.hasNext()) {
Object next = iterator.next();
System.out.println(next +"="+map.get(next));
}
// Collection<V> values();
Collection values = map.values();
Iterator iterator1 = values.iterator();
while (iterator1.hasNext()) {
Object next = iterator1.next();
System.out.println(next);
}
// public Set<Map.Entry<K,V>> entrySet()
Set set = map.entrySet();
Iterator iterator2 = set.iterator();
while (iterator2.hasNext()) {
System.out.println(iterator2.next());
}
树化扩容
**条件:**节点个数 >= 8 ,并且散列表长度为64
当散列表的节点数 >= 8 时,如果散列表长度小于64,会对散列表进行扩容,扩容以 << 1 倍扩容。
当散列表的节点数 >= 8 时,如果散列表长度不小于64,则对此节点进行树化。
Hashtable
**特点:**和HashMap基本一致,在所有方法上都加了锁,效率低下,键和值都不允许有空值。
HashMap & Hashtable
HashMap | Hashtable | |
---|---|---|
线程安全 | 否,效率高 | 是,效率低 |
底层哈希 | 与运算,(table.length - 1) & hash(key) | 位运算(hash & 0x7FFFFFFF) % tab.length |
是否为空 | 可以有一个null | 不能为空 |
底层结构 | 数组+链表+红黑树 | 数组+链表 |
扩容机制 | 初始值为16,(扩容为 table.lenth*2) | 初始值为11,(扩容为 table.length*2) +1 |
初始容量 | 为给定值的最小二次幂 | 任意值 |
Properties
集合工具类
一、排序
1、反转
void reverse(List list)
1
2、随机排序
void shuffle(List list)
1
3、按自然排序的升序排序
void sort(List list)
1
4、定制排序,由Comparator控制排序逻辑
void sort(List list, Comparator c)
1
5、交换两个索引位置的元素
void swap(List list, int i , int j)
1
6、旋转。当distance为正数时,将list后distance个元素整体移到前面。当distance为负数时,将 list的前distance个元素整体移到后面
void rotate(List list, int distance)
1
二、查找、替换操作
1、对List进行二分查找,返回索引,注意List必须是有序的
int binarySearch(List list, Object key)
1
2、根据元素的自然顺序,返回最大的元素。 类比int min(Collection coll)
int max(Collection coll)
1
3、根据定制排序,返回最大元素,排序规则由Comparatator类控制。类比int min(Collection coll, Comparator c)
int max(Collection coll, Comparator c)
1
4、用指定的元素代替指定list中的所有元素
void fill(List list, Object obj)
1
5、统计元素出现次数
int frequency(Collection c, Object o)
1
6、统计target在list中第一次出现的索引,找不到则返回-1,类比int lastIndexOfSubList(List source, list target)
int indexOfSubList(List list, List target)
1
7、用新元素替换旧元素
boolean replaceAll(List list, Object oldVal, Object newVal)
1
三、同步控制
Collections 提供了多个synchronizedXxx()方法·,该方法可以将指定集合包装成线程同步的集合,从而解决多线程并发访问集合时的线程安全问题。
我们知道 HashSet,TreeSet,ArrayList,LinkedList,HashMap,TreeMap 都是线程不安全的。Collections 提供了多个静态方法可以把他们包装成线程同步的集合。
最好不要用下面这些方法,效率非常低,需要线程安全的集合类型时请考虑使用 JUC 包下的并发集合。
方法如下:
1、返回指定 collection 支持的同步(线程安全的)collection。
synchronizedCollection(Collection c)
1
2、返回指定列表支持的同步(线程安全的)List。
synchronizedList(List list)
1
3、返回由指定映射支持的同步(线程安全的)Map。
synchronizedMap(Map<K,V> m)
1
4、返回指定 set 支持的同步(线程安全的)set。
synchronizedSet(Set s)