初识集合的应用

说明:

什么是哈希表?

来吧!一文彻底搞懂哈希表!

【数据结构】史上最好理解的红黑树讲解,让你彻底搞懂红黑树

可以先看看: java 集合详解完整版(超详细) Java 集合框架最全详解 (看这篇就够了) 各种数据结构看看:Java 集合总结,详细且易懂

面试问题可以看看: Java 集合总结

关于底层看看数据和链表区别:数组和链表的常用操作时间复杂度分析_数组,链表适用场景,时间复杂度

Redis 的四种搭建模式

1.集合前置介绍

(1) 集合是一个工具类 ,其可以存储任意数量的具有共同属性的对象;

(2)集合的长度可以动态改变,这有别于数组;

(3)应用场景:


2.集合框架的体系结构

image.png

Collection:存储类的对象

(1)List接口:序列;有序,允许重复。实现类: ArrayList

(2)Queue接口:队列;有序,允许重复。实现类:LinkedList(LinkedList类同时也实现了List接口,表示的是链表的内容)。

(3)Set接口:集;无序,不允许重复。实现类: HashSet(哈希集)

image.png

Map:存储键值对

(1)实现类: HashMap(哈希表) :存储键值对。

……………………………………………………


3.集合和数组的区别


List集合体系及应用

1.List概述,仅供浏览,重点还是List的常用方法的用法


Collection接口是输入util包的,需要手动导入。

其中主要方法有:

add():添加元素的方法;clear():清空集合的方法;contains()方法:判断集合中时候有某元素的方法;isEmpty():判断集合是否为空的方法;iterrator():集合遍历方法;

size()方法:获取集合元素数量的方法等等;

……………………………………………………

List接口为Collection接口的子接口,其中新添的方法有:

get(int index)方法:返回列表中指定位置处的元素;indexOf(object o):返回某个对象在列表中的索引;sort():对列表进行排序;

……………………………………………………

ArrayList实现类的构造方法:

ArrayList常用方法见演示实例,也可查API等。


2.问题列举

(1) eclipse使用变量创建构造方法

…………………………………………………

(2) 常见的 add()方法是在list的末尾追加内容,如何在list的中间添加元素?

……………………………………………………

(3) remove() 方法

remove(int index):删除指定索引位置处的元素;

remove(Object o):移除搜索到的第一个;

……………………………………………………

(4) 修改某个元素, set()方法

Set集合体系及应用

1.Set简述

Set(接口):元素无序,不可以重复(内存地址不可重复),称为集;

常见的是实现类HashSet,即哈希集;

由于HashSet可以出现null元素,又因为其不可以重复,所以做多只能有一个null元素;

HashSet具有良好的存取和查找性能,更适用于频繁存取和查找的场景。


HashSet构造方法


2.方法,问题列举

add() 方法:Set只有这一种add()方法

取Set中元素 时,因为Set是无序的,所以没有index,此时需要使用迭代器去获取

So如何得到迭代器?→ iterator()方法

Set放到迭代器后,下一步就是遍历迭代器输出:

……………………………………………………

当向Set中添加重复元素时,不会起什么作用,只会浪费时间浪费内存。 用Set去重,以前这样用过

删除某个元素 :boolean remove(Object o)

boolean removeAll(Set set):删除所有集合中所有元素。

!!!注意: 当通过传统方法删除集合中满足条件的多个元素时候,会报错(因为Set为了安全,不允许在读取Set的时候再删除元素):

对于这种情况,正确的做法是:将集合中所有满足条件的元素组成一个新的子集合,然后调用集合的removeAll()方法即可,如下示例:

判断集合是否为空:isEmpty()


(1) eclipse中自动添加toString()方法

Object类中的 toString()方法用于返回对象的字符串表示,直接打印类就是调用类的toString()方法;一般我们在实际中,习惯重写toString()方法


3.hashcode

Set:不允许存放 内存地址 一样的元素。

(预先说一下(下面会有解释):向Set中存放数据时候,会先通过hashCode()方法看看,这个数据该放在那个“区块中”,然后通过equals()方法查看在该区块中是否有相同的元素)

Set中自然不能添加相同的字符串,相同的数字,在向Set中添加元素时大概率用到了自动装箱,把数字转成了包装类,而直接add(1)时,1是存放在常量池中的,所以通过add(1)形式添加时候,当数字相同时,其包装类对象就是同一个,所以在哈希算法的情况下,都是相同的,即不能重复添加。这个hashcode值的细节咱可不深究,只需要知道这是个“牛逼”的类似数据库的索引即可

问题:

如果,向Set中添加bean类,当new两个各属性信息一样的对象,向Set中添加的时候,是可以添加的,这个时候,看起来是向Set中添加了两个信息完全一样的对象,但其实 Set:不允许存放 内存地址 一样的元素 所以,而, _ Set中控制不可添加重复元4素的方法主要是hashcode()方法和equals()方法,所以有的时候为了防止两个信息完全一样对象重复添加的问题,需要重写这两个方法。_

一般,hashCode()方法我们维持原样不管,面也不用理解该方法的内容;equals()方法可以改一下

……………………………………………………

hashCode()方法背后有个哈希表的概念:

哈希表的简要介绍:

如果用ArrayList(没有利用哈希算法)存储100个正整数,这100个数据在内存中是连续的,当我们向查询某个数据是否在该list中时候,需要挨个比较,效率特别低。而哈希表可以解决这个问题。

那么如何使用哈希表嘞?

哈希表可以理解为通过某种规则将数据进行分类(分块),根据一种计算规则,把数据存放在内存中的不同的“区块中”,存储数据的时候什么数据存在什么“区块中”,这需要一个规则,这个规则其实是根据hashcode,具体的hashcode如何设计和得到需要根据需求来处理。一个好的hashcode的获得,需要严格的算法去完成的,对于一般情况,通过IDE自动生成的hashCode()方法生成的hashcode就够用了,直接拿来用就行了。

下面是一个简单的示例,通过hash规则如下图存放数据,这样当查询数据的时候,可以先根据计算规则,计算该数据在哪个“区块中”,然后就可以直接再在这个“区块中”,通过equals方法去搜寻,这样可以快速地的提高查询速度(不需要从头一个个比较)。以下是一个简单举例。(实际hashcode算法比除三取余复杂的多)。

如当查询97时候,通过97%3 = 1,得知97在1号筒里面,直接在1号桶里去找就行了,提高速度。

注:通过后面的泛型可以知道,一般我们在集合中存放同类的对象,所以在该对象的类中重写hashcode方法和equals方法,就可很好地完成实际的业务需求。?

……………………………………………………

由上面hash表的原理可知,两个对象hashcode相同,只能说明这两个对象是在同一个“区块中”的那么具体如果在同一个区块中区分不同的对象?这是就需要用到符合实际需求的equals()方法。

通过这样改写equals()方法后,虽然两个对象是不同的对象(内存地址不同),但其各个属相相同,那么在该业务场景下也认为这两个对象是“同一个”对象,这样再向Set中添加该对象元素的时候,就不会出现上面描述的那个重复添加相同属性对象的不符合实际业务需求的问题了

注:疑问,到底什么样的对象会被放在不同的内存区域范围中,查多大的对象会被放在相同的内存区域块中,这个和hashCode()方法有关。 ​同一个类的两个对象,这两个对象各属性完全一样,那么这两个对象的hashcode值一定相同吗(在不重写hashCode()方法的情况下)。换句话说,在存储过程中,如果hashcode值不一样,就认为不是重复元素,直接存入,用不到equals()方法了吗?还是只有在hashcode值一样的情况下,才会用到 equals()方法?待解答。****


4.Set查找和删除

contains(object o):判断集合中是否有对象o,其他搜寻的是对象时,也可以利用迭代器等,来遍历通过逻辑判断搜寻(其中的效率估计够呛)。

Set的删除见第二部分【方法,问题列举】

……………………………………………………

Map集合体系及其应用

1.Map简介

Map接口中常见方法:

常用方法有:clear()清空Map;

get(key k) :根据key获取value;

keySet():取出所有key的集合;

put(K key,V value) :向Map中添加元素;

remove(Object key):根据key删除某个键值对元素;

containsKey(Object key):判断Map中是否已有某个key;

等。

……………………………………………………

HashMap的构造方法有多种,等到用到的时候再深入研究的。

其中有一个加载因子,默认是0.75,这其中设计hash表的数据结构,可暂不深究。


2.Map常用方法

Map的定义,添加元素,遍历输出所有Value,遍历输出Key和Value;根据key获取value;

public class DicDemo {
 
	public static void main(String[] args) {
 
		// 1.Map定义
		Map<String,String> animal = new HashMap<String,String>();
		// 2.添加键值对
		animal.put("FristKey", "FristValue");
		animal.put("SecondKey", "SecondValue");
		// 3.打印键值对
		// 3.1使用迭代器:values()得到Map中value的一个collection集合,然后调用集合的iterator()得到集合的迭代器对象
		Iterator<String> it = animal.values().iterator();
		while(it.hasNext()){
			System.out.println(it.next());    // 所以,这个只是打印Map中所有value的值
		}
		// 3.2  一个键值对对象对应一个Entry类对象;entrySet()方法返回Map所有键值对entry类对象的集合;
		Set<Entry<String,String>> entrySet = animal.entrySet();
		for(Entry<String,String> entry:entrySet){
			System.out.println(entry.getKey());
			System.out.println(entry.getValue());
		}

		// 4.根据key获取value值
		// keySet():得到所有key的set集合
		Set<String> keySet = animal.keySet();
		for(String key : keySet){
			// get(Object key):根据key返回value值
			System.out.println(animal.get(key));
			System.out.println(animal.get("dsf"));  // 参数为不存在的key,会返回null
		}
 
	}
 
}

附:

(1)

(2)Scanner错误输入时候,需要next()一下,把错误结果给接收消化掉。

集合排序

疑问:存储在集合中的数据如何排序?

1)存储在集合中的基本数据类型和字符串类型如何排序?

2)存储在集合中的字符串类型如何排序?

3)自定义的类型,在集合中如何排序?(Comparator接口和Comparable接口,这两个接口中定义了比较器,自定义的类可以依照这个比较器进行排序)


回顾:以前数组排序的排序是:Arrays类的sort()方法

那么集合如何排序?

集合排序是使用Collections类中定义的sort()方法对List集合中的数据进行排序:


1.基本数据类型和字符串数据类型的排序

List中整型数据的排序:Collections类的Sort()方法:

List中String类型数据的排序:Collections类的Sort()方法:


2.自定义数据类型的集合排序:Comparator接口

使用Comparator接口或Comparable接口去定义排序的依据.

Comparator接口:

(1)Comparator接口相当于是一个比较器,其可以强行对集合中自定义数据进行排序;即一个实现了这个接口,并按照自己的逻辑重写了compare()方法的类就是一个可以使用的比较器;

(2)可以将Comparator接口(实际是是实现类这个接口的类的示例对象)作为一个参数传递给sort()方法;

即如果是对集合中的自定义数据排序的话,Comparator接口传递给collections.sort()方法;

如果是对数组中的自定义数据进行排序的话,Comparator接口传递给Arrays.sort()方法;

(3) Comparator接口中有一个 int compare(T o1,T o2)方法 :(o1和o2是需要比较的两个对象):

如果o1<o2,返回负整数;

如果o1==o2,返回0;

如果o1>o2,返回正整数;返回值是排序的依据。

(4)Comparator接口中有一个 boolean equals(Object obj) 方法:

这个是继承自Object的equals(),即被Object类中的equals()方法覆盖了,不必要重写,即不用管它。

(5)java.util包下的接口。

……………………………………………………

如下示例:集合中存储Cat类对象,按名字升序,或者,按年龄降序

Cat类:集合中存储对象的类型;

NameComparator类:实现Comparator接口并实现其int compare(T o1,T o2)方法,从而作为一个比较器;

CatTest类:测试类;

/*******************

注:

其排序最终还是调用Collections.sort()方法,只不过调用的是,sort()方法的另一个有两个参数的重载形式而已。

***********************************/

/**
 * Cat类,集合中存储对象的类型
 * @author Administrator
 *
 */
public class Cat {
	private String name;
	private int month;
	private String species;
 
	public Cat(){}
 
	public Cat(String name,int month,String species){
		this.name= name;
		this.month = month;
		this.species = species;
	}
 
	public String getName() {
		return name;
	}
 
	public void setName(String name) {
		this.name = name;
	}
 
	public int getMonth() {
		return month;
	}
 
	public void setMonth(int month) {
		this.month = month;
	}
 
	public String getSpecies() {
		return species;
	}
 
	public void setSpecies(String species) {
		this.species = species;
	}
 
	@Override
	public String toString(){
		return "名字:"+ name+"年龄:"+month+"品种:"+species;
	}
}

这儿因为业务需求,需要比较字符串,所有用到了 **String类中的 compareTo()**方法

/**
 * NameComparator类实现了Comparator接口,并根据业务逻辑要求,实现了接口中的compare方法。
 * 该类可以作为一个比较器,后面其作为Collection.sort()方法的一个参数,影响排序策略和结果
 * @author Administrator
 *
 */
public class NameComparator implements Comparator<Cat> {
 
	@Override
	public int compare(Cat arg0, Cat arg1) {
 
		String name1 = arg0.getName();
		String name2 = arg1.getName();
		// int compareTo(String str)方法:如果name1<name2,返回负整数;如果相等返回0;否则返回正整数。
		// 正好compareTo()方法的返回值逻辑和Comparator接口的compare()方法(即本方法)的逻辑是一样的;
		// 所以本compare()方法直接返回n即可了
		int n = name1.compareTo(name2);
		// 很显然,如果向按名字倒叙排列的话,只需要将上改成:int n = name2.compareTo(name1);即可
 
		return n;
	}
 
}

public class CatTest {
 
	public static void main(String[] args) {
 
		Cat cat1 = new Cat("huahua",5,"短毛猫");
		Cat cat2 = new Cat("fanfan",2,"田园猫");
		Cat cat3 = new Cat("maomao",3,"小黑猫");
 
		ArrayList<Cat> list = new ArrayList<Cat>();
		list.add(cat1);
		list.add(cat2);
		list.add(cat3);
		System.out.print("排序前: ");
		for(Cat cat:list){
			System.out.print(cat+"******");
		}
		System.out.println();
		// 把实现了Comparator接口的类的对象作为参数传入,作为集合的比较器
		Collections.sort(list, new NameComparator());  // Collections方法中sort()方法的重载
		System.out.print("排序后来: ");
		for(Cat cat:list){
			System.out.print(cat+"******");
		}
 
	}
 
}

……………………………………………………

按年龄降序排列:比较器AgeComperator类

/**
 * NameComparator类实现了Comparator接口,并根据业务逻辑要求,实现了接口中的compare方法。
 * 该类可以作为一个比较器,后面其作为Collection.sort()方法的一个参数,影响排序策略和结果.
 * 按年龄降序排序
 * @author Administrator
 *
 */
public class AgeComparator implements Comparator<Cat> {
 
	@Override
	public int compare(Cat o1, Cat o2) {
		int age1 = o1.getMonth();
		int age2= o2.getMonth();
 
		// 按年龄进行降序排序
		return age2-age1;
		//  return age1-age2;这是升序。这而的返回值,只要符合逻辑即可。
	}
	}

然后将CatTest类中的Collections.sort(list, new NameComparator());改为Collections.sort(list, new AgeComparator());即可,即修改下比较器即可。

public class CatTest {
 
	public static void main(String[] args) {
 
		Cat cat1 = new Cat("huahua",5,"短毛猫");
		Cat cat2 = new Cat("fanfan",2,"田园猫");
		Cat cat3 = new Cat("maomao",3,"小黑猫");
 
		ArrayList<Cat> list = new ArrayList<Cat>();
		list.add(cat1);
		list.add(cat2);
		list.add(cat3);
		System.out.print("排序前: ");
		for(Cat cat:list){
			System.out.print(cat+"******");
		}
		System.out.println();
		// 把实现了Comparator接口的类的对象作为参数传入,作为集合的比较器
		Collections.sort(list, new AgeComparator());  // Collections方法中sort()方法的重载
		System.out.print("排序后来: ");
		for(Cat cat:list){
			System.out.print(cat+"******");
		}
 
	}
 
}

Comparable

1.Comparable接口

Comparable接口:

(1)java.lang包下的接口;

(2)Comparable接口只有一个方法:int compareTo(T o)方法;

如: obj1.compareTo(obj2):obj1小于、等于、大于obj2时,分别返回负整数、零、正整数。

(3)一个集合中的元素是某个自定类型,如果要多其排序,那么这个自定义类需要实现Comparable接口,并且按照业务需求,实现Comparable接口的compareTo(T o)方法。

(4)对于集合,其中的元素的类实现了Comparable接口的话,可以调用Collections.sort()方法完成集合的排序;

即如果是对集合中的自定义数据排序的话,使用Collections.sort()方法;

如果是对数组中的自定义数据进行排序的话,使用Arrays.sort()方法;

……………………………………………………

示例:按照价格从小到大正序排列:

Goods类:集合中存储的元素为Goods类对象;Goods类需要实现Comparable接口,并根据实际业务需要,实现Comparable接口的compareTo()方法;

GoodsTest类:测试类;

 
    /**
     * (1)集合中存放Goods类型的对象,那么Goods类就要实现Comparable接口
     * @author Administrator
     *
     */
    public class Goods implements Comparable<Goods> {
 
    	private String id;
    	private String name;
    	private double price;
 
    	public Goods(){}
    	public Goods(String id,String name,double price){
    		this.id = id;
    		this.name = name;
    		this.price = price;
    	}
 
    	public String getId() {
    		return id;
    	}
    	public void setId(String id) {
    		this.id = id;
    	}
    	public String getName() {
    		return name;
    	}
    	public void setName(String name) {
    		this.name = name;
    	}
    	public double getPrice() {
    		return price;
    	}
    	public void setPrice(double price) {
    		this.price = price;
    	}
 
    	@Override
    	public String toString(){
    		return "编号:"+id+"名称:"+name+"价格:"+price;
    	}
 
    	// 根据业务要求,实现Comparable接口中的compareTo()方法
    	@Override
    	public int compareTo(Goods arg0) {
 
    		double price1 = this.getPrice();
    		double price2 = arg0.getPrice();
    		double diff = price1 - price2;// 根据价格升序排列
    		if(diff >0){
    			return 1;
    		}else if(diff == 0){
    			return 0;
    		}else{
    			return -1;
    		}
 
    	}
 
    }
 
 


    public class GoodsTest {
 
    	public static void main(String[] args) {
 
    		Goods g1 = new Goods("s0001","手机",2000);
    		Goods g2 = new Goods("s0002","冰箱",5000);
    		Goods g3 = new Goods("s0003","电视机",3000);
    		ArrayList<Goods> list = new ArrayList<Goods>();
    		list.add(g1);
    		list.add(g2);
    		list.add(g3);
    		System.out.println("排序前:");
    		for(Goods good:list){
    			System.out.println(good);
    		}
    		Collections.sort(list);    // 调用方法进行集合排序
    		System.out.println("排序后:");
    		for(Goods good:list){
    			System.out.println(good);
    		}
 
    	}
 
    }
 

注:此时调用的sort()方法是一个参数的那个


2.compareTo()方法

可以看到,在使用Comparable接口的时候,接口的实现类实现了compareTo()方法;即这个方法是Comparable接口的compareTo()方法;

其实,对于基本数据类型的包装类,字符串类也都有compareTo()方法

(1)基本数据类型包装类的compareTo()方法就是比较大小的:

……………………………………………………

(2)String类的compareTo()方法,是挨个比较字符串中每个字符的Unicode值;全相等就返回0;挨个比较后,遇到小于的了就返回负数,遇到大的了就返回整数。


3.Comparator接口和Comparable接口比较

这儿都是重复阐述,看一下就行

具体在实际应用那种,选择哪种,或二者如何结合使用,需要慢慢积累。

TreeSet

由前面可知,List是有序的集合,其可以排序;而Set和Map是不涉及到排序的;

但是,除了List,Set和Map,还有一个TreeSet也是有序的,所有对于TreeSet集合也是可以进行排序的。


1.TreeSet存储String类型数据

……………………………………………………

实测发现:


2.TreeSet存储Integer型数据

实测发现:如果没有相等的,那么会返回集合中小于这个数的 最大的那一个数


3.TreeSet存储自定义类型对象