对象数组
- Java内存图解
- 内存图解
泛型
RandomAccess
- 该接口啥也没干,只是标识实现该接口的实现类具有快速随机访问功能,数组可以,链表不行
集合
1.由来
Java是面向对象语言,需要一个容器存储对象--->StringBuffer
但StringBuffer的结果是一个字符串--->对象数组
但对象数组的长度是固定的--->集合
2.数组和集合的差别
- 长度区别
数组长度固定
集合长度可变
- 内容不同
数组存的是同一类型的元素
集合可以存的不同类型的元素
- 元素的数据类型不同
数组可以存基本数据类型,也可存引用数据类型
集合只能存引用数据类型
3.集合注意事项
- 集合判空
使用isEmpty()而不是size==0,isEmpty()可读性更强且时间复杂度为O(1)
- 集合转Map
toMap(),注意value!=null,否则抛异常
- 集合遍历
注意并发修改异常,避免在遍历过程中使用add/remove,多线程情况下对Iteractor加锁
- 集合去重
利用Set唯一性,HashSet依赖contains(),contains又依赖HashMap的containsKey(),时间复杂度接近O(1)
List中contains()通过遍历所有元素完成,时间复杂度接近O(n)
- 集合转数组
toArray(T[] array),其中T为了标识返回类型,参数数组长度为0
- 数组转集合
Arrays.asList()只用于对象数组,且注意asList()返回的是Arrays的内部类,其并未重写add()/remove()/...方法
4.集合继承体系
Collection
1.功能概述
- 添加功能
boolean add(Object obj)
添加一个元素
boolean addAll(Collection c)
添加集合的所有元素到调用对象中(左)
- 删除功能
void clean()
移除所有元素
boolean remove(Object o)
移除一个元素
boolean removeAll(Collection c)
移除集合的全部or某些元素到调用对象中(左)
只有一个元素相同时也返回true,只要有一个被移除了就是移除
- 判断功能
boolean contains(Object o)
判断集合中是否包含指定元素
boolean containsAll(Collection c)
判断集合中是否包含指定的集合元素
只有包含所有的元素才true
boolean isEmpty()
判断集合是否为空
- 长度功能
int size()
元素的个数
面试题
数组有没有length()方法?
字符串有没有length()方法?
集合有没有length()方法?
- 交集功能
boolean retainAll(Collection c)
两个集合都有的元素保存到调用对象中(左)
假设有两个集合AB,A对B做交集即A.retainAll(B),最终的结果保存在A中,B不变
**返回值表示的是A是否发生过改变
- 将集合转换为数组
Object[] toArray()
- 获取功能(迭代器)
Iterator<E> iterator()
集合的专用的遍历方式,依赖集合而存在
方法
Object next():获取元素,并移动到下一个元素位置
即下次再使用此方法时,获取的是下一个元素
hasNext():判断是否存在下个元素
2.遍历集合
- 集合转数组
- 迭代器(集合专用遍历方式)
3.Iterator解析
Collection儿子
1.List
- 特点
有序(存储顺序和取出顺序一致)+ 可重复
- 特有方法
添加功能
void add(int index,Object element)
指定位置添加元素,在index上加该元素
注意index不要越界[0,size]
三个元素-->index[0,3]
删除功能
Object remove(int index)
根据索引删除元素,返回被删除的元素
注意index不要越界
获取功能
Object get(int index)
获取指定位置的元素,注意index不要越界
修改功能
Object set(int index,Object element)
根据索引修改元素,返回被修改的元素
注意index不要越界
列表迭代器
ListIterator listIterator()
List集合特有的迭代器,继承自Iterator
特有成员方法
Object previous()
获取上一个元素
boolean hasPrevious()
判断是否有元素
注意:ListIterator可以实现逆向遍历,但必须先正向遍历,才可逆向遍历,所以一般无意义不不使用
但它的方法比Iterator多,且Iterator不可以实现逆序
- 遍历
2.迭代器并发修改异常
- ConcurrentModificationException
- 产生原因
迭代器是依赖于集合而存在的,在判断成功后,集合的中新添加了元素,而迭代器却不知道,所以就报错了,这个错叫并发修改异常
问题描述:迭代器遍历元素的时候,通过集合是不能修改元素的
- 解决办法
2.Set
- 特点
无序(存储和取出的元素不一致)+唯一(不可重复)
虽然无序,但他有他自己的顺序,注意输出顺序正好与其输出顺序一致的情况
List儿子
1.ArrayList
- 特点
1.底层结构是对象数组,查询快,增删慢
2.线程不安全(不同步),效率高
3.1.5倍扩容
- 方法
add(T t)
get(int index)
remove(int index)
isEmpty()
clear()
size()
- toString()源码解析
- 数组转ArrayList
1.new ArrayList(Arrays.asList("a","b","c"))
2.引用类型流:Arrays.stream(myArray).collect(Collectors.toList())
3.基本类型流:Arrays.stream(myArray).boxed().collect(Collectors.toList())
2.Vector
- 特点
1.底层结构是数组,查询快,增删慢
2.线程安全(同步),效率低
3.2倍扩容
- 方法
添加功能
public void addElement(Object obj)——add()替代
获取功能
public Object elementAt(int index)——get()替代
public Enumeration elements()——Iterator iterator()替代
boolean hasMoreElements()——hasNext()替代
Object nextElement()——next()替代
3.LinkedList
- 特点
1.底层结构是双向链表,查询慢,增删快
2.线程不安全,效率高
- 方法
添加功能
public void addFirst(Object e)
public void addLast(Object e)
获取功能
public Object getFirst()
public Object getLast()
删除功能
public Object removeFirst()
public Object removeLast()
4.思考题
Set儿子
1.HashSet
- 特点
无序唯一
底层HashMap
线程不安全
- 遍历
不可以使用for循环(无序),但增强for循环可以
- add()源码解析—>保证唯一性原理
2.LinkedHashSet
- 底层LinkedHashMap
- 线程不安全
3.TreeSet
- 作用
对元素按某种规则排序,根据构造方法选择哪种排序
- 特点
1.保证元素排序和唯一性(有序唯一)
2.底层数据结构:红黑树(自平衡二叉树)
3.线程不安全
- 排序方式
1.自然排序:元素具备比较性
TreeSet<Student> ts = new TreeSet<Student>();
public class Student implements Comparable<Student> {
@Override
public int compareTo(Student s) {
// 根据自定义排序规则
// 主要条件
int num = this.age - s.age;
// 次要条件 num=0表示年龄相同
int num2 = num == 0 ? this.name.compareTo(s.name) : num;
return num2;
}
}
2.比较器排序:集合具备比较性
TreeSet<Student> ts = new TreeSet<Student>(new MyComparator());
public class MyComparator implements Comparator<Student> {
@Override
public int compare(Student s1, Student s2) {
// 根据自定义排序规则this -- s1 s -- s2
// 主要条件
int num = s1.getAge() - s2.getAge();
// 次要条件 num=0表示年龄相同
int num2 = num == 0 ? s1.getName().compareTo(s2.getName()) : num;
return num2;
}】
}
- add()源码解析—>保证元素顺序+唯一原理
- compareTo重写规则
HashSet儿子
1.LinkedHashSet
- 特点
1.元素有序唯一
2.由链表保证元素有序
3.由哈希表保证元素唯一
Map<k,v>
- 键值对:键不能重复,一个键(k)对应一个值,值(v)可以重复
1.功能概述
- 添加功能
V put(K key,V value)
添加元素
如果键是第一次存储,就直接存储元素,返回null
如果键不是第一次存在,就用值把以前的值替换掉,返回以前的值
这个其实还有另一个功能?先不告诉你,等会讲
- 删除功能
void clear()
移除所有的键值对元素
V remove(Object key)
根据键删除键值对元素,并返回值
- 判断功能
boolean containsKey(Object key)
判断集合是否包含指定的键
boolean containsValue(Object value)
判断集合是否包含指定的值
boolean isEmpty()
判断集合是否为空
- 长度功能
int size()
返回集合中的键值对的对数
- 获取功能
Set<Map.Entry<K,V>> entrySet()
???
V get(Object key)
根据键获取值,没有就返回null
Set<K> keySet()
获取集合中所有键的集合
Collection<V> values()
获取集合中所有值的集合
2.遍历键值对
- 根据键找值
A:获取所有的键
B:遍历键的集合,获取得到每一个键
C:根据键去找值
--循环中有使用map.get()寻找值,加大时间复杂度,entrySet性能更佳
- 根据键值对对象找键和值
A:获取所有键值对对象的集合
B:遍历键值对对象的集合,得到每一个键值对对象
C:根据键值对对象获取键和值
Map儿子
1.HashMap
- 线程不安全,效率高
- 其键值都可以为null
- 子类依靠hashCode(),equals()找出重复元素(注意要重写这两个方法)
- 底层
1.底层有哈希表实现,key的hashCode通过扰动函数获取hash值,通过(n-1)&&hash判断元素存储位置,若当前存在元素则比较key,key相同则覆盖,不同则拉链
2.扰动函数:HashMap的hash(),减少哈希冲突,扰动实际是无符号右移,JDK8以前有4次扰动,之后只有一次,性能更佳
3.解决哈希冲突:若链表长度大于某阈值(默认8),调用treeifyBin()判断数组长度n,若n<64对数组2倍扩容,否则将链表转为红黑树
4.HashMap的长度为2的整数次幂,因为hash值范围加起来有40亿映射空间,内存存不下,所以hash值不能直接使用,要经过(n-1)&&hash
5.HashMap多线程导致死循环,因为并发下的Rehash造成循环链表,并发下建议使用ConcurrentHashMap
2.TreeMap
- 键底层数据结构:红黑树(自平衡二叉树)
- 保证键排序和唯一性
- 排序:重写compareTo()
3.HashTable
- 线程安全,效率低
- 键值都不可以为null
- 特有方法
load()
从文件输入流中加载配置信息
store()
保存到文件输出流中
getProperty()
setProperty(key,value)
添加键值
HashMap儿子
1.LinkedHashMap
- 特点
1.键有序唯一
2.由链表保证键有序(存储和取出的顺序一致)
3.由哈希表保证键唯一
综上案例
1.统计字符串中字符出现的个数
- 分析
- 代码
// 定义一个字符串(可以改进为键盘录入)
Scanner sc = new Scanner(System.in);
System.out.println("请输入一个字符串:");
String line = sc.nextLine();
// 定义一个TreeMap集合
TreeMap<Character, Integer> tm = new TreeMap<Character, Integer>();
//把字符串转换为字符数组
char[] chs = line.toCharArray();
//遍历字符数组,得到每一个字符
for(char ch : chs){
//拿刚才得到的字符作为键到集合中去找值,看返回值
Integer i = tm.get(ch);
//是null:说明该键不存在,就把该字符作为键,1作为值存储
if(i == null){
tm.put(ch, 1);
}else {
//不是null:说明该键存在,就把值加1,然后重写存储该键和值
i++;
tm.put(ch,i);
}
}
//定义字符串缓冲区变量
StringBuilder sb= new StringBuilder();
//遍历集合,得到键和值,进行按照要求拼接
Set<Character> set = tm.keySet();
for(Character key : set){
Integer value = tm.get(key);
sb.append(key).append("(").append(value).append(")");
}
//把字符串缓冲区转换为字符串输出
String result = sb.toString();
System.out.println("result:"+result);
2.模拟斗地主洗牌、发牌并对发好的牌排序
-
分析
-
代码
public static void main(String[] args) {
// 创建一个HashMap集合
HashMap<Integer, String> hm = new HashMap<Integer, String>();
// 创建一个ArrayList集合
ArrayList<Integer> array = new ArrayList<Integer>();
// 创建花色数组和点数数组
// 定义一个花色数组
String[] colors = { "♠", "♥", "♣", "♦" };
// 定义一个点数数组
String[] numbers = { "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q",
"K", "A", "2", };
// 从0开始往HashMap里面存储编号,并存储对应的牌,同时往ArrayList里面存储编号即可。
int index = 0;
for (String number : numbers) {
for (String color : colors) {
String poker = color.concat(number);
hm.put(index, poker);
array.add(index);
index++;
}
}
hm.put(index, "小王");
array.add(index);
index++;
hm.put(index, "大王");
array.add(index);
// 洗牌(洗的是编号)
Collections.shuffle(array);
// 发牌(发的也是编号,为了保证编号是排序的,就创建TreeSet集合接收)
TreeSet<Integer> fengQingYang = new TreeSet<Integer>();
TreeSet<Integer> linQingXia = new TreeSet<Integer>();
TreeSet<Integer> liuYi = new TreeSet<Integer>();
TreeSet<Integer> diPai = new TreeSet<Integer>();
for (int x = 0; x < array.size(); x++) {
if (x >= array.size() - 3) {
diPai.add(array.get(x));
} else if (x % 3 == 0) {
fengQingYang.add(array.get(x));
} else if (x % 3 == 1) {
linQingXia.add(array.get(x));
} else if (x % 3 == 2) {
liuYi.add(array.get(x));
}
}
// 看牌(遍历TreeSet集合,获取编号,到HashMap集合找对应的牌)
lookPoker("风清扬", fengQingYang, hm);
lookPoker("林青霞", linQingXia, hm);
lookPoker("刘意", liuYi, hm);
lookPoker("底牌", diPai, hm);
}
// 写看牌的功能
public static void lookPoker(String name, TreeSet<Integer> ts,
HashMap<Integer, String> hm) {
System.out.print(name + "的牌是:");
for (Integer key : ts) {
String value = hm.get(key);
System.out.print(value + " ");
}
System.out.println();
}
Collections
- 针对集合进行操作的工具类,都是静态方法
1.面试题
2.成员方法
public static <T> void sort(List<T> list)
排序
默认情况下是自然顺序 Collections.sort(list);
比较器排序 Collections.sort(list, new Comparator<Student>() {
@Override
public int compare(Student s1, Student s2) {
...
}
});
public static <T> int binarySearch(List<?> list,T key)
二分查找
public static <T> T max(Collection<?> coll)
最大值
public static void reverse(List<?> list)
反转
public static void shuffle(List<?> list)
随机置换顺序
面试题
1.Queue与Deque区别
- Queue:单端队列,只能在一端插入和删除
- Deque:双端队列
2.ArrayDeque和LinkedList区别
- ArrayDeque:基于数组和双指针,不支持null,支持扩容
- LinkedList:基于链表,支持null,不支持扩容,每次插入都要申请新空间,性能低下
3.Priority特点
- 基于二叉堆(默认小顶堆)数据结构,可变长度数组存储数据
- 线程不安全,不支持null对象
4.ConcurrentHashMap与HashTable保证线程安全区别
- ConcurrentHashMap底层使用可变长度数组与链表,将整个数组分段,每段一把锁,访问不同数据段时不涉及锁竞争,提高并发量,改进后使用Node+链表/红黑树,并发控制采用synchronized+CAS
- Hashtable底层只采用一把锁,效率低下
5.ConcurrentHashMap底层具体实现
- 原来底层采用Segment数据与HashEntry数组
Segment继承自ReentrantLock(可重入锁)
一个ConcurrentHashMap含多个Segment(分段数组)
Segment数组底层结构与HashMap相似(键,值)
Segment不可扩容,默认16,即最多支持16个线程并发
----------------------------------------------
HashEntry是链表中的元素节点
一个一个Segment含多个HashEntry
对HashEntry的修改操作前需要获取Segment锁
HashEntry可扩容
- 之后取消Segment部分,改用synchronized+CAS
只锁定当前链表或红黑树的头节点,认为不存在哈希冲突就不存在并发