目录
泛型
即参数化类型
就是将类型参数化,在使用/调用时传入具体的类型
public class Demo<T,E> { //参数化类型
T name;
E age;
public void eat(T person){
System.out.println(age+"的"+person+"吃");
}
public static void main(String[] args) {
Demo<String,Integer> demo = new Demo<>(); //传入类型(只能是类类型),默认Object
demo.name = "张三";
demo.age = 18;
demo.eat(demo.name);
}
}
----------------------------------------------
18的张三吃
集合概述
放数据对象引用的容器 , 动态增长长度的容器
集合类存放的都是对象的引用,而非对象本身,出于表达上的便利,我们称集合中的对象就是指集合中对象的引用
Java的集合框架是由很多接口、抽象类、具体类组成的
集合类型主要有3种:set(集)、list(列表)和map(映射)
Collection
单列存储
List
- 有序集合,按照插入元素的顺序排列
- 可以存储重复元素
- 有索引
ArrayList
- 底层是数组
- 查询快,增删慢
- 线程不安全,效率高
add()底层实现
- 检测容器是否已满
- 未满将元素添加到末尾
- 满了就扩容
- 获取oldCapacity,原容积
- newCapacity为原来的1.5倍
- 使用
Array.copyof()
将之前数组元素复制到新数组中
//方法
boolean add(E e) 将指定的元素追加到此列表的末尾。
void add(int index, E element) 在此列表中的指定位置插入指定的元素。
E get(int index) 返回此列表中指定位置的元素。
int indexOf(Object o) 返回此列表中指定元素的第一次出现的索引,如果此列表不包含元素,则返回-1。
int lastIndexOf(Object o) 返回此列表中指定元素的最后一次出现的索引,如果此列表不包含元素,则返回-1。
E remove(int index) 删除该列表中指定位置的元素。
boolean remove(Object o) 从列表中删除指定元素的第一个出现(如果存在)。
protected void removeRange(int fromIndex, int toIndex) 从这个列表中删除所有索引在 fromIndex (含)和 toIndex之间的元素。
set(int index, E element) 用指定的元素替换此列表中指定位置的元素。
List<E> subList(int fromIndex, int toIndex) 从原集合中复制一段返回新集合,原集合不变
条件删除 removeIf
boolean removeIf(Predicate<? super E> filter) 删除满足给定谓词的此集合的所有元素。
ArrayList<String> arrayList = new ArrayList<>();
arrayList.add("a");
arrayList.add("b");
arrayList.add("b");
arrayList.add("c");
arrayList.removeIf(Predicate.isEqual("b")); //删除b元素
arrayList.removeIf(new Predicate<String>() { //new 的是一个匿名内部类,相当于一个外部类继承了Predicate<E>接口然后重写了方法
@Override
public boolean test(String s) {
return "a".equals(s)||"c".equals(s); //条件满足删除元素
}
});
System.out.println(arrayList); // []
自定义排序规则
arrayList.sort(String::compareTo);
arrayList.sort(new Comparator<String>() {//匿名内部类
@Override
public int compare(String o1, String o2) {
return o1.compareTo(o2); //指定规则
}
});
LinkedList
- 底层是双向链表
- 查询慢(通过序号从头或者尾查找,没有索引。按照索引是否大于一半来决定从左或者从右开始)
- 增删快 , 只需改变指针域 , 元素在内存中不发生变化
- 线程不安全,效率高
Vector
- 底层是数组
- 查询快;增删慢
- 线程安全(添加同步锁Synchronized) 对读写都加锁,效率低
- 扩容为原来的2倍
Set
不可以重复 , 没有索引
HashSet
- 底层是哈希表+链表+红黑树
- 无序
- 可存储null键
add() 操作原理
首先调用hashCode()方法计算哈希值,如果产生哈希冲突再调用equals()方法比较
我们自己写的Student类没有重写hashcode()和equals(),就会调用Object中的
“s”、String、Integer…这些类重写了hashCode()是根据对象中包含的内容来计算哈希值
/*
HashSet添加时如何判断值是否重复
添加时会调用hashCode(),equals()
添加时要比较内容是否相等,既要保证效率,又要保证安全
* 先调用hashCode()计算出一个哈希值,比较哈希值非常快,但是不安全
* 当哈希值相同时,再调用equals()方法比较
*/
int h1 = "通话".hashCode();
int h2 = "重地".hashCode();
System.out.println(h1+"::::"+h2);
System.out.println(h1==h2);
HashSet<String> set1 = new HashSet<>();
set1.add("a");
set1.add("通话");
set1.add("重地");
set1.add("a");
set1.add("c");
set1.add("b");
System.out.println(set1);
------------------------------------------
1179395::::1179395
true
[a, 通话, 重地, b, c]
TreeSet
- 底层是红黑树
- 有序:自然排序按照值编码的顺序排序;比较器排序继承Comparable接口,通过比较器返回的值来排序或去重
- 不允许放入null值
//自然排序
TreeSet<String> tset = new TreeSet<>();
tset.add("b");
tset.add("a");
System.out.println(tset); //[a,b]
//自定义排序
//继承Comparable<T>接口
//向TreeSet中添加元素时调用进行排序比较,以及去重
@Override
public int compareTo(Student o) {
//return this.age-o.age; // 小于0 ==0 大于0
return this.name.compareTo(o.name);
}
Iterator
迭代器在迭代期间可以从集合中移除元素
ArrayList<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
list.add(4);
list.add(1);
/*方式1:for循环 length (数组) length()(字符串) size()(集合)
循环时允许删除元素,但是我们需要注意索引与集合长度之间关系*/
for (int i = 0; i < list.size(); i++) {
if (list.get(i) == 1) {
list.remove(i);
i--;
}
}
System.out.println(list);
//方式2:增强for循环,循环时不允许从中删除元素 ConcurrentModificationException
for (Integer item : list) {
if (item == 1) {
list.remove(item);
}
System.out.println(item);
}
//方式3迭代器遍历
Iterator<Integer> it = list.iterator();
while (it.hasNext()) {
Integer item = it.next();
if (item == 1) {
it.remove();
}
}
System.out.println(list);
/*list.removeIf(item -> item == 1);*/
三种遍历方式
// listIterator() 只能用于List接口的集合
ListIterator<Integer> lit = list.listIterator();
while(lit.hasNext()){
if (lit.next()==2){ //在2后面加2
lit.add(2);
}
//lit.remove();
}
System.out.println(list);
//从指定的位置开始, list.size()从后向前遍历
ListIterator<Integer> lit1 = list.listIterator(list.size());
while(lit1.hasPrevious()){
System.out.println(lit1.previous());
}
//以流的形式输出遍历
Stream<Integer> s = list.stream();
s.forEach(new Consumer<Integer>() {
@Override
public void accept(Integer t) {
System.out.println(t);
}
});
//s.forEach(t->System.out.println(t));
Map
- Map中的集合,元素是成对存在的。每个元素由键与值两部分组成,通过键可以找对所对应的值
- 双列集合
- key(键不能重复) value(可以重复)
常用方法
V put(K key, V value) //插入
V remove(Object key) //删除
V get(Object key) //获取
boolean containsKey(Object key)//包含
boolean containsValue(Object value)
Collection<V> values() //返回此图中包含的值的Collection视图
遍历
//遍历方式1: KeySet()
Set<String> keySet = new HashSet<>(map.keySet());
for (String key : keySet) {
System.out.println("key:" + key + ",\tvalue:" + map.get(key));
}
//遍历方式2 entrySet()将map中底层存储键值的Entry对象,封装到一个Set集合中
Set<Map.Entry<String, String>> entrySet = map.entrySet();
for (Map.Entry<String, String> entry : entrySet) {
System.out.println(entry.getKey() + "::" + entry.getValue());
}
//方式3 forEach
map.forEach(new BiConsumer<String, String>() {
@Override
public void accept(String k, String v) {
System.out.println(k + ":::" + v);
}
});
HashMap
- 底层是哈希表+链表+红黑树
- 无序:根据哈希值排序
- 键值都可为null
- 哈希值的使用要重新计算hash值
put()底层原理
- 将键值对传递给put()方法时,它调用键对象的hashCode()方法来计算key的哈希值(数组下标)
- 遍历该数组下标下的链表,找到此次插入元素的key的原来位置的元素并返回(put()的返回值)
- 判断这个结构是链表还是树;红黑树就是用红黑树的插入方式
- (尾插法)一直遍历链表,判断到是最后一个元素,在尾部插入,并统计元素个数,大于8就转成红黑树
- 判断扩容
为什么用红黑树
-
链表:插入效率最高,查询效率最低
-
完全二叉树:插入效率最低,查询效率最高
-
红黑树:介于链表和完全二叉树之间,因为hasmap不仅需要增还要查
-
jdk8以后 , 做了优化 , 当链表长度为8时,哈希表长度大于64,自动转为红黑树;红黑树需要左旋、右旋、变色操作来保持平衡,当元素小于8时,链表可保持查询性能,元素多时,红黑树时间复杂度为logn,链表是n,所以用红黑树
为什么负载因子0.75
哈希表默认初始长度为16 (entry类型的数组,entry封装了元素<k,v>), 负载因子 0.75(到75%时扩容);对时间和空间的平衡分布,根据泊松分布,0.75性能最佳
为什么扩容两倍
每次处罚扩容机制时 , 扩容为原来的2倍;可以使用位运算代替取余操作,提高了性能
头插法&尾插法
(头插法)将新插入的元素的后继指向之前第一个位置的对象,将数组上的引用改为新插入元素的引用
(尾插法)一直遍历链表,判断到是最后一个元素,在尾部插入,并统计元素个数,大于8就转成红黑树
resize死循环
在JDK1.7当中,多线程同时put时,如果同时调用了resize操作,可能会导致循环链表产生,进而使得后面get的时候,会出现死循环。
比如有ABC这样一个链表。T1和T2线程同时进行扩容,T1和T2现在都指向A,他们的next都是B,T1开始扩容也就是节点转移,因为使用的是头插法,转移后节点排列就是CBA,也就是B指向A,T1的转移对于T2是不可知的,T2恢复执行后,A还是指向B,就形成了环,死循环就产生了。
JDK1.8使用尾插法就避免了链表的死循环。但链表转换为树或对树进行操作时仍会出现死循环。
解决hash冲突方案
拉链法
对于相同的hash值,使用链表进行连接
- 处理简单,处理一次就不会发生冲突
- 占用空间小
- 增删容易实现
- 会出现聚集现象(链表过长),导致查询效率低,所以使用hashMap红黑树,兼顾读写效率
再hash法
提供多个hash函数,如果第一个hash函数计算出的hash值冲突,使用其他的hash函数计算
- 不易聚集
- 增加了计算时间
建立公共溢出区
将hash表分为 基本表
和公共溢出区
,当和基本表冲突时,放入溢出区
开放定址法
当计算出的hash值发生冲突时,以计算出的hash值为基础,再产生一个hash值,直到不发生冲突;有一下计算方法
- 线性探测再散列:顺序找到下一个元素
- 平方探测再散列:在表的左右两侧进行跳跃式探测
删除节点麻烦,占用空间较大
TreeMap
- 底层是红黑树
- 有序
- key值所在类必须实现Comparable接口
- 键是红黑树结构,可以保证键的排序和唯一性
HashTable
- 底层是哈希表+链表(红黑树)
- 线程安全:put()方法被synchronized修饰,相当于把整个hash表锁住了,锁的力度较大,效率低,用于低并发可以
- key不能为null
- 对于哈希值计算直接使用对象的HashCode()
Collections类
集合类的工具类,与数组的工具类Arrays类似
static <T> boolean addAll(Collection<? super T> c, T... elements)
将所有指定的元素添加到指定的集合。
static <T> int binarySearch(List<? extends Comparable<? super T>> list, T key)
使用二叉搜索算法搜索指定对象的指定列表。
static <T extends Comparable<? super T>> void sort(List<T> list)
根据其元素的natural ordering对指定的列表进行排序。
static <T> void sort(List<T> list, Comparator<? super T> c)
根据指定的比较器引起的顺序对指定的列表进行排序。
static void swap(List<?> list, int i, int j)
交换指定列表中指定位置的元素。
static <T> void copy(List<? super T> dest, List<? extends T> src)
将所有元素从一个列表复制到另一个列表中。
static <T> List<T> emptyList()
返回空列表(immutable)。
static <T> void fill(List<? super T> list, T obj)
用指定的元素代替指定列表的所有元素。
static <T extends Object & Comparable<? super T>>T max(Collection<? extends T> coll)
根据其元素的 自然顺序返回给定集合的最大元素。
static <T extends Object & Comparable<? super T>>T min(Collection<? extends T> coll)
根据其元素的 自然顺序返回给定集合的最小元素。
static <T> boolean replaceAll(List<T> list, T oldVal, T newVal)
将列表中一个指定值的所有出现替换为另一个。
static void reverse(List<?> list)
反转指定列表中元素的顺序。
可变长度参数
//int...n : 可变长度的参数,本质是一个数组,一个参数列表只能有一个,必须放在参数列表的最后一个
public static void test(String a ,int...n){
System.out.println(a+n);
}
public static void main(String[] args) {
test("樊",0,1,2,3); //樊[I@1b6d3586
}