文章目录
集合
特点
可以存储多个数据而且长度可以改变的容器
Collection:包含子集合List,Set、Queue
三种常用集合
List
特点
- List集合的特点:存入数据有序,可重复
常用实现类
- ArrayList:线程不安全
- Vector:线程安全
- LinkedList:线程不安全
- Stack:线程安全
Set
特点
- Set集合的特点:存入数据无序,不可重复
常用实现类
- HashSet:线程不同步,效率高
- TreeSet:自动排序
Map
特点
- Map集合的特点:使用key=value的形式存储数据(JavaEE的常用工具类的数据结构)
常用实现类
HashMap:使用key=value的形式存储数据
栈内存:多数用来做计算
泛型<E>
参数化类型
<E>
—泛型,用于指定集合中元素的类型
泛型指定的集合中的元素类型只能是引用数据类型
- Integer、Double、Character、String…………
特点
- 泛型是JDK1.5的新特性
- 书写形式:
List<String> list = new ArrayList<>();
- JDK1.7开始出现
- 泛型可以接受所有引用类型,当指定了泛型的类型,后续操作都是这个类型,就不能接受其他类型:泛型的擦除(发生在编译期)
- 泛型的上下限(规定接收类型的范围):
<? extends 类/接口>
: 泛型的上限- 表示可以接收当前【类/接口】的【子类/子接口/实现类】
<? super String>
:泛型的下限- 表示可以接收当前【类/接口】的【父类/父接口】
List
ArrayList
ArrayList集合的特点:
-
存入数据有序,可重复
-
查询效率高;增加,删除效率低
-
线程不安全
-
内存连续
-
底层由数组实现,默认初始容量为10,底层根据右移运算来扩容在原有大小的基础上增加一半
创建
List<String> list = new ArrayList<String>();
添加add()
add();
list.add("String");
list.add("absd");
list.add("badsf");
list.add(1, "张三");
遍历get()
增强for或者普通for
list.get(index);
------获取索引index处的元素
//方法一
for(String str:list) {
System.out.println(str);
}
//方法二
for(int i = 0;i<list.size();i++) {
System.out.println(list.get(i));
}
使用迭代器遍历iterator()
iterator()
迭代器底层通过指针的挪动来遍历集合元素
遍历期间不能增删原集合
public static void test2(){
List<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
}
修改set()
set(int index, String str);
------在索引为index处添加str元素
//修改
list.set(2, "admin");
删除remove()
remove(int index);
------删除索引为index的元素
remove(String str);
------删除第一次出现str的元素
//删除
list.remove(0);
list.remove("badsf");
清空集合clear()
clear();
:清空该集合(格式化)
List<String> list = new ArrayList<>();
list.add("1");
list.add("2");
System.out.println(list);//[1,2]
list.clear();
System.out.println(list);//[]
判断是否包含某元素contains()
判断指定的数据是否包含在集合中
//创建一个集合对象
List<String> list = new ArrayList<>();
list.add("1");
list.add("2");
System.out.println(list.contains("1"));//true
判断是否为空集合isEmpty()
isEmpty()
//创建一个集合对象
List<String> list = new ArrayList<>();
list.add("1");
System.out.println(list.isEmpty());//false
查询指定元素的下标indexOf()
返回指定数据在集合中第一次出现的下标
//创建一个集合对象
List<String> list = new ArrayList<>();
list.add("1");
list.add("2");
list.add("2");
System.out.println(list.indexOf("2"));//1
集合大小size()
返回集合的长度,大小
List<String> list = new ArrayList<>();
list.add("1");
System.out.println(list.size());//1
截取子列表subList()
[ 0,3) 截取子列表
//创建一个集合对象
List<String> list = new ArrayList<>();
list.add("1");
list.add("2");
list.add("3");
list.add("4");
System.out.println(list.subList(0,3));//[1,2,3]
集合转成数组toArray(T[] a)
list.toArray(new 类型[0]);
括号里声明转换数组的数据类型
//怎么把list集合转换成数组
String[] strArray = list.toArray(new String[0]);
把数组转成集合Arrays.asList()
Arrays.asList(数组名);
//把数组转换成集合
String[] strs = {
"we","are","students"};
List<String> strsList = Arrays.asList(strs);
排序
Collections.sort(list);
public static void test2() {
//给ArrayList的元素排序
List<String> list = new ArrayList<>();
list.add("a");
list.add("c");
list.add("b");
//给list集合排序
Collections.sort(list);
for(String str:list) {
System.out.println(str);
}
}
对象集合的排序
Collections.sort(list);
自定义类要实现Comparable接口,重写compareTo方法
升序:
this.age-o.age
降序:
o.age-this.age
(或者是-(this.age-o.age)
)
this.age - o.age
= 0 表示相等
@Override
public int compareTo(Student o) {
//从小到大 this.age-o.age
//this.age-o.age=0;表示两个对象相等
return this.age-o.age;
}
LinkedList
使用链表的形式
特点
- 底层基于—Node内部类:节点 实现;通过节点之间的地址值的指向来维系节点
- 增删速度比ArrayList快,查询比较慢
- 线程不安全
- 内存不连续,不需要扩容
链表添加元素
如果是头节点:
this.first = node;
this.last = node;
如果不是头节点:
this.last.next=node;
新节点指向原尾节点的下一个节点node.prev=this.last;
原尾节点指向新节点的删一个节点
//添加
public void add(String str){
//新建节点
Node node=new Node(str, null, null);
//判断是头部添加还是尾部添加
if(size0){
//新节点指向头节点
this.first=node;
}else{
//新节点指向原尾节点的下一个节点
this.last.next=node;
//原尾节点指向新节点的删一个节点
node.prev=this.last;
}
//新节点指向尾节点
this.last=node;
//
size++;
}
链表的删除
删除头节点:
this.first.next.prev = null;
this.first = this.fitst.next;
删除尾节点:
this.last.prev.next = null;
this.last = this.last.prev;
删除中间节点:先获取节点并通过变量(no)保存
no.next.prev = no.prev;
no.prev.next = no.next;
图片
//根据下标进行删除
public void remove(int index){
//下标越界
out(index);
//判断删除的位置
if(index0){
//删除头节点
//原头节点的下一个节点的上一个节点为null
this.first.next.prev=null;
//原头节点的下一个节点指向头节点
this.first=this.first.next;
}else if(indexsize-1){
//尾节点
//原尾节点的上一个节点的下一个节点为null
this.last.prev.next=null;
//原尾节点的上一个节点指向尾节点
this.last=this.last.prev;
}else{
//删除中间元素
//找到index下标对应的节点
Node no=getNode(index);
//no节点的上一个节点指向no结点下一个节点的上一个节点
no.next.prev=no.prev;
//no下一个节点指向no上一个节点的下一个
no.prev.next=no.next;
}
size--;
}
链表的内存图
面试
ArrayList和Vector:线程安全性的不同
ArrayList和LinkedList:查询,删除和添加的效率不一样
-
思考题:
增删和查询相当,ArrayList和LinkedList消耗的时间几乎相同,决定两个集合效率高低的是占用的空间,因为
Vector
向量
特点
-
底层基于数组实现;默认初始容量:10
- 默认的扩容是根据底层的三目运算符扩容为原来的两倍
- 如果指定了增量大小,每次扩容在原来的基础上加上增量值
-
最早的集合类
-
线程安全
构造
Vector(int initialCapacity, int capacityIncrement)
initialCapacity
:初始容量
capacityIncrement
:容量增量使用指定的初始容量和容量增量构造一个空的向量。
Vector<String> vector = new Vector<>(10,5);
for (int i = 0; i < 11; i++) {
vector.add("a");
}
System.out.println(vector.capacity());//15,每次扩容5个容量
方法
当前容量capacity()
返回此向量的当前容量。
容量翻2倍增加
如果使用有参构造,自定义增量,每次扩容自定容量
Vector<String> vector = new Vector<>();
for (int i = 0; i < 11; i++) {
vector.add("a");
}
System.out.println(vector.capacity());
//20,默认每次增加上一次相同的容量
//10+10
//20+20
//40+40
返回最早的迭代器,遍历集合elements()
Enumeration接口,最早的迭代器
只有vector能用
Vector<String> vector = new Vector<>(10,5);
for (int i = 0; i < 11; i++) {
vector.add(String.valueOf(i));
}
Enumeration<String> elements = vector.elements();
while (elements.hasMoreElements()) {
System.out.print(elements.nextElement()+"\t");
}
//0 1 2 3 4 5 6 7 8 9 10
返回迭代器:iterator()
Iterator接口,新版迭代器
所有的集合都可以使用
Iterator<String> iterator = vector.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
Stack栈结构
入栈/压栈:向栈中添加元素
出栈/弹栈:从栈中获取元素
栈顶元素:最后存放的元素
栈底元素:第一个存放的元素
特点
- 遵循先进后出(FILO)原则
- 继承Vector(和Vector的特点一致)
方法
测试堆栈是否为空empty()
public static void test2(){
Stack<String> s = new Stack<>();
s.push("abc");
s.push("ac");
System.out.println(s.empty());//false
}
获取栈顶元素peek()
并不将他们从栈中删除
public static void test2(){
Stack<String> s = new Stack<>();
s.push("abc");
s.push("ac");
System.out.println(s.peek());//ac
}
获取栈顶元素并删除pop()
public static void test2(){
Stack<String> s = new Stack<>();
s.push("abc");
s.push("ac");
System.out.println(s.pop());
//ac,并将ac从占栈中删除
}
查找元素位置search(Object o)
从栈顶开始查找,从1开始计数
public static void test2(){
Stack<String> s = new Stack<>();
s.push("abc");
s.push("ac");
s.push("bc");
s.push("c");
System.out.println(s.search("ac"));//3
}
Set
HashSet
底层由HashMap实现;HashMap底层由数组+链表实现数据存储
底层默认长度为16的数组,每个元素空间称之为:桶(bucket)
存储:
先获取对象的哈希码值,再二次运算保证结果落在某个桶中
用新的对象和对应桶上的所有对象一次进行equals比较,如果有相等的就把新的对象舍弃掉,如果都不想等就放在当前桶中所有对象的最前面—链式栈结构(单链表)
扩容:
- 如果 【已经使用的桶数/总桶数 >加载因子(0.75)】,默认就需要扩容为原来的2倍
- 对元素重新二次计算(高低位运算),重新分布,保证数据散列分布,该过程称为:rehash
加载因子:
- 加载因子越大,导致大量的元素对象存储在某些链上,导致链的长度过长,降低了对元素查询的效率
- 加载因子越小,导致频繁的扩容和rehash操作,浪费大量内存空间
JDK1.8中改良:
- 在某一桶上的链式结构中,元素超过8个,会自动转换数据结构,以红黑二叉树存储(红黑树的查找效率最高)。
- 新元素添加到最后
内存图详解:
特点
- 无序的(不能保证迭代顺序),没有索引
- 底层基于数组+链表实现
- 加载因子
- 从JDK1.8开始如果链表长度大于8就会扭转成二叉树
删除,获取不能通过索引操作
没有修改的概念
线程不同步,效率高
方法和List的基本一致
TreeSet
特点
- 默认对存储的内容排序:升序(自然排序)
- 底层使用二叉树实现
- 自定义存储对象的排序,实现Comparable接口,重写comparTo方法,参考
LinkedHashSet(不是重点)
特点
- 采用hash存储,并用双向循环链表数据结构
- 内部是LinkedHashMap
Queue队列结构
队列
队头元素:第一个存放到队列的元素
队尾元素:最后一个存放到队列的元素
特点
- 遵循先进先出(FIFO)的原则
方法
获取队头但不删除element()
没有队头元素就会报错:
NoSuchElementException
获取队头但不删除peek()
没有队头元素就会返回
null
方法预览
Throws exception | Returns special value | |
---|---|---|
Insert | add(e) | offer(e) |
Remove | remove() | poll() |
Examine | element() | peek() |
映射Map:不是集合
映射
映射不是集合,但是java collections FrameWork(java集合类框架)的成员;同样的:Map、Collection、Iterator、Arrays、Collections…………
- 可以存储多个有关系的一对数据的容器
- 一个键对应一个值,键不能重复,值可以重复,映射由多个键和值组成
- K:键 V:值;泛型的指定使映射中的元素只能是引用数据类型
- 因为映射存储的是一对对数据,为了方便表示与操作这些数据,可以把这些数据抽取成一个类(Entry),Entry产生的对象是具体的一对数据(键值对),多个映射也是由多个键值对组成
比较
Hashtable :不允许null键null值,线程同步,线程安全
HashMap : 允许null键null值,线程不同步,线程不安全。
HashMap
特点
- 底层由数组+链表实现数据存储
- HashMap : 允许null键null值
- 默认初始容量为16(数组长度),默认的加载因子(是使用桶数/总桶数)为0.75;
- 可以指定初始容量,如果初始容量在2n<指定容量<(2n+1)之间,结果最终的数组长度就是2n+1
- 大于0.75后,默认扩容增加一倍;扩容后需要进行rehash
- 链表长度大于8个在JDK1.8就开始转成二叉树
- 异步式线程不安全
创建
可以指定长度,但是最后的长度会经过一系列计算,转换为2的整数次幂的长度
Map<Integer,String> map = new HashMap<>();
添加、修改
put(key,value)
map.put(1, "张三");
map.put(2, "李四");
map.put(3, "wangwu");
删除
remove(key)
:根据key删除
remove(key,value)
:根据key,value删除
map.remove(3);
map.remove(3,"wangwu");
查询是否包含Key,Value
containsKey(Key)
:存在Key返回true
containsValue(Value)
:存在Value返回true
System.out.println(map.containsKey("a"));
System.out.println(map.containsValue(1));
获取值
get(key)
当获取一个不存在的key-value时,返回null
String valueby1 = map.get(1);
String valueby2 = map.get(2);
String valueby3 = map.get(3);
返回所有value
values()
:把所有的value存放到collection集合中
public static void main(String[] args) {
Map<String, Integer> map = new HashMap<>();
map.put("a", 1);
map.put("12a", 6);
map.put("a12", 4);
map.put("7658a", 2);
Collection<Integer> values = map.values();
System.out.println(values);//[6, 1, 4, 2]
}
遍历
keySet()
循环获取map集合中的数据
Set<E> keys = map.keySet();
//使用循环获取map集合中的数据
public static void test2() {
Map<String,Object> map = new HashMap<>();
//设置key,value
map.put("name", "张三");
map.put("pw", "123456");
map.put("age", "20");
map.put("phone", "17603393007");
map.put("Email", "[email protected]");
//获取map集合中所有的key
Set<String> keys = map.keySet();
for(String key:keys) {
System.out.println(key+"="+map.get(key));
}
}
entrySet()
通过entrySet获取Map中的数据
//通过entrySet获取map中的数据
public static void test3() {
Map<String,Object> map = new HashMap<>();
//设置key,value
map.put("name", "张三");
map.put("pw", "123456");
map.put("age", "20");
map.put("phone", "17603393007");
map.put("Email", "[email protected]");
Set<Map.Entry<String, Object>> set = map.entrySet();
for(Map.Entry<String, Object> entry:set) {
System.out.println(entry.getKey()+"="+entry.getValue());
}
//第二种简单写法;两步合一步
for (Map.Entry<String, Integer> i : map.entrySet()){
System.out.println(i.getKey() + ":" + i.getValue());
}
}
computeIfAbsent
Map<String,Object> locks = new HashMap<>();
Object o = locks.computeIfAbsent(userId, k -> new Object());
判断key为userId的Object是否存在,存在返回,不存在向集合中添加一个新的Object
Hashtable
最早的映射类
特点
-
最早的映射类
-
底层基于数组+链表实现存储
-
Hashtable :不允许null键null值,线程同步
-
默认初始容量为11,默认加载因子为0.75
-
初始容量指定多少就是多少
-
默认的扩容是增加一倍再加一【11----> 11*2+1】
-
同步式线程安全
Iterator
特点
-
底层通过指针的挪动来遍历集合元素
-
遍历期间不能增删原集合
-
Iterable接口里定义了可以返回Iterator接口的方法
iterator()
-
只要实现了Iterable接口的集合,就可以使用foreach遍历
- 增强for循环底层使用迭代器实现(JDK1.5新特性)
使用迭代器遍历iterator()
iterator()
迭代器
public static void test2(){
List<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
}
remove()
方法
把当前指向的元素删除
是根据在遍历期间改变标记值来简介删除原集合元素
如果直接操作集合的
remove()
方法,
- 遍历期间改变原集合,遍历结束时会根据标记值和原集合比较
- 就会报错,无法继续迭代
public static void test2(){
List<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String str = iterator.next();
//是根据在遍历期间改变标记值来简介删除原集合元素
iterator.remove();//把当前指向的元素删除
System.out.println(str);
}
System.out.println(list);//[]
}
Comparator比较器
函数式接口
list.sort(Comparator c)
Comparator
:比较器返回值>0 表面前面对象大于后面对象
返回值<0 表明前面对象小于后面对象
返回值=0 表明前面对象等于后面对象
匿名内部类使用
public static void test1(){
List<String> list = new ArrayList<>();
list.add("abc");
list.add("dc");
list.add("cc");
list.add("ab");
list.sort(new Comparator<String>() {
//重写方法-指定比较规则
@Override
public int compare(String o1, String o2) {
/**
* 返回值>0 表面前面对象大于后面对象
* 返回值<0 表明前面对象小于后面对象
* 返回值=0 表明前面对象等于后面对象
*/
int i = o1.compareTo(o2);
return i;
}
});
System.out.println(list);
}
λ表达式
list.sort(
(str1,str2)->str1.charAt(0)-str2.charAt(0)
);
对象自定义排序
使用
自定义类要实现Comparable接口,重写compareTo方法
升序:
this.age-o.age
降序:
o.age-this.age
(或者是-(this.age-o.age)
)
this.age - o.age
= 0 表示相等
public class Student implement Comperable<Student>{
.......
}
@Override
public int compareTo(Student o) {
//从小到大 this.age-o.age
//this.age-o.age=0;表示两个对象相等
return this.age-o.age;
}