Java集合常用方法
视频教程地址:
https://www.bilibili.com/video/BV1kt411v7gi/?p=1
List
List是有序、可重复的容器
有序:List中每个元素都有索引标记
可以根据元素的索引标记访问元素
可重复:List允许加入重复的元素
更准确的将,List允许慢速e1.equals(e2)的元素重复加入容器
List接口常用的实现类:
ArrayList
底层由数组实现
特点:查询效率高,增删效率低,线程不安全
一般使用它
LinkedList
LinkedList底层用双向链表实现存储。
特点:查询效率低,增删效率高,线程不安全
双向链表也叫双链表,是链表的一种,它的每个数据节点中都有两个指针,分别指向前一个节点和后一个节点。
所以,从双向链表中的任意一个节点开始,都可以很方便地找到所有节点。
每个节点都应该有3部分内容:
class Node {
Node previous; //前一个节点
Objedct element;//本节点保存的数据
Node next;//后一个节点
}
Vector
Vector底层是用数组实现的List,相关的方法都加了同步检查,因此“线程安全,效率低”
建议:
如何选用ArrayList、LinkedList、Vector?
需要线程安全时,用Vector
不存在线程安全问题,查找较多,用ArrayList
不存在线程安全问题,增删元素较多,用LinkedList
ArrayList<>()
常用方法
增删改查方法
List list = new ArrayList<>();
list.add(“1”);//添加元素
list.add(0,“2”);//指定位置添加元素
list.set(0,“高老二”);//设置元素
res = list.get(0);//获取指定位置元素
int i = list.indexOf(“1”);//返回元素的索引,如果list中不存在该元素,返回-1
i = list.lastIndexOf(“1”);//返回元素最后一次出现的位置
代码实例
public static void test04() {
List<String> list = new ArrayList<>();
list.add("1");//添加元素
list.add(0,"2");//指定位置添加元素
String res = list.get(0);
System.out.println(res);
list.set(0,"高老二");//设置元素
res = list.get(0);//获取指定位置元素
System.out.println(res);
int i = list.indexOf("1");//返回元素的索引,如果list中不存在该元素,返回-1
i = list.lastIndexOf("1");//返回元素最后一次出现的位置,
System.out.println(i);
}
集合运算方法
list01: [aa, bb, cc]
list02: [aa, dd, ee]
list01.addAll(list02);//把list02中的元素添加到list01中 list01: [aa, bb, cc, aa, dd, ee]
list01.removeAll(list02);//去除list01中与list02相同的元素 list01: [bb, cc]
list01.retainAll(list02);//去除两个集合共有的元素
list01: [aa]
list01.containsAll(list02)//判断list01中是否包含list02中所有元素
代码实例:
package cn.sxt.collection;
//测试Collection接口中的方法
public static void test02() {
List<String> list01 = new ArrayList<>();
list01.add("aa");
list01.add("bb");
list01.add("cc");
List<String> list02 = new ArrayList<>();
list02.add("aa");
list02.add("dd");
list02.add("ee");
System.out.println("list01: "+list01);
System.out.println("list02: "+list02);
// list01.addAll(list02);//把list02中的元素添加到list01中 list01: [aa, bb, cc, aa, dd, ee]
// list01.removeAll(list02);//去除list01中与list02相同的元素 list01: [bb, cc]
list01.retainAll(list02);//去除两个集合共有的元素 list01: [aa]
System.out.println(list01.containsAll(list02));//判断list01中是否包含list02中所有元素
System.out.println("list01: "+list01);
}
}
Map
Map(接口)用来存储“键-值”对 (key-value)
Map类存储的“键-值”对通过“键”来表示,所以“键”对象不能重复
Map的实现类有HashMap、TreeMap、HashTable、Properties等
常用方法
HashMap
常用方法
m1.put(key, value);//存对象
//Map中key不能重复
//是否重复根据equals方法比较
//如果重复,新的覆盖旧的
m1.get(key);//根据key取对象
m1.remove(key);//删除键值对
m1.size();//获取键值对的数量
boolean b = m1.isEmpty();//判断map是否为空
m1.clear();//清空map对象所有键值对
m1.putAll(map);//将map中的所有键值对加入m1中
m1.containsKey(key));//是否包含指定的key
m1.containsValue(value);//是否包含指定的value
HashMap底层实现采用了哈希表,这是一种非常重要的数据结构
哈希表的基本结构就是“数组+链表”
HashMap存储键值对过程
Entry对象,为单向链表结构,属性有key,value,hash(key的hash值),next(链表结构中指向下一个Entry对象)
jdk1.8把Entry[]改名为Node[]
HashMap中维护一个Node[]数组:Node[] table,默认初始大小为16
Entry[] table是HashMap的核心数组结构,也叫做位桶数组
哈希表的基本结构就是 数组+链表
put一对key-value->
key-value对象包装为一个Entry对象
根据key得到key的hashcode,key.hashcode()->
用散列算法,根据hashcode得到一个hash值(一个int数值,范围在0-table数组大小-1,例如,12)->
把Entry对象存入相应下标的Entry[]数组中(例如,存入Entry[12])->
如果下次根据key2计算得到的数组下标与之前相同,即该数组下标后面已经存放了一个Entry对象,则存到第一个key-value对象后面
因此根据key.hashcode()计算得到的Entry[]数组下标index越分散越好(散列算法),因此经常开发新的散列算法
i 一种极端简单低下算法:hashcode/hashcode
得到的hash总是1,意味着所有key-value对象都会存储到数组索引1的位置,每一个存储对象都会发生hash冲突,HashMap退化成一个链表
ii 一种简单常用算法(相除取余法):hash = hashcode%数组长度 效率低下
iii hash = hashcode&(数组长度-1) (按位与,要求数组长度为2的n次方,可以实现均匀的散列算法)
jdk1.8以后,当链表长度大于8时,链表转换为红黑树,大大提高查找的效率
HashMap取数据过程 get(key)
获得key的hashcode,通过hash()散列算法得到hash值,进而定位到数组的位置
在链表上挨个比较key对象,调用equals方法,将key对象和链表上所有节点的key对象进行比较,直到遇到返回true的节点对象为止
返回equals()为true的节点对象的value对象
java中规定,两个内容相同(equals()为true)的对象必须具有相等的hashcode。因为如果equals()为true而两个对象的hashcode不同,那在整个存储过程中就发生了悖论
扩容问题
HashMap的位桶数组,初试大小为16
实际使用时,大小是可变的
如果位桶数组中的元素达到(0.75*数组.length),就重新调整数组大小变为原来2倍大小
扩容本质是定义新的更大的数组,并将旧数组中的内容挨个拷贝到新数组中
扩容很耗时
JDK8将链表在大于8的情况下变为红黑二叉树,提高查找效率
HashMap与HashTable的区别
HashMap线程不安全, 效率高,允许key或value为null
HashTable线程安全,效率低,,不允许key或value为null
TreeMap
TreeMap是红黑二叉树的典型实现
TreeMap和HashMap实现了同样的接口Map,因此,用法对于调用者来说没有区别
HashMap效率高于TreeMap
在需要排序的Map时才选用TreeMap
如果采用自定义的类作为HashMap的key, 自定义的类需要实现Comparable接口, 覆写compareTo()方法
Set
无序,不可重复
无序:元素没有索引,只能遍历查找
不可重复:不允许加入重复的元素,更确切的讲,新元素如果和Set中某个元素通过equals()方法对比为true,则不能加入
常用子类:HashSet,TreeSet
HashSet
HashSet是采用哈希算法实现,底层实际是用HashMap实现的
HashSet本质就是一个简化版的HashMap,因此查询和增删效率都比较高
set.add(E element)方法实际添加的是HashMap中map.put(E element, new Object())
(把HashSet中的元素添加到HashMap中的key中,value用new Object()填充)
public HashSet() {
map = new HashMap<>();
}
...
private static final Object PRESENT = new Object();
...
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
TreeSet
TreeSet底层实际是使用TreeMap实现的, 通过key来存储Set的元素
TreeSet内部需要对存储的元素进行排序
因此, 对应的类需要实现Cmparable接口
Collection工具类
java.util.Collections提供了对Set, List, Map进行排序、填充、查找元素的辅助方法
void sort(List)//对List容器内的元素按照升序排序
void shuffle(List)//度List容器内的元素进行随机排列
void reverse(List)//对List容器内的元素进行逆序排列
void fill(List, Object)//用一个特定的对象重写整个List容器
int binarySearch(List, Object)//对于顺序的List容器,采用这般查找的方法查找特定对象