關於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重碼還是程序員需要將其認定爲同一個對象的
相等 不等
這兩個對象不是真正的同一個對象 這兩個對象根本就不一樣
但是程序員把它們視作同一個對象 哈希碼重碼
捨棄一個 直接比較下一個或添加進集合
新元素的上一個的下一個 指向新元素本身