3 集合框架
3.1 ArrayList
使用数组的局限性
如果要存放多个对象,可以使用数组,但是数组有局限性
比如 声明长度是10的数组
不用的数组就浪费了
超过10的个数,又放不下
ArrayList存放对象
为了解决数组的局限性,引入容器类的概念。 最常见的容器类就是
ArrayList
容器的容量"capacity"会随着对象的增加,自动增长
3.1.1常用方法
增加
public class IOTest {
public static void main(String[] args){
ArrayList al=new ArrayList();
al.add(5);
System.out.println(al); // [5]
}
}
判断是否存在
通过方法contains 判断一个对象是否在容器中
判断标准: 是否是同一个对象,而不是name是否相同
获取指定位置的对象
通过get获取指定位置的对象,如果输入的下标越界,一样会报错
获取对象所处的位置
indexOf用于判断一个对象在ArrayList中所处的位置
与contains一样,判断标准是对象是否相同,而非对象的name值是否相等
删除
remove用于把对象从ArrayList中删除
remove可以根据下标删除ArrayList的元素
替换
set用于替换指定位置的元素
获取大小
public class IOTest {
public static void main(String[] args){
ArrayList al=new ArrayList();
System.out.println(al.size()); // 0
al.add(5);
System.out.println(al); // [5]
System.out.println(al.size()); // 1
}
}
转换为数组
toArray可以把一个ArrayList对象转换为数组
把另一个容器所有对象都加进来
addAll 把另一个容器所有对象都加进来
清空
public class IOTest {
public static void main(String[] args){
ArrayList al=new ArrayList();
al.add(5);
System.out.println(al); // [5]
al.clear();
System.out.println(al.size()); // 0
}
}
3.1.2 List接口
ArrayList实现了接口List
常见的写法会把引用声明为接口List类型
注意:是java.util.List,而不是java.awt.List
List al=new ArrayList();
3.1.3泛型 Generic
泛型 Generic
不指定泛型的容器,可以存放任何类型的元素
指定了泛型的容器,只能存放指定类型的元素以及其子类
List<Object> list=new ArrayList<Object>();
// 不过JDK7提供了一个可以略微减少代码量的泛型简写方式
List<Object> list=new ArrayList<>();
3.1.4 遍历
用for循环遍历
迭代器遍历
public class Test {
public static void main(String[] args) {
// 不过JDK7提供了一个可以略微减少代码量的泛型简写方式
List<Object> list=new ArrayList<>();
list.add(10);
list.add(20);
list.add(30);
System.out.println(list); // [10,20,30]
// 通过迭代器遍历
Iterator<Object> it=list.iterator();
while(it.hasNext()){
Object l=it.next();
System.out.println(l); // 10 20 30
}
}
}
用增强型for循环 foreach遍历
public class Test {
public static void main(String[] args) {
// 不过JDK7提供了一个可以略微减少代码量的泛型简写方式
List<Object> list=new ArrayList<>();
list.add(10);
list.add(20);
list.add(30);
System.out.println(list); // [10,20,30]
// 通过foreach遍历
for (Object object : list) {
System.out.println(object); // 10 20 30
}
}
}
3.2 LinkedList
序列分先进先出FIFO,先进后出FILO
FIFO在Java中又叫Queue 队列
FILO在Java中又叫Stack 栈
3.2.1 LinkedList 与 List接口
与ArrayList一样,LinkedList也实现了List接口,诸如add,remove,contains等等方法。
双向链表 – Deque
除了实现了List接口外,LinkedList还实现了双向链表结构Deque,可以很方便的在头尾插入删除数据
队列 – Queue
LinkedList 除了实现了List和Deque外,还实现了Queue接口(队列)。
Queue是先进先出队列 FIFO,常用方法:
offer 在最后添加元素
poll 取出第一个元素
peek 查看第一个元素
3.3 二叉树
二叉树概念
二叉树由各种节点组成
二叉树特点:
每个节点都可以有左子节点,右子节点
每一个节点都有一个值
二叉树排序-插入数据
假设通过二叉树对如下10个随机数进行排序
67,7,30,73,10,0,78,81,10,74
排序的第一个步骤是把数据插入到该二叉树中
插入基本逻辑是,小、相同的放左边,大的放右边
public class Node {
// 左子节点
public Node leftNode;
// 右子节点
public Node rightNode;
// 值
public Object value;
// 插入 数据
public void add(Object v) {
// 如果当前节点没有值,就把数据放在当前节点上
if (null == value)
value = v;
// 如果当前节点有值,就进行判断,新增的值与当前值的大小关系
else {
// 新增的值,比当前值小或者相同
if ((Integer) v -((Integer)value) <= 0) {
if (null == leftNode)
leftNode = new Node();
leftNode.add(v);
}
// 新增的值,比当前值大
else {
if (null == rightNode)
rightNode = new Node();
rightNode.add(v);
}
}
}
public static void main(String[] args) {
int randoms[] = new int[] { 67, 7, 30, 73, 10, 0, 78, 81, 10, 74 };
Node roots = new Node();
for (int number : randoms) {
roots.add(number);
}
}
}
二叉树排序-遍历
通过上一个步骤的插入行为,实际上,数据就已经排好序了。 接下来要做的是看,把这些已经排好序的数据,遍历成我们常用的List或者数组的形式
二叉树的遍历分左序,中序,右序
左序即: 中间的数遍历后放在左边
中序即: 中间的数遍历后放在中间
右序即: 中间的数遍历后放在右边
如图所见,我们希望遍历后的结果是从小到大的,所以应该采用中序遍历
public class Node {
// 左子节点
public Node leftNode;
// 右子节点
public Node rightNode;
// 值
public Object value;
// 插入 数据
public void add(Object v) {
// 如果当前节点没有值,就把数据放在当前节点上
if (null == value)
value = v;
// 如果当前节点有值,就进行判断,新增的值与当前值的大小关系
else {
// 新增的值,比当前值小或者相同
if ((Integer) v -((Integer)value) <= 0) {
if (null == leftNode)
leftNode = new Node();
leftNode.add(v);
}
// 新增的值,比当前值大
else {
if (null == rightNode)
rightNode = new Node();
rightNode.add(v);
}
}
}
// 中序遍历所有的节点
public List<Object> values() {
List<Object> values = new ArrayList<>();
// 左节点的遍历结果
if (null != leftNode)
values.addAll(leftNode.values());
// 当前节点
values.add(value);
// 右节点的遍历结果
if (null != rightNode)
values.addAll(rightNode.values());
return values;
}
public static void main(String[] args) {
int randoms[] = new int[] { 67, 7, 30, 73, 10, 0, 78, 81, 10, 74 };
Node roots = new Node();
for (int number : randoms) {
roots.add(number);
}
System.out.println(roots.values());
}
}
3.4 HashMap
HashMap的键值对
HashMap储存数据的方式是—— 键值对
public class Test {
public static void main(String[] args){
HashMap<String, String> hm=new HashMap<>();
hm.put("name","Tom");
hm.put("age","18");
System.out.println(hm.get("name")); //Tom
System.out.println(hm.get("age")); // 18
}
}
键不能重复,值可以重复
对于HashMap而言,key是唯一的,不可以重复的。
所以,以相同的key 把不同的value插入到 Map中会导致旧元素被覆盖,只留下最后插入的元素。
不过,同一个对象可以作为值插入到map中,只要对应的key不一样
3.5 HashSet
Set中的元素,不能重复
Set中的元素,没有顺序。
严格的说,是没有按照元素的插入顺序排列
HashSet的具体顺序,既不是按照插入顺序,也不是按照hashcode的顺序。
public class Test {
public static void main(String[] args){
HashSet<Integer> hs=new HashSet<>();
hs.add(10);
System.out.println(hs); // [10]
}
}
Set不提供get()来获取指定位置的元素
所以遍历需要用到迭代器,或者增强型for循环
HashSet和HashMap的关系
可以发现HashSet自身并没有独立的实现,而是在里面封装了一个Map.
HashSet是作为Map的key而存在的
而value是一个命名为PRESENT的static的Object对象,因为是一个类属性,所以只会有一个。
3.6 Collection
Collection是 Set List Queue和 Deque的接口
Queue: 先进先出队列
Deque: 双向链表
注:Collection和Map之间没有关系,Collection是放一个一个对象的,Map 是放键值对的
注:Deque 继承 Queue,间接得继承了 Collection
3.7 Collections
Collections是一个类,容器的工具类,就如同Arrays是数组的工具类
反转
reverse 使List中的数据发生翻转
Collections.reverse();
混淆
shuffle 混淆List中数据的顺序
Collections.shuffle();
排序
sort 对List中的数据进行排序
Collections.sort(numbers);
交换
swap 交换两个数据的位置
Collections.swap(numbers,0,5);
滚动
rotate 把List中的数据,向右滚动指定单位的长度
Collections.rotate(numbers,2);
线程安全化
synchronizedList 把非线程安全的List转换为线程安全的List
Collections.synchronizedList(numbers);
3.8 ArrayList与HashSet
ArrayList: 有顺序
HashSet: 无顺序
List中的数据可以重复
Set中的数据不能够重复
重复判断标准是:
首先看hashcode是否相同
如果hashcode不同,则认为是不同数据
如果hashcode相同,再比较equals,如果equals相同,则是相同数据,否则是不同数据
3.9 ArrayList和LinkedList
ArrayList 插入,删除数据慢
LinkedList, 插入,删除数据快
ArrayList是顺序结构,所以定位很快,指哪找哪。 就像电影院位置一样,有了电影票,一下就找到位置了。
LinkedList 是链表结构,就像手里的一串佛珠,要找出第99个佛珠,必须得一个一个的数过去,所以定位慢
public class TestCollection {
public static void main(String[] args) {
List<Integer> l;
l = new ArrayList<>();
insertFirst(l, "ArrayList");
l = new LinkedList<>();
insertFirst(l, "LinkedList");
}
private static void insertFirst(List<Integer> l, String type) {
int total = 1000 * 100;
final int number = 5;
long start = System.currentTimeMillis();
for (int i = 0; i < total; i++) {
l.add(0, number);
}
long end = System.currentTimeMillis();
System.out.printf("在%s 最前面插入%d条数据,总共耗时 %d 毫秒 %n", type, total, end - start);
}
}
定位数据
public class TestCollection {
public static void main(String[] args) {
List<Integer> l;
l = new ArrayList<>();
modify(l, "ArrayList");
l = new LinkedList<>();
modify(l, "LinkedList");
}
private static void modify(List<Integer> l, String type) {
int total = 100 * 1000;
int index = total/2;
final int number = 5;
//初始化
for (int i = 0; i < total; i++) {
l.add(number);
}
long start = System.currentTimeMillis();
for (int i = 0; i < total; i++) {
int n = l.get(index);
n++;
l.set(index, n);
}
long end = System.currentTimeMillis();
System.out.printf("%s总长度是%d,定位到第%d个数据,取出来,加1,再放回去%n 重复%d遍,总共耗时 %d 毫秒 %n", type,total, index,total, end - start);
System.out.println();
}
}
3.10 HashMap和Hashtable的区别
HashMap和Hashtable的区别
HashMap和Hashtable都实现了Map接口,都是键值对保存数据的方式
区别1:
HashMap可以存放 null
Hashtable不能存放null
区别2:
HashMap不是线程安全的类
Hashtable是线程安全的类
3.11 HashSet LinkedHashSet TreeSet
HashSet: 无序
LinkedHashSet: 按照插入顺序
TreeSet: 从小到大排序
public class TestCollection {
public static void main(String[] args) {
HashSet<Integer> numberSet1 =new HashSet<Integer>();
//HashSet中的数据不是按照插入顺序存放
numberSet1.add(88);
numberSet1.add(8);
numberSet1.add(888);
System.out.println(numberSet1);
LinkedHashSet<Integer> numberSet2 =new LinkedHashSet<Integer>();
//LinkedHashSet中的数据是按照插入顺序存放
numberSet2.add(88);
numberSet2.add(8);
numberSet2.add(888);
System.out.println(numberSet2);
TreeSet<Integer> numberSet3 =new TreeSet<Integer>();
//TreeSet 中的数据是进行了排序的
numberSet3.add(88);
numberSet3.add(8);
numberSet3.add(888);
System.out.println(numberSet3);
}
}
3.12 hashcode原理
hashcode概念
所有的对象,都有一个对应的hashcode(散列值)
List查找的低效率
HashMap的性能表现快
这是一种用空间换时间的思维方式
HashSet判断是否重复
HashSet判断是否重复
HashSet的数据是不能重复的,相同数据不能保存在一起,到底如何判断是否是重复的呢?
根据HashSet和HashMap的关系,我们了解到因为HashSet没有自身的实现,而是里面封装了一个HashMap,所以本质上就是判断HashMap的key是否重复。
再通过上一步的学习,key是否重复,是由两个步骤判断的:
hashcode是否一样
如果hashcode不一样,就是在不同的坑里,一定是不重复的
如果hashcode一样,就是在同一个坑里,还需要进行equals比较
如果equals一样,则是重复数据
如果equals不一样,则是不同数据
3.13 Comparator比较器
假设Hero有三个属性 name,hp,damage
一个集合中放存放10个Hero,通过Collections.sort对这10个进行排序
那么到底是hp小的放前面?还是damage小的放前面?Collections.sort也无法确定
所以要指定到底按照哪种属性进行排序
这里就需要提供一个Comparator给定如何进行两个对象之间的大小比较
Comparable
使Hero类实现Comparable接口
在类里面提供比较算法
Collections.sort就有足够的信息进行排序了,也无需额外提供比较器Comparator