1. ArrayList
1.1 与数组的区别
1.1.1 使用数组的局限性
如果要存放多个对象,可以使用数组,但是数组有局限性
比如 声明长度是10的数组
不用的数组就浪费了
超过10的个数,又放不下
1.1.2 ArrayList存放对象
为了解决数组的局限性,引入容器类的概念。 最常见的容器类就是
ArrayList
容器的容量"capacity"会随着对象的增加,自动增长
只需要不断往容器里增加英雄即可,不用担心会出现数组的边界问题。
package collection;
import java.util.ArrayList;
import charactor.Hero;
public class TestCollection1to1 {
public static void main(String[] args) {
//容器类ArrayList,用于存放对象
ArrayList heros=new ArrayList();
heros.add(new Hero("盖伦"));
System.out.println(heros.size());
//容器的容量capacity会随着对象的增加自动增长
//只需不断忘容器里添加英雄即可,不用担心会出现数组越界的问题
heros.add(new Hero("提莫"));
System.out.println(heros.size());
}
}
1.2 常用方法
1.2.1 add添加
add 有两种用法
第一种是直接add对象,把对象加在最后面
heros.add(new Hero("hero " + i));
第二种是在指定位置加对象
heros.add(3, specialHero);
package charactor;
public class Hero {
public String name;
public float hp;
public int damage;
public Hero() {
}
// 增加一个初始化name的构造方法
public Hero(String name) {
this.name = name;
}
// 重写toString方法
public String toString() {
return name;
}
}
package collection;
import java.util.ArrayList;
import charactor.Hero;
public class TestCollection1to2to1 {
public static void main(String[] args) {
ArrayList heros=new ArrayList();
//把5个对象加入到ArrayList中
for(int i=0;i<5;i++){
heros.add(new Hero("hero "+i));
}
//在指定位置添加对象
Hero specialHero=new Hero("special hero");
heros.add(3,specialHero);
System.out.println(heros.toString());
}
}
1.2.2 判断是否存在
通过方法contains 判断一个对象是否在容器中
判断标准: 是否是同一个对象,而不是name是否相同
package collection;
import java.util.ArrayList;
import charactor.Hero;
public class TestCollection1to2to2 {
public static void main(String[] args) {
ArrayList heros=new ArrayList();
for(int i=0;i<5;i++){
heros.add(new Hero("hero"+i));
}
Hero specialHero=new Hero("specialhero");
heros.add(3,specialHero);
System.out.println(heros);
//判断一个对象是否在一个容器中
//判断标准:是否是同一个对象,而不是name是否相同
System.out.println("虽然一个新的对象名字是hero1,但是contains的返回是:"+heros.contains(new Hero("hero1")));
System.out.println("而对specialHero的判断,contains的返回是:"+heros.contains(specialHero));
}
}
1.2.3 获取指定位置的对象
通过get获取指定位置的对象,如果输入的下标越界,一样会报错
package collection;
import java.util.ArrayList;
import charactor.Hero;
public class TestCollection1to2to3 {
public static void main(String[] args) {
ArrayList heros=new ArrayList();
//把5个对象加入到ArrayList中
for(int i=0;i<5;i++){
heros.add(new Hero("hero"+i));
}
//在指定位置添加对象
Hero specialHero=new Hero("special hero");
heros.add(3,specialHero);
System.out.println(heros.toString());
System.out.println(heros.get(5));
System.out.println(heros.get(6));
}
}
1.2.4 获取对象所处的位置
indexOf用于判断一个对象在ArrayList中所处的位置
与contains一样,判断标准是对象是否相同,而非对象的name值是否相等
package collection;
import java.util.ArrayList;
import charactor.Hero;
public class TestCollection1to2to4 {
public static void main(String[] args) {
ArrayList heros=new ArrayList();
//把5个对象加入到ArrayList中
for(int i=0;i<5;i++){
heros.add(new Hero("hero"+i));
}
//在指定位置添加对象
Hero specialHero=new Hero("special hero");
heros.add(3,specialHero);
System.out.println(heros.toString());
//获取specialHero所处的位置
System.out.println("specialHero所处的位置"+heros.indexOf(specialHero));
System.out.println("新的英雄但是名字是hero1,所处的位置"+heros.indexOf(new Hero("hero1")));
}
}
1.2.5 删除
remove用于把对象从ArrayList中删除
remove可以根据下标删除ArrayList的元素
heros.remove(2);
也可以根据对象删除
heros.remove(specialHero);
package collection;
import java.util.ArrayList;
import charactor.Hero;
public class TestCollection1to2to5 {
public static void main(String[] args) {
ArrayList heros=new ArrayList();
//把5个对象加入到ArrayList中
for(int i=0;i<5;i++){
heros.add(new Hero("hero "+i));
}
//在指定位置添加对象
Hero specialHero=new Hero("special hero");
heros.add(3,specialHero);
System.out.println(heros.toString());
heros.remove(2);
System.out.println("删除下标为2的元素:"+heros.toString());
heros.remove(specialHero);
System.out.println("删除specialHero对象:"+heros.toString());
}
}
1.2.6 替换
set用于替换指定位置的元素
package collection;
import java.util.ArrayList;
import charactor.Hero;
public class TestCollection1to2to6 {
public static void main(String[] args) {
ArrayList heros=new ArrayList();
//把5个对象加入到ArrayList中
for(int i=0;i<5;i++){
heros.add(new Hero("hero"+i));
}
//在指定位置添加对象
Hero specialHero=new Hero("special hero");
heros.add(specialHero);
System.out.println(heros.toString());
heros.set(5,new Hero("hero5"));
System.out.println("把下标为5的元素替换为hero5:"+heros);
}
}
1.2.7 获取大小
size 用于获取ArrayList的大小
package collection;
import java.util.ArrayList;
import charactor.Hero;
public class TestCollection1to2to7 {
public static void main(String[] args) {
ArrayList heros=new ArrayList();
//把5个对象加入到ArrayList中
for(int i=0;i<5;i++){
heros.add(new Hero("hero"+i));
}
//在指定位置添加对象
Hero specialHero=new Hero("specialhero");
heros.add(3,specialHero);
System.out.println(heros.toString());
System.out.println("获取ArrayList大小:"+heros.size());
}
}
1.2.8 转换为数组
toArray可以把一个ArrayList对象转换为数组。
需要注意的是,如果要转换为一个Hero数组,那么需要传递一个Hero数组类型的对象给toArray(),这样toArray方法才知道,你希望转换为哪种类型的数组,否则只能转换为Object数组
package collection;
import java.util.ArrayList;
import charactor.Hero;
public class TestCollection1to2to8 {
public static void main(String[] args) {
ArrayList heros=new ArrayList();
//把5个对象加入到ArrayList中
for(int i=0;i<5;i++){
heros.add(new Hero("hero"+i));
}
//在指定位置添加对象
Hero specialHero=new Hero("specialhero");
heros.add(specialHero);
System.out.println(heros);
Hero hs[]=(Hero[])heros.toArray(new Hero[]{});
System.out.println("数组:"+hs);
}
}
1.2.9 把另 一个容器所有对象都加进来
addAll 把另一个容器所有对象都加进来
package collection;
import java.util.ArrayList;
import charactor.Hero;
public class TestCollection1to2to9 {
public static void main(String[] args) {
ArrayList heros=new ArrayList();
//把5个对象加入到ArrayList中
for(int i=0;i<5;i++){
heros.add(new Hero("hero"+i));
}
Hero specialHero=new Hero("specialhero");
heros.add(specialHero);
System.out.println("heros:\t"+heros);
ArrayList otherheros=new ArrayList();
otherheros.add(new Hero("heroa"));
otherheros.add(new Hero("herob"));
otherheros.add(new Hero("heroc"));
System.out.println("otherheros:\t"+otherheros);
heros.addAll(otherheros);
System.out.println("添加了otherheros之后的heros:\t"+heros);
}
}
1.2.10 清空
clear 清空一个ArrayList
package collection;
import java.util.ArrayList;
import charactor.Hero;
public class TestCollection1to2to10 {
public static void main(String[] args) {
ArrayList heros=new ArrayList();
//把5个对象加入到ArrayList中
for(int i=0;i<5;i++){
heros.add(new Hero("hero "+i));
}
Hero specialHero=new Hero("special hero");
heros.add(3,specialHero);
System.out.println(heros);
heros.clear();
System.out.println(heros.toString());
}
}
1.2.11 练习-判断是否相同
如果就是要判断集合里是否存在一个 name等于 "hero1"的对象,应该怎么做?
package collection;
import java.util.ArrayList;
import charactor.Hero;
public class TestCollection1to2to11 {
public static void main(String[] args) {
ArrayList heros=new ArrayList();
int f=0;
String str="hero1";
for(int i=0;i<5;i++)
heros.add(new Hero("hero"+i));
for(int i=0;i<heros.size();i++){
String str1=heros.get(i).toString();
if(str1.equals(str)){
System.out.println("yes");
f=1;
}
}
if(f==0)
System.out.println("no");
}
}
1.3 List接口
1.3.1 ArrayList和List
ArrayList实现了接口List
常见的写法会把引用声明为接口List类型
注意:是java.util.List,而不是java.awt.List
package collection;
import java.util.ArrayList;
import java.util.List;
import charactor.Hero;
public class TestCollection1to3to1 {
public static void main(String[] args) {
//ArrayList实现了List接口
//常见的方法会把引用声明为接口List类型
//接口引用指向子类对象(多态)
List heros=new ArrayList();
heros.add(new Hero("盖伦"));
System.out.println(heros.size());
}
}
1.3.2 List接口的方法
因为ArrayList实现了List接口,所以List接口的方法ArrayList都实现了。
在ArrayList 常用方法章节有详细的讲解,在此不作赘述
1.4 泛型 Gerneric
1.4.1 泛型 Generic
不指定泛型的容器,可以存放任何类型的元素
指定了泛型的容器,只能存放指定类型的元素以及其子类
package collection;
import java.util.ArrayList;
import java.util.List;
import property.Item;
import charactor.Hero;
public class TestCollection1to3to2 {
public static void main(String[] args) {
//对于不使用泛型的容器,可以往里边放英雄,也可以放物品
List heros=new ArrayList();
heros.add(new Hero("盖伦"));
//本来用来放英雄的容器,现在也可以放物品
heros.add(new Item("冰杖"));
//对象转型会出现问题
Hero h1=(Hero)heros.get(0);
//尤其是在容器里放很多对象的时候,就记不清哪个位置放的是哪种类型的对象了
//Hero h2=(Hero)heros.get(1);
//引入泛型Generic
//声明容器的时候,就指定了这种容器,只能放Hero及其子类,放其他的会报错
List<Hero> genericheros=new ArrayList<Hero>();
genericheros.add(new Hero("盖伦"));
//如果不是Hero类型,根本就放不进去
//genericheros.add(new Item("冰杖"));
//放Hero子类的时候,取数据的时候不需要再转型,因为里边肯定放的是Hero或其子类
}
}
1.4.2 泛型的简写
为了不使编译器出现警告,需要前后都使用泛型,像这样:
List genericheros = new ArrayList();
不过JDK7提供了一个可以略微减少代码量的泛型简写方式
List genericheros2 = new ArrayList<>();
后面的泛型可以用<>来代替,聊胜于无吧
1.4.3 练习-支持泛型的ArrayList
借助泛型和前面学习的面向对象的知识,设计一个ArrayList,使得这个ArrayList里,又可以放Hero,又可以放Item,但是除了这两种对象,其他的对象都不能放
我把Item继承了Hero的类,这样不就只有两个对象了
package collection;
import java.util.ArrayList;
import charactor.Hero;
import property.Item;
public class TestColection1to3to3 {
public static void main(String[] args) {
ArrayList<Hero> heros=new ArrayList<>();
heros.add(new Hero("盖伦"));
heros.add(new Item("冰杖"));
Hero h1=heros.get(0);
Item i1=(Item)heros.get(1);
System.out.println(h1.name+" "+i1.name);
}
}
1.5 泛型遍历
1.5.1 用for循环遍历
通过前面的学习,知道了可以用size()和get()分别得到大小,和获取指定位置的元素,结合for循环就可以遍历出ArrayList的内容
package collection;
import java.util.List;
import java.util.ArrayList;
import charactor.Hero;
public class TestCollection1to5to1 {
public static void main(String[] args) {
List<Hero> heros=new ArrayList<>();
for(int i=0;i<5;i++)
heros.add(new Hero("Hero"+i));
System.out.println("---------for循环---------");
for(int i=0;i<heros.size();i++){
Hero h=heros.get(i);
System.out.println(h);
}
}
}
1.5.2 迭代器遍历
使用迭代器Iterator遍历集合中的元素
package collection;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import charactor.Hero;
public class TestCollection1to5to2 {
public static void main(String[] args) {
List<Hero> heros=new ArrayList<>();
for(int i=0;i<5;i++)
heros.add(new Hero("hero"+i));
//使用迭代器
System.out.println("------------使用while的iterator-----------");
Iterator<Hero> it=heros.iterator();
//从最开始的位置就判断“下一个”是否有数据
//如果有就通过next取出来,并且把指针向下移动
//直到“下一个”位置没有数据
while(it.hasNext()){
Hero h=it.next();
System.out.println(h);
}
System.out.println("----------使用for的iterator");
for(Iterator<Hero> iterator=heros.iterator();iterator.hasNext();){
Hero h=iterator.next();
System.out.println(h);
}
}
}
1.5.3 用增强型for循环
使用增强型for循环可以非常方便的遍历ArrayList中的元素,这是很多开发人员的首选。
不过增强型for循环也有不足:
无法用来进行ArrayList的初始化
无法得知当前是第几个元素了,当需要只打印单数元素的时候,就做不到了。 必须再自定下标变量。
package collection;
import java.util.ArrayList;
import java.util.List;
import charactor.Hero;
public class TestCollection1to5to3 {
public static void main(String[] args) {
List<Hero> heros=new ArrayList<>();
for(int i=0;i<5;i++)
heros.add(new Hero("hero"+i));
for(Hero h:heros){
System.out.println(h);
}
}
}
1.5.4 练习-删除ArrayList中的数据
首先初始化一个Hero集合,里面放100个Hero对象,名称分别是从
hero 0
hero 1
hero 2
…
hero 99.
通过遍历的手段,删除掉名字编号是8的倍数的对象
package collection;
import java.util.List;
import java.util.ArrayList;
import charactor.Hero;
public class TestCollection1to5to4 {
public static void main(String[] args) {
List<Hero> heros=new ArrayList<>();
for(int i=0;i<100;i++)
heros.add(new Hero("hero"+i));
int sentinel=0;
//remove后,List的各个元素index会改变,也就是List会缩小
//所以来一个sentinel记录缩小
for(int i=0;i<heros.size();i+=8){
heros.remove(i-sentinel);
sentinel+=1;
}
System.out.println(heros);
}
}
2. 其他集合
2.1 LinkedList
序列分先进先出FIFO,先进后出FILO
FIFO在Java中又叫Queue 队列
FILO在Java中又叫Stack 栈
2.1.1 LinkedList 与 List接口
与ArrayList一样,LinkedList也实现了List接口,诸如add,remove,contains等等方法。 详细使用,请参考 ArrayList 常用方法,在此不作赘述。
接下来要讲的是LinkedList的一些特别的地方
2.1.2 双向链表 - Deque
除了实现了List接口外,LinkedList还实现了双向链表结构Deque,可以很方便的在头尾插入删除数据
什么是链表结构: 与数组结构相比较,数组结构,就好像是电影院,每个位置都有标示,每个位置之间的间隔都是一样的。 而链表就相当于佛珠,每个珠子,只连接前一个和后一个,不用关心除此之外的其他佛珠在哪里。
package collection;
import java.util.LinkedList;
import charactor.Hero;
public class TestCollection2to1to2 {
public static void main(String[] args) {
//LinekedList是一个双向链表结构的List
LinkedList<Hero> ll=new LinkedList<>();
//所以可以很方便的在头部和尾部插入元素
//在最后插入新的元素
ll.addLast(new Hero("hero"+1));ll.addLast(new Hero("hero"+2));
System.out.println(ll);
//在最前面插入元素
ll.addFirst(new Hero("heroa"));ll.addFirst(new Hero("herob"));
System.out.println("ll:"+ll);
//查看最前面的元素
System.out.println("最前边的元素"+ll.getFirst());
//查看最后边的元素
System.out.println("最后边的元素"+ll.getLast());
//取出最前边的元素
System.out.println(ll.removeFirst());
//取出最后边的元素
System.out.println(ll.removeLast());
System.out.println(ll);
}
}
2.1.3 队列 - Queue
LinkedList 除了实现了List和Deque外,还实现了Queue接口(队列)。
Queue是先进先出队列 FIFO,常用方法:
offer 在最后添加元素
poll 取出第一个元素
peek 查看第一个元素
package collection;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import charactor.Hero;
public class TestCollection2to1to3 {
public static void main(String[] args) {
//和ArrayList一样,LinkedList也实现了List接口
List ll=new LinkedList<Hero>();
//所不同的是LinkedList还实现了Deque,进而又实现了Queue这个接口
//Queue代表FIFO先进先出的队列
Queue<Hero> q=new LinkedList<Hero>();
//加在队列的最后面
q.offer(new Hero("hero1"));q.offer(new Hero("hero2"));
q.offer(new Hero("hero3"));q.offer(new Hero("hero4"));
System.out.println("初始化队列:\t"+q);
//取出第一个Hero,FIFO先进先出
Hero h=q.poll();
System.out.println(h);
System.out.println("取出第一个元素之后的队列:"+q);
//把第一个元素拿出来看一看,但是不取出来
h=q.peek();
System.out.println("查看peek()第一个元素:\t"+h);
System.out.println("查看第一个元素并不会导致第一个元素被取出来:\t"+q);
}
}
2.1.4 ArrayList 与 LinkedList的区别
ArrayList 与 LinkedList的区别是面试常常会问到的考题
具体区别,详见 ArrayList 与 LinkedList的区别
package collection;
import java.util.LinkedList;
import charactor.Hero;
public class TestCollection2to1to4 {
LinkedList<Hero> heros=new LinkedList<>();
public static void main(String[] args) {
TestCollection2to1to4 h=new TestCollection2to1to4();
for(int i=0;i<5;i++)
h.push(new Hero("hero"+i));
for(int i=0;i<5;i++)
System.out.println("依次取出对象"+h.pull());
}
//把英雄推入到最后位置
public void push(Hero h){
heros.addLast(h);
}
//把最后一个英雄取出来
public Hero pull(){
return heros.removeLast();
}
//查看最后一个英雄
public Hero peek(){
return heros.getLast();
}
}
2.2 二叉树
2.2.1 二叉树概念
二叉树由各种节点组成
二叉树特点:
每个节点都可以有左子节点,右子节点
每一个节点都有一个值
package collection;
public class Node {
// 左子节点
public Node leftNode;
// 右子节点
public Node rightNode;
// 值
public Object value;
}
2.2.2 二叉树排序-插入数据
假设通过二叉树对如下10个随机数进行排序
67,7,30,73,10,0,78,81,10,74
排序的第一个步骤是把数据插入到该二叉树中
插入基本逻辑是,小、相同的放左边,大的放右边
- 67 放在根节点
- 7 比 67小,放在67的左节点
- 30 比67 小,找到67的左节点7,30比7大,就放在7的右节点
- 73 比67大, 放在67的右节点
- 10 比 67小,找到67的左节点7,10比7大,找到7的右节点30,10比30小,放在30的左节点。
…
… - 10比67小,找到67的左节点7,10比7大,找到7的右节点30,10比30小,找到30的左节点10,10和10一样大,放在左边
2.2.3 二叉树排序-遍历
通过上一个步骤的插入行为,实际上,数据就已经排好序了。 接下来要做的是看,把这些已经排好序的数据,遍历成我们常用的List或者数组的形式
二叉树的遍历分左序,中序,右序
左序即: 中间的数遍历后放在左边
中序即: 中间的数遍历后放在中间
右序即: 中间的数遍历后放在右边
如图所见,我们希望遍历后的结果是从小到大的,所以应该采用中序遍历
package collection;
import java.util.ArrayList;
import java.util.List;
public class Node1 {
//左节点
public Node1 leftNode;
//右节点
public Node1 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 Node1();
leftNode.add(v);
}
else{
if(null==rightNode)
rightNode=new Node1();
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};
Node1 roots=new Node1();
for(int number:randoms)
roots.add(number);
System.out.println(roots.values());
}
}
2.2.4 练习-英雄二叉树
根据上面的学习和理解,设计一个Hero二叉树,HeroNode.
可以向这个英雄二叉树插入不同的Hero对象,并且按照Hero的血量倒排序。
随机生成10个Hero对象,每个Hero对象都有不同的血量值,插入这个HeroNode后,把排序结果打印出来。
package collection;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Random;
public class Node2 {
public Node2 leftNode;
public Node2 rightNode;
public Node3 node;
public void add(Node3 v){
if(null==node) node=v;
else{
if((Integer)node.hp-((Integer)v.hp)<=0){
if(null==leftNode)
leftNode=new Node2();
leftNode.add(v);
}
else{
if(null==rightNode)
rightNode=new Node2();
rightNode.add(v);
}
}
}
public List<String> values(){
List<String> values=new ArrayList<String>();
if(null!=leftNode) values.addAll(leftNode.values());
values.add("name:"+node.name+" hp:"+node.hp);
if(null!=rightNode) values.addAll(rightNode.values());
return values;
}
public static void main(String[] args) {
Node3[] heros=new Node3[5];
for(int i=0;i<5;i++)
heros[i]=new Node3("hero"+i,(int)(Math.random()*1000));
Node2 roots=new Node2();
for(Node3 hero:heros){
roots.add(hero);
}
System.out.println(roots.values());
}
}
package collection;
public class Node3 {
public String name;
public int hp;
public Node3(String name,int hp){
this.name=name;this.hp=hp;
}
public Node3(){
}
}
2.3 HashMap
2.3.1 HashMap的键值对
HashMap储存数据的方式是—— 键值对
package collection;
import java.util.HashMap;
public class TestCollection2to3to1 {
public static void main(String[] args) {
HashMap<String,String> directory=new HashMap<>();
directory.put("adc","物理英雄");
directory.put("apc", "魔法英雄");
directory.put("t","坦克");
System.out.println(directory.get("t"));
}
}
2.3.2 键不能重复,值可以重复
对于HashMap而言,key是唯一的,不可以重复的。
所以,以相同的key 把不同的value插入到 Map中会导致旧元素被覆盖,只留下最后插入的元素。
不过,同一个对象可以作为值插入到map中,只要对应的key不一样
package collection;
import java.util.HashMap;
import charactor.Hero;
public class TestCollection2to3to2 {
public static void main(String[] args) {
HashMap<String,Hero> heroMap=new HashMap<String,Hero>();
heroMap.put("gareen", new Hero("gareen1"));
System.out.println(heroMap);
//key位gareen已经有value了,再以gareen作为key放入数据,会导致原英雄被覆盖
heroMap.put("gareen",new Hero("gareen2"));
System.out.println(heroMap);
heroMap.clear();
Hero gareen=new Hero("gareen");
//同一个对象可以作为值插入到map中,只要对应的key值不一样
heroMap.put("hero1",gareen);heroMap.put("hero2",gareen);
System.out.println(heroMap);
}
}
2.3.3 练习-查找内容性能比较
准备一个ArrayList其中存放3000000(三百万个)Hero对象,其名称是随机的,格式是hero-[4位随机数]
hero-3229
hero-6232
hero-9365
…
因为总数很大,所以几乎每种都有重复,把名字叫做 hero-5555的所有对象找出来
要求使用两种办法来寻找
- 不使用HashMap,直接使用for循环找出来,并统计花费的时间
- 借助HashMap,找出结果,并统计花费的时间
package collection;
import java.util.ArrayList;
import java.util.List;
import java.util.HashMap;
import charactor.Hero;
public class TestCollection2to3to3 {
public static void main(String[] args) {
List<Hero> hs=new ArrayList<>();
System.out.println("初始化开始");
for(long i=0;i<300000;i++){
Hero h=new Hero("hero-"+((int)Math.random()*9000+1000));
hs.add(h);
}
HashMap<String,List<Hero>> heroMap=new HashMap<>();
for(Hero h:hs){
List<Hero> list=heroMap.get(h.name);
if(list==null){
list=new ArrayList<Hero>();
heroMap.put(h.name,list);
}
list.add(h);
}
System.out.print("初始化结束");
System.out.print("开始查找");
findByMap(heroMap);
findByIterator(hs);
}
private static List<Hero> findByMap(HashMap<String,List<Hero>> m)
{
long start=System.currentTimeMillis();
List<Hero> result=m.get("hero-1000");
long end=System.currentTimeMillis();
System.out.printf("通过map查找,一共找到了%d个英雄,耗时%d毫秒%n",result.size(),end-start);
return result;
}
private static List<Hero> findByIterator(List<Hero> hs)
{
long start=System.currentTimeMillis();
List<Hero> result=new ArrayList<>();
for(Hero h:hs){
if(h.name.equals("hero-1000")){
result.add(h);
}
}
long end=System.currentTimeMillis();
System.out.printf("通过for查找,一共找到了%d个英雄,耗时%d毫秒%n",result.size(),end-start);
return result;
}
}
2.4 HashSet
2.4.1 元素不能重复
Set中的元素,不能重复
package collection;
import java.util.HashSet;
public class TestCollection2to4to1 {
public static void main(String[] args) {
HashSet<String> names=new HashSet<String>();
names.add("gareen");
System.out.println(names);
//第二次插入同样的数据,是插不进去的,容器中智慧保留一个
names.add("gareen");
System.out.println(names);
}
}
2.4.2 没有顺序
Set中的元素,没有顺序。
严格的说,是没有按照元素的插入顺序排列
HashSet的具体顺序,既不是按照插入顺序,也不是按照hashcode的顺序。关于hashcode有专门的章节讲解: hashcode 原理。
以下是HashSet源代码中的部分注释
/**
- It makes no guarantees as to the iteration order of the set;
- in particular, it does not guarantee that the order will remain constant over time.
*/
不保证Set的迭代顺序; 确切的说,在不同条件下,元素的顺序都有可能不一样
换句话说,同样是插入0-9到HashSet中, 在JVM的不同版本中,看到的顺序都是不一样的。 所以在开发的时候,不能依赖于某种臆测的顺序,这个顺序本身是不稳定的
package collection;
import java.util.HashSet;
public class TestCollection2to4to2 {
public static void main(String[] args) {
HashSet<Integer> numbers=new HashSet<Integer>();
numbers.add(9);
numbers.add(512);
numbers.add(1);
//Set中的元素顺序,不是按照插入顺序
System.out.println(numbers);
}
}
2.4.3 遍历
Set不提供get()来获取指定位置的元素
所以遍历需要用到迭代器,或者增强型for循环
package collection;
import java.util.HashSet;
import java.util.Iterator;
public class TestCollection2to4to3 {
public static void main(String[] args) {
HashSet<Integer> numbers=new HashSet<Integer>();
for(int i=0;i<20;i++)
numbers.add(i);
//set不提供get方法来获取指定位置的元素
//遍历set可以采用迭代器Iterator
for(Iterator<Integer> iterator=numbers.iterator();iterator.hasNext();){
Integer i=(Integer)iterator.next();
System.out.print(i+" ");
}
System.out.println();
//或者采用增强型for循环
for(Integer i:numbers){
System.out.print(i+" ");
}
}
}
2.4.4 HashSet和HashMap的关系
通过观察HashSet的源代码(如何查看源代码)
可以发现HashSet自身并没有独立的实现,而是在里面封装了一个Map.
HashSet是作为Map的key而存在的
而value是一个命名为PRESENT的static的Object对象,因为是一个类属性,所以只会有一个。
private static final Object PRESENT = new Object();
2.4.5 练习-HashSet
在比较字符串章节,有一个同样的练习
创建一个长度是100的字符串数组
使用长度是2的随机字符填充该字符串数组
统计这个字符串数组里重复的字符串有多少种
使用HashSet来解决这个问题
package collection;
import java.util.HashSet;
public class TestCollection2to4to4 {
public static void main(String[] args) {
String[] str=new String[100];
for(int i=0;i<100;i++){
char[] ch=new char[2];
for(int j=0;j<2;){
char a=(char)(Math.random()*(122-65)+65);
if(Character.isLetter(a)){
ch[j]=a;j++;
}
}
str[i]=new String(ch);
}
System.out.println("str的内容为");
for(String a:str){
System.out.println(a);
}
System.out.println("-----------------");
HashSet<String> content =new HashSet<>();
HashSet<String> plus=new HashSet<>();
for(int i=0;i<str.length;i++){
if(content.contains(str[i])){
plus.add(str[i]);
}
else
content.add(str[i]);
}
System.out.printf("总共有%d种相同的字符串,分别为:\n",plus.size());
for(String b:plus)
System.out.println(b);
}
}
2.5 Collection
Collection是一个接口
Collection是 Set List Queue和 Deque的接口
Queue: 先进先出队列
Deque: 双向链表
注:Collection和Map之间没有关系,Collection是放一个一个对象的,Map 是放键值对的
注:Deque 继承 Queue,间接的继承了 Collection
2.6 Collections 是一个类,容器的工具类,就如同Arrays是数组的工具类
2.6.1 反转
reverse 使List中的数据发生翻转
package collection;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class TestCollection2to6to1 {
public static void main(String[] args) {
//初始化numbers
List<Integer> numbers=new ArrayList<>();
for(int i=0;i<10;i++){
numbers.add(i);
}
System.out.println("集合中的数据");
System.out.println(numbers);
Collections.reverse(numbers);
System.out.println("翻转后的集合中的数据:");
System.out.println(numbers);
}
}
2.6.2 混淆
shuffle 混淆List中数据的顺序
package collection;
import java.util.ArrayList;
import java.util.List;
import java.util.Collections;
public class TestCollection2to6to2 {
public static void main(String[] args) {
List<Integer> numbers=new ArrayList<>();
for(int i=0;i<10;i++) numbers.add(i);
System.out.println("集合中的数据:");
System.out.println(numbers);
Collections.shuffle(numbers);
System.out.println("混淆后的数据:");
System.out.println(numbers);
}
}
2.6.3 排序
sort 对List中的数据进行排序
package collection;
import java.util.ArrayList;
import java.util.List;
import java.util.Collections;
public class TestCollection2to6to2 {
public static void main(String[] args) {
List<Integer> numbers=new ArrayList<>();
for(int i=0;i<10;i++) numbers.add(i);
System.out.println("集合中的数据:");
System.out.println(numbers);
Collections.shuffle(numbers);
System.out.println("混淆后的数据:");
System.out.println(numbers);
Collections.sort(numbers);
System.out.println("排序之后的数据:");
System.out.println(numbers);
}
}
2.6.4 交换
swap 交换两个数据的位置
package collection;
import java.util.ArrayList;
import java.util.List;
import java.util.Collections;
public class TestCollection2to6to4 {
public static void main(String[] args) {
List<Integer> numbers=new ArrayList<>();
for(int i=0;i<10;i++) numbers.add(i);
System.out.println("集合中的数据:");
System.out.println(numbers);
Collections.swap(numbers, 0, 5);
System.out.println("交换位置0和位置5的数据:");
System.out.println(numbers);
}
}
2.6.5 滚动
rotate 把List中的数据,向右滚动指定单位的长度
package collection;
import java.util.ArrayList;
import java.util.List;
import java.util.Collections;
public class TestCollection2to6to5 {
public static void main(String[] args) {
List<Integer> numbers=new ArrayList<>();
for(int i=0;i<10;i++) numbers.add(i);
System.out.println("集合中的数据:");
System.out.println(numbers);
Collections.rotate(numbers,2);
System.out.println("把集合向右滚动2个单位后:");
System.out.println(numbers);
}
}
2.6.6 线程安全化
synchronizedList 把非线程安全的List转换为线程安全的List。 因为截至目前为止,还没有学习线程安全的内容,暂时不展开。 线程安全的内容将在多线程章节展开。
package collection;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class TestCollection {
public static void main(String[] args) {
List<Integer> numbers = new ArrayList<>();
System.out.println("把非线程安全的List转换为线程安全的List");
List<Integer> synchronizedNumbers = (List<Integer>) Collections.synchronizedList(numbers);
}
}
2.6.7 练习-统计概率
package collection;
import java.util.ArrayList;
import java.util.List;
import java.util.Collections;
public class TestCollection2to6to7 {
public static void main(String[] args) {
List<Integer> numbers=new ArrayList<>();
int t=0;
for(int i=0;i<9;i++) numbers.add(i);
for(int i=0;i<1000000;i++){
Collections.shuffle(numbers);
if(numbers.get(0)==3&&numbers.get(1)==1&&numbers.get(2)==4)
t++;
}
System.out.println("shuffle 1000,000 次,统计3 1 4出现的概率:");
System.out.println((double)t*100.0/1000000+"%");
}
}
3 ArrayList vs HashSet
3.1 ArrayList vs LinkedList
3.1.1 是否有顺序
ArrayList: 有顺序
HashSet: 无顺序
HashSet的具体顺序,既不是按照插入顺序,也不是按照hashcode的顺序。关于hashcode有专门的章节讲解: hashcode 原理。
以下是HasetSet源代码中的部分注释
不保证Set的迭代顺序; 确切的说,在不同条件下,元素的顺序都有可能不一样
换句话说,同样是插入0-9到HashSet中, 在JVM的不同版本中,看到的顺序都是不一样的。 所以在开发的时候,不能依赖于某种臆测的顺序,这个顺序本身是不稳定的
3.1.2 能否重复
List中的数据可以重复
Set中的数据不能够重复
重复判断标准是:
首先看hashcode是否相同
如果hashcode不同,则认为是不同数据
如果hashcode相同,再比较equals,如果equals相同,则是相同数据,否则是不同数据
更多关系hashcode,请参考hashcode原理
3.1.3 练习-不重复的随机数
package collection;
import java.util.HashSet;
import java.util.Set;
public class TestCollection3to1to2 {
public static void main(String[] args) {
Set<Integer> numbers=new HashSet<>();
while(numbers.size()<50){
int i=(int)(Math.random()*10000);
numbers.add(i);
}
System.out.println("得到50个不重复的随机数:");
System.out.println(numbers);
}
}
3.2 ArrayList vs LinkedList
3.2.1 ArrayList和LinkedList的区别
ArrayList 插入,删除数据慢
LinkedList, 插入,删除数据快
ArrayList是顺序结构,所以定位很快,指哪找哪。 就像电影院位置一样,有了电影票,一下就找到位置了。
LinkedList 是链表结构,就像手里的一串佛珠,要找出第99个佛珠,必须得一个一个的数过去,所以定位慢
3.3 HashMap和Hashtable的区别
3.3.1
HashMap和Hashtable都实现了Map接口,都是键值对保存数据的方式
区别1:
HashMap可以存放 null
Hashtable不能存放null
区别2:
HashMap不是线程安全的类
Hashtable是线程安全的类
鉴于目前学习的进度,不对线程安全做展开,在线程章节会详细讲解
package collection;
import java.util.HashMap;
import java.util.Hashtable;
public class TestCollection {
public static void main(String[] args) {
//HashMap和Hashtable都实现了Map接口,都是键值对保存数据的方式
HashMap<String,String> hashMap = new HashMap<String,String>();
//HashMap可以用null作key,作value
hashMap.put(null, "123");
hashMap.put("123", null);
Hashtable<String,String> hashtable = new Hashtable<String,String>();
//Hashtable不能用null作key,不能用null作value
hashtable.put(null, "123");
hashtable.put("123", null);
}
}
3.3.2 练习-反转key和value
使用如下键值对,初始化一个HashMap:
adc - 物理英雄
apc - 魔法英雄
t - 坦克
对这个HashMap进行反转,key变成value,value变成key
提示: keySet()可以获取所有的key, values()可以获取所有的value
package collection;
import java.util.HashMap;
import java.util.Set;
public class TestCollection3to3to2 {
public static void main(String[] args) {
HashMap<String,String> heros=new HashMap<>(),heross=new HashMap<>();
heros.put("adc","物理英雄");
heros.put("apc","魔法英雄");
heros.put("t","坦克");
System.out.println(heros);
Set<String> keys=heros.keySet();
for(String str:keys){
heross.put(heros.get(str), str);
}
System.out.println(heross);
}
}
3.4 几种set
3.4.1 HashSet | LinkedHashSet | TreeSet
HashSet: 无序
LinkedHashSet: 按照插入顺序
TreeSet: 从小到大排序
package collection;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.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.4.2 练习-既不重复,又有顺序
利用LinkedHashSet的既不重复,又有顺序的特性,把Math.PI中的数字,按照出现顺序打印出来,相同数字,只出现一次
package Collection;
import java.util.LinkedHashSet;
public class Test7 {
public static void main(String[] args) {
LinkedHashSet<Integer> set = new LinkedHashSet<>();
double pi = Math.PI;
while(set.size()<10) {
set.add((Integer)(int)(pi%10));
pi*=10;
}
System.out.println(set);
}
}
4 其他
4.1 hashcode原理
4.1.1 List查找的低效率
假设在List中存放着无重复名称,没有顺序的2000000个Hero
要把名字叫做“hero 1000000”的对象找出来
List的做法是对每一个进行挨个遍历,直到找到名字叫做“hero 1000000”的英雄。
最差的情况下,需要遍历和比较2000000次,才能找到对应的英雄。
测试逻辑:
- 初始化2000000个对象到ArrayList中
- 打乱容器中的数据顺序
- 进行10次查询,统计每一次消耗的时间
不同计算机的配置情况下,所花的时间是有区别的。 在本机上,花掉的时间大概是600毫秒左右
4.1.2 HashMap的性能表现
使用HashMap 做同样的查找
- 初始化2000000个对象到HashMap中。
- 进行10次查询
- 统计每一次的查询消耗的时间
可以观察到,几乎不花时间,花费的时间在1毫秒以内
4.1.3 HashMap原理与字典
在展开HashMap原理的讲解之前,首先回忆一下大家初中和高中使用的汉英字典。
比如要找一个单词对应的中文意思,假设单词是Lengendary,首先在目录找到Lengendary在第 555页。
然后,翻到第555页,这页不只一个单词,但是量已经很少了,逐一比较,很快就定位目标单词Lengendary。
555相当于就是Lengendary对应的hashcode
4.1.4 分析HashMap性能卓越的原因
-----hashcode概念-----
所有的对象,都有一个对应的hashcode(散列值)
比如字符串“gareen”对应的是1001 (实际上不是,这里是方便理解,假设的值)
比如字符串“temoo”对应的是1004
比如字符串“db”对应的是1008
比如字符串“annie”对应的也是1008
-----保存数据-----
准备一个数组,其长度是2000,并且设定特殊的hashcode算法,使得所有字符串对应的hashcode,都会落在0-1999之间
要存放名字是"gareen"的英雄,就把该英雄和名称组成一个键值对,存放在数组的1001这个位置上
要存放名字是"temoo"的英雄,就把该英雄存放在数组的1004这个位置上
要存放名字是"db"的英雄,就把该英雄存放在数组的1008这个位置上
要存放名字是"annie"的英雄,然而 "annie"的hashcode 1008对应的位置已经有db英雄了,那么就在这里创建一个链表,接在db英雄后面存放annie
-----查找数据-----
比如要查找gareen,首先计算"gareen"的hashcode是1001,根据1001这个下标,到数组中进行定位,(根据数组下标进行定位,是非常快速的) 发现1001这个位置就只有一个英雄,那么该英雄就是gareen.
比如要查找annie,首先计算"annie"的hashcode是1008,根据1008这个下标,到数组中进行定位,发现1008这个位置有两个英雄,那么就对两个英雄的名字进行逐一比较(equals),因为此时需要比较的量就已经少很多了,很快也就可以找出目标英雄
这就是使用hashmap进行查询,非常快原理。
这是一种用空间换时间的思维方式
4.1.5 HashSet判断是否重复
HashSet的数据是不能重复的,相同数据不能保存在一起,到底如何判断是否是重复的呢?
根据HashSet和HashMap的关系,我们了解到因为HashSet没有自身的实现,而是里面封装了一个HashMap,所以本质上就是判断HashMap的key是否重复。
再通过上一步的学习,key是否重复,是由两个步骤判断的:
hashcode是否一样
如果hashcode不一样,就是在不同的坑里,一定是不重复的
如果hashcode一样,就是在同一个坑里,还需要进行equals比较
如果equals一样,则是重复数据
如果equals不一样,则是不同数据。
4.2 比较器
4.2.1 Comparator
假设Hero有三个属性 name,hp,damage
一个集合中放存放10个Hero,通过Collections.sort对这10个进行排序
那么到底是hp小的放前面?还是damage小的放前面?Collections.sort也无法确定
所以要指定到底按照哪种属性进行排序
这里就需要提供一个Comparator给定如何进行两个对象之间的大小比较
package charactor;
public class Hero {
public String name;
public float hp;
public int damage;
public Hero() {
}
public Hero(String name) {
this.name = name;
}
public String toString() {
return "Hero [name=" + name + ", hp=" + hp + ", damage=" + damage + "]\r\n";
}
public Hero(String name, int hp, int damage) {
this.name = name;
this.hp = hp;
this.damage = damage;
}
}
package collection;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Random;
import charactor.Hero;
public class TestCollection {
public static void main(String[] args) {
Random r =new Random();
List<Hero> heros = new ArrayList<Hero>();
for (int i = 0; i < 10; i++) {
//通过随机值实例化hero的hp和damage
heros.add(new Hero("hero "+ i, r.nextInt(100), r.nextInt(100)));
}
System.out.println("初始化后的集合:");
System.out.println(heros);
//直接调用sort会出现编译错误,因为Hero有各种属性
//到底按照哪种属性进行比较,Collections也不知道,不确定,所以没法排
//Collections.sort(heros);
//引入Comparator,指定比较的算法
Comparator<Hero> c = new Comparator<Hero>() {
@Override
public int compare(Hero h1, Hero h2) {
//按照hp进行排序
if(h1.hp>=h2.hp)
return 1; //正数表示h1比h2要大
else
return -1;
}
};
Collections.sort(heros,c);
System.out.println("按照血量排序后的集合:");
System.out.println(heros);
}
}
4.2.2 Comparable
使Hero类实现Comparable接口
在类里面提供比较算法
Collections.sort就有足够的信息进行排序了,也无需额外提供比较器Comparator
注: 如果返回-1, 就表示当前的更小,否则就是更大
package charactor;
public class Hero implements Comparable<Hero>{
public String name;
public float hp;
public int damage;
public Hero(){
}
public Hero(String name) {
this.name =name;
}
//初始化name,hp,damage的构造方法
public Hero(String name,float hp, int damage) {
this.name =name;
this.hp = hp;
this.damage = damage;
}
@Override
public int compareTo(Hero anotherHero) {
if(damage<anotherHero.damage)
return 1;
else
return -1;
}
@Override
public String toString() {
return "Hero [name=" + name + ", hp=" + hp + ", damage=" + damage + "]\r\n";
}
}
package collection;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Random;
import charactor.Hero;
public class TestCollection {
public static void main(String[] args) {
Random r =new Random();
List<Hero> heros = new ArrayList<Hero>();
for (int i = 0; i < 10; i++) {
//通过随机值实例化hero的hp和damage
heros.add(new Hero("hero "+ i, r.nextInt(100), r.nextInt(100)));
}
System.out.println("初始化后的集合");
System.out.println(heros);
//Hero类实现了接口Comparable,即自带比较信息。
//Collections直接进行排序,无需额外的Comparator
Collections.sort(heros);
System.out.println("按照伤害高低排序后的集合");
System.out.println(heros);
}
}
4.3 聚合操作
JDK8之后,引入了对集合的聚合操作,可以非常容易的遍历,筛选,比较集合中的元素。
像这样:
String name =heros
.stream()
.sorted((h1,h2)->h1.hp>h2.hp?-1:1)
.skip(2)
.map(h->h.getName())
.findFirst()
.get();
但是要用好聚合,必须先掌握Lambda表达式,聚合的章节讲放在Lambda与聚合操作部分详细讲解
package lambda;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Random;
import charactor.Hero;
public class TestAggregate {
public static void main(String[] args) {
Random r = new Random();
List<Hero> heros = new ArrayList<Hero>();
for (int i = 0; i < 10; i++) {
heros.add(new Hero("hero " + i, r.nextInt(1000), r.nextInt(100)));
}
System.out.println("初始化集合后的数据 (最后一个数据重复):");
System.out.println(heros);
//传统方式
Collections.sort(heros,new Comparator<Hero>() {
@Override
public int compare(Hero o1, Hero o2) {
return (int) (o2.hp-o1.hp);
}
});
Hero hero = heros.get(2);
System.out.println("通过传统方式找出来的hp第三高的英雄名称是:" + hero.name);
//聚合方式
String name =heros
.stream()
.sorted((h1,h2)->h1.hp>h2.hp?-1:1)
.skip(2)
.map(h->h.getName())
.findFirst()
.get();
System.out.println("通过聚合操作找出来的hp第三高的英雄名称是:" + name);
}
}