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个字节。

【java提高】---细则(2)的更多相关文章

  1. Java提高篇——对象克隆(复制)

    假如说你想复制一个简单变量.很简单: int apples = 5; int pears = apples; 不仅仅是int类型,其它七种原始数据类型(boolean,char,byte,short, ...

  2. Java提高篇(三三)-----Map总结

    在前面LZ详细介绍了HashMap.HashTable.TreeMap的实现方法,从数据结构.实现原理.源码分析三个方面进行阐述,对这个三个类应该有了比较清晰的了解,下面LZ就Map做一个简单的总结. ...

  3. Java提高篇(三二)-----List总结

    前面LZ已经充分介绍了有关于List接口的大部分知识,如ArrayList.LinkedList.Vector.Stack,通过这几个知识点可以对List接口有了比较深的了解了.只有通过归纳总结的知识 ...

  4. Java提高篇(三一)-----Stack

    在Java中Stack类表示后进先出(LIFO)的对象堆栈.栈是一种非常常见的数据结构,它采用典型的先进后出的操作方式完成的.每一个栈都包含一个栈顶,每次出栈是将栈顶的数据取出,如下: Stack通过 ...

  5. java提高篇(三十)-----Iterator

    迭代对于我们搞Java的来说绝对不陌生.我们常常使用JDK提供的迭代接口进行Java集合的迭代. Iterator iterator = list.iterator(); while(iterator ...

  6. java提高篇(二九)-----Vector

    在java提高篇(二一)-–ArrayList.java提高篇(二二)-LinkedList,详细讲解了ArrayList.linkedList的原理和实现过程,对于List接口这里还介绍一个它的实现 ...

  7. Java提高篇(二八)------TreeSet

    与HashSet是基于HashMap实现一样,TreeSet同样是基于TreeMap实现的.在<Java提高篇(二七)-----TreeMap>中LZ详细讲解了TreeMap实现机制,如果 ...

  8. Java提高篇(二七)-----TreeMap

    TreeMap的实现是红黑树算法的实现,所以要了解TreeMap就必须对红黑树有一定的了解,其实这篇博文的名字叫做:根据红黑树的算法来分析TreeMap的实现,但是为了与Java提高篇系列博文保持一致 ...

  9. Java提高篇(二六)-----hashCode

          在前面三篇博文中LZ讲解了(HashMap.HashSet.HashTable),在其中LZ不断地讲解他们的put和get方法,在这两个方法中计算key的hashCode应该是最重要也是最 ...

  10. java提高篇(二四)-----HashSet

          在前篇博文中(java提高篇(二三)-----HashMap)详细讲解了HashMap的实现过程,对于HashSet而言,它是基于HashMap来实现的,底层采用HashMap来保存元素. ...

随机推荐

  1. Quartz集群增强版_00.How to use?(如何使用)

    Quartz集群增强版_00.How to use?(如何使用) 转载请著名出处 https://www.cnblogs.com/funnyzpc/p/18540378 开源地址 https://gi ...

  2. pytest框架之fixture

    1.在进行接口关联时,一般很多个接口共用一个上行接口(例如)登录,可以使用fixture定义一个测试夹具,将登录的接口写在框架的conftest.py文件中: @pytest.fixture(scop ...

  3. SQL排序分组

    --按某一字段分组取最大(小)值所在行的数据(2007-10-23于浙江杭州) /* 数据如下: name val memo a 2 a2(a的第二个值) a 1 a1--a的第一个值 a 3 a3: ...

  4. vant+vue控制列表展开

    <van-list v-model="loading" :finished="finished" finished-text="没有更多了&qu ...

  5. Liunx-Shell脚本

    shell可以理解为对命令行的一个解释器,命令行输入命令,shell执行,linux系统输出结果 1. shell脚本格式 开头: #!/bin/bash #!告诉系统其后路径所指定的程序即是解释此脚 ...

  6. Linux防火墙工具之firewall

    CentOS7 的防火墙配置跟以前版本有很大区别,CentOS7这个版本的防火墙默认使用的是firewall,与之前的版本Centos 6.x使用iptables不一样 一.iptables防火墙1. ...

  7. mysql忘记密码的终极解决方案(docker-compose)

    MYSQL8的安全性能有所提高,装好后,各种不适应,需要各种调试. 1. 首先,root密码忘记或是更改,操作步骤: vi mysql/config/my.cnf 在[mysqld]的段中加上一句:s ...

  8. 共建共荣金融生态!金融级数字底座“源启”与GoldenDB数据库完成互认证

    近日,中电金信金融级数字底座"源启"顺利与金篆信科GoldenDB分布式数据库完成互认证.GoldenDB数据库安全稳定运行在"源启"之上,整体性能表现卓越,进 ...

  9. Dockerr安装Oracle以及使用DBeaver连接

    拉取镜像 pull container-registry.oracle.com/database/free:latest 创建容器 说明一下我现在的最新版本是23 docker run -d --na ...

  10. 如何使用Jet家软件进行学习(intellij、idea、IDEA、Pycharm……)

    如何使用Jet家软件进行学习 本文档提供方法只用于学习研究,不得用于其他用途 以下,萌狼蓝天将会提供两种方式 目录 如何使用Jet家软件进行学习 第一种方式:使用EAP版本 第二种方式:使用插件 (1 ...