集合
可以将集合分为2个大类
Collection接口
API方法:
注意Collection是接口,需要子类才能实现,里面只能存储引用类型,所以会进行自动拆箱和装箱。
遍历:
增强for,还有迭代器遍历
// 迭代器遍历
Iterator it = col.iterator();
while (it.hasNext()) {
System.out.println(it.next());
}
实现类以及子接口
图示:
List接口
ArrayList实现类源码
JDK1.7
类中的成员变量
初始化,在JDK1.7中会默认初始化成员变量elementData数组的大小为10
对应的内存关系
add()方法
- 如果数组的10个位置都满后,就开始进行数组的扩容,扩容长度为原数组的1.5倍
- 判断是否扩容
- 扩容步骤
总结:底层数组,在调用构造器的时候,数组长度初始化10,扩容的时候扩展为原数组的1.5倍
JDK1.8
类中的成员变量,和1.7一致。
new 一个对象时的初始化,注意此时初始化数组的大小为一个常量,而常量值为0,则JDK1.8中初始化并没有申请一个长度为10的数组。
add方法
- 调用初始化方法
- 此时在参数中会调用初始化扩容大小的方法
- 此时minCapacity已经被赋值为10,与原数组大小比较判断是否进行扩容
- 此时才是grow的扩容实现
总结:底层数组实现,在调用构造器的时候,底层数组为f,在调用add方法以后底层数组才重新赋值为新数组,新数组的长度为10-》节省了内存,在add后才创建长度为10的数组。
Vector实现类
成员变量,也是数组实现
初始化值,为10
add方法
- 注意add方法加了锁,所以是线程安全的
- 扩容中间步骤和ArrayList大致一样
- 扩容步骤,注意此时扩容的大小是原数组2倍,而不是1.5倍
总结:都是数组进行实现,但和ArrayList的区别就是,线程安全,扩容的大小是2倍,因此造成了效率低,所以被弃用。
LinkedList实现类
LinkedList时有双向链表实现
transient int size = 0; // 节点个数
/**
* Pointer to first node.
* Invariant: (first == null && last == null) ||
* (first.prev == null && first.item != null)
*/
transient Node<E> first; // 前节点
/**
* Pointer to last node.
* Invariant: (first == null && last == null) ||
* (last.next == null && last.item != null)
*/
transient Node<E> last; // 后节点
/**
* Constructs an empty list.
*/
public LinkedList() { // 构造方法
}
public LinkedList(Collection<? extends E> c) {
this();
addAll(c);
}
add方法:
- 会返回boolean类型
public boolean add(E e) {
linkLast(e);
return true;
}
- 调用linkLast方法,方法实现
/**
* Links e as last element.
*/
void linkLast(E e) {
final Node<E> l = last; // 创建一个节点,作为最后一个节点的临时值
final Node<E> newNode = new Node<>(l, e, null); // 此时添加节点在最后的位置,l作为前节点,null作为后节点
last = newNode; // 赋值给last,更新最后一个元素
if (l == null) // 如果添加的时第一个元素
first = newNode; // 将第一个节点指向newNode节点
else
l.next = newNode; // 如果不是,进行连接,l指向新生成的节点
size++; // 大小加一
modCount++;
}
get方法:
- getLast()和getFirst()方法直接通过first和last节点取值即可,而get方法有些区别:
public E get(int index) {
checkElementIndex(index); // 健壮性检查
return node(index).item;
}
node方法的具体实现,注意优化步骤
Node<E> node(int index) {
// assert isElementIndex(index);
if (index < (size >> 1)) { // 将链表分成两半,提高检索效率,如果此时查找的元素在前半部分,那么从first开始向后查找
Node<E> x = first;
for (int i = 0; i < index; i++)
x = x.next;
return x;
} else { // 如果在后半部分,从last元素开始向前查找
Node<E> x = last;
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}
Iterator迭代器
- 接口关系
- 可以知道Itr类是ArrayList的内部类
- Itr具体实现,Itr方法可以调用ArrayList的成员变量,也就是通过cursor表示代表指针来操作数组。
ListIterator迭代器
用于允许程序员沿任一方向遍历列表的列表的迭代器,在迭代期间修改列表,并获取列表中迭代器的当前位置。
常用方法
测试遍历方法
public class ListIteratorTest {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("aa");
list.add("bb");
list.add("cc");
list.add("dd");
list.add("ee");
// 将ff插入cc的后面
// Iterator<String> it = list.iterator();
// while (it.hasNext()) {
// if ("cc".equals(it.next())) {
// list.add("ff"); // 报错ConcurrentModificationException,就是迭代器和list共同操作数组
// }
// }
// 此时可以使用LinkIterator操作数组
ListIterator<String> it1 = list.listIterator();
while (it1.hasNext()) {
if ("cc".equals(it1.next())) {
it1.add("ff"); // 此时都是迭代器来操作
}
}
System.out.println(list); // [aa, bb, cc, ff, dd, ee]
// 可以逆向遍历
while (it1.hasPrevious()) {
System.out.println(it1.previous()); // ee dd ff cc bb aa
}
}
}
后序遍历
Set接口
HashSet实现类
此类实现Set
接口,由哈希表(实际为HashMap
实例)支持。对集合的迭代次序不作任何保证;特别是,它不能保证订单在一段时间内保持不变。这个类允许null
元素。
这个类提供了基本操作(add,remove,contains
和size)
固定的时间性能,假定哈希函数将分散的桶中正确的元素。 迭代此集合需要与HashSet
实例的大小(元素数量)和后台HashMap
实例(桶数)的“容量”的总和成比例
的时间。 因此,如果迭代性能很重要,不要将初始容量设置得太高(或负载因子太低)是非常重要的。
请注意,此实现不同步。
常用方法
可以看出hashset没有关于索引的方法。
在add元素时,如果没有重写equals方法,那么set将不能判重。
原理图
比较器
比较int
int a = 10;
int b = 20;
System.out.println(a-b); // =0 >0 <0
比较double
double a = 9.6;
double b = 9.3;
/* System.out.println((int)(a-b));*/
System.out.println(((Double) a).compareTo((Double) b));
比较String
String a = "A";
String b = "B";
System.out.println(a.compareTo(b));
比较自定义类型
内部比较器
实现Comparable接口,重写compareTo方法public class Student implements Comparable<Student> {
private int age;
private double height;
private String name;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public double getHeight() {
return height;
}
public void setHeight(double height) {
this.height = height;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Student(int age, double height, String name) {
this.age = age;
this.height = height;
this.name = name;
}
@Override
public String toString() {
return "Student{" +
"age=" + age +
", height=" + height +
", name='" + name + '\'' +
'}';
}
@Override
public int compareTo(Student o) {
// 以年龄进行比较
// return this.age - o.getAge();
// 以name进行比较
return this.getName().compareTo(o.getName());
}
}
- 外部比较器
可以自己写一个类,也可以使用匿名内部类public class ComparatorTest {
public static void main(String[] args) {
// 比较两个自定义类型
Student s1 = new Student(14, 160.5, "alili");
Student s2 = new Student(14, 170.5, "bnana");
Comparator<Student> com = new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
// 按照年龄进行比较
return o1.getAge() - o2.getAge();
}
};
System.out.println(com.compare(s1, s2));
}
}
TreeSet实现类
TreeSet和HashSet一样,保证了元素时唯一,但内部使其排列(可以自定义比较器来控制其升序还是降序)。但升序的比较是按照比较器来定义。
常用方法
查api
原理结构图
底层逻辑结构便是二叉搜索树,通过比较器进行比较来判断往哪放,输出的话逻辑上就是中序遍历,保证升序输出。
使用实例
如果在TreeSet中加入String和Integer等包装类类型的话TreeSet会自动排序,因为包装类中都实现了内部比较器。
例如Double:
而自定义类需要自己定义
- 内部比较器需要类实现Comparable接口,重写compareTo方法
- 外部比较器
public class TreeSetTest {
public static void main(String[] args) {
//创建一个TreeSet:
TreeSet<Student> ts = new TreeSet<>(new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
// 按照年龄进行升序
return o1.getAge() - o2.getAge();
}
});
ts.add(new Student(10, 180, "elili"));
ts.add(new Student(8, 184, "blili"));
ts.add(new Student(4, 165, "alili"));
ts.add(new Student(9, 155, "elili"));
ts.add(new Student(10, 143, "flili"));
ts.add(new Student(1, 175, "dlili"));
System.out.println(ts.size());
System.out.println(ts);
}
}
外部比较器利用堕胎,扩展性好
Map接口
实现类以及子接口
常用方法
package com.msb.test11;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
/**
* @author : msb-zhaoss
*/
public class Test01 {
//这是main方法,程序的入口
public static void main(String[] args) {
/*
增加:put(K key, V value)
删除:clear() remove(Object key)
修改:
查看:entrySet() get(Object key) keySet() size() values()
判断:containsKey(Object key) containsValue(Object value)
equals(Object o) isEmpty()
*/
//创建一个Map集合:无序,唯一
Map<String,Integer> map = new HashMap<>();
System.out.println(map.put("lili", 10101010));
map.put("nana",12345234);
map.put("feifei",34563465);
System.out.println(map.put("lili", 34565677));
map.put("mingming",12323);
/*map.clear();清空*/
/*map.remove("feifei");移除*/
System.out.println(map.size());
System.out.println(map);
System.out.println(map.containsKey("lili"));
System.out.println(map.containsValue(12323));
Map<String,Integer> map2 = new HashMap<>();
System.out.println(map2.put("lili", 10101010));
map2.put("nana",12345234);
map2.put("feifei",34563465);
System.out.println(map2.put("lili", 34565677));
map2.put("mingming2",12323);
System.out.println(map==map2);
System.out.println(map.equals(map2));//equals进行了重写,比较的是集合中的值是否一致
System.out.println("判断是否为空:"+map.isEmpty());
System.out.println(map.get("nana"));
System.out.println("-----------------------------------");
//keySet()对集合中的key进行遍历查看:
Set<String> set = map.keySet();
for(String s:set){
System.out.println(s);
}
System.out.println("-----------------------------------");
//values()对集合中的value进行遍历查看:
Collection<Integer> values = map.values();
for(Integer i:values){
System.out.println(i);
}
System.out.println("-----------------------------------");
//get(Object key) keySet()
Set<String> set2 = map.keySet();
for(String s:set2){
System.out.println(map.get(s));
}
System.out.println("-----------------------------------");
//entrySet()
Set<Map.Entry<String, Integer>> entries = map.entrySet();
for(Map.Entry<String, Integer> e:entries){
System.out.println(e.getKey()+"----"+e.getValue());
}
}
}
TreeMap
和TreeSet使用一样,需要实现内部或者外部比较器
HashMap
HashMap底层代码细节参照🔗
红黑树介绍参照🔗