【java提高】---细则(2)

TreeSet(一)

一、TreeSet定义:

     与HashSet是基于HashMap实现一样,TreeSet同样是基于TreeMap实现的。     
      1)TreeSet类概述
        使用元素的自然顺序对元素进行排序
        或者根据创建 set 时提供的 Comparator 进行排序
        具体取决于使用的构造方法。 
     2)TreeSet是如何保证元素的排序和唯一性的
        底层数据结构是红黑树(红黑树是一种自平衡的二叉树)
 
  (1)  自然排序
复制代码
import java.util.TreeSet;  
  
    /** TreeSet集合的特点:排序和唯一 
     * 
     * 通过观察TreeSet的add()方法,我们知道最终要看TreeMap的put()方法。 
     */  
    public class TreeTest1 {  
        public static void main(String[] args) {  
            // 创建集合对象  
            // 自然顺序进行排序  
            TreeSet<Integer> treeSet = new TreeSet<Integer>();  
            // 创建元素并添加  
            treeSet.add(8);  
            treeSet.add(4);  
            treeSet.add(5);  
            treeSet.add(6);  
            treeSet.add(6);                      
            // 遍历  
            for(Integer i : treeSet){  
                System.out.print(i);  
            }  
        }  
    }  
    /*后台输出:4568     重复会被覆盖*/  
复制代码

(2)如果存入对象怎么排序,记住如果是对象一定要重写Comparator方法

  People对象

复制代码
 1 public class People implements Comparable {  
 2   
 3     private String name;  
 4     private String sex;  
 5     private int age;  
 6       
 7     /*提供set和get方法,全参和无参构造方法*/  
 8   
 9     @Override  
10     public int compareTo(Object o) {  
11   
12         People people = (People) o;  
13         if (people.age > this.age) {  
14   
15             return 1;  
16         }  
17         if (this.age == people.age) {  
18   
19             return this.name.compareTo(people.name);  
20         }  
21         if ( people.age<this.age ) {  
22   
23             return -1;  
24         }  
25   
26         return 0;  
27     }  
28 }  
复制代码

测试类

复制代码
 1 public class SortTest {  
 2   
 3     public static void main(String[] args) {  
 4   
 5         People people1 = new People("小a", "男", 18);  
 6         People people2 = new People("小a", "女", 16);  
 7         People people3 = new People("小c", "女", 18);  
 8         People people4 = new People("小b", "女", 22);  
 9         People people5 = new People("小d", "男", 19);  
10   
11         Set<People> treeSet = new TreeSet<People>();  
12   
13         treeSet.add(people1);  
14         treeSet.add(people2);  
15         treeSet.add(people3);  
16         treeSet.add(people4);  
17         treeSet.add(people5);  
18   
19         Iterator iterator = treeSet.iterator();  
20   
21         while (iterator.hasNext()) {  
22   
23             People people = (People) iterator.next();  
24   
25             System.out.println("姓名:" + people.getName() + "\t年龄:" + people.getAge());  
26         }  
27   
28     }  
29 }  
复制代码
运行结束:后台报错:
Exception in thread "main" java.lang.ClassCastException: com.treeset.sort.People cannot be cast to java.lang.Comparable

(3)举例一个学生有语文、数学、 英语三门课,按总分从高到底排序
     Student对象
复制代码
 1 public class Student {  
 2   
 3     private String name;  
 4     private int   chinese;  
 5     private int   math;  
 6     private int   english;  
 7        
 8     /*提供set和get方法,同时提供无参数,有参数构造方法*/  
 9       
10       
11     //同时单独要加上getSum方法  
12     public int getSum(){  
13         return this.chinese + this.english + this.math;  
14     }     
15 }  
复制代码

 

  测试类

复制代码
 1 import java.util.Iterator;  
 2 import java.util.TreeSet;  
 3   
 4 public class TreeTest2 {  
 5   
 6     public static void main(String[] args) {  
 7       
 8         Student student1=new Student("小明", 80, 90, 70);  
 9         Student student2=new Student("小王", 60, 80, 90);  
10         Student student3=new Student("小钱", 100, 100, 80);  
11         Student student4=new Student("小徐", 20, 10, 90);  
12         Student student5=new Student("小李", 80, 80, 80);  
13         Student student6=new Student("小李", 70, 80, 90);  
14   
15           
16         TreeSet<Student>  treeSet=new TreeSet(new MyComparable());  
17           
18         treeSet.add(student1);  
19         treeSet.add(student2);  
20         treeSet.add(student3);  
21         treeSet.add(student4);  
22         treeSet.add(student5);  
23         treeSet.add(student6);  
24           
25         Iterator<Student>  iterator=treeSet.iterator();  
26           
27         while(iterator.hasNext()){  
28               
29             Student student=iterator.next();  
30               
31             System.out.println(student.toString());  
32         }     
33     }     
34 }  
复制代码

MyComparable类

复制代码
 1 import java.util.Comparator;  
 2   
 3 public class MyComparable implements Comparator<Student>{  
 4       
 5     @Override  
 6     public int compare(Student s1, Student s2) {  
 7      
 8          // 总分从高到低(注意这里是s2减s1)  
 9         int num = s2.getSum() - s1.getSum();  
10           
11         if(num>0){  
12             return 1;  
13         }  
14         if(num<0){  
15             return -1;  
16         }  
17           
18         if(num==0){  
19             //这步非常关键,没有这个如果总成绩相同名字不同 ,那set集合就默认是相同元素,就会被覆盖掉  
20             return s2.getName().compareTo(s1.getName());  
21         }         
22             return 0;     
23     }     
24 }  
复制代码

复制代码
 1   /** 
 2  * 是不是很奇怪为什么只有五条数据,而不是六条,那是因为有一条数据被覆盖了。 
 3  * 你的Comparator实现类,是先比较成绩,成绩相同,在比较名字,那如果总成绩 
 4  * 相同,姓名也相同,那不是默认是重复数据,TreeSet当然给你覆盖掉了。所以这 
 5  * 里有个小李被覆盖掉了。那如何写才规范,下面这样就不会出现覆盖。 
 6         */  
 7 @Override  
 8    // 创建一个TreeSet集合  
 9    public int compare(Student s1, Student s2) {  
10        // 总分从高到低(注意这里是s2减s1)  
11        int num = s2.getSum() - s1.getSum();  
12        // 总分相同的不一定语文相同  
13        int num2 = num == 0 ? s1.getChinese() - s2.getChinese() : num;  
14        // 总分相同的不一定数学相同  
15        int num3 = num2 == 0 ? s1.getMath() - s2.getMath() : num2;  
16        // 总分相同的不一定英语相同  
17        int num4 = num3 == 0 ? s1.getEnglish() - s2.getEnglish() : num3;  
18        // 姓名还不一定相同  
19        int num5 = num4 == 0 ? s1.getName().compareTo(s2.getName()) : num4;  
20        return num5;  
21    }  
复制代码
       最后在思考,set集合在开发中到底有啥用,好像我们开发当中要用集合一般会用ArrayList,好像很少用到TreeSet集合
      这个时候你就要想TreeSet集合的特点了:排序和唯一
 
 举个小例子:
     (4) 编写一个程序,获取10个1至20的随机数,要求随机数不能重复。
复制代码
 1 import java.util.Iterator;  
 2 import java.util.TreeSet;  
 3 import java.util.Random;  
 4 /* 
 5  * 编写一个程序,获取10个1至20的随机数,要求随机数不能重复。 
 6  * 
 7  * 分析: 
 8  *         A:创建随机数对象 
 9  *         B:创建一个TreeSet集合 
10  *         C:判断集合的长度是不是小于10 
11  *             是:就创建一个随机数添加 
12  *             否:不搭理它 
13  *         D:遍历TreeSet集合 
14  */  
15 public class HashSetDemo {  
16     public static void main(String[] args) {  
17         // 创建随机数对象  
18         Random r = new Random();  
19         // 创建一个Set集合  
20         TreeSet<Integer> treeSet = new TreeSet<Integer>();  
21         // 判断集合的长度是不是小于10  
22         while (treeSet.size() < 10) {  
23             int x = r.nextInt(20) + 1;  
24             treeSet.add(x);  
25         }          
26         // 遍历Set集合  
27         for (int x : treeSet) {  
28             System.out.println(x);  
29         }  
30     }  
31 }  
复制代码

 

     用这个例子,希望你能有扩散你的思维,也应该知道在什么时候用TreeSet集合了。

ArrayList源码

    一、定义

public class ArrayList<E> extends AbstractList<E> implements List<E>,RandomAccess,Cloneable,java.io.Serializable

从中我们可以了解到:

  • ArrayList<E>:说明ArrayList支持泛型。
  • extends AbstractList<E> :继承了AbstractList。AbstractList提供List接口的骨干实现,以最大限度地减少“随机访问”数据存储(如ArrayList)实现Llist所需的工作。
  • implements List<E>:实现了List。实现了所有可选列表操作。
  • implements RandomAccess:表明ArrayList支持快速(通常是固定时间)随机访问。此接口的主要目的是允许一般的算法更改其行为,从而在将其应用到随机或连续访问列表时能提供良好的性能。
  • implements Cloneable:表明其可以调用clone()方法来返回实例的field-for-field拷贝。
  • implements java.io.Serializable:表明该类具有序列化功能。

 二、构造函数 

复制代码
1 // 默认构造函数  
2 ArrayList()  
3   
4 // capacity是ArrayList的默认容量大小。当由于增加数据导致容量不足时,容量会添加上一次容量大小的一半。  
5 ArrayList(int capacity)  
6   
7 // 创建一个包含collection的ArrayList  
8 ArrayList(Collection<? extends E> collection)  
复制代码

三、ArrayList源码解析

复制代码
  1 import java.util.*;    
  2      
  3  public class ArrayList<E> extends AbstractList<E>    
  4          implements List<E>, RandomAccess, Cloneable, java.io.Serializable    
  5  {    
  6      // 序列版本号    
  7      private static final long serialVersionUID = 8683452581122892189L;    
  8      
  9      // 保存ArrayList中数据的数组    
 10      private transient Object[] elementData;    
 11      
 12      // ArrayList中实际数据的数量    
 13      private int size;    
 14      
 15      // ArrayList带容量大小的构造函数。    
 16      public ArrayList(int initialCapacity) {    
 17          super();    
 18          if (initialCapacity < 0)    
 19              throw new IllegalArgumentException("Illegal Capacity: "+    
 20                                                 initialCapacity);    
 21          // 新建一个数组    
 22          this.elementData = new Object[initialCapacity];    
 23      }    
 24      
 25         // ArrayList构造函数。默认容量是10。    
 26      public ArrayList() {    
 27          this(10);    
 28      }    
 29      
 30         // 创建一个包含collection的ArrayList    
 31      public ArrayList(Collection<? extends E> c) {    
 32          elementData = c.toArray();    
 33          size = elementData.length;    
 34         // c.toArray might (incorrectly) not return Object[] (see 6260652)    
 35          if (elementData.getClass() != Object[].class)    
 36              elementData = Arrays.copyOf(elementData, size, Object[].class);    
 37      }    
 38          
 39         // 添加元素e    
 40      public boolean add(E e) {    
 41         // 确定ArrayList的容量大小    
 42          ensureCapacity(size + 1);  // Increments modCount!!    
 43         // 添加e到ArrayList中    
 44          elementData[size++] = e;    
 45          return true;    
 46      }    
 47      
 48        // 确定ArrarList的容量。    
 49        // 若ArrayList的容量不足以容纳当前的全部元素,设置 新的容量=“(原始容量x3)/2 + 1”    
 50      public void ensureCapacity(int minCapacity) {    
 51        // 将“修改统计数”+1    
 52          modCount++;    
 53          int oldCapacity = elementData.length;    
 54        // 若当前容量不足以容纳当前的元素个数,设置 新的容量=“(原始容量x3)/2 + 1”    
 55          if (minCapacity > oldCapacity) {    
 56              Object oldData[] = elementData;    
 57              int newCapacity = (oldCapacity * 3)/2 + 1;    
 58              if (newCapacity < minCapacity)    
 59                  newCapacity = minCapacity;    
 60              elementData = Arrays.copyOf(elementData, newCapacity);    
 61          }    
 62      }    
 63          
 64        // 返回ArrayList的实际大小    
 65      public int size() {    
 66          return size;    
 67      }    
 68      
 69       // 返回ArrayList是否包含Object(o)    
 70      public boolean contains(Object o) {    
 71          return indexOf(o) >= 0;    
 72      }    
 73           
 74       // 正向查找,返回元素的索引值    
 75      public int indexOf(Object o) {    
 76          if (o == null) {    
 77              for (int i = 0; i < size; i++)    
 78              if (elementData[i]==null)    
 79                  return i;    
 80              } else {    
 81                  for (int i = 0; i < size; i++)    
 82                  if (o.equals(elementData[i]))    
 83                      return i;    
 84              }    
 85              return -1;    
 86          }    
 87      
 88       // 返回ArrayList是否为空    
 89      public boolean isEmpty() {    
 90          return size == 0;    
 91      }    
 92             
 93      
 94       // 返回ArrayList的Object数组    
 95      public Object[] toArray() {    
 96          return Arrays.copyOf(elementData, size);    
 97      }    
 98      
 99       // 返回ArrayList的模板数组。所谓模板数组,即可以将T设为任意的数据类型    
100      public <T> T[] toArray(T[] a) {    
101          // 若数组a的大小 < ArrayList的元素个数;    
102          // 则新建一个T[]数组,数组大小是“ArrayList的元素个数”,并将“ArrayList”全部拷贝到新数组中    
103          if (a.length < size)    
104              return (T[]) Arrays.copyOf(elementData, size, a.getClass());    
105      
106          // 若数组a的大小 >= ArrayList的元素个数;    
107          // 则将ArrayList的全部元素都拷贝到数组a中。    
108          System.arraycopy(elementData, 0, a, 0, size);    
109          if (a.length > size)    
110              a[size] = null;    
111          return a;    
112      }    
113      
114       // 获取index位置的元素值    
115      public E get(int index) {    
116       //判断数组是否越界    
117          RangeCheck(index);    
118      
119          return (E) elementData[index];    
120      }    
121      
122       // 将e添加到ArrayList的指定位置    
123      public void add(int index, E element) {    
124          if (index > size || index < 0)    
125              throw new IndexOutOfBoundsException(    
126              "Index: "+index+", Size: "+size);    
127      
128          ensureCapacity(size+1);  // Increments modCount!!    
129          System.arraycopy(elementData, index, elementData, index + 1,    
130               size - index);    
131          elementData[index] = element;    
132          size++;    
133      }    
134      
135       // 删除ArrayList指定位置的元素    
136      public E remove(int index) {    
137          RangeCheck(index);    
138      
139          modCount++;    
140          E oldValue = (E) elementData[index];    
141      
142          int numMoved = size - index - 1;    
143          if (numMoved > 0)    
144              System.arraycopy(elementData, index+1, elementData, index,    
145                   numMoved);    
146          elementData[--size] = null; // Let gc do its work    
147      
148          return oldValue;    
149      }    
150      
151        // 删除ArrayList的指定元素    
152      public boolean remove(Object o) {    
153          if (o == null) {    
154                  for (int index = 0; index < size; index++)    
155              if (elementData[index] == null) {    
156                  fastRemove(index);    
157                  return true;    
158              }    
159          } else {    
160              for (int index = 0; index < size; index++)    
161              if (o.equals(elementData[index])) {    
162                  fastRemove(index);    
163                  return true;    
164              }    
165          }    
166          return false;    
167      }    
168      
169        // 清空ArrayList,将全部的元素设为null    
170      public void clear() {    
171          modCount++;    
172      
173          for (int i = 0; i < size; i++)    
174              elementData[i] = null;    
175      
176          size = 0;    
177      }    
178         
179        // 将ArrayList的“容量,所有的元素值”都写入到输出流中    
180      private void writeObject(java.io.ObjectOutputStream s)    
181          throws java.io.IOException{    
182        // Write out element count, and any hidden stuff    
183      int expectedModCount = modCount;    
184      s.defaultWriteObject();    
185      
186          // 写入“数组的容量”    
187          s.writeInt(elementData.length);    
188      
189        // 写入“数组的每一个元素”    
190      for (int i=0; i<size; i++)    
191              s.writeObject(elementData[i]);    
192      
193      if (modCount != expectedModCount) {    
194              throw new ConcurrentModificationException();    
195          }    
196      }    
197          
198        // java.io.Serializable的读取函数:根据写入方式读出    
199        // 先将ArrayList的“容量”读出,然后将“所有的元素值”读出    
200      private void readObject(java.io.ObjectInputStream s)    
201          throws java.io.IOException, ClassNotFoundException {    
202          // Read in size, and any hidden stuff    
203          s.defaultReadObject();    
204      
205          // 从输入流中读取ArrayList的“容量”    
206          int arrayLength = s.readInt();    
207          Object[] a = elementData = new Object[arrayLength];    
208      
209          // 从输入流中将“所有的元素值”读出    
210          for (int i=0; i<size; i++)    
211              a[i] = s.readObject();    
212      }    
213  }   
复制代码

总结
(01) ArrayList 实际上是通过一个数组去保存数据的。当我们构造ArrayList时;若使用默认构造函数,则ArrayList的默认容量大小是10
(02) 当ArrayList容量不足以容纳全部元素时,ArrayList会重新设置容量:新的容量=“(原始容量x3)/2 + 1”
(03) ArrayList的克隆函数,即是将全部元素克隆到一个数组中。
(04) ArrayList实现java.io.Serializable的方式。当写入到输出流时,先写入“容量”,再依次写入“每一个元素”;当读出输入流时,先读取“容量”,再依次读取“每一个元素”。

 

HashMap解析(一)

        平时一直再用hashmap并没有稍微深入的去了解它,自己花点时间想往里面在深入一点,发现它比arraylist难理解很多。

     数据结构中有数组和链表来实现对数据的存储,但这两者基本上是两个极端。

    数组:数组存储区间是连续的,占用内存严重,故空间复杂的很大。但数组的二分查找时间复杂度小,为O(1);数组的特点是:寻址容易,插入和删除困难;

    链表:链表存储区间离散,占用内存比较宽松,故空间复杂度很小,但时间复杂度很大,达O(N)链表的特点是:寻址困难,插入和删除容易。   

 

一、HashMap的数据结构                                                                            

    HashMap实际上是一个“链表散列”的数据结构,即数组和链表的结合体。看下面图;来理解:

 

     从上图中可以看出,HashMap底层就是一个数组结构,只数组中的每一项又是一个链表。当新建一个HashMap的时候,就会初始化一个数组。

 
transient Entry[] table;
 
static class Entry<K,V> implements Map.Entry<K,V> {
    final K key;
    V value;
    Entry<K,V> next;
    final int hash;
    ……
}
 

   可以看出,Entry就是数组中的元素,每个 Map.Entry 其实就是一个key-value对,它持有一个指向下一个元素的引用,这就构成了链表。

 

二、HashMap的存取实现                                                           

存储

 
public V put(K key, V value) {
    // HashMap允许存放null键和null值。
    // 当key为null时,调用putForNullKey方法,将value放置在数组第一个位置。  
    if (key == null)
        return putForNullKey(value);
    // 根据key的keyCode重新计算hash值。
    int hash = hash(key.hashCode());
    // 搜索指定hash值在对应table中的索引。
    int i = indexFor(hash, table.length);
    // 如果 i 索引处的 Entry 不为 null,通过循环不断遍历 e 元素的下一个元素。
    for (Entry<K,V> e = table[i]; e != null; e = e.next) {
        Object k;
        if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
            V oldValue = e.value;
            e.value = value;
            e.recordAccess(this);
            return oldValue;
        }
    }
    // 如果i索引处的Entry为null,表明此处还没有Entry。
    modCount++;
    // 将key、value添加到i索引处。
    addEntry(hash, key, value, i);
    return null;
}
 

        从上面的源代码中可以看出:当我们往HashMap中put元素的时候,先根据key的hashCode重新计算hash值,根据hash值得到这个元素在数组中的位置(即下标), 如果数组该位置上已经存放有其他元素了,那

么在这个位置上的元素将以链表的形式存放,新加入的放在链头,最先加入的放在链尾。如果数组该位置上没有元素,就直接将该元素放到此数组中的该位置上。

ddEntry(hash, key, value, i)方法根据计算出的hash值,将key-value对放在数组table的i索引处。addEntry 是 HashMap 提供的一个包访问权限的方法,代码如下:

 
void addEntry(int hash, K key, V value, int bucketIndex) {
    // 获取指定 bucketIndex 索引处的 Entry  
    Entry<K,V> e = table[bucketIndex];
    // 将新创建的 Entry 放入 bucketIndex 索引处,并让新的 Entry 指向原来的 Entry  
    table[bucketIndex] = new Entry<K,V>(hash, key, value, e);
    // 如果 Map 中的 key-value 对的数量超过了极限
    if (size++ >= threshold)
    // 把 table 对象的长度扩充到原来的2倍。
        resize(2 * table.length);
}
 

        当系统决定存储HashMap中的key-value对时,完全没有考虑Entry中的value,仅仅只是根据key来计算并决定每个Entry的存储位置。我们完全可以把 Map 集合中的 value 当成 key 的附属,当系统决定了 key 的

存储位置之后,value 随之保存在那里即可。

读取

 
public V get(Object key) {
    if (key == null)
        return getForNullKey();
    int hash = hash(key.hashCode());
    for (Entry<K,V> e = table[indexFor(hash, table.length)];
        e != null;
        e = e.next) {
        Object k;
        if (e.hash == hash && ((k = e.key) == key || key.equals(k)))  
            return e.value;
    }
    return null;
}
 

        有了上面存储时的hash算法作为基础,理解起来这段代码就很容易了。从上面的源代码中可以看出:

  从HashMap中get元素时,首先计算key的hashCode,找到数组中对应位置的某一元素,然后通过key的equals方法在对应位置的链表中找到需要的元素。

归纳

     1)hashMap的key允许为null,当key为null时,调用putForNullKey方法,将value放置在数组第一个位置。

     2)判断是否key值唯一的标准,是通过对key值的hashCode计算得出,通过通过key获取value值时也是计算key的hashCode的值去找value值。

    3)HashMap 在底层将 key-value 当成一个整体进行处理,这个整体就是一个 Entry 对象。HashMap 底层采用一个 Entry[] 数组来保存所有的 key-value 对,当需要存储一个 Entry 对象时,会根据hash算法来决定

其在数组中的存储位置,在根据equals方法决定其在该数组位置上的链表中的存储位置;当需要取出一个Entry时,也会根据hash算法找到其在数组中的存储位置,再根据equals方法从该位置上的链表中取出该Entry。

 

三、hashmap源码解读                                                                                      

       HashMap有两个参数影响其性能:初始容量加载因子。默认初始容量是16,加载因子是0.75。容量是哈希表中桶(Entry数组)的数量,初始容量只是哈希表在创建时的容量。

加载因子是哈希表在其容量自动增加之前可以达到多满的一种尺度。当哈希表中的条目数超出了加载因子与当前容量的乘积时,通过调用 rehash 方法将容量翻倍。

  HashMap中定义的成员变量如下:

 
 
static final int DEFAULT_INITIAL_CAPACITY = 16;// 默认初始容量为16,必须为2的幂  
  
static final int MAXIMUM_CAPACITY = 1 << 30;// 最大容量为2的30次方  
  
static final float DEFAULT_LOAD_FACTOR = 0.75f;// 默认加载因子0.75  
    
transient Entry<K,V>[] table;// Entry数组,哈希表,长度必须为2的幂  
  
transient int size;// 已存元素的个数  
  
int threshold;// 下次扩容的临界值,size>=threshold就会扩容  
  
final float loadFactor;// 加载因子  
 

HashMap一共重载了4个构造方法,分别为:

HashMap()
          构造一个具有默认初始容量 (16) 和默认加载因子 (0.75) 的空 HashMap

HashMap(int initialCapacity)
          构造一个带指定初始容量和默认加载因子 (0.75) 的空 HashMap

HashMap(int initialCapacity, float loadFactor)
          构造一个带指定初始容量和加载因子的空 HashMap

HashMap(Map<? extendsK,? extendsV> m)
          构造一个映射关系与指定 Map 相同的 HashMap

看一下第三个构造方法源码,其它构造方法最终调用的都是它。

 
public HashMap(int initialCapacity, float loadFactor) {  
    // 参数判断,不合法抛出运行时异常  
    if (initialCapacity < 0)  
        throw new IllegalArgumentException("Illegal initial capacity: " +  
                                           initialCapacity);  
    if (initialCapacity > MAXIMUM_CAPACITY)  
        initialCapacity = MAXIMUM_CAPACITY;  
    if (loadFactor <= 0 || Float.isNaN(loadFactor))  
        throw new IllegalArgumentException("Illegal load factor: " +  
                                           loadFactor);  
  
    // Find a power of 2 >= initialCapacity  
    // 这里需要注意一下  
    int capacity = 1;  
    while (capacity < initialCapacity)  
        capacity <<= 1;  
  
    // 设置加载因子  
    this.loadFactor = loadFactor;  
    // 设置下次扩容临界值  
    threshold = (int)Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);  
    // 初始化哈希表  
    table = new Entry[capacity];  
    useAltHashing = sun.misc.VM.isBooted() &&  
            (capacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);  
    init();  
}  

 

枚举

   

   一、枚举和静态常量的区别  

       讲到枚举我们首先思考,它和public static final String 修饰的常量有什么不同。

        我举枚举的两个优点:

     1. 保证了类型安全:调用者无法随意传一个 int或者String 等值;

     2.代码可读性非常高;

举个例子

        在实际编程中,往往存在着这样的“数据集”,它们的数值在程序中是稳定的,而且“数据集”中的元素是有限的。例如春夏秋冬四个数据元素组成了四季的“数据集”。你写了方法

get(String season),输入的类型只能是String类型,同时要String只能是(春、夏。秋。冬)。

这个时候。你写四个字符串常量

复制代码
public class Common {
    public static final String SPRING="春";
    public static final String SEASON="夏";
    public static final String SUMMER="秋";
    public static final String AUTUMN="冬";
}
复制代码

     在get方法里放入get(Common.SEASON),确实是把"春",放进去了,但是这个时候你会发现这里面有一个隐患,你get(String season),毕竟放入的是String类型的,如果新同事或者不知情的同事,

不知道这个方法里只能放“春、夏、秋、冬”,它放了个其期它字符串比如get("小小“),这个时候,在编译期它是不会报错的,只有运行之后,才发现错了。   为了防止上面的隐患,枚举出现了

复制代码
   public enum Season {
        SPRING("春"),
        SUMMER("夏"),
        AUTUMN("秋"),
        WINTER("冬");
   .....
}
复制代码

        这个时候,我们修改get方法的传参,改成get(Season   season)   这个时候加入get(Season.SPRING),这就能保证传入的参数只能是这几个。

 

二、理解枚举

        首要我们要明确,其实枚举也是个class类,我写个枚举来理解。

复制代码
//我们把枚举当做一个普通类
public enum Season {
    SPRING(1,"春"),
    SUMMER(2,"夏" ),
    AUTUMN(3,"秋" ),
    WINTER(4,"冬");  //这里最后一个一定要分号,否则报错
    
    /*我们可以理解成
     *public static final Season SPRING = new Season(1,春); 
     *public static final Season SUMMER = new Season(2,夏); 
     *public static final Season AUTUMN = new Season(3,秋); 
     *public static final Season WINTER = new Season(4,冬); 
     *既然是对象,那下面就很好理解了
     */
    
    /*
     * 1.上面对象里放了两个参数,那下面就肯定要有这个类型的构造函数
     * 2.这里是private,因为不能在被new对象了
     */
    private Season(int code,String name) {
        this.name = name;
        this.code = code;
    }
    
    //对象的属性
    private String name;
    private int code;

    
    //获取对象属性的方法
    public String getName() {
        return this.name;
    }
    public String getCode() {
        return this.name;
    }

    //通过code获得对象,我们就可以获得对象的其它属性
    public static Season decode(int code) {
        Season season = null;
        for (Season type : Season.values()) {
            if (type.code==code) {
                season = type;
                break;
            }
        }
        return season;
    }
    
    //重新toString方法
    public String toString() {
        return this.name;
    }
}
复制代码

      上面这个例子,就很好解释了枚举,它和普通类没什么区别,只是用另一种写法创建了几个有属性的对象,这也必须写这样有属性的构造函数,仅此而已。

这里顺便列举下枚举的一些特点:

    1.它不能有public的构造函数,这样做可以保证客户代码没有办法新建一个enum的实例。

    2.  枚举不能在继承其它类了,因为它默认继承了java.lang.Enum

    3.  常量值地址唯一,可以用==直接对比,性能会有提高.

    4.Enum还提供了values方法,这个方法使你能够方便的遍历所有的枚举值。

    5.Enum还有一个oridinal的方法,这个方法返回枚举值在枚举类种的顺序,这个顺序根据枚举值声明的顺序而定。

 

三、枚举的常见用法

第一种:switch运用

先建一个枚举:

复制代码
public enum Common {

    INSERT,
    MODIFY,
    DELETE
}
//因为这里是无参的对象,所以可以用系统默认的构造函数。也不用写属性和方法。
复制代码

在写实现代码

复制代码
public class CommonUtils {

    public static void getType(Common common){
        Common c=common;
        switch(c)
        {
        case INSERT:
            System.out.println("进行插入操作");
            break;
        case MODIFY:
            System.out.println("进行修改操作");
            break;
        case DELETE:
            System.out.println("进行删除操作");
            break;
        }    
        
    }
    public static void main(String[] args) {
        getType(Common.DELETE);  //后台输出:进行删除操作
    }
}
复制代码

第二种用法,通过key值获得value值获取其它值

  枚举类

复制代码
public enum Season {
    SPRING(1,"春","春天放风筝"),
    SUMMER(2,"夏","夏天去游泳"),
    AUTUMN(3,"秋","秋天去秋游"),
    WINTER(4,"冬","冬天吃火锅");  
    
    private Season(int code,String name,String bz) {
        this.code = code;
        this.name = name;
        this.bz=bz;
    }

    private int code;
    private String name;
    private String bz;


    public static Season decode(int code) {
        Season season = null;
        for (Season type : Season.values()) {
            if (type.code==code) {
                season = type;
                break;
            }
        }
        return season;
    }


    public int getCode() {
        return code;
    }

    public String getName() {
        return name;
    }

    public String getBz() {
        return bz;
    }

}
复制代码

测试类

好了,就写这么多,以后有需要会更深入了解。

通过UUID、SHA-1、Base64组合加密

 该篇文章实现的最终效果是:            

     1)加密是不可逆的。

     2)相同字符串加密产生后的字符串都不一样

     3)所以要想比较两个字符串是否相等,需要用已经加过密的字符串进行处理后,在与另一个字符串比较。

下面直接代码演示:

加密工具类

复制代码
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.UUID;

import org.apache.commons.codec.binary.Base64;

import cn.edu.zju.grs.alufer.exception.InvalidParameterException;

/**
 * 加密工具类
 */
public class EncryptionUtil {
    
    /**
     *  先生成一个10位的随机字符串(这个随意)
     */
    public static String dealPassword(String password) throws UnsupportedEncodingException {
        String salt = UUID.randomUUID().toString().replaceAll("-", "").substring(0, 10);
        System.out.println(salt+"---111111");
        try {
            
            // salt.getBytes("UTF-8"):字符串转成UTF-8的字节数组
            return dealPasswordWithSalt(password, salt.getBytes("UTF-8"));
        } catch (InvalidParameterException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 通过SHA-1和Base64处理之后,给字符串数据加密在返回字符串
     */
    public static String dealPasswordWithSalt(String password, byte[] salt)
            throws InvalidParameterException, UnsupportedEncodingException {
        if (password == null)
            throw new InvalidParameterException("Parameter is null");

        // 将两个数组合2为1
        byte[] msg = byteCat(password.getBytes("UTF-8"), salt);
        String dealedPassword = null;

        /*
         * MessageDigest 类为应用程序提供信息摘要算法的功能,如 MD5 或 SHA 算法。
         * 信息摘要是安全的单向哈希函数,它接收任意大小的数据,并输出固定长度的哈希值。
         */
        MessageDigest md;
        try {

            // 返回实现指定摘要算法的 MessageDigest
            // 对象。MessageDigest是java自带加密工具类,通过SHA-1加密,也可以采用MD5
            md = MessageDigest.getInstance("SHA-1");

            // 使用指定的 byte 数组更新摘要。
            md.update(msg);

            // 通过执行诸如填充之类的最终操作完成哈希计算。digest 方法只能被调用一次。在调用 digest
            // 之后,MessageDigest 对象被重新设置成其初始状态。
            byte[] dig = md.digest();

            // 在合2为1
            byte[] passb = byteCat(dig, salt);

            // 最后通过BASE64算法转换二进 制数据为ASCII字符串格式。
            dealedPassword = new String(Base64.encodeBase64(passb));
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        return dealedPassword;
    }

    /**
     * 这个方法的目的是将两个数组合成一个数组
     */
    private static byte[] byteCat(byte[] l, byte[] r) {
        byte[] b = new byte[l.length + r.length];
        System.arraycopy(l, 0, b, 0, l.length);
        System.arraycopy(r, 0, b, l.length, r.length);
        return b;
    }

    /**
     * 通过上面生成的字符串来进行解密,其最终目的就是获得上面随机生成的UUID
     * 这样只要你password一致,UUID一致,那dealPasswordWithSalt方法产生的加密字符串就会一致
     */
    public static byte[] getSalt(String dealedPassword) throws InvalidParameterException {
        
        
        /*
         * 解码:这里获得的是上面passb数组,因为SHA-1是固定20个字节,所以从20位置开始截取,MD516个字节。
         */
        byte[] decoded = Base64.decodeBase64(dealedPassword);
        byte[][] bs = null;
        bs = byteSplit(decoded, 20);
        byte[] salt = bs[1];
        System.out.println(new String(salt)+"---222222");
        return salt;
    }

    /**
     * 将数组1分为2,其实就是获得上面uuid所产生的数组
     * 第一个数组是上面dig数组,第二个是salt数组
     */
    private static byte[][] byteSplit(byte[] src, int n) {
        byte[] l, r;
        if (src == null || src.length <= n) {
            l = src;
            r = new byte[0];
        } else {
            l = new byte[n];
            r = new byte[src.length - n];
            System.arraycopy(src, 0, l, 0, n);
            System.arraycopy(src, n, r, 0, r.length);
        }
        byte[][] lr = { l, r };
        return lr;
    }

}
复制代码

   

测试类

复制代码
import java.io.UnsupportedEncodingException;

import cn.edu.zju.grs.alufer.exception.InvalidParameterException;

public class Test {

public static void main(String[] args) {
    try {
        try {
            
            
        //模拟获得用户注册的密码
        String password="zhang123456";
    
        //通过加密获得的密码,存放到数据库
        String plusPassword = EncryptionUtil.dealPassword(password);
        System.out.println("加密后:"+plusPassword);
    
      
       //模拟用户登录
        String loginpassword="zhang123456";
        String LessPassword= EncryptionUtil.dealPasswordWithSalt(loginpassword,EncryptionUtil.getSalt(plusPassword));
         System.out.println("在加密:"+LessPassword);
        
        System.out.println(plusPassword.equals(LessPassword)); 
        } catch (InvalidParameterException e) {
            e.printStackTrace();
        }
      
    
    } catch (UnsupportedEncodingException e) {
        e.printStackTrace();
    }
}    
/**
 * 总结:
 * 1:通过UUID有个最大的好处就是它会产生随机数,所以你同样的两个字符串它的加密后结果是不一样的,
 *    你要解密,那你首先要做的是要先得到那个UUID字符串,在进行加密,才会之前的加密字符串一致
 * 
 */
}
复制代码

看后台打印:

看上面的例子,如果你对System.arraycopy()不是很了解,可以看这篇博客:

使用System.arraycopy()实现数组之间的复制

它对里面的参数做了详细讲解。

 总结下 

  加密过程

   1)将用户前段传入的password传入加密方法中。

   2)加密方法先通过UUID随机生成字符串,可以理解为salt。

   3)将password和salt都转为byte[],在通过system.arrayCopy方法变成一个byte[]

   4)将上面的byte[]进行SHA-1加密,之后在于salt组成的byte[],组成一个新的byte[](也就是说这个salt在加密过程中使用了两次,第二次是为解密用的)

   5)在通过Base64进行加密。

 解密过程

    解密过程的关键就是要获得加密过程的salt。

   1)通过Base64.decodeBase64将数据的密码转为byte数组。

   2)截取byte数组20个字节后的字节(因为SHA-1是固定20个字节,那么剩下的就是盐了)

   3) 只要获得盐,那就把用户登录的密码和盐再加密一次,和数据库的密码一样就代码验证通过。

 

MD5  和 SHA-1的区别           

最后说下:MD5  和 SHA-1  的一些区别:

由于MD5与SHA-1均是从MD4发展而来,它们的结构和强度等特性有很多相似之处

因为二者均由MD4导出,SHA-1和MD5彼此很相似。相应的,他们的强度和其他特性也是相似,但还有以下几点不同:
1. 对强行攻击的安全性:最显著和最重要的区别是SHA-1摘要比MD5摘要长32 位。使用强行技术,产生任何一个报文使其摘要等于给定报摘要的难度对MD5是2^128数量级的操作,而对SHA-1则是2^160数量级的操作。这样,SHA-1对强行攻击有更大的强度。
2. 对密码分析的安全性:由于MD5的设计,易受密码分析的攻击,SHA-1显得不易受这样的攻击。
3. 速度:在相同的硬件上,SHA-1的运行速度比MD5慢。

4. MD5最后生成的摘要信息是16个字节,SHA1是20个字节。

 

posted @ 2022-02-25 16:27  hanease  阅读(44)  评论(0编辑  收藏  举报