java 集合相关
1 集合框架概述
1.1 出现的背景:
一方面,面向对象语言对事物的体现都是以对象的形式,为了方便对多个对象进行操作,就要对对象进行存储的容器。另外一方面,使用Array来存储对象方面具有一些弊端,,一旦创建了,数组的长度就不可以发生变化,而java集合就像一种容器,可以动态的对集合的大小进行扩充。java集合类可以用于存储数量不等的多个对象,还可以用于保存具有映射关系的关联数组。集合,数组是对多个数据进行存储操作的结构,简称Java容器。
说明:此时的存储,主要是指内存层面的存储,不涉及持久化存储。
2 数组存储
- 数组内存存储方面的特点:
- 数组初始化后,长度就确定了。
- 数组声明的类型,就决定了进行元素初始化时的类型,即数组元素的类型就已经决定了。
- 数组在存储方面的弊端
- 数组初始化之后,长度就不变了,不便于扩展。
- 数组中提供的属性方法少,不便于添加,删除,插入等操作,且效率比较低,无法直接获取存储元素的个数(实际元素的个数)。
- 存储特点单一,数组存储的数据是有序的,可以重复的。对于有些特殊的需求不能满足。
3 java集合
- Collection接口:单列数据,定义了存取一组对象的方法的集合
- list:元素有序,可重复的集合。
- set:元素无序,不可重复的集合。
- Map接口:双列数据,保存具有映射关系的“key-value对”的集合,可以根据key来获取value,但是一个key不能对应多个value。
2 Collection接口
2.1 Collection接口概述
2.1.1 常见方法
- 添加
- add(Object obj):将元素添加到集合中,如果是一个集合则会将这个集合当成一个整体。
- addAll(Collection coll):如果添加的是一个集合,将参数集合中的元素逐个添加到当前集合。
- 获取有效元素的个数
- int size():获取有效元素的个数。
- 清空集合
- void clear():将集合中的元素指向null,size设置为0
- 是否是空集合
- boolean isEmpty():判断当前的size是否为0
- 是否包含某个元素
- boolean contains(Object obj):是通过元素的equals方法来判断是否是同一个对象,在判断的时候是逐个进行对比。
- boolean containsAll(Collection c):也是调用元素的equals方法来比较的。拿两个集合的元素逐个比较,要求形参集合中的元素全部都在当前的集合中则返回true。
- 删除
- boolean remove(Object obj) :通过元素的equals方法判断是否是要删除的那个元素。只会删除找到的第一个元素
- boolean removeAll(Collection coll):取当前集合的差集
- 取两个集合的交集
- boolean retainAll(Collection c):把交集的结果存在当前集合中,不影响c集合。
- 是否相等
- boolean equals(Object obj):需要判断元素是否一一对应相等。
- 转成对象数组
- Object[] toArray()
- 获取集合对象的哈希值
- hashCode()
- 遍历
-
iterator():返回迭代器对象,用于集合遍历。不是容器,只是一个迭代器。
说明:Iterator对象称为迭代器(设计模式的一种),主要用于遍历Collection集合中的元素。GOF给迭代器模式的定义为:提供一种方法访问一个容器对象中各个元素,而又不暴露该对象的内部细节==>迭代器模式,就是为容器而生。 要求往Collection中添加的自定义的类要重写equals方法,如果不重写,则会调用Object的equals对比(比较对象的地址值)。
//执行遍历操作 while(iterator.hasNext()){ Object next = iterator.next(); System.out.println(next); }
实现原理解析:
2.1.2 遍历Collection集合数组的方式
1)使用迭代器:iterator
@Test
public void testIterator(){
//Iterator():集合元素的遍历,返回值是一个迭代器接口
Collection collection=new ArrayList();
collection.add(123);
collection.add(456);
collection.add(new String("Tom"));
collection.add(false);
Iterator iterator = collection.iterator();
//执行遍历操作
while(iterator.hasNext()){
Object next = iterator.next();
if ("Tom".equals(next)){
iterator.remove();
}
System.out.println(next);
}
//会进入死循环,无限输出第一个。每次调用iterator()就会返回一个新的iterator,默认游标在第一个之前。
/* while(collection.iterator().hasNext()){
System.out.println(collection.iterator().next());
}*/
//存在remove方法,可以用来移除集合中的元素。此方法不同于集合调用remove()。
//如果在next之前调用remove或者调用next之后调用remove然后再次调用remove会出现错误:IllegalStateException
Iterator iterator1 = collection.iterator();
while (iterator1.hasNext()){
System.out.println(iterator1.next());
}
}
2)JDK5.0新增的foreach循环迭代访问Collection和数组
/**
* foreach遍历集合
*/
@Test
public void testForeach(){
Collection collection=new ArrayList();
collection.add(123);
collection.add(456);
collection.add(new String("Tom"));
collection.add(false);
//for(集合中元素的类型 局部变量:集合对象)
for (Object o:collection) {
System.out.println(o);
}
}
2.1.3 简单练习
/**
* 练习题:增强for循环通常用来遍历,无法对数据进行修改。
*/
@Test
public void testForeach02(){
String[] strings=new String[]{"MM","MM","MM"};
for (int i = 0; i < strings.length; i++) {
strings[i]="GG";
}
for (int i = 0; i < strings.length; i++) {
//GG
System.out.println(strings[i]);
}
for (String s:strings) {
s="HH";
}
for (int i = 0; i <strings.length; i++) {
//GG
System.out.println(strings[i]);
}
}
2.2 Collection–List接口
2.2.1 List接口概述
- 鉴于Java中数组用来存储数据的局限性,我们通常使用List接口的实现类来替代数组
- List集合类中元素有序、且可重复,集合中的每个元素都有其对应的顺序索引。
- 底层使用动态数组实现,List容器中的元素都对应一个整数型的序号记载其在容器中的位置,可以根据序号存取容器中的元素,此番方式比较快。
- JDK API中List接口的实现类常用的有:ArrayList、LinkedList和Vector。
2.2.2 ArrayList
作为List接口的主要实现类而出现。
- 特点
- 线程不安全的,因此效率比较高。
- 底层使用Object类型的数组进行封装==>Object[] elementData存储。
- 对于查询和修改操作比较多的情况下效率比较高,值得是根据下标来进行查找。
- 因为底层使用数组,因此在内存中的存储是连续的。
2.源码分析:JDK7和8之中有所不同–>1.8懒加载
JDK7版本:
//1.调用空参构造器创建对象。
public ArrayList() {
//2.调用重载的构造器
this(10);
}
public ArrayList(int initialCapacity) {
super();
//3.判断初始化容量是否小于0
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+initialCapacity);
//4.将Object[] elementData进行初始化,初始化容量为10
this.elementData = new Object[initialCapacity];
}
//1.调用添加数据的方法(不够的情况下)
public boolean add(E e) {
//2.确认容量是否够,数组容量和已添加的元素个数+1进行比较。
ensureCapacityInternal(size + 1); // Increments modCount!!
//9.添加新的数据到数组中去
elementData[size++] = e;
return true;
}
//3.判断容量是否足够的方法
private void ensureCapacityInternal(int minCapacity) {
modCount++;
// overflow-conscious code
//4.如果数组容量不够了
if (minCapacity - elementData.length > 0)
//5.开始调用扩容的方法
grow(minCapacity);
}
//5.调用扩容的方法。
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
//6.新的容量的计算。例如旧的容量是10==>新的容量10+5==15,扩容为1.5倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
//7.1如果新的容量依旧小于之前容量+1==>将之前容量+1赋给新的容量
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
//7.2如果新的容量大于MAX_ARRAY_SIZE
if (newCapacity - MAX_ARRAY_SIZE > 0)
//7.2.1将容量设置为int的最大值
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
//8.将数组进行复制到新的数组中去
elementData = Arrays.copyOf(elementData, newCapacity);
}
//7.2.2容量赋值为整形最大值
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
总结: > 如果添加数据导致底层数组的容量不够,则进行扩容,默认为1.5倍扩容。
> 开发者中建议使用带参数的构造器。
JDK8**版本**
//1.调用空参构造器
public ArrayList() {
//2.调用默认属性,并没有直接创建数组对象,类似于单例模式中的懒汉式
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
//2.调用默认属性
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//1.调用**add**方法添加数据
public boolean add(E e) {
//2.确认容量是否足够
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
//2.**判断容量是否足够的方法*
private void ensureCapacityInternal(int minCapacity) {
//3.**判断是不是第一次添加数据。*
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
//4.1**如果是第一次添加数据。进行默认初始化。**DEFAULT_CAPACITY==10*
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
//4.2**不是第一次添加数据,判断容量*
ensureExplicitCapacity(minCapacity);
}
//4.2**判断容量*
private void ensureExplicitCapacity(int minCapacity) {
modCount++; //初始为0
// overflow-conscious code*
//4.2.1**如果容量小了*
if (minCapacity - elementData.length > 0)
//4.2.2**增加容量*
grow(minCapacity);
}
//**增加容量的方法:和**JDK7.0**一致
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
//**容量**1.5**倍进行增加。
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
- 结论:
JDK8中调用空参构造器的时候底层Object[] elementData初始化的时候并没有创建长度为10的数组。在第一次调用add的时候,创建长度为10的数组,将数据添加到其中操作和扩容操作和JDK7保持一致。1.5倍扩容
JDK8对比JDK7的优点:
- 延迟了数组的创建时间,节省了内存。
- JDK7中ArrayList数组的创建类似于饿汉式。
- JDK8中ArrayList数组的创建类似于懒汉式。
- 方法
- void add(int index,E element):指定的下标出添加元素。(index<0||index>size)
-
E get(int index):根据下标获取指定元素。
E set(int index E element):修改指定索引处的值为参数值,返回原元素值
-
int indexOf(Object o):获取第一次出现参数值的下标
-
int lastIndexOf(Object o):获取最后一次出现指定参数值的下标。
-
E remove(int index):删除指定下标处的元素
@Test
public void test01() {
ArrayList<Object> list = new ArrayList<Object>();
list.add("123");
list.add("456");
list.add("789");
//[123, 456, 789]
System.out.println(list);
list.add(1,"hello");
//[123, hello, 456, 789]
System.out.println(list);
System.out.println(list.get(2));
System.out.println(list.set(2, "world"));
System.out.println(list.get(2));
System.out.println(list.indexOf("hello"));
System.out.println(list.lastIndexOf("hello"));
System.out.println(list.remove(2)); //world
}
@Test
public void test() {
Scanner input=new Scanner(System.in);
System.out.println("请输入20个字符");
String str=input.next();
char[] chars=str.toCharArray();
ArrayList<Object> list=new ArrayList<>();
for (int i = 0; i < chars.length; i++) {
list.add(chars[i]);
}
System.out.println("输入的字符串序列为:"+list);
ArrayList<Object> list2=new ArrayList<>();
for (int i = 0; i < chars.length; i++) {
if (list2.contains(chars[i])) {
continue;
}
list2.add(chars[i]);
}
System.out.println("去除重复后的字符集合为:"+list2);
for (int i = 0; i < list2.size(); i++) {
int nums=0;
for (int j = 0; j < chars.length; j++) {
if ((char)(list2.get(i))==chars[j]) {
nums++;
}
}
System.out.println(list2.get(i)+"出现了:"+nums+"次");
}
}
2.2.3 LinkedList
- 特点
- 底层使用双向链表的方式进行存储。
- 对于频发的插入,删除操作效率比ArrayList高。
- 因为底层是双向链表,在内存中的存储是不连续。
-
源码分析:在JDK7和JDK8之中没有特别大的区别
//存储数据的结构是“双向链表”,数据存储在一个个Node中 private static class Node<E> { E item; Node<E> next; Node<E> prev; Node(Node<E> prev, E element, Node<E> next) { this.item = element; this.next = next; this.prev = prev; } } //构造器 public LinkedList() { } //1.执行添加操作 public boolean add(E e) { //2.调用linkLast方法来执行添加节点的操作。 linkLast(e); return true; } //linkLast方法 // Links e as last element. void linkLast(E e) { final Node<E> l = last; //放入数据,构建新的节点:前一个,当前的,后一个指向null final Node<E> newNode = new Node<>(l, e, null); //将新添的元素作为最后一个。 last = newNode; if (l == null) first = newNode; else l.next = newNode; //节点个数++ size++; modCount++; }
-
结论:
LinkList list=new LinkList(),内部声明了Node类型的first和last属性,头尾节点,便于我们直接对头尾节点进行操作默认值为null,执行添加操作list.add(123)==>实际上是将数据封装到了node节点中,如果节点是第一个元素,将该节点赋值给first节点。
- 常见面试题
1)Arraylist和linkedList之间的区别?
arraylist底层使用数组实现,linkedlist使用双向链表实现,因此如果对于查询和修改来说,如我们知道该元素的下标,arraylist可以根据下标直接定位到我们需要的元素,可以直接获取或修改其属性,在这种情况下,由于linkedList必须一个一个进行比较,直到获取到我们需要的元素,在这种情况下,arraylist的效率要高于LinkedList。如果直接直接获取或修改头尾节点,两者的效率没有什么差别。如果是删除或者新增的操作,linkedlist的效率要高于arraylist,因为数组的特性是一块连续的存储空间,因此linkedlist需要对删除或新增位置的以后的元素进行移位,比较消耗性能,而linkedlist只要连上修改pre和next引用地址即可。
具体操作为:比如要在ab之间插入c,那么应该c的pre指向a,next指向b,a的next指向c,a的next.next.pre指向c,
2.2.4 Vector
作为List的古老实现类出现,出现于jdk1.0。
- 特点
- 线程安全的,效率比较低
- 底层使用Object类型的数组进行封装==>Object[] elementData存储。
- 源码分析==JDK7和8中没有什么不同
//构造器
public Vector() {
//1.数组初始化长度为10
this(10);
}
//2.执行添加数据的操作
public synchronized boolean add(E e) {
modCount++;
//3.判断容量是否充足
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = e;
return true;
}
//判断容量的方法
private void ensureCapacityHelper(int minCapacity) {
// overflow-conscious code
if (minCapacity - elementData.length > 0)
//扩容
grow(minCapacity);
}
//扩容的方法
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
//初始capacityIncrement==0===>扩容了两倍
int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
capacityIncrement : oldCapacity);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
elementData = Arrays.copyOf(elementData, newCapacity);
}
- 结论:
JDK7和JDK8中Vector(向量类)没什么大的改变,默认数组初始化长度为10,首次扩容的时候,默认扩容为之前的两倍。
2.2.5 List接口中方法测试
- void add(int index, Object ele): 在index 位置插入ele 元素
- boolean addAll(int index, Collection eles): 从index 位置开始将eles中的所有元素添加进来
- Object get(int index): 获取指定index 位置的元素
- int indexOf(Object obj): 返回obj在集合中首次出现的索引位置
- int lastIndexOf(Object obj): 返回obj在当前集合中末次出现的索引位置
- Object remove(int index): 移除指定index位置的元素,并返回此元素
- Object set(int index, Object ele): 设置指定index位置的元素改为ele
- List subList(int fromIndex, int toIndex): 返回从fromIndex 到toIndex位置的子集合,左闭右开。
总结:常用方法
-
增:add(Object obj)
-
删:remove(Int index)/remove(Object o)
-
改:set(Int index,Object ele)
-
查:get(Int index)
-
插:add(Int index,Object obj)
-
长度:size()
-
遍历: ①Iterator迭代器方式
②增强for循环
③普通的循环
@Test
public void testListMethods(){
ArrayList<Object> list = new ArrayList<>();
//add(Object o)
list.add(123);
list.add(456);
list.add("AA");
list.add(new Person("张三",21));
list.add(456);
//[123, 456, AA, Person{name='张三', age=21}, 456]
System.out.println(list);
//add(int index,Object o)
list.add(1,"李四");
//[123, 李四, 456, AA, Person{name='张三', age=21}, 456]
System.out.println(list);
list.addAll(2, Arrays.asList("王五","赵六","田七"));
//[123, 李四, 王五, 赵六, 田七, 456, AA, Person{name='张三', age=21}, 456]
System.out.println(list);
//赵六
System.out.println(list.get(3));
//5
System.out.println(list.indexOf(456));
//8
System.out.println(list.lastIndexOf(456));
Object remove = list.remove(6);
//AA
System.out.println(remove);
//[123, 李四, 王五, 赵六, 田七, 456, Person{name='张三', age=21}, 456]
System.out.println(list);
list.set(0,"张三");
System.out.println(list);
List<Object> sub = list.subList(0, 5);
//[张三, 李四, 王五, 赵六, 田七]
System.out.println(sub);
//遍历:
//方式一:
Iterator<Object> iterator = list.iterator();
while (iterator.hasNext()){
System.out.print(iterator.next()+"\t");
}
System.out.println("**********");
//方式二
for (Object o:list) {
System.out.print(o+"\t");
}
System.out.println("**********");
//方式三
for (int i = 0; i < list.size(); i++) {
System.out.print(list.get(i)+"\t");
}
}
//javaBean
class Person{
String name;
int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public Person() {
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Person)) return false;
Person person = (Person) o;
if (age != person.age) return false;
return name != null ? name.equals(person.name) : person.name == null;
}
@Override
public int hashCode() {
int result = name != null ? name.hashCode() : 0;
result = 31 * result + age;
return result;
}
}
2.3 Collection–Set接口
Set接口概述
- Set接口是Collection的子接口,内部没有提供额外的方法。
- Set接口存储数据是无序的,不可重复的数据,如果试图添加相同的元素到Set集合中去,则添加操作失败。
- Set判断两个对象是否相等不是使用的==,而是使用的equals方法。
2.3.1 HashSet
作为Set接口的主要实现类
- 特点:
- 线程不安全的==>效率高。
- 可以存储Null值。
- 无序性不代表是随机性,不代表每次遍历集合都不相同。以HashSet为例,插入顺序和读出顺序不一定一致。
- 插入值的方式:底层使用hashmap实现,即hash的表的结构,首先计算插入元素的hash值,然后hash值和当前哈希桶数量减一相与,获取其在数组中的位置,然后查看改为是否存在元素,如果没有,插入成功,如果存在元素,则逐个对比,如果hashcode值不一致则添加成功,如果一致则使用equals方法进行比较,如果不同,添加成功,如果相同进行替换。
- 底层结构是:哈希表,即数组+链表
-
添加元素的过程:以HashSet为例
添加过程:调用待添加数据的hashCode()方法,计算hash值,然后此hash值通过某种算法(如散列函数)进行计算存放的位置(索引位置),判断该位置是否存在元素:如果该位置没有元素则存放在此处;当发现存放的位置已经存在元素b了,则进行比较他们的hash值是否相同,不相同则添加成功,一旦相同就调用新元素的equals方法,如果返回值为true==>就认为两个元素是相同的元素,添加失败。不相同的时候则以链表的形式进行添加。某位置存在元素的情况下添加元素的规则:JDK7则将新的元素放到旧元素的位置,旧元素下移,JDK8则相反(7上8下)。
注意:
-
Object类中的hashCode值是进行随机计算的==>自定义类进行添加的时候需要重写hashCode()和equals()方法,且这两个方法尽可能的保持一致性==>相同的对象必有相同的散列码。
-
对象中用作equals()方法的field,都应该被用来计算hashCode()==>生产过程中使用ide工具直接自动生成。
- 扩容机制:
底层也是数组进行存储,初始容量为16,当如果使用率超过0.75,(16*0.75=12)就会扩大容量为原来的2倍。(16扩容为32,依次为64,128…等)。
- 重写hashCode(),为什么会存在31这个数字?
@Override
public int hashCode() {
int result = name != null ? name.hashCode() : 0;
result = 31 * result + age;
return result;
}
原因:
- 选择系数的时候,尽量要选择大的系数,因为如果计算出来的hash值越大,所谓的“冲突”就越少,查找起来效率比较高(减少哈希碰撞)。
- 并且31只占用5个bit,相乘造成数据溢出的概率比较小。
- 31可以使用1*31==>(1<<5)-1来表示,很多虚拟机里面都有相关的优化(提高算法的效率)
- 31是一个素数,素数的作用就是如果我用一个数字来乘以这个数,那么这个数最多只能被素数本身和被乘数还有1来整除(减少冲突)。
简单测试一下:
@Test
public void testSet(){
Set set = new HashSet();
set.add(123);
set.add(456);
set.add("张三");
set.add("张三");
set.add("李四");
System.out.println(set);
Iterator iterator = set.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
//重写了hashCode()
System.out.println("张三".hashCode());
System.out.println("张三".hashCode());
}
输出结果:
[李四, 张三, 456, 123]
李四
张三
456
123
774889
774889
2.3.2 LinkedHashSet
是HashSet的子类
特点:
- 依旧是无序的。
- 在添加数据的同时每个数据还维护了两个引用,记录此数据的前一个和后一个数据,在遍历其内部数据的时候,可以按照添加的顺序进行遍历。
- 对于频繁的增删操作,效率要高于HashSet。
@Test
public void testLinkedHashSet(){
Set set = new LinkedHashSet();
set.add(123);
set.add(456);
set.add("张三");
set.add("张三");
set.add("李四");
System.out.println(set);
Iterator iterator = set.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
}
输出结果:
[123, 456, 张三, 李四]
123
456
张三
李四
2.3.3 TreeSet
- 特点:
- 使用红黑二叉树,平衡二叉树,进行存储==>查询速快。
- 像TreeSet中添加数据,必须存储同一个类new的对象。
- 该对象可以排序。
- 添加数据:
- 自然排序:比较两个对象是否相同的标准不再是equals()方法,而是CompareTo()返回0,一旦他们的。
- 定制排序:
/**
* TreeSet的测试
*/
@Test
public void testTreeSet(){
TreeSet<Object> treeSet = new TreeSet<>();
treeSet.add(123);
treeSet.add(456);
//添加失败:java.lang.ClassCastException:
// java.lang.Integer cannot be cast to java.lang.String
//treeSet.add("张三");
treeSet.add(-2);
treeSet.add(78);
Iterator<Object> iterator = treeSet.iterator();
while(iterator.hasNext()){
//输出是排好顺序的:-2 78 123 456
System.out.print(iterator.next()+"\t");
}
System.out.println("*************************");
//自然排序:
TreeSet<Object> treeSet1 = new TreeSet<>();
treeSet1.add(new Person("Tom",21));
treeSet1.add(new Person("BB",21));
treeSet1.add(new Person("Tom",18));
treeSet1.add(new Person("AA",21));
treeSet1.add(new Person("Tam",24));
Iterator<Object> iterator1 = treeSet1.iterator();
while (iterator1.hasNext()){
System.out.print(iterator1.next()+"\t");
}
System.out.println();
System.out.println("-----------------");
//存在定制排序:优先使用定制排序的规则。
TreeSet<Object> treeSet2 = new TreeSet<>(new Comparator<Object>() {
@Override
public int compare(Object o1, Object o2) {
if (!(o1 instanceof Person&&o2 instanceof Person)){
try {
throw new Exception("传入的参数有误!!");
} catch (Exception e) {
System.out.println(e.getMessage());
}
}
Person person1=(Person) o1;
Person person2=(Person) o2;
//如果名字相同,则年龄从大到小进行排列
if (person1.name.compareTo(person2.name)==0){
return -(new Integer(person1.age).compareTo(person2.age));
}
return person1.name.compareTo(person2.name);
}
});
treeSet2.add(new Person("Tom",21));
treeSet2.add(new Person("BB",21));
treeSet2.add(new Person("Tom",18));
treeSet2.add(new Person("AA",21));
treeSet2.add(new Person("Tam",24));
Iterator<Object> iterator2 = treeSet2.iterator();
while (iterator2.hasNext()){
System.out.print(iterator2.next()+"\t");
}
}
class Person implements Comparable {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public Person() {
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof Person)) {
return false;
}
Person person = (Person) o;
if (age != person.age){
return false;
}
return name != null ? name.equals(person.name) : person.name == null;
}
@Override
public int hashCode() {
int result = name != null ? name.hashCode() : 0;
result = 31 * result + age;
return result;
}
/**
* 名字从小到大,名字相同时,年龄从小到大。
* @param o
* @return
*/
@Override
public int compareTo(Object o) {
if (!(o instanceof Person)){
try {
throw new Exception("传入的参数有误!!");
} catch (Exception e) {
System.out.println(e.getMessage());
}
}
Person person=(Person) o;
//如果两者的名字相同,则进行比较其年龄。
if (this.name.compareTo(person.name)==0){
return new Integer(this.age).compareTo(person.age);
}
return this.name.compareTo(person.name);
}
}
- 常见方法
E first():获得第一个元素
E last():获取最后一个元素
E floor(E e):获取小于参数的最大元素
E ceiling(E e):获取大于参数的最大元素
总结:
- Collection中存储的如果是自定义类的对象,需要自定义类从重写哪个方法?为什么?
- List接口的实现类:equals()
- Set接口: HashSet,LinkedHashSet:equals,hashCode()。
- TreeSet:Comparabe:compareTo(Object o);Comparator:compare(Object o1,Object o2)。
- ArrayList,LinkedList,Vector三者的相同点,不同点?
- 相同点:都实现了List接口,存储都是有序的,可重复的。
- 不同点:Vector是线程安全的,ArrayList,linkedList线程不安全。参考知识点。
- List接口中的常用方法有那些?
增:add(Object o)
删:remove(Object o),remove(Int index)==>ArrayList
改:set(Int index,Object o)
查:get(Int index)
插入:add(Int index,Object o )
长度:size(),
遍历:①Iterator
②foreach
③for
-
Set存储数据的特点==>无序,不可重复性,常见实现类==>HashSet,TreeSet,LinkedHashSet
-
一个简单测试:考察对底层的理解
public static void main(String[] args) {
HashSet set=new HashSet();
Person p1 = new Person(1001, "AA");
Person p2 = new Person(1002, "BB");
set.add(p1);
set.add(p2);
System.out.println(set);
p1.name="CC";
//极大的概率没找到,删除失败
System.out.println(set);
set.remove(p1);
//{p1.toString(),p2.toString()}
System.out.println(set);
//添加成功,对比了HashCode添加的位置发生了变化
set.add(new Person(1001,"CC"));
System.out.println(set);
//添加成功,对于了Hashcode然后对比了equals()
set.add(new Person(1001,"AA"));
//{p1.toString(),p2.toString,CC.toString,AA.toString}
System.out.println(set);
}
class Person{
int id;
String name;
public Person() {
}
public Person(int id, String name) {
this.id = id;
this.name = name;
}
@Override
public boolean equals(Object o) {
if (this == o) {return true;}
if (!(o instanceof Person)) {return false;}
Person person = (Person) o;
if (id != person.id) {return false;}
return name != null ? name.equals(person.name) : person.name == null;
}
@Override
public int hashCode() {
int result = id;
result = 31 * result + (name != null ? name.hashCode() : 0);
return result;
}
@Override
public String toString() {
return "Person{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}
- 定制排序的使用
public static void main(String[] args) {
TreeSet treeSet = new TreeSet();
Employee e1 = new Employee("liudehua", 55, new MyDate(1965, 5, 4));
Employee e2 =new Employee("zhangxueyou",44,new MyDate(1978,5,4));
Employee e3 =new Employee("guofucheng",44,new MyDate(1978,5,9));
Employee e4 =new Employee("liming",51,new MyDate(1969,5,4));
Employee e5 =new Employee("liangchaowei",21,new MyDate(1999,5,4));
treeSet.add(e1);
treeSet.add(e2);
treeSet.add(e3);
treeSet.add(e4);
treeSet.add(e5);
Iterator iterator = treeSet.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
System.out.println("***********************************************");
TreeSet treeSet1 = new TreeSet(new Comparator() {
//按照生日进行比较,可以将date这个排序方式放到MyDate类中作为自然排序。
@Override
public int compare(Object o1, Object o2) {
if (o1 instanceof Employee&&o2 instanceof Employee){
Employee e1=(Employee)o1;
Employee e2=(Employee)o2;
MyDate b1 = e1.getBirthday();
MyDate b2 = e2.getBirthday();
int minusYear=b1.getYear()-b2.getYear();
if (minusYear!=0){
return minusYear;
}
int minusMonth=b1.getMonth()-b2.getMonth();
if (minusMonth!=0){
return minusMonth;
}
return b1.getDay()-b2.getDay();
}
throw new RuntimeException("传入的参数有误!");
}
});
treeSet1.add(e1);
treeSet1.add(e2);
treeSet1.add(e3);
treeSet1.add(e4);
treeSet1.add(e5);
Iterator iterator1 = treeSet1.iterator();
while (iterator1.hasNext()){
System.out.println(iterator1.next());
}
}
class Employee implements Comparable{
private String name;
private int age;
private MyDate birthday;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public MyDate getBirthday() {
return birthday;
}
public void setBirthday(MyDate birthday) {
this.birthday = birthday;
}
public Employee(String name, int age, MyDate birthday) {
this.name = name;
this.age = age;
this.birthday = birthday;
}
public Employee() {
}
@Override
public String toString() {
return "Employee{" +
"name='" + name + '\'' +
", age=" + age +
", birthday=" + birthday +
'}';
}
//按照姓名进行排序,如果是汉字的话就就可能会出现问题,拿的是UTF-8来进行计算,不是使用abcd来计算。特殊需求的话找jar包,根据拼音来比较。
@Override
public int compareTo(Object o) {
if (o instanceof Employee){
Employee e=(Employee)o;
return this.name.compareTo(e.name);
}
throw new RuntimeException("传入参数有误");
}
}
class MyDate{
private int year;
private int month;
private int day;
public int getYear() {
return year;
}
public void setYear(int year) {
this.year = year;
}
public int getMonth() {
return month;
}
public void setMonth(int month) {
this.month = month;
}
public int getDay() {
return day;
}
public void setDay(int day) {
this.day = day;
}
public MyDate(int year, int month, int day) {
this.year = year;
this.month = month;
this.day = day;
}
public MyDate() {
}
@Override
public String toString() {
return "MyDate{" +
"year=" + year +
", month=" + month +
", day=" + day +
'}';
}
}
3 Map的测试
3.1 map概述–JDK1.2
Map是与Collection并列的存在,用于存储具有映射关系的双列数据,存储key-value键值对,可以实任何类型的引用变量,key-value存在单向一对一的关系,即一个key指向唯一的一个value。
3.2 HashMap
map的主要实现类
1 特点:
-
线程不安全的,效率高。
-
可以存储null的key和value,相key相同的时候,添加的时候是覆盖的操作。
2 Map的key-value的理解
以HashMap的键值对为例:key使用Set来进行存储,即无序的,不可重复的数据==>自定义类需要重写equals()和hashCode()。value是可以重复的。
Collection存储所有的value==>要重写equals()方法,底层contains方法会调用。实际上我们put的时候,将数据封装到entry对象中(entry:无序,不可重复)。使用Set存储所有的entry。
3 底层结构
JDK7及之前:数组+链表
JDK8:数组+链表+红黑树
4 HashMap的底层实现原理:
① JDK7版本:
HashMap map=new HashMap();>在实例化之后,底层创建了长度为16的一维数组Entry[] table。map.put(key1,value1);>非第一次存放数据,首先调用key1所在类的hashCode()计算key1的哈希值,经过某种算法得到在Entry数组中的存放位置。如果此位置上的数据为空,则表明不存在数据,key1-value1添加成功,如果此位置上的数据不为空,意味着存在一个或者多个数据(链表的方式存在),比较当前的key1和当前位置上的key的hash值进行比较,如果都不相同,则可以存储成功,如果k1和某一个数据的hash值相同了,则使用equals方法进行比较,如果不同,则进行存储。如果相同则使用value1来进行替换原有的数据。
扩容机制:默认的扩容方式是扩容为原来的两倍,然后将原有的数据复制过来。
② JDK8相较于JDK7在底层实现方面的不同。
-
new HashMap():底层没有立即创建一个长度为16的数组。
-
JDK8 底层的数组是Node[ ] ,而非Entry[ ]
-
首次调用put()方法的时候,底层创建长度为16的数组
-
JDK7底层结构只有:数组+链表。JDK8中底层结构:数组+链表+红黑树。
注:当数组的某一个索引位置上的元素以链表的形式存在的数据个数>8,且当前数组的长度>64,此时此索引位置上的所有数据改为红黑树进行存储。
5 底层源码==>JDK7和JDK8
//创建HashMap对象
HashMap map=new HashMap();
//1.调用空参构造器
public HashMap() {
//2.调用有参构造器:
//DEFAULT_INITIAL_CAPACITY<==>默认初始化长度
//DEFAULT_LOAD_FACTOR<==>默认加载因子0.75
this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
}
//有参构造器
public HashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
// Find a power of 2 >= initialCapacity
int capacity = 1;
//创建初始化长度为2的多少次幂;例如15,则实际创建16
while (capacity < initialCapacity)
capacity <<= 1;
//加载因子:默认值0.75
this.loadFactor = loadFactor;
//计算临界值=当前容量*加载因子,例如使用默认情况下。临界值=16*0.75=12,当超过12的时候,就进行扩容操作
threshold = (int)Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
//初始化底层数组:Entry<k,v> table
table = new Entry[capacity];
useAltHashing = sun.misc.VM.isBooted() &&
(capacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);
init();
}
//put()操作:
public V put(K key, V value) {
//可以存放null的key值
if (key == null)
return putForNullKey(value);
//计算hash值
int hash = hash(key);
//计算存放的位置
int i = indexFor(hash, table.length);
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
addEntry(hash, key, value, i);
return null;
}
//计算存放位置的方法
static int indexFor(int h, int length) {
return h & (length-1);
}
//底层存储的操作
void addEntry(int hash, K key, V value, int bucketIndex) {
//当size>12且该位置不空==>想要生成链表则进行扩容操作。
if ((size >= threshold) && (null != table[bucketIndex])) {
//默认扩容为之前的2倍
resize(2 * table.length);
//计算hash值,如果是null则hash值为0,否则计算hash值
hash = (null != key) ? hash(key) : 0;
//扩容之后重新计算索引位置
bucketIndex = indexFor(hash, table.length);
}
//不需要扩容则执行createEntry()
createEntry(hash, key, value, bucketIndex);
}
void createEntry(int hash, K key, V value, int bucketIndex) {
//直接将这个位置上的元素取出来。
Entry<K,V> e = table[bucketIndex];
//将新的元素放到该索引位置。将原来数组上的元素放到新的元素next中出现。
//即新的元素next指向老的元素
table[bucketIndex] = new Entry<>(hash, key, value, e);
size++;
}
//底层源码分析==>JDK8版本
//构造器
public HashMap() {
//区别于7,只有加载因子,并没有调用有参构造器
this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
//put方法的调用
public V put(K key, V value) {
//调用putVal的方法
return putVal(hash(key), key, value, false, true);
}
//putVal方法。
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
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);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
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;
}
//创建底层数组的方法
final Node<K,V>[] resize() {
Node<K,V>[] oldTab = table;
int oldCap = (oldTab == null) ? 0 : oldTab.length;
int oldThr = threshold;
int newCap, newThr = 0;
if (oldCap > 0) {
if (oldCap >= MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return oldTab;
}
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
newThr = oldThr << 1; // double threshold
}
else if (oldThr > 0) // initial capacity was placed in threshold
newCap = oldThr;
else { // zero initial threshold signifies using defaults
newCap = DEFAULT_INITIAL_CAPACITY;
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
if (newThr == 0) {
float ft = (float)newCap * loadFactor;
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
(int)ft : Integer.MAX_VALUE);
}
threshold = newThr;
@SuppressWarnings({"rawtypes","unchecked"})
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
table = newTab;
if (oldTab != null) {
for (int j = 0; j < oldCap; ++j) {
Node<K,V> e;
if ((e = oldTab[j]) != null) {
oldTab[j] = null;
if (e.next == null)
newTab[e.hash & (newCap - 1)] = e;
else if (e instanceof TreeNode)
((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
else { // preserve order
Node<K,V> loHead = null, loTail = null;
Node<K,V> hiHead = null, hiTail = null;
Node<K,V> next;
do {
next = e.next;
if ((e.hash & oldCap) == 0) {
if (loTail == null)
loHead = e;
else
loTail.next = e;
loTail = e;
}
else {
if (hiTail == null)
hiHead = e;
else
hiTail.next = e;
hiTail = e;
}
} while ((e = next) != null);
if (loTail != null) {
loTail.next = null;
newTab[j] = loHead;
}
if (hiTail != null) {
hiTail.next = null;
newTab[j + oldCap] = hiHead;
}
}
}
}
}
return newTab;
}
6 底层结构涉及到的几个名词
-
DEFAULT_INITIAL_CAPACITY : HashMap的默认容量,16
-
MAXIMUM_CAPACITY : : HashMap的最大支持容量,2^30
-
DEFAULT_LOAD_FACTOR :HashMap的默认加载因子
-
TREEIFY_THRESHOLD :Bucket中链表长度大于该默认值,转化为红黑树
-
UNTREEIFY_THRESHOLD :Bucket中红黑树存储的Node小于该默认值,转化为链表
-
MIN_TREEIFY_CAPACITY :桶中的Node被树化时最小的hash表容量。(当桶中Node的数量大到需要变红黑树时,若hash表容量小于MIN_TREEIFY_CAPACITY时,此时应执行(64)
-
resize扩容操作这个MIN_TREEIFY_CAPACITY的值至少是TREEIFY_THRESHOLD的4倍。
-
table :存储元素的数组,总是2的n次幂
-
entrySet: :存储具体元素的集
-
size :HashMap中存储的键值对的数量
-
modCount :HashMap扩容和结构改变的次数。
-
threshold :扩容的临界值,=容量*填充因子
-
loadFactor: :填充因子
7 为什么默认的加载因子是0.75?
因为满的概念定义的比较模糊,根据统计学得出的结论:加载因子过小则数组利用率比较低,过大的时候链表的长度比较大。最终选定了0.75。
8 常见方法
-
V put(k key,V value):往map中存放数据,如果key存在,返回值为被替换的value;否则为null
-
void clear():删除所有的元素(键值对)
-
boolean containKey(Object key):map中是否包含key
-
boolean containValue(Object value):map中是否包含value
-
V get(Object key):通过key来获取value
-
boolean isEmpty():判断是否为空
-
V remove (Object key):根据键删除键值对,返回值是被删除的value。
-
Set keySet():获取所有的键对应的set
-
Collection values():获取值集合
-
Set<Map.Entry<k,v> entrySet()>( ):键值对对应的Entry对象
3.3 LinkedHashMap
1 特点:
-
在原有的HashMap的基础上加了两个指针,保证在遍历map元素的时候,可以按照添加的顺序进行遍历。
-
对于频繁的遍历操作,执行效率要高于HashMap。
2 底层实现原理
static class Entry<K,V> extends HashMap.Node<K,V> {
Entry<K,V> before, after;可以记录添加的元素的先后顺序。
Entry(int hash, K key, V value, Node<K,V> next) {
super(hash, key, value, next);
}
}
3.4 TreeMap
1 特点:
- 考虑key的自然排序和定制排序,底层是红黑二叉树的结构==>保证按照添加的key-value来进行排序,实现排序遍历。
- 向TreeMap中添加key-value,要求key必须是由同一个类创建的对象==>按照key来进行排序。
2 排序的方式:自然排序,定制排序。
/**
* 排序
*/
@Test
public void testTreeMap(){
System.out.println("自然排序:::");
TreeMap treeMap = new TreeMap();
Person p1 = new Person("Tom", 23);
Person p2 = new Person("Jerry", 27);
Person p3 = new Person("Rose",22);
Person p4 = new Person("Jack",20);
Person p5 = new Person("James",18);
Person p6 = new Person("Tom",25);
treeMap.put(p1,"NBA");
treeMap.put(p2,"CBA");
treeMap.put(p3,"nab");
treeMap.put(p4,"qwe");
treeMap.put(p5,123);
treeMap.put(p6,125);
Set set = treeMap.entrySet();
Iterator iterator = set.iterator();
while (iterator.hasNext()){
Object next = iterator.next();
Map.Entry entry=(Map.Entry)next;
System.out.println(entry.getKey()+"="+entry.getValue());
}
System.out.println("定制排序:::");
TreeMap treeMap1 = new TreeMap(new Comparator() {
//直接按照年龄从小到大进行排序
@Override
public int compare(Object o1, Object o2) {
if (o1 instanceof Person && o2 instanceof Person) {
Person p1 = (Person) o1;
Person p2 = (Person) o2;
return p1.age - p2.age;
}
throw new RuntimeException("传入的参数不匹配");
}
});
treeMap1.put(p1,"NBA");
treeMap1.put(p2,"CBA");
treeMap1.put(p3,"nab");
treeMap1.put(p4,"qwe");
treeMap1.put(p5,123);
treeMap1.put(p6,125);
Set set1 = treeMap1.entrySet();
Iterator iterator1 = set1.iterator();
while (iterator1.hasNext()){
Object next = iterator1.next();
Map.Entry entry=(Map.Entry)next;
System.out.println(entry.getKey()+"="+entry.getValue());
}
}
class Person implements Comparable{
String name;
int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public Person() {
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) {return true;}
if (!(o instanceof Person)) {return false;}
Person person = (Person) o;
if (age != person.age) {return false;}
return name != null ? name.equals(person.name) : person.name == null;
}
@Override
public int hashCode() {
int result = name != null ? name.hashCode() : 0;
result = 31 * result + age;
return result;
}
/**
* 自然排序:按照姓名从大到小,年龄从小到大
* @param o
* @return
*/
@Override
public int compareTo(Object o) {
if (o instanceof Person){
Person p=(Person)o;
int i = -this.name.compareTo(p.name);
if (i!=0){
return i;
}else {
return this.age-p.age;
}
}
throw new RuntimeException("传入的参数有误!!!");
}
}
3.5 Hashtable
map的古老实现类:出现于JDK1.0
1 特点:
- 线程安全的,效率低。
- 不可以存储null的key和value。
2 子类之properties:常用来处理配置文件。key和value都是String类型。
默认的识别目录为项目下:当前模块的根目录,非当前项目的根目录
@Test
public void testProperties() throws IOException {
Properties properties = new Properties();
//加载流对应的文件
properties.load(new FileInputStream("JDBC.properties"));
String name = properties.getProperty("name");
String password = properties.getProperty("password");
//james詹姆斯=1997
System.out.println(name+"="+password);
}
3.6 Map中常用的方法
以HashMap为例
- 添加 、 删除、修改操作 :
-
Object put(Object key,Object value):将指定key-value添加到(或修改)当前map对象中
-
void putAll(Map m):将m中的所有key-value对存放到当前map中
-
Object remove(Object key):移除指定key的key-value对,并返回value
-
void clear():清空当前map中的所有数据
@Test
public void testMap(){
HashMap hashMap = new HashMap();
hashMap.put(null,"张三"); //{null=张三}
hashMap.put(null,"李四"); //null="李四";
System.out.println(hashMap); //{null=李四}
hashMap.put("AA",123);
hashMap.put("BB",456);
//{null=李四, AA=123, BB=456}
System.out.println(hashMap);
HashMap hashMap1 = new HashMap();
hashMap1.put("CC",123);
hashMap1.put("DD",456);
hashMap.putAll(hashMap1);
//{null=李四, AA=123, BB=456, CC=123, DD=456}
System.out.println(hashMap);
Object remove = hashMap.remove(null);
System.out.println(remove); //李四
System.out.println(hashMap); //{AA=123, BB=456, CC=123, DD=456}
//不等同于map=null。清空的是数据
hashMap.clear();
System.out.println(hashMap.size()+"\t"+hashMap); //0 {}
Object aa = hashMap.get("AA");
System.out.println(aa); //null
}
- 元素 查询的操作:
- Object get(Object key):获取指定key对应的value
- boolean containsKey(Object key):是否包含指定的key
- boolean containsValue(Object value):是否包含指定的value
- int size():返回map中key-value对的个数
- boolean isEmpty():判断当前map是否为空
- boolean equals(Object obj):判断当前map和参数对象obj是否相等
@Test
public void testMap2(){
HashMap hashMap = new HashMap();
hashMap.put("AA",123);
hashMap.put(45,123);
hashMap.put("BB",56);
hashMap.put("45",345);
//{AA=123, BB=56, 45=345, 45=123}
System.out.println(hashMap);
Object o = hashMap.get(45);
//123
System.out.println(o);
boolean b = hashMap.containsKey(45);
//true
System.out.println(b);
boolean b1 = hashMap.containsValue(123);
//true
System.out.println(b1);
hashMap.clear();
//true
System.out.println(hashMap.isEmpty());
//equals();判断两个map是否相同
hashMap.put("AA",123);
hashMap.put("bb",123);
hashMap.put("cc",123);
HashMap hashMap1 = new HashMap();
hashMap1.putAll(hashMap);
boolean equals = hashMap.equals(hashMap1);
//true
System.out.println(equals);
}
- 元视图操作的方法:
-
Set keySet():返回所有key构成的Set集合
-
Collection values():返回所有value构成的Collection集合
-
Set entrySet():返回所有key-value对构成的Set集合
/**
* 元视图的操作
*/
@Test
public void testMap03(){
HashMap hashMap = new HashMap();
hashMap.put("AA",123);
hashMap.put("bb",123);
hashMap.put("cc",123);
System.out.println("遍历map方式一:先获取key,然后根据key获取value。");
Set set = hashMap.keySet();
Iterator iterator = set.iterator();
while (iterator.hasNext()){
Object next = iterator.next();
System.out.println(next+"="+hashMap.get(next));
}
System.out.println("遍历map方式二:entrySet()");
Set set1 = hashMap.entrySet();
Iterator iterator1 = set1.iterator();
while (iterator1.hasNext()){
Object next = iterator1.next();
Map.Entry entry=(Map.Entry)next;
System.out.println(entry.getKey()+"="+entry.getValue());
}
}
3.7 练习
- 写一个方法键盘输入字符串返回一个map记录时每个字符出现的次数。
@Test
public void test02() {
//1.从键盘中获取字符串并转化为char数组
@SuppressWarnings("resource")
Scanner input=new Scanner(System.in);
System.out.println("请输入一串字符串!!!");
String str=input.next();
char[] chars=str.toCharArray();
//2.遍历数组,并存入到HashMap中去
HashMap<Character,Integer> hashMap = new HashMap<>();
for (int i = 0; i < chars.length; i++) {
hashMap.put(chars[i],hashMap.get(chars[i])==null?new Integer(1):hashMap.get(chars[i])+1);
}
Iterator<Character> iterator = hashMap.keySet().iterator();
while (iterator.hasNext()) {
Character next = iterator.next();
System.out.println(next+"出现了:"+hashMap.get(next)+"次");
}
}
-
创建一个学生类:name sex score sid,创建一个map 装10个学生:键是sid值是student对象,给所有女生分数+1,删除分数小于60的键值对,获取平均分。
@Test public void test03() { HashMap<String, Student> hashMap=new HashMap<>(); hashMap.put("1", new Student("1","zhangsan","男",70)); hashMap.put("2", new Student("2","zhangsan","男",70)); hashMap.put("3", new Student("3","zhangsan","男",50)); hashMap.put("4", new Student("4","zhangsan","男",70)); hashMap.put("5", new Student("5","zhangsan","男",70)); hashMap.put("6", new Student("6","zhangsan","女",70)); hashMap.put("7", new Student("7","zhangsan","女",80)); hashMap.put("8", new Student("8","zhangsan","女",30)); hashMap.put("9", new Student("9","zhangsan","女",70)); hashMap.put("10", new Student("10","zhangsan","女",70)); System.out.println("初始hashMap:"+hashMap); Set<String> keySet = hashMap.keySet(); Iterator<String> iterator = keySet.iterator(); ArrayList list=new ArrayList<String>(); int sum=0; while (iterator.hasNext()) { String sid=iterator.next(); Student student=hashMap.get(sid); if ("女".equals(student.getSex())) { student.setScore(student.getScore()+1); hashMap.put(sid, student); } student=hashMap.get(sid); if (student.getScore()<60) { list.add(student.getSid()); }else { sum+=student.getScore(); } } System.out.println("修改hashMap:"+hashMap); for (int i = 0; i < list.size(); i++) { hashMap.remove(list.get(i)); } System.out.println("删除小于60之后:"+hashMap); System.out.println("avg="+(sum*1.0/hashMap.size())); } class Student{ private String sid; private String name; private String sex; private int score; public String getSid() { return sid; } public void setSid(String sid) { this.sid = sid; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getSex() { return sex; } public void setSex(String sex) { this.sex = sex; } public int getScore() { return score; } public void setScore(int score) { this.score = score; } public Student(String sid, String name, String sex, int score) { super(); this.sid = sid; this.name = name; this.sex = sex; this.score = score; } public Student() { super(); } @Override public String toString() { return "Student [sid=" + sid + ", name=" + name + ", sex=" + sex + ", score=" + score + "]"; } }
4 Collections工具类
4.1 Collections概述
Collections是操作Collection和Map的工具类。其中提供了一些静态的方法对集合元素进行排序,查询和修改等操作,还提供了对集合对象设置不可变,对集合对象实现同步控制等方法。
4.2 常用的方法
1 排序操作:(均为static方法)
- reverse(List):反转 List 中元素的顺序
- shuffle(List):对 List 集合元素进行随机排序
- sort(List):根据元素的自然顺序对指定 List 集合元素按升序排序
- sort(List,Comparator):根据指定的 Comparator 产生的顺序对 List 集合元素进行排序
- swap(List,int, int):将指定 list 集合中的 i 处元素和 j 处元素进行交换
@Test
public void testCollections(){
ArrayList arrayList = new ArrayList();
arrayList.add(123);
arrayList.add(234);
arrayList.add(423);
arrayList.add(18);
//[123, 234, 423, 18]
System.out.println(arrayList);
//对集合进行反转操作
Collections.reverse(arrayList);
//输出:[18, 423, 234, 123]
System.out.println(arrayList);
//进行随机化处理
Collections.shuffle(arrayList);
System.out.println(arrayList);
//对集合中的元素进行升序排序,还有个重载的方法进行定制排序
Collections.sort(arrayList);
//[18, 123, 234, 423]
System.out.println(arrayList);
//对集合中的两个位置元素进行换位
Collections.swap(arrayList,1,3);
//[18, 423, 234, 123]
System.out.println(arrayList);
}
2 查找、替换
- Object max(Collection):根据元素的自然顺序,返回给定集合中的最大元素
- Object max(Collection,Comparator):根据 Comparator 指定的顺序,返回给定集合中的最大元素
- Object min(Collection):找出最小的元素
- Object min(Collection,Comparator):根据比较器的规则找到最小的元素
- int frequency(Collection,Object):返回指定集合中指定元素的出现次数
- void copy(List dest,List src):将src中的内容复制到dest中
- boolean replaceAll(List list,Object oldVal,Object newVal):使用新值替换List对象的所有旧值
@Test
public void testCollections02() {
ArrayList arrayList = new ArrayList();
arrayList.add(123);
arrayList.add(234);
arrayList.add(423);
arrayList.add(423);
arrayList.add(423);
arrayList.add(18);
//返回指定数的出现的次数
int frequency = Collections.frequency(arrayList, 423);
//3
System.out.println(frequency);
/* ArrayList list = new ArrayList();
Collections.copy(list,arrayList);
//java.lang.IndexOutOfBoundsException: Source does not fit in dest
//源码
//int srcSize = src.size();
//if (srcSize > dest.size())
//throw new IndexOutOfBoundsException("Source does not fit in dest");
System.out.println(list);*/
//正确的写法
List<Object> objects = Arrays.asList(new Object[arrayList.size()]);
Collections.copy(objects,arrayList);
System.out.println(objects);
objects.set(1,999);
//java.lang.UnsupportedOperationException:Arrays.asList返回的list集合不可以进行添加操作==>长度不可变。
System.out.println(objects);
//replaceAll()
Collections.replaceAll(objects,18,180);
System.out.println(objects);
}
3 线程同步的相关操作
@Test
public void testCollections03() {
ArrayList arrayList = new ArrayList();
arrayList.add(123);
arrayList.add(234);
arrayList.add(423);
arrayList.add(423);
arrayList.add(423);
arrayList.add(18);
//返回的list1是线程安全的list
List list = Collections.synchronizedList(arrayList);
}
总结:
每日一考:
1.Map存储数据的特点是什么?并指出key,value,entry存储数据的特点。
map:双列数据,存储Key-Value对
key:无序,不可重复–>Set存储
value:无序,可重复–>Collection存储
entry:无序,不可重复–>Set来存储
2.描述HashMap底层实现结构
JDK7:数组+链表
JDK8:数组+链表+红黑二叉树
3.Map的实现类有那些?各自的特点是什么。
|–HashMap:map的主要实现类,线程不安全的,效率比较高;可以存储key为null的键值对。
|–LinkedHashMap:对map进行遍历的时候可以按照添加的顺序进行遍历==>维持了一对指针。
|–TreeMap:保证按照key-value进行排序,实现排序遍历,key只能是同一类型的数据
|–Hashtable:map的古老实现类,线程安全的,效率低,不能存储null值。
|–properties:常用来处理配置文件,通常key和value是String类型。
4.如何遍历key-value键值对?
HashMap map=new HashMap();
Set set=map.keySet();
Iterator iterator=set.iterator();
while(iterator.hasNext()){
Object o=iterator.next(); //此时o是map中的key值。
System.out.printlb(o+"="+map.getValue(o))
}
5.Collection和Collections的区别?
Collection:是单列集合类接口,包含了一些基本操作,是Set和List接口的父接口。
Collections:是操作Collection和map的工具类,存在很多静态方法。
6.想要使用map,还想要保证线程安全,如何做?
1)使用HashTable,线程安全的,但是由于内部的大部分方法都由synchronized所修饰,导致效率并不高。
2)使用Collections的synchronized的方法来保证线程安全,其原理是给各个方法加上synchronized关键字,所以效率也不高。
3)使用ConcurrentHashmap。在1.7及以前,底层采用了数组(分段数组)加链表,使用segment分段锁来保证线程安全,继承了Reentranlock,尝试获取锁的时候存在并发竞争,自旋,阻塞。在jdk1.8的时候,底层采用了数组加链表加红黑树的数据结构,锁采用cas+synchronized,cas失败的时候保证自旋成功,如果再次失败使用synchronized的方式保证成功,保证效率。
CAS:compareAndSwap,比较并替换,相当于一个轻量级的锁,如果并发量比较大的时候,会出现自旋阻塞(存在忙循环),存在ABA的问题,可以使用一个戳或者版本号来进行区分。建议使用状态机或者锁。
7.synchronized的原理
1)偏向锁
2)轻量级的锁(适用于低并发)
3)自旋锁(死循环)此过程是可重入锁
4)重量级锁
n(objects);
}
3 线程同步的相关操作
```java
@Test
public void testCollections03() {
ArrayList arrayList = new ArrayList();
arrayList.add(123);
arrayList.add(234);
arrayList.add(423);
arrayList.add(423);
arrayList.add(423);
arrayList.add(18);
//返回的list1是线程安全的list
List list = Collections.synchronizedList(arrayList);
}
总结:
每日一考:
1.Map存储数据的特点是什么?并指出key,value,entry存储数据的特点。
map:双列数据,存储Key-Value对
key:无序,不可重复–>Set存储
value:无序,可重复–>Collection存储
entry:无序,不可重复–>Set来存储
2.描述HashMap底层实现结构
JDK7:数组+链表
JDK8:数组+链表+红黑二叉树
3.Map的实现类有那些?各自的特点是什么。
|–HashMap:map的主要实现类,线程不安全的,效率比较高;可以存储key为null的键值对。
|–LinkedHashMap:对map进行遍历的时候可以按照添加的顺序进行遍历==>维持了一对指针。
|–TreeMap:保证按照key-value进行排序,实现排序遍历,key只能是同一类型的数据
|–Hashtable:map的古老实现类,线程安全的,效率低,不能存储null值。
|–properties:常用来处理配置文件,通常key和value是String类型。
4.如何遍历key-value键值对?
HashMap map=new HashMap();
Set set=map.keySet();
Iterator iterator=set.iterator();
while(iterator.hasNext()){
Object o=iterator.next(); //此时o是map中的key值。
System.out.printlb(o+"="+map.getValue(o))
}
5.Collection和Collections的区别?
Collection:是单列集合类接口,包含了一些基本操作,是Set和List接口的父接口。
Collections:是操作Collection和map的工具类,存在很多静态方法。
6.想要使用map,还想要保证线程安全,如何做?
1)使用HashTable,线程安全的,但是由于内部的大部分方法都由synchronized所修饰,导致效率并不高。
2)使用Collections的synchronized的方法来保证线程安全,其原理是给各个方法加上synchronized关键字,所以效率也不高。
3)使用ConcurrentHashmap。在1.7及以前,底层采用了数组(分段数组)加链表,使用segment分段锁来保证线程安全,继承了Reentranlock,尝试获取锁的时候存在并发竞争,自旋,阻塞。在jdk1.8的时候,底层采用了数组加链表加红黑树的数据结构,锁采用cas+synchronized,cas失败的时候保证自旋成功,如果再次失败使用synchronized的方式保证成功,保证效率。
CAS:compareAndSwap,比较并替换,相当于一个轻量级的锁,如果并发量比较大的时候,会出现自旋阻塞(存在忙循环),存在ABA的问题,可以使用一个戳或者版本号来进行区分。建议使用状态机或者锁。
7.synchronized的原理
1)偏向锁
2)轻量级的锁(适用于低并发)
3)自旋锁(死循环)此过程是可重入锁
4)重量级锁