关于LinkedList:采用双向循环链表实现 可以从前往后查找或是从后往前查找
循环链表:如果最后一个元素的下一个元素指向的是开头的第一个元素,而开头的第一个元素的上一个元素指向的是最后一个元素 那么就是所谓的循环链表
LinkedList和ArrayList的优势分别是什么?
LinkedList添加和删除元素的效率会很高,但查找遍历效率会很低 因为采用的是双向训话链表
ArrayList查找遍历的效率很高,因为底层采用的是数组且存储时是采用等大且连续的空间
6.0 Entry: 理解为 船
船有三个重要的属性:
船舱:用来装数据
船头:用来指向上一艘船
船尾:用来指向下一艘船
header: 可以理解为岸边上拴船用的桩子
新元素的下一个 指向header 一艘新船加入舰队 新船的船尾应当连接岸边的桩子
新元素的上一个 指向header的上一个 新船的船头应该连接岸边桩子指向的上一艘船(如果这艘船是整个舰队第一艘船的话 那么它的船头也将直接指向它自己)
新元素的上一个的下一个 指向新元素本身 新船的船尾连接的那艘船的船头的绳子,应该连接上新船的本身
新元素的下一个的上一个 指向新元素本身 新船的船头连接的那艘船的船尾 应该连接上新船本身
//在new LinkedList的时候 它首先会先把它的三个属性赋值为空(船舱,船头,船尾)但是在无参的构造方法当中 有让它自己的内存地址赋值给了它的上一个和它的下一个
//如果说 岸边上一艘船都没有的时候,那岸边上用来拴船的上一根绳子和下一根绳子都应该绑在它自己身上
private transient Entry<E> header = new Entry<E>(null, null, null);
private transient int size = 0;
/**
* Constructs an empty list.
*/
public LinkedList() {
header.next = header.previous = header;
}
7.0 Node:
HashSet:
哈希表 散列表
所谓的散列 就是将一大堆数据分散排列到若干小组
基本用法:
import java.util.HashSet;
import java.util.Set;
//hashSet的基本用法
//HashSet的唯一 应当理解为"唯一"
//HashSet 如何判定唯一: 1st.hashCode() && (2nd. == || 3rt.equals)
public class TestHashSet1 {
public static void main(String[] args){
Set<String> set = new HashSet<String>();
set.add("Tom");
set.add("Tom");
System.out.println(set.size());//1
HashSet<Stu> set2 = new HashSet<Stu>();
Stu s1 = new Stu("Tom");
Stu s2 = new Stu("Tom");
set2.add(s1);
set2.add(s2);
System.out.println(set2.size());//2 ?
}
}
class Stu{
String name;
public Stu(String name){
this.name = name;
}
//1st. 分别返回两个对象的哈希码 根据返回的哈希码进入不同的分组(如果两个哈希码相同的话,则进入到同一个小组,那么就需要下边的equals方法再次进行判断了)
@Override
public int hashCode(){//散列依据
return name.hashCode()*137;//(*137)用来尽量降低重码概率
}
//2nd. 比较具体内容是否相同
@Override
public boolean equals(Object obj){
if(obj == null) return false;
if(!(obj instanceof Stu)) return false;
if(obj ==this) return true;
Stu s1 = this;
Stu s2 = (Stu)obj;
return s1.name.equals(s2.name);
}
}
HashSet的比较机制
import java.util.HashSet;
import java.util.Set;
//HashSet的比较机制
//其实 hashSet 判定唯一根本不是两个步骤,而是三个步骤
//hashCode == equals
// 1st <span style="white-space:pre"> </span>2nd 3rt
// 1st && (2nd || 3rt) // HashSet判断是否是相同的表达式
//如果你希望你的HashMap 根据 == 判断是否唯一的,而不适用hashCode()和equals()可以使用IdentityHashMap 类
public class TestHashSet2 {
public static void main(String[] args) {
Set<Student> set = new HashSet<Student>();
Student stu = new Student("小明");
set.add(stu);
set.add(stu);
System.out.println(set.size());
}
}
class Student{
String name;
public Student(String name){
this.name = name;
}
@Override
public int hashCode(){
//return 1;//直接返回1,也就是说上边的两次add() 返回的哈希码是相同的
return (int)(Math.random()*10000);//每次调用hashCode() 返回一个随机数 确保Hash值是一样的
}
@Override
public boolean equals(Object obj){
return false;//返回false 表示认定为不同的两个对象
}
}
HashSet添加新元素时 如果判定为重复元素,是舍弃还是替换?
import java.util.HashMap;
//HashSet在添加元素的时候 如果判定为重复元素,那么该如何处理? 是舍弃新来还是替换原有的
//HashSet先入为主,先到先得,后来的重复元素会直接舍弃,不会替换原有的元素
public class TestHashSet3 {
public static void main(String[] args){
/*
HashSet<Teacher> set = new HashSet<Teacher>();
Teacher t1 = new Teacher("Tom");
Teacher t2 = new Teacher("Jerry");
set.add(t1);
set.add(t2);
System.out.println(set);//tom
*/
/**
* HashMap添加元素的时候
* 主键 先入为主,先到先得
* 值 后来替换的值
* 从而让我们更方便的替换一个原有主键所对应的那个值
*
*/
HashMap<Teacher,Integer> map = new HashMap<Teacher,Integer>();
Teacher t1 = new Teacher("Tom");
Teacher t2 = new Teacher("Jerry");
map.put(t1, 60);
map.put(t2,100);
System.out.println(map);//{Tom=100}
//HashMap集合中 新来的主键被舍弃了,但是值却保存了下来
}
}
class Teacher{
String name;
public Teacher(String name){
this.name = name;
}
@Override
public int hashCode(){
//return name.hashCode()*137;
return 1;
}
@Override
public boolean equals(Object obj){
// if(obj == null) return false;
// if(!(obj instanceof Student))return false;
// if(obj == this)return true;
// Teacher s1 = this;
// Teacher s2 = (Teacher)obj;
// return s1.name.equals(s2.name);
return true;
}
@Override
public String toString(){
return name;
}
}
HashSet的remove方法
import java.util.HashSet;
import java.util.Set;
//HashSet 的remove方法 同样尊重hashCode() == equals三个比较步骤,只不过结论相反
//add();认定重复元素 那么久不添加进集合了,但在remove()的时候认定相同元素才能进行删除操作
public class TestHashSet5 {
public static void main(String[] args){
Set<Student2> set = new HashSet<Student2>();
Student2 s1 = new Student2("Tom");
set.add(s1);
System.out.println(set.size());//1
set.remove(s1);
System.out.println(set.size());
}
}
class Student2{
String name;
public Student2(String name){
this.name = name;
}
@Override
public int hashCode(){
return name.hashCode();
//return (int)(Math.random()*100);//返回的Hash码不同,不是同一个对象
}
@Override
public boolean equals(Object obj){
Student2 s1 = this;
Student2 s2 = (Student2)obj;
if(obj == null)return false;
if(!(obj instanceof Student2)) return false;
if(obj == this) return true;
//return s1.name.equals(s2.name);
return false;
}
public String toString(){
return name;
}
}
HashSet是如何遍历元素的
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.util.HashSet;
import javax.swing.JOptionPane;
//理解HashSet底层是如何存数和遍历元素的
public class TestHashSet6{
public static void main(String[] args) throws Exception{
//分组组数*加载因子 = 阈值(threshold) => 哈希表能hold对少元素 哈希表其实就是一个链表形成的数组,而每一个链表都能连接无数个元素,哈希表扩容不是因为内存空间不足,
//而是因为它要保证性能 在元素总量不变的情况下你的分组组数越多每一组散列到的元素个数就越少 然后比较的步骤就会越简练,效率就会越高
//默认值 16分组组数 默认分组组数必须要是2的N次方,0.75F:加载因子
HashSet<String> set = new HashSet<String>(16,0.75F);
//逐行的读取,BufferedReader 包装流
BufferedReader buf = new BufferedReader(new FileReader("D:/MyEclipse 8.5/TestHashSet/src/com/etoak/hashSet/focus.txt"));
long time1 = System.nanoTime();
String str;
while((str = buf.readLine())!=null){
set.add(str);
}
long time2 = System.nanoTime();
System.out.println("装填"+set.size()+"个元素总共耗时"+(time2 - time1)+" SSS");
buf.close();
//一屏幕正中央弹出一个对话框
String word ;
word= JOptionPane.showInputDialog(null,"请输入要查找的信息:");
//set.contains(word) 是否包含word中元素的的意思
/*if(set.contains(word)){
JOptionPane.showMessageDialog(null, "找到了:"+word);
}else{
JOptionPane.showMessageDialog(null, "错误:"+word);
}*/
while(true){
if(word.equals("1")){
break;
}else{
JOptionPane.showMessageDialog(null,((set.contains(word))?"找到了:"+word:"错误:"+word));
word = JOptionPane.showInputDialog(null,"请输入要查找的信息:");
}
}
}
}
HashSet在元素添加完成以后,再次对集合内元素进行操作时需要注意的
import java.util.HashSet;
/*
千万不要在添加元素之后尝试修改HashSet当中参与生成哈希码的属性,否则会导致删除失败,添加元素时 重复添加
因为:
static class Entry<K,V> implements Map.Entry<K,V> {
final K key;//
V value;
Entry<K,V> next;
final int hash//************** 在方法结束的时候 自动保存了传进来的hash码 这样的好处是不用每次调用hashCode()
可是我们这里在添加了一次元素后,改变了cat中参与生成哈希码的age属性,那么两个age的hash码肯定就不一样了
刻舟求剑:你的hash码已经被刻在船体上了
解决方案:
先删除,再修改,最后再添加回去
*/
public class TestHashSet7 {
public static void main(String[] args){
HashSet<Cat> set = new HashSet<Cat>(4,0.5F);
Cat cat = new Cat("Tom",2);
set.add(cat);
//1st.删除
set.remove(cat);
//tom生日到了 年龄增加一岁
//2nd.修改
cat.age = 3;
//尝试去删除cat
//set.remove(cat);
//3rt.添加
set.add(cat);
}
}
class Cat{
String name;
int age;
public Cat(String name,int age){
this.name = name;
this.age = age;
}
@Override
public int hashCode(){
return name.hashCode() + age;
}
@Override
public boolean equals(Object obj){
Cat c1 = this;
Cat c2 = (Cat)obj;
return c1.name.equals(c2.name) && c1.age == c2.age;
}
@Override
public String toString(){
return name+"|"+age;
}
}
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
//不要在使用迭代器遍历HashSet集合的过程中,对集合整体的进行 add() remove()的操作,否则会触发兵法修改异常 跟ArrayList一样
//如果要删除的只有一个元素,则不会触发修改并发异常 因为循环条件或是判断条件就已经结束了
//在使用Iterator对整个几个进行操作的时候,add元素时 已经存在的元素不会被添加进去("唯一"特性) 如果添加其它集合中不存在的元素时 同样会触发 并发修改异常ConcurrentModificationException
//如果 需求决定 一定要去删除元素 那么使用迭代器的remove方法
public class TestHashSet8 {
public static void main(String[] args){
Set<Integer> set = new HashSet<Integer>(16,0.75F);
set.add(77);
set.add(22);
//set.add(30);
//set.add(50);
Iterator car = set.iterator();
while(car.hasNext()){
Integer num = (Integer) car.next();
if(num>50){
//set.remove(num);
//set.add(55);
car.remove();//迭代器的remove方法 没有参数,光标指向哪个 就直接删除哪个
}
}
System.out.println(set);
}
}
HashSet的内存布局
import java.util.HashSet;
import java.util.Iterator;
//HashSet 的内存布局
/*
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 17, 16, 19, 18]
因为:底层将hash码值又进行了一次处理
static int hash(int h) {
// This function ensures that hashCodes that differ only by
// constant multiples at each bit position have a bounded
// number of collisions (approximately 8 at default load factor).
//这样的处理 是为了让高位也参与到散列的影响当中,也就是高位的变化也能影响到分组的不同从而尽量的降低重码的概率,降低hash碰撞的可能,提高hash表的效率
h ^= (h >>> 20) ^ (h >>> 12);
return h ^ (h >>> 7) ^ (h >>> 4);
*/
public class TestHashSet9 {
public static void main(String[] args){
HashSet<Integer> set = new HashSet<Integer>();
for(int i = 0;i<20;i++){
set.add(i);
}
System.out.println(set);
Iterator car = set.iterator();
while(car.hasNext()){
Integer num = (Integer) car.next();
System.out.println(num);
}
System.out.println("==================");
System.out.println(hash(16));//17 17==>16
}
static int hash(int h) {
// This function ensures that hashCodes that differ only by
// constant multiples at each bit position have a bounded
// number of collisions (approximately 8 at default load factor).
h ^= (h >>> 20) ^ (h >>> 12);
return h ^ (h >>> 7) ^ (h >>> 4);
}
}
HashSet / HashMap添加元素的流程
新元素的上一个的下一个 指向新元素本身
HashSet添加元素的完整流程:
1.HashSet 在添加一个新的元素的时候 首先会调用int hash = obj.hashCode();//通过传进的对象.hashCode() 得到散列特征的依据
2.进一步的对返回的这个hash进行了处理,得到一个新的整数,目的是为了让散列更加均匀,尽量的减少重复.得到的这个新的整数就是散列分组的真正的依据
3.用这个新的整数&(分组组数 - 1) (可以理解为%分组组数)得到元素应该散列进哪个小组
4.开始真正的比较步骤:
先拿着返回来(处理过)的哈希值进行新 老元素的连等比较
(1) hash == hash 新老元素的哈希值比较返回的只有两种可能
if (e.hash == hash && ((k = e.key) == key || key.equals(k)))
相等:(e.hash == hash) 不等:
(条件 同一个对象添加两次,我们的需求认定为同一个对象,Hash重码)
直接比较下一个(如果已经是最后一个的话)添加进集合
(2)新元素 == 老元素
相等 (k = e.key) == key || key.equals(k) 不等(我们需求要将其认定为同一个对象或是hash重码)
如果相等的话,这根本不是两个对象,而是内存当中真正的同一个对象(拿连等比较返回true)
"唯一"的集合不需要重复的 舍弃一个
3rt.新元素.equals(老元素)
为了保证是hash重码还是程序员需要将其认定为同一个对象的
相等 不等
这两个对象不是真正的同一个对象 这两个对象根本就不一样
但是程序员把它们视作同一个对象 哈希码重码
舍弃一个 直接比较下一个或添加进集合
新元素的上一个的下一个 指向新元素本身