【java提高】---细则(2)
TreeSet(一)
一、TreeSet定义:
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 }
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 }
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 }
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()不是很了解,可以看这篇博客:
它对里面的参数做了详细讲解。
总结下
加密过程:
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发展而来,它们的结构和强度等特性有很多相似之处
4. MD5最后生成的摘要信息是16个字节,SHA1是20个字节。