以下为本人观看尚硅谷Java学习视频所做的笔记
?
Java集合框架概述
集合、数组都是对多个数据进行存储操作的结构,简称Java容器 说明:此时的存储,主要指的是内存层面的存储,不涉及到持久化的存储
一方面,面向对象语言对事物的体现都是以对象的形式,为了方便对多个对象的操作,就要对对象进行存储。另一方面,使用Array存储对象方面具有一些弊端,而Java集合就像一种容器,可以动态地把多个对象的引用放入容器中。
- 数组在内存存储方面的特点:
- 数组初始化以后,长度就确定了。
- 数组声明的类型,就决定了进行元素初始化时的类型
- 数组在存储数据方面的弊端:
- 数组初始化以后,长度就不可变了,不便于扩展
- 数组中提供的属性和方法少,不便于进行添加、删除、插入等操作,且效率不高。同时无法直接获取存储元素的个数
- 获取数组中实际元素的个数的需求,数组没有现成的属性或方法可用
- 数组存储的数据是有序的、可以重复的,不能满足村粗无需、不可重复数据的需求 → 存储数据的特点单一
Java集合类可以用于存储数量不等的多个对象,还可用于保存具有映射关系的关联数组。
Java集合可分为Collection和Map两种体系
面试会经常问同一种集合类型的不同实现类的区别,例如问ArrayList和LinkedList的区别等等,所以应该都要掌握它们的底层实现原理
- Collection接口:单列数据,定义了存取一组对象的方法的集合
- List:元素有序、可重复的集合(“动态”数组)
- ArrayList
- LinkedList
- Vector
- Set:元素无需、不可重复的集合
- HashSet
- LinkedHashSet
- TreeSet
- Map接口:双列数据,保存具有映射关系“key-value对”的集合
- HashMap(在面试中高频被问,例如底层实现原理等,区分jdk7和jdk8)
- LinkedHashMap
- TreeMap
- Hashtable
- Properties
?
Collection接口方法
1、添加 ? add(Object obj) ? addAll(Collection coll) 2、获取有效元素的个数 ? int size() 3、清空集合 ? void clear() 4、是否是空集合 ? boolean isEmpty() 5、是否包含某个元素 ? boolean contains(Object obj): 是通过元素的equals方法来判断是否 是同一个对象 ? boolean containsAll(Collection c): 也是调用元素的equals方法来比较的。拿两个集合的元素挨个比较。 6、删除 ? boolean remove(Object obj): 通过元素的equals方法判断是否是 要删除的那个元素。只会删除找到的第一个元素 ? boolean removeAll(Collection coll): 取当前集合的差集 7、取两个集合的交集 ? boolean retainAll(Collection c): 把交集的结果存在当前集合中,不 影响c 8、集合是否相等 ? boolean equals(Object obj) 9、转成对象数组 ? Object[] toArray() 10、获取集合对象的哈希值 ? hashCode() 11、遍历 ? iterator(): 返回迭代器对象,用于集合遍历
以下代码示例 Collection接口部分方法的使用
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
public class Sample {
public static void main(String[] args) {
Collection coll = new ArrayList();
coll.add("A");
coll.add("B");
coll.add(123);
coll.add(new Date());
System.out.println(coll.size());
Collection coll1 = new ArrayList();
coll1.add(456);
coll1.add("C");
coll1.addAll(coll);
System.out.println(coll1);
coll.clear();
System.out.println(coll);
System.out.println(coll.isEmpty());
System.out.println(coll1.isEmpty());
System.out.println(coll1.contains(123));
coll.add("A");
coll.add("B");
System.out.println(coll1.containsAll(coll));
System.out.println(coll);
System.out.println(coll.remove("C"));
System.out.println(coll.remove("A"));
System.out.println(coll);
coll1.removeAll(coll);
System.out.println(coll1);
coll1.add("B");
coll1.retainAll(coll);
System.out.println(coll1);
System.out.println(coll.equals(coll1));
coll.clear();
coll.add("A");
coll.add("B");
coll1.clear();
coll1.add("B");
coll1.add("A");
System.out.println(coll.equals(coll1));
System.out.println(coll.hashCode());
Object[] arr = coll.toArray();
for (Object obj : arr) {
System.out.println(obj);
}
}
}
contains方法和remove方法注意点: contains(Object obj)方法是通过调用obj对象所做类的equals方法来判断是否存在元素的,remove方法移除前的对比也是同理,所以会存在以下情况:
import java.util.ArrayList;
import java.util.Collection;
class Person {
private String name;
private int age;
Person(String n, int a) {
name = n;
age = a;
}
}
public class ContainsTest {
public static void main(String[] args) {
Collection coll = new ArrayList();
coll.add(new String("Tom"));
System.out.println(coll.contains(new String("Tom")));
Person tom = new Person("Tom", 12);
coll.add(tom);
System.out.println(coll.contains(tom));
coll.add(new Person("Jerry", 13));
System.out.println(coll.contains(new Person("Jerry", 13)));
System.out.println(coll.remove(tom));
System.out.println(coll.remove(new Person("Jerry", 13)));
}
}
第一个打印true是因为String类重写了equals方法,equals方法比较的是String对象中的内容而不是String对象的地址。
第二个打印true是因为集合coll中存在tom对象
第三个打印false是因为Person类没有重写equals方法,所以使用的是Object类的equals方法,比较的是对象的地址值,而两个对象都是匿名对象,地址值不同,所以打印false。如果想让这种情况打印true,则需要重写Person类的equals方法
结论:向Collection接口的实现类对象中添加数据obj,要求obj所在类重写equals()
? 数组转换为集合:调用Arrays类的静态方法asList()
import java.util.Arrays;
import java.util.List;
public class Sample {
public static void main(String[] args) {
List<String> list = Arrays.asList(new String[]{"A", "B", "C"});
System.out.println(list);
List<Integer> list1 = Arrays.asList(123, 456);
System.out.println(list1);
}
}
注意:如果asList的参数是int类型的数组,会发现返回的list只有一个元素且打印的是int数组的地址,因为它把int[]作为存储元素的类型,即返回的List是List<Int[]>,而如果使用的是包装类Integer,则是List<Integer>
import java.util.Arrays;
import java.util.List;
public class Sample {
public static void main(String[] args) {
List list = Arrays.asList(new int[]{123, 456});
System.out.println(list);
List list1 = Arrays.asList(new Integer[]{123, 456});
System.out.println(list1);
}
}
?
Iterator迭代器接口
lterator对象称为迭代器(设计模式的一种),主要用于遍历Collection集合中的元素。
GOF给迭代器模式的定义为: 提供一种方法访问一个容器(container)对象中各个元素,而又不需暴露该对象的内部细节。迭代器模式,就是为容器而生。 类似于“公交车上的售票员”、“火车上的乘务员”、“空姐”。
Collection接口继承了java.lang.lterable接口,该接口有一个iterator()方法,那么所有实现了Collection接口的集合类都有一个iterator()方法,用以返回一个实现了lterator接口的对象。
lterator仅用于遍历集合,lterator木身并不提供承装对象的能力。如果需要创建lterator对象,则必须有一个被迭代的集合。
集合对象每次调用iterator()方法都得到一个全新的迭代器对象,默认游标都在集合的第一个元素之前。
- hasNext():判断是否还有下一个元素
- next():①迭代器指针下移 ②将下移以后集合在指针位置上的元素返回
- remove():在集合中删除迭代器指针上的元素,不同于集合直接调用remove方法
remove方法的注意点:
- lterator可以删除集合的元素,但是是遍历过程中通过迭代器对象的remove方法,不是集合对象的remove方法。
- 如果还未调用next()或在上一次调用next方法之后已经调用了remove方法,再调用remove(即此时迭代器指针指向的是空元素)都会报IllegalStateException。
示例代码:
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
public class Sample {
public static void main(String[] args) {
Collection coll = new ArrayList();
coll.add(123);
coll.add(456);
coll.add(new String("Tom"));
coll.add(false);
Iterator iterator = coll.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
}
}
?
使用foreach循环遍历集合元素
- Java 5.0提供了foreach循环迭代访问Collection和数组
- 遍历操作不需获取Collection或数组的长度,无需使用索引访问元素
- 遍历集合的底层调用Iterator完成操作
- foreach还可以用来遍历数组
注意以下代码:
public class Sample {
public static void main(String[] args) {
String[] arr = new String[]{"MM", "MM", "MM"};
for (int i = 0; i < arr.length; i++) {
arr[i] = "GG";
}
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
for (String s : arr) {
s = "AA";
}
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
}
}
方式2之所以没修改数组本身的值,是因为在循环中是将数组中的值赋值给了新的String对象s,然后在循环中修改的是对象s的值,原数组的值没变
?
Collection子接口一:List
- 鉴于Java中数组用来存储数据的局限性,我们通常使用List替代数组
- List集合类中元素有序、且可重复,集合中的每个元素都有其对应的顺序索引
- List容器中的元素都对应一个整数型的序号记载其在容器中的位置,可以根据予号存取容器中的元素。
- JDK API中List接口的实现类常用的有: ArrayList、LinkedList和Vector
|—Collection接口:单列集合,用来存储一个一个的对象 ? |—List接口:存储有序的、可重复的数据 → “动态”数组,替换原有的数组 ? ? |—ArrayList:作为List接口的主要实现类,线程不安全,效率高,底层使用Object[]存储 ? ? |—LinkedList:底层使用双向链表存储,对于频繁的插入、删除操作,使用此类效率比ArrayList高 ? ? |—Vector:作为List接口的古老实现类,线程安全的,效率低,底层使用Object[]存储(jdk1.0就有了,而List接口、ArrayList、LinkedList都是jdk1.2才有的)
面试题:ArrayList、LinkedList、Vector三者的异同? 同:三个类都是实现了List接口,存储数据的特点相同:存储有序的、可重复的数据 不同:见上
LinkedList: 双向链表,内部没有声明数组,而是定义了Node类型的first和last, 用于记录首末元素。同时,定义内部类Node,作为LinkedList中保存数据的基本结构。Node除了保存数据,还定义了两个变量:
- prev变量记录前一个元素的位置
- next变量记录下一个元素的位置
?
ArrayList的源码分析
jdk7情况下
ArrayList list = new ArrayList(); → 在这个语句下,底层创建了长度是10的Object[]数组elementData list.add(1); → elementData[0] = new Integer(123); … list.add(11); → 如果此次的添加导致底层elementData数组容量不够,则扩容。 默认情况下,扩容为原来容量的1.5倍,同时需要将原有数组中的数据复制到新的数组中。
结论:建议开发中使用带参的构造器:ArrayList list = new ArrayList(int capacity)
jdk8情况下
ArrayList list = new ArrayList(); → 底层Objec[] elementData初始化为{},并没有创建长度为10的数组 list.add(123); → 第一次调用add()时,底层才创建了长度为10的数组,并将数据123添加到elementData中 … 后续的添加和扩容操作与jdk7无异
小结
jdk7中的ArrayList对象的创建类似于单例的饿汉式,而jdk8中的ArrayList对象的创建类似于单例的懒汉式,延迟了数组的创建,节省内存
?
LinkedList的源码分析
LinkedList list = new LinkedList(); → 内部声明了Node类型的first和last属性,默认值为null list.add(123); → 将123封装到Node中,创建了Node对象 其中,Node的定义体现了LinkedList是双向链表,其定义为:
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;
}
}
?
Vector的源码分析
jdk7和djk8中使用Vector()构造器创建对象时,底层都创建了长度为10的数组(注意与ArrayList区分)。在扩容方面,默认扩容为原来的2倍(ArrayList是1.5倍)
?
List接口中的常用方法
List除了从Collection集合继承的方法外,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在集合中首次出现的位置,找不到的话返回-1
- int lastlndexOf(Object obj): 返回obj在当前集合中末次出现的位置,找不到的话返回-1
- Object remove(int index): 移除指定index位置的元素,并返回此元素 【注意这个是重载方法,与Collection类的remove方法区分,这里删除的是index位置的元素】
- Object set(int index, Object ele): 设置指定index位置的元素为ele
- List subList(int fromIndex, int tolndex): 返回从fromIndex到tolndex位置(不包含toIndex上的元素,是左闭右开的)的子集合
总结: 增:add(Object obj) 删:remove(int index) / remove(Object obj) 改:set(int index, Object ele) 查:get(int index) 插:add(int index, Object ele) 长度:size() 遍历:①Iterator迭代器方式 ②增强for循环 ③普通循环
注意: 以下代码之所以第一个输出3而第二个输出1,是因为当使用add方法且参数为list对象时,是将list对象整一个作为一个元素,而不是将参数list对象中的元素添加到当前list对象中,要与addAll方法区分
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class Sample {
public static void main(String[] args) {
List list = new ArrayList();
List list1 = Arrays.asList(1, 2, 3);
list.addAll(list1);
System.out.println(list.size());
System.out.println(list);
list.clear();
list.add(list1);
System.out.println(list.size());
System.out.println(list);
}
}
?
List的一个面试小题
问以下代码的输出: 其实就是在考验代码标红处的remove方法是依据index删除还是删除值为2的元素
答案是[1,2],这是因为由于add方法没有进行重载所以在add(2)中是进行了2的自动装箱,而remove方法是有重载的,所以这里传入的2就没必要进行自动装箱,直接调用的是remove(int index)方法。如果是要删除值为2的元素,写法应该是 list.remove(new Integer(2));
?
Collection子接口二:Set
- Set接口是Collection的子接口,Set接口没有提供额外的方法
- Set集合具有无序性:不等于随机性。以HashSet为例,存储的数据在底层数组中并非按照数组索引的顺序添加,而是根据数据的哈希值决定
- Set集合具有不可重复性:不允许包含相同的元素,如果试把两个相同的元素加入同一个Set集合中,则添加操作失败
- Set判断两个对象是否相同不是使用==运算符,而是根据equals()方法
添加元素的过程(以HashSet为例): 我们向HashSet中添加元素a,首先调用元素a所在类的hashCode()方法,计算元素a的哈希值,此哈希值接着通过某种计算出在HashSet底层数组中的存放位置(即为索引位置),判断数组此位置上是否已经有元素: ? 如果此位置上没有其他元素,则元素a添加成功 → 情况1 ? 如果次位置上有其他元素b(或以链表形式存在的多个元素),则比较元素a与元素b的hash值: ? ? 如果hash值不同,则元素a添加成功 → 情况2 ? ? 如果hash值相同,进而需要调用元素a所在类的equals()方法: ? ? ? equals()返回true,元素a添加失败 ? ? ? equals()返回false,元素a添加成功 → 情况3
对于添加成功的情况2和情况3而言:元素a与已经存在指定索引位置上数据以链表的方式存储。 jdk7:元素a放在数组中,指向原来的元素 jdk8:原来的元素在数组中,指向元素a 巧记:七上八下 HashSet底层:数组+链表(HashMap)
|—Collection接口:单列集合,用来存储一个一个的对象 ? |—Set接口:存储无序的、不可重复的数据 → 高中讲的“集合” ? ? |—HashSet:作为Set接口的主要实现类,线程不安全,可以存储null值 ? ? ? |—LinkedHashSet:作为HashSet的子类,遍历其内部顺序时,可以按添加的顺序遍历 ? ? |—TreeSet:可以按照添加对象指定属性进行排序 ?
重写hashCode()方法
原则:
- 在程序运行时,同一个对象多次调用hashCode()方法应该返回相同的值。
- 当两个对象的equals()方法比较返回true时,这两个对象的hashCode()方法的返回值也应相等
- 技巧:对象中用作equals()方法比较的Field,都应该用来计算hashCode值
- 当一个类有自己特有的“逻辑相等”概念,当改写equals()的时候,总是要改写hashCode(),根据一个类的equals方法(改写后),两个截然不同的实例有可能在逻辑上是相等的,但是,根据Object.hashCode()方法,它们仅仅是两个对象
- 因此,违反了**“相等的对象必须具有相等的散列码”**
- 结论: 复写equals方法的时候一般都需要同时复写hashCode方法。通常参与计算hashCode的对象的属性也应该参与到equals()中进行计算。
以Eclipse/IDEA为例,在自定义类中可以调用工具自动重写equals和hashCode。问题: 为什么用Eclipse/IDEA复写hashCode方法,有31这个数字?
- 选择系数的时候要选择尽量大的系数。因为如果计算出来的hash地址越大,所谓的“冲突”就越少,查找起来效率也会提高(减少冲突)
- 并且31只占用5bits, 相乘造成数据溢出的概率较小
- 31可以由i*31== (i<<5)-1来表示,现在很多虚拟机里面都有做相关优化(提高算法效率)
- 31是一个素数,素数作用就是如果我用一个数字来乘以这个素数,那么最终出来的结果只能被素数本身和被乘数还有1来整除!(减少冲突)
HashSet
- HashSet是Set接口的典型实现,大多数时候使用Set集合时都使用这个实现类
- HashSet按 Hash 算法来存储集合中的元素,因此具有很好的存取、查找、删除性能
- HashSet具有以下特点:
- 不能保证元素的排列顺序
- HashSet不是线程安全的
- 集合元素可以是 null
- HashSet集合判断两个元素相等的标准: 两个对象通过hashCode()方法比较相等,并且两个对象的equals()方法返回值也相等
- 对于存放在Set容器中的对象,对应的类一定要重写equals()和hashCode(Objectobj)方法,以实现对象相等规则。即:“相等的对象必须具有相等的散列码”。
HashSet底层使用的是HashMap来实现的,即使用HashMap的key来记录HashSet中的每一个元素,但同时对应的value也不是设为null,而是通过new一个static的Object()对象,没有特殊含义但用于避免调用时发生空指针问题,同时使用的是static修饰,所以所有的HashSet元素作为key在HashMap中映射到的都是同一个Object对象(这里理解就可以了,面试一般不会问)
LinkedHashSet
- LinkedHashSet是HashSet的子类
- LinkedHashSet根据元素的hashCode值来决定元素的存储位置,但它同时使用双向链表维护元素的次序,这使得元素看起来是以插入顺序保存的(但它仍然是存储无序的数据,但凡是用hashCode值来决定元素的存储位置的,都是无序的,不要跟插入顺序搞混)
- LinkedHashSet插入性能略低于HashSet,但在迭代访问Set里的全部元素时有很好的性能。
- LinkedHashSet不允许集合元素重复。
TreeSet
- 向TreeSet中添加的数据,要求是相同类的对象(不然没法进行排序)
- TreeSet是 SortedSet接口的实现类,TreeSet可以确保集合元素处于排序状态
- TreeSet和TreeMap底层都是使用红黑树结构存储数据,特点是:有序,查询速度比List快
- 新增的方法如下:(了解)
- Comparator comparator()
- Object first()
- Object last()
- Object lower(Object e)
- Object higher(Object e)
- SortedSet subSet(fromElement, toElement)
- SortedSet headSet(toElement)
- SortedSet tailSet(fromElement)
- TreeSet两种排序方法:
- 自然排序(即实现Comparable接口,且自然排序中比较两个对象是否相等的标准:compareTo()是否返回0,而不是使用equals())
- 定制排序(使用Comparator,且定制排序中比较两个对象是否相等的标准:compare()是否返回0,而不是使用equals())
- 默认情况下,TreeSet采用自然排序。
Set的两道面试题
1、在List内去除重复数字值,要求尽量简单
答案:直接使用HashSet,注意这样做的话list内的对象如果是自定义类,记得重新写hashCode方法和equals方法 2、请问以下代码的输出是什么 答案:(如果答案看不懂,就看尚硅谷java视频p547)要注意Person类已经重写了hashCode方法和equals方法,下面是每一个输出的解析: 第一个输出中,p1对象并没有被移除,这是因为p1的name属性值进行了改变,其哈希值也有变化,但是p1在set中存储的位置仍然是使用之前的哈希值计算得来的,所以在调用remove方法时,使用p1新的哈希值进行检测p1是否存在于set中,所以检测不到,p1没有被移除 第二个输出中,添加新对象,这时候是会成功添加的,set里面会存在两个name都是"CC"的对象,产生了重复元素。原因是因为在加入新的对象时,使用新对象的哈希值判断是否存在这个元素,而p1存储的位置是使用p1之前的哈希值计算得来的,所以判断结果是没有重复存在这个元素,所以会添加成功 第三个输出中,添加新对象,这时候也是会成功添加的。因为在加入新的对象时,使用新对象的哈希值判断是否存在这个元素,计算得出的存储位置跟p1的存储位置是相同的,发现这个存储位置上存在元素,会调用新对象的equals方法与p1进行比较,结果两人的哈希值不同,所以会添加成功 ?
Map接口
|—Map:双列数据,存储key-value对的数据,类似于高中的函数 y = f(x) ? |—HashMap:作为Map的主要实现类,线程不安全,效率高,可以存储null的key和value ? ? |—LinkedHashMap:保证在遍历map元素时,可以按照添加的顺序实现遍历。原因:在原有的HashMap底层结构基础上,添加了一对指针,指向前一个和后一个元素的,对于频繁的遍历操作,执行效率高于HashMap ? |—TreeMap:保证按照添加的key-value对进行排序,实现排序遍历,此时考虑key的自然排序或定制排序,因为具有排序特点,底层使用的是红黑树 ? |—Hashtable:作为古老的实现类,线程安全的,效率低,不能存储null的key和value(jdk1.0才有,Map、HashMap、TreeMap都是在jdk1.2出现的,LinkedHashMap是在jdk1.4出现的) ? ? |—Properties:常用来处理配置文件,key和value都是String类型 ? 面试题: 1、HashMap和Hashtable的异同? 答:见上 2、CurrentHashMap与Hashtable的异同?(暂时不讲)
Map的结构
一个key-value对为一个Entry对象 与函数 y = f(x) 进行类比,x为key,y为value,(x, y)是Entry
- Map中的key:无序的、不可重复的,使用Set存储所有的key,具体使用哪种Set的实现类会使key有不同的特征 → key所在的类要重写equals()和hashCode()(以HashMap为例)
- Map中的value:无序的、可重复的,使用Collection存储所有的value → value所在的类要重写equals()(这是因为查找value依据的是key,所以value不用重写hashCode())
- Map中的Entry:无序的、不可重复的,使用Set存储所有的entry
HashMap
HashMap的底层: jdk7及之前:数组+链表 jdk8:数组+链表+红黑树
高频面试题:HashMap的底层实现原理 以jdk7为例说明: HashMap map = new HashMap():在实例化之后,底层创建了长度为16的一维数组Entry[] table map.put(key, value):首先,调用key所在类的hashCode()计算key哈希值,此哈希值经过某种算法计算以后,得到在Entry数组中的存放位置: ? 如果此位置上的数据为空,此时的key-value添加成功 → 情况1 ? 如果此位置上的数据不为空,意味着此位置上存在一个或多个数据(以链表形式存在),比较key和已经存在的一个或多个数据的哈希值 ? ? 如果key的哈希值与已经存在的数据的哈希值不相同,此时key-value添加成功 → 情况2 ? ? 如果key的哈希值与已经存在的某一个数据的哈希值相同,继续进行比较: ? ? ? 如果equals()返回false:此时key-value添加成功 → 情况3 ? ? ? 如果equals()返回true:使用新的键值对替换原本的键值对,实现了修改功能
补充:关于情况2和情况3,此时的key-value和原来的数据以链表的方式存储
在不断的添加过程中,会涉及到扩容问题,当已存储个数超出临界值(threshold,是通过数组长度乘加载因子计算得到的,默认加载因子为0.75,则threshold值为12)且要新存放的位置非空值时(即已经有存放元素),会进行扩容,默认的扩容方式:扩容为原来容量的2倍,并将原有的数据复制过来
jdk8与jdk7在底层实现方面的不同处: 1、new HashMap():底层没有创建一个长度为16的数组 2、jdk8中的数组是Node[],而不是Entry[] 3、首次使用put方法时,底层才创建长度为16的数组 4、jdk7底层结构只有数组+链表,jdk中的底层结构是数组+链表+红黑树,当数组的某一个索引位置上的元素以链表形式存在的数据个数大于8且当前数组长度大于64时,此时此索引位置上的所有数据改为使用红黑树存储(提高了查找效率) 5、这里也存在七上八下的区别,参照HashSet
HashMap源码中存在的一些变量的解释: DEFAULT_INITIAL_CAPACITY : HashMap的默认容量,默认是16 DEFAULT_LOAD_FACTOR: HashMap的默认加载因子,值是0.75 threshold: 扩容的临界值,为容量×填充因子 → 16 × 0.75 = 12 TREETFY_ THRESHOLD: Bucket中链表长度大于该默认值时转化为红黑树,值是8 MIN_TREEIFY_CAPACITY:桶中的Node被树化时最小的hash表容量,值是64
对于HashMap的源码分析其实还包含很多这里没有提到的内容,有兴趣可以看尚硅谷java视频p552和p553听老师的源码分析
LinkedHashMap
底层实现原理:LinkedHashMap中的Entry继承HashMap中的Node,同时添加了新的before和after,用于记录添加的先后顺序(双向链表)
Map中的常用方法
添加、删除、修改操作:
- 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中的所有数据
元素查询的操作:
- 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是否相等
元视图操作的方法:
- Set keySet(): 返回所有key构成的Set集合
- Collection values(): 返回所有value构成的Collection集合
- Set entrySet(): 返回所有key-value对构成的Set集合
元视图操作方法其实对应的就是key、value、entry各自的实现,我们上面讲到key、entry是用Set实现的,value是用Collection实现的,于是就有对应的方法
元视图操作方法示例代码:
import java.util.*;
public class Sample {
public static void main(String[] args) {
Map map = new HashMap();
map.put("AA", 123);
map.put(45, 1234);
map.put("BB", 56);
Set set = map.keySet();
Iterator iterator = set.iterator();
while (iterator.hasNext())
System.out.print(iterator.next() + " ");
System.out.println();
Collection values = map.values();
for (Object obj : values)
System.out.print(obj + " ");
System.out.println();
Set entrySet = map.entrySet();
Iterator iterator1 = entrySet.iterator();
while (iterator1.hasNext()) {
Object obj = iterator1.next();
Map.Entry entry = (Map.Entry) obj;
System.out.println(entry.getKey() + " --> " + entry.getValue());
}
Set set2 = map.keySet();
Iterator iterator2 = set2.iterator();
while (iterator2.hasNext()) {
Object key = iterator2.next();
Object value = map.get(key);
System.out.println(key + " ===> " + value);
}
}
}
输出:
AA BB 45 123 56 1234 AA --> 123 BB --> 56 45 --> 1234 AA ===> 123 BB ===> 56 45 ===> 1234
总结: 增:put(Object key, Object value) 删:remove(Object key) 改:put(Object key, Object value) 查:get(Object key) 插:没有,因为是无序的 长度:size() 遍历:keySet() / values() / entrySet()
TreeMap
向TreeMap中添加key-value,要求key必须是由同一个类创建的对象,因为要按照key进行排序:自然排序 / 定制排序 注意:自然排序和定制排序都只能按照key排序,key是由Set实现的,所以详细的排序方法可以参考TreeSet,都是通过实现Comparable或Comparator
Properties
- Properties类是Hashtable的子类,该对象用于处理属性文件
- 由于属性文件里的key、value都是字符串类型,所以 Properties 里的 key 和 value 都是字符串类型
- 存取数据时,建议使用setProperty(String key, String value)方法和getProperty(String key)方法
?
Collections工具类
- Collections是一个操作Set、List和Map等集合的工具类(类似的,操作数组的工具类是Arrays)
- Collections中提供了一系列静态的方法对集合元素进行排序、查询和修改等操作,还提供了对集合对象设置不可变、对集合对象实现同步控制等方法
Collections常用方法
-
排序操作: (均为static方法)
- reverse(List): 反转List中元素的顺序
- shuffle(List): 对List集合元素进行随机排序
- sort(List): 根据元素的自然顺序对指定List集合元素按升序排序
- sort(List,Comparator): 根据指定的Comparator产生的顺序对List集合元素进行排序
- swap(List,int,int): 将指定list集合中的i处元素和j处元素进行交换
-
查找、替换操作:
- 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对象的所有旧值
注意copy方法的使用:
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
public class Sample {
public static void main(String[] args) {
List list = new ArrayList();
list.add(1);
list.add(2);
list.add(3);
List dest = Arrays.asList(new Object[list.size()]);
Collections.copy(dest, list);
System.out.println(dest);
}
}
上文所述后来出现的高效率的集合都是线程不安全的,Collections类中提供了多个 synchronizedXxx() 方法,该方法可使将指定集合包装成线程同步的集合,从而可以解决多线程并发访问集合时的线程安全问题
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class Sample {
public static void main(String[] args) {
List list = new ArrayList();
list.add(1);
list.add(2);
list.add(3);
List list1 = Collections.synchronizedList(list);
}
}
面试题:Collection和Collections的区别
|