容器框架
Java容器是必须掌握的Java内置工具,非常非常非常重要,而且必须熟悉使用方法和原理(源码),容器原理必须会,总之就是超级重要。每种容器都有各自的特点,要掌握这些容器的特点以及实现的原理。虽然容器有很多,但是常用的就那么几个,本文对不太常用的或者已经淘汰的容器只做简单介绍,重点介绍常用容器。
前置知识
容器涉及到的基础知识点特别多,想学透容器必须得会以下的前置知识,不会的话估计也只能学个使用方法,尤其是前三点必须要会的,不然根本没法学,不会的先去补完之后再学容器!
- 扎实的数据结构功底,数组、链表、栈、队列、哈希表、红黑树都得掌握
- Java的比较器接口Comparable接口和Comparator接口
- Java面向对象的基础知识和Object类中的equals、hashCode方法
- 匿名内部类(非必须)
- 流Stream(非必须)
- 函数式接口(非必须)
Collection
Collection是单列容器的父类,分别介绍List、Set和Deque
List
List特点是支持索引下标,类似于数组。
ArrayList与LinkedList比较
ArrayList和LinkedList的优缺点都是相辅相成的,作为互相的补充。
- 数据结构方面
- ArrayList底层通过数组实现,物理存储空间连续
- LinkedList底层是双向链表,物理存储不连续
- 优缺点
- ArrayList是固定容器,有初始容量为10,当容器满了之后需要扩容,每次扩容为原来1.5倍,但是LinkedList没有这个问题,因为它是链表,按需分配长度。
- LinkedList不连续存储,又要满足List下标访问的特点,因此每个节点都一个index字段,每次查询都要遍历链表,源码中是通过双向链表前后同时向中间靠拢来遍历的,提高查询速度。
- 使用技巧
- ArrayList查询性能更高,LinkedList插入删除性能更高,因此可以按照实际需求选择容器。
- ArrayList创建时支持指定初始容量,如果对数据规模有个预估,最好指定,避免触发自动扩容,因为自动扩容会影响代码性能。
ArrayList使用
ArrayList基本方法,当然还有很多有用的方法,需要大家自己去探索。
boolean add(E e); // 加入元素e
public E get(int index) // 获取下标为index的元素
boolean remove(int index); // 移除下标为index的元素
int size(); // 返回元素个数
boolean contains(Object o); // 是否包含元素o
Iterator iterator(); // 返回迭代器
示例代码ArrayList的增删改查和遍历,ArrayList遍历通常可以有4中方法
public class Demo01ArrayList {
public static void main(String[] args) {
List<Student> arraylist = new ArrayList<>();
// List增
arraylist.add(new Student(1,"zzz","beijing"));
arraylist.add(new Student(2,"yasuo","japan"));
arraylist.add(new Student(3,"yongen","japan"));
arraylist.add(new Student(4,"cat","germany"));
// List删
arraylist.remove(1); // 这个方法可以传入下标也可以传入要删除的对象
// List改 就是在相应的位置重新加入一个对象就覆盖了
// List查
arraylist.get(2); // 返回相应下标对应的值
// 遍历
// 1.直接for循环通过下标访问
for (int i = 0; i < arraylist.size(); i++) {
System.out.println(arraylist.get(i));
}
// 2.Iterator迭代器
Iterator<Student> iterator = arraylist.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
// 3.增强for循环
for(Student student : arraylist){
System.out.println(student);
}
// 4.流 *** 实际开发一般都用这个
arraylist.stream().forEach(System.out::println);
}
}
Student类
public class Student {
int id;
String name;
String address;
public Student() {
}
public Student(int id, String name, String address) {
this.id = id;
this.name = name;
this.address = address;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
@Override
public String toString() {
return id + "\t\t" + name + "\t\t" + address;
}
}
LinkedList使用
LinkedList基本方法和ArrayList几乎一样,使用上也一样,示例代码中ArrayList改成LinkedList即可,为节省篇幅不贴代码了。虽然操作都一样,但是根据使用场景不同,一定要区分两种List,一定要掌握两种List的优缺点对比。
boolean add(E e); // 加入元素e
public E get(int index) // 获取下标为index的元素
boolean remove(int index); // 移除下标为index的元素
int size(); // 返回元素个数
boolean contains(Object o); // 是否包含元素o
Iterator iterator(); // 返回迭代器
Set
Set是一种没有重复元素的单列集合,它是底层数据结构是基于Map来实现的,对你没有听错,是基于Map实现的,Map是双列的集合key-value,其key也具有不重复的特性,只保存key,所有的value都设置成一个空的Object就可以了。这个数据结构不理解的话,还需要补一下数据结构。
Set的使用
public class Demo01Set {
public static void main(String[] args) {
Set<Integer> set = new HashSet<>(); // 以HashSet为例
// 1.增
set.add(0);
set.add(1);
set.add(2);
set.add(3);
set.add(4);
set.add(0);
System.out.println(set); // [0, 1, 2, 3, 4] 重复元素0只出现一次
// 2.删
set.remove(2);
System.out.println(set); // [0, 1, 3, 4] 删掉了2
// 3.查 只能遍历
// 遍历方法1.迭代器
Iterator<Integer> iterator = set.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next());
}
// 遍历方法2.增强for循环
for(Integer i : set){
System.out.println(i);
}
// 遍历方法3.流
set.stream().forEach(System.out::println);
}
}
HashSet与TreeSet比较
- 数据结构
- HashSet也是数组+链表(链表长度>8时转化红黑树)
- TreeSet是红黑树
- 唯一性实现原理
- HashSet是先通过哈希值定位到哈希表,再根据equals判定元素是否存在
- TreeSet是根据排序器定义的排序规则判定元素是否相同,同时进行排序
- 特点
- HashSet无序存储,存取速度快
- TreeSet存储有顺序,存取速度不如HashSet
- 使用技巧
- 对于不需要有序存储的元素,使用HashSet
- 需要有序存储的选择TreeSet
- 使用HashSet必须要重写equals和hashCode方法
- 使用TreeSet必须要实现Comparable接口重写compareTo方法或者是对Set传入一个实现Comparator接口并重写compare方法的外部比较器
两个使用到的类
第一个是没有重写equals、hashCode、compareTo方法的Student
第二个是重写了的Student,通过使用HashSet与TreeSet体会重写不重写的区别
public class StudentNonOverride {
int id;
String name;
int age;
public StudentNonOverride() {
}
public StudentNonOverride(int id, String name, int age) {
this.id = id;
this.name = name;
this.age = age;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
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;
}
@Override
public String toString() {
return "{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
'}';
}
}
public class StudentWithOverride implements Comparable{
int id;
String name;
int age;
public StudentWithOverride() {
}
public StudentWithOverride(int id, String name, int age) {
this.id = id;
this.name = name;
this.age = age;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
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;
}
@Override
public String toString() {
return "{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
StudentWithOverride that = (StudentWithOverride) o;
return id == that.id && age == that.age && Objects.equals(name, that.name);
}
@Override
public int hashCode() {
return Objects.hash(id, name, age);
}
@Override
public int compareTo(Object o) {
if(o instanceof StudentWithOverride){
int diff = this.age - ((StudentWithOverride) o).getAge();
return diff != 0 ? diff : this.getId() - ((StudentWithOverride) o).getId();
}
return 0;
}
}
StudentWithOverride中重写compareTo方法定义的比较规则是先按年龄比较,如果年龄相同,则按id比较。
HashSet示例代码
下面是HashSet的使用
public class Demo02HashSet {
public static void main(String[] args) {
hashSetDemo();
}
public static void hashSetDemo(){
String s1 = "张三";
String s2 = "李四";
String s3 = "王二麻子";
String s4 = "李四";
Set<String> set1 = new HashSet<>();
set1.add(s1);
set1.add(s2);
set1.add(s3);
set1.add(s4);
System.out.println(set1); // [李四, 张三, 王二麻子] 结果正常
StudentNonOverride sn1 = new StudentNonOverride(1,"张三",23);
StudentNonOverride sn2 = new StudentNonOverride(2,"李四",18);
StudentNonOverride sn3 = new StudentNonOverride(3,"王二麻子",29);
StudentNonOverride sn4 = new StudentNonOverride(2,"李四",18);
Set<StudentNonOverride> set2 = new HashSet<>();
set2.add(sn1);
set2.add(sn2);
set2.add(sn3);
set2.add(sn4); // [{id=2, name='李四', age=18}, {id=2, name='李四', age=18}, {id=1, name='张三', age=23}, {id=3, name='王二麻子', age=29}]
System.out.println(set2); // 输出结果看到有2个李四,不满足Set的唯一性,因为调用了默认的hashCode方法
StudentWithOverride sw1 = new StudentWithOverride(1,"张三",23);
StudentWithOverride sw2 = new StudentWithOverride(2,"李四",18);
StudentWithOverride sw3 = new StudentWithOverride(3,"王二麻子",29);
StudentWithOverride sw4 = new StudentWithOverride(2,"李四",18);
Set<StudentWithOverride> set3 = new HashSet<>();
set3.add(sw1);
set3.add(sw2);
set3.add(sw3);
set3.add(sw4); // [{id=3, name='王二麻子', age=29}, {id=1, name='张三', age=23}, {id=2, name='李四', age=18}]
System.out.println(set3); // 输出结果正常
}
}
分析输出结果可以发现:
String的Set和StudentWithOverride的Set是正常的,因为他们都重写了equals和hashCode方法!StudentNonOverride的Set是不对的,没有重写equals和hashCode根本无法区分哪些元素是相同的,之所以没报错,是因为它的父类Object中有默认的equals和hashCode的方法,但是不管用,因为从父类继承来的方法只是根据地址值来区分元素是否相同,所以当然判定sn2和sn4是不同的元素。
TreeSet示例代码
public class Demo03TreeSet {
public static void main(String[] args) {
TreeSetComparableDemo();
TreeSetComparatorDemo();
}
public static void TreeSetComparableDemo(){
String s1 = "张三";
String s2 = "李四";
String s3 = "王二麻子";
String s4 = "李四";
Set<String> set1 = new TreeSet<>();
set1.add(s1);
set1.add(s2);
set1.add(s3);
set1.add(s4);
System.out.println(set1); // [李四, 张三, 王二麻子] 结果正常
/* StudentNonOverride sn1 = new StudentNonOverride(1,"张三",23);
StudentNonOverride sn2 = new StudentNonOverride(2,"李四",18);
StudentNonOverride sn3 = new StudentNonOverride(3,"王二麻子",29);
StudentNonOverride sn4 = new StudentNonOverride(2,"李四",18);
Set<StudentNonOverride> set2 = new TreeSet<>(); // 抛出异常java.lang.ClassCastException 因为类StudentNonOverride没有实现比较器
set2.add(sn1);
set2.add(sn2);
set2.add(sn3);
set2.add(sn4);
System.out.println(set2);*/
StudentWithOverride sw1 = new StudentWithOverride(1,"张三",23);
StudentWithOverride sw2 = new StudentWithOverride(2,"李四",18);
StudentWithOverride sw3 = new StudentWithOverride(3,"王二麻子",18);
StudentWithOverride sw4 = new StudentWithOverride(2,"李四",18);
Set<StudentWithOverride> set3 = new TreeSet<>();
set3.add(sw1);
set3.add(sw2);
set3.add(sw3);
set3.add(sw4); // [{id=2, name='李四', age=18}, {id=3, name='王二麻子', age=18}, {id=1, name='张三', age=23}]
System.out.println(set3); // 输出结果符合预期
}
public static void TreeSetComparatorDemo(){
StudentNonOverride sn1 = new StudentNonOverride(1,"张三",23);
StudentNonOverride sn2 = new StudentNonOverride(2,"李四",18);
StudentNonOverride sn3 = new StudentNonOverride(3,"王二麻子",18);
StudentNonOverride sn4 = new StudentNonOverride(2,"李四",18);
Set<StudentNonOverride> set2 = new TreeSet<>(new Comparator<StudentNonOverride>() {
@Override
public int compare(StudentNonOverride o1, StudentNonOverride o2) {
int diff = o1.getAge() - o2.getAge();
return diff != 0 ? diff : o1.getId() - o2.getId();
}
}); // 匿名内部类的方式实现comparator接口中的compare方法
set2.add(sn1);
set2.add(sn2);
set2.add(sn3);
set2.add(sn4);
System.out.println(set2); // [{id=2, name='李四', age=18}, {id=3, name='王二麻子', age=18}, {id=1, name='张三', age=23}]
}
}
分析TreeSetComparableDemo结果可知:
String和StudentWithOverride使用TreeSet结果正常,符合预期,StudentNonOverride使用TreeSet会直接抛异常,StudentNonOverride不具备比较能力,之所以这个会抛异常,其实因为类的比较能力不像equals和hashCode方法那样可以从父类继承,这个不写的话干脆就没有,当然会报异常。
为了使得类具备“比较能力”有两种方式,第一种是类实现Comparable接口,重写comparaTo方法,使得类本身具备排序能力,叫“自然排序”,就像类StudentWithOverride那样;第二种是给TreeSet传入一个比较器,叫“外部比较器”,将一个实现了Comparator接口并重写compara方法的类作为参数传入到TreeSet中,就像方法TreeSetComparatorDemo中。但是我是用了匿名内部类,这块不会的额外再补补吧。
Deque
Deque是一种队列容器,数据结构是一个双端队列,内置了支持队列和栈的基本操作方法,还有一些高级方法,一般使用这个容器就是为了使用栈或队列。
ArrayDeque和LinkedList比较
- 数据结构
- ArrayDeque是一个支持队列的数组,有一个front和一个rear指针,分别指向队头和队尾,本质上是一个循环队列,当front == rear时队列为空,当(rear+1)%maxsize=front时队列为满。
- LinkedList就是相比于数组的,是个双端队列,其实和List里面的LinkedList一样,刚好可以复用。
- 优缺点
- 其实说到数组和链表,无非就是增删和查找的性能优劣势,但是如果这个容器仅作为栈和队列,插入删除都是在栈顶或队头队尾,不存在说在中间某个位置插入的情况 ,选择哪个性能或许没什么影响?或者有什么我想不到的场景,求高手指点。
常用使用方法
还有很多其他方法,需要的时候自查Api文档就好
boolean offer(E e)将e添加到末尾
E poll() 检索但是删除列表的头
E pop()当列表当栈使用时,出栈
void push(E e)将元素入栈
public class DequeDemo {
public static void main(String[] args) {
Deque<Integer> dequeDemo01 = new ArrayDeque<>(); // Deque当栈使用
dequeDemo01.push(0);
dequeDemo01.push(1);
dequeDemo01.push(2);
System.out.println(dequeDemo01); // [2, 1, 0]
System.out.println(dequeDemo01.pop()); // 2
System.out.println(dequeDemo01); // [1, 0]
Deque<Integer> dequeDemo02 = new ArrayDeque<>(); // Deque当队列用
dequeDemo02.offer(0);
dequeDemo02.offer(1);
dequeDemo02.offer(2);
System.out.println(dequeDemo02); // [0, 1, 2]
System.out.println(dequeDemo02.poll()); // 0
System.out.println(dequeDemo02); // [1, 2]
// 这里只列举ArrayDeque,因为从操作上和LinkedList()没有任何区别,这个容器还是比较简单的
}
}
Map
Map是一种双列容器,由key-value键值对组成,一个键对应一个值,特点是key不会重复,和Set一样,这也是为什么Set底层可以通过Map实现的原因之一。
HashMap与TreeMap的区别
- 数据结构
- HashMap底层是一个数组(哈希表)+链表(长度>8转化为红黑树)
- TreeMap底层就是一颗红黑树
- 实现原理
- HashMap先通过key的hashCode定位到哈希表中,再根据key的equals从链表中搜索对应的key,如果没搜索到,则将key-value插入到链表中,如果搜到,则替换原来的key-value
- TreeMap是通过key的比较器结果判定值大小以及是否相同元素的
- 使用技巧
- HashMap有扩容问题,在可预知元素个数的情况下,初始化时最好指定hashMap大小
- HashMap装的类一定要重写equals和hashCode方法,TreeMap一定要实现比较器
- 对于需要指定存储顺序的类,使用TreeMap,否则使用HashMap
Map使用
public class Demo01Map {
public static void main(String[] args) {
Map<String,Integer> map = new HashMap<>(); // 以HashMap为例
// 1.增
map.put("first", 1);
map.put("second", 2);
map.put("third", 3);
map.put("fourth", 4);
// 2.删
map.remove("third");
// 3.改
map.put("fourth", 3);
// 4.查
System.out.println(map.get("fourth"));
// 5.遍历 都是通过set间接的取值
// 遍历方法1.取出keySet即键的集合,再遍历keySet,根据key取出value
Set<String> strings = map.keySet();
for(String s : strings){
System.out.print(map.get(s) + " "); // 3 1 2
}
System.out.println();
// 遍历方法2.取出键值对的集合entrySet,再遍历entrySet集合,通过entrySet.getValue获取值
Set<Entry<String, Integer>> entries = map.entrySet();
for (Entry<String, Integer> entry : entries){
System.out.print(entry.getValue() + " "); // 3 1 2
}
System.out.println();
// 遍历方法3.流
map.entrySet().stream().forEach(entry -> System.out.print(entry.getValue() + " ")); // 3 1 2
}
}
HashMap示例代码
HashMap重中之重,它的数据结构还有源码都很重要,基本上面试必问。
下面的两段代码中的StudentNonOverride和StudentNonOverride与Set中的两个使用到的类相同,直接复制过来就好。
public class Demo02HashMap {
public static void main(String[] args) {
hashMapDemo();
}
public static void hashMapDemo(){
String s1 = "张三";
String s2 = "李四";
String s3 = "王二麻子";
String s4 = "李四";
Map<String, String> map1 = new HashMap<>();
map1.put(s1, "s1");
map1.put(s2, "s2");
map1.put(s3, "s3");
map1.put(s4, "s4");
System.out.println(map1); // {李四=s4, 张三=s1, 王二麻子=s3}
SetDemo.StudentNonOverride sn1 = new SetDemo.StudentNonOverride(1,"张三",23);
SetDemo.StudentNonOverride sn2 = new SetDemo.StudentNonOverride(2,"李四",18);
SetDemo.StudentNonOverride sn3 = new SetDemo.StudentNonOverride(3,"王二麻子",29);
SetDemo.StudentNonOverride sn4 = new SetDemo.StudentNonOverride(2,"李四",18);
Map<StudentNonOverride, String> map2 = new HashMap<>();
map2.put(sn1, "sn1");
map2.put(sn2, "sn2");
map2.put(sn3, "sn3");
map2.put(sn4, "sn4");
System.out.println(map2);
// {{id=2, name='李四', age=18}=sn4, {id=2, name='李四', age=18}=sn2, {id=1, name='张三', age=23}=sn1, {id=3, name='王二麻子', age=29}=sn3}
// 观察StudentNonOverride没有重写equals和hashCode导致sn2和sn4相同的key,都存到了map里,不符合map的唯一性
SetDemo.StudentWithOverride sw1 = new SetDemo.StudentWithOverride(1,"张三",23);
SetDemo.StudentWithOverride sw2 = new SetDemo.StudentWithOverride(2,"李四",18);
SetDemo.StudentWithOverride sw3 = new SetDemo.StudentWithOverride(3,"王二麻子",29);
SetDemo.StudentWithOverride sw4 = new SetDemo.StudentWithOverride(2,"李四",18);
Map<StudentWithOverride, String> map3 = new HashMap<>();
map3.put(sw1, "sw1");
map3.put(sw2, "sw2");
map3.put(sw3, "sw3");
map3.put(sw4, "sw4");
System.out.println(map3);
// {{id=3, name='王二麻子', age=29}=sw3, {id=1, name='张三', age=23}=sw1, {id=2, name='李四', age=18}=sw4}
// StudentWithOverride重写equals和hashCode使得sw2和sw4重复的key不能存到同一个map里
}
}
从结果中可以分析得出:
String和StudentWithOverride都重写了equals和hashCode方法,所以使用HashMap是输出的结果是正常的,StudentNonOverride输出的结果是错误的,因为有重复的key出现。
TreeMap示例代码
public class Demo03TreeMap {
public static void main(String[] args) {
treeMapDemo();
}
public static void treeMapDemo(){
String s1 = "张三";
String s2 = "李四";
String s3 = "王二麻子";
String s4 = "李四";
Map<String, String> map1 = new TreeMap<>();
map1.put(s1, "s1");
map1.put(s2, "s2");
map1.put(s3, "s3");
map1.put(s4, "s4");
System.out.println(map1); // {张三=sn1, 李四=s4, 王二麻子=s3} 排除重复key
/*SetDemo.StudentNonOverride sn1 = new SetDemo.StudentNonOverride(1,"张三",23);
SetDemo.StudentNonOverride sn2 = new SetDemo.StudentNonOverride(2,"李四",18);
SetDemo.StudentNonOverride sn3 = new SetDemo.StudentNonOverride(3,"王二麻子",29);
SetDemo.StudentNonOverride sn4 = new SetDemo.StudentNonOverride(2,"李四",18);
Map<StudentNonOverride, String> map2 = new TreeMap<>(); // 抛异常java.lang.ClassCastException 因为没有排序器
map2.put(sn1, "sn1"); // 类StudentNonOverride没有实现排序接口,因此可以通过传入一个排序器来解决
map2.put(sn2, "sn2");
map2.put(sn3, "sn3");
map2.put(sn4, "sn4");
System.out.println(map2);*/
SetDemo.StudentNonOverride sn1 = new SetDemo.StudentNonOverride(1,"张三",23);
SetDemo.StudentNonOverride sn2 = new SetDemo.StudentNonOverride(2,"李四",18);
SetDemo.StudentNonOverride sn3 = new SetDemo.StudentNonOverride(3,"王二麻子",29);
SetDemo.StudentNonOverride sn4 = new SetDemo.StudentNonOverride(2,"李四",18);
Map<StudentNonOverride, String> map2 = new TreeMap<>(new Comparator<StudentNonOverride>() {
@Override
public int compare(StudentNonOverride o1, StudentNonOverride o2) {
return o1.getAge() - o2.getAge(); // 根据年龄排序
}
});
map2.put(sn1, "sn1");
map2.put(sn2, "sn2");
map2.put(sn3, "sn3");
map2.put(sn4, "sn4");
System.out.println(map2);
// {{id=2, name='李四', age=18}=sn4, {id=1, name='张三', age=23}=sn1, {id=3, name='王二麻子', age=29}=sn3}
SetDemo.StudentWithOverride sw1 = new SetDemo.StudentWithOverride(1,"张三",23);
SetDemo.StudentWithOverride sw2 = new SetDemo.StudentWithOverride(2,"李四",18);
SetDemo.StudentWithOverride sw3 = new SetDemo.StudentWithOverride(3,"王二麻子",29);
SetDemo.StudentWithOverride sw4 = new SetDemo.StudentWithOverride(2,"李四",18);
Map<StudentWithOverride, String> map3 = new TreeMap<>();
map3.put(sw1, "sw1");
map3.put(sw2, "sw2");
map3.put(sw3, "sw3");
map3.put(sw4, "sw4");
System.out.println(map3);
// {{id=2, name='李四', age=18}=sw4, {id=1, name='张三', age=23}=sw1, {id=3, name='王二麻子', age=29}=sw3}
// 可以看到只有3个元素,相同的key被排除了,因为StudentWithOverride实现了Comparable接口并重写了compareTo方法
}
}
结果分析:
String和StudentWithOverride都实现了接口Comparable并重写了compareTo方法,所以使用TreeMap是正常的,至于StudentNonOverride没有实现排序器类,直接会抛异常,因此下面我又列举了通过匿名内部类Comparator接口重写compare方法传入Set中的方法定义了比较器,之后TreeMap就正常了
总结
线程安全问题
上面介绍的这些容器都不是同步的,即不是线程安全的,如果在多线程中使用,可以通过Collections工具类中的synchronizedCollection()方法使得线程安全,但是不推荐这种方法,因为现在有更高级更简便的线程安全容器类,封装在java.util.concurrent包内,每一种容器都有对应的线程安全容器。之后学到多线程时,可以再深入研究。
不常用容器简介
继承与List的Vector和它的子类Stack,Vector本来也是一种数组容器,但是它是同步的线程安全的,内置了锁,效率比较低,因此已经弃用了,同时它的子类stack自然也就弃用了,现在我们使用Deque实现栈。
同样比较容易被提到的是HashTable,这个也是个哈希Map其实,但是它是线程安全带的,而且是通过锁机制来实现,所以也不推荐用了。
常见容器的横向纵向比较
- List、Set、Map都什么区别?
- ArrayList和LinkedList什么区别?实现原理分别是什么?
- HashSet和TreeSet什么异同点?实现原理分别是什么?
- HashMap和TreeMap什么异同点?实现原理分别是什么?
- HashSet和HashMap什么异同点?实现原理分别是什么?
- TreeSet和TreeMap什么异同点?实现原理分别是什么?
- Comparable和Comparator区别是什么?有什么用?
- compareTo和compare什么区别?有什么用?
- ArrayList默认容量?什么时候扩容?扩容几倍?
- LinkedList有默认大小吗?需要扩容吗?为什么它可以通过下标index访问?
- HashMap的默认大小是多少?什么情况下会扩容?装填因子是多少?如何设置装填因子才合理?出现哈希冲突怎么解决的?
- HashMap装填因子为什么选0.75?