八种基本数据类型:int、short、float、double、long、boolean、byte、char。html
封装类分别是:Integer、Short、Float、Double、Long、Boolean、Byte、Character。java
引用数据类型是由类的编辑器定义的,他们是用于访问对象的。这些变量被定义为不可更改的特定类型。node
例如:Employee, Puppy 等等web
jdk7以前 switch 只能支持 byte、short、char、int 这几个基本数据类型和其对应的封装类型。算法
switch后面的括号里面只能放int类型的值,但因为byte,short,char类型,它们会?自动?转换为int类型(精精度小的向大的转化),因此它们也支持。spring
jdk1.7后 整形,枚举类型,字符串均可以。数据库
原理express
switch (expression) // 括号里是一个表达式,结果是个整数{ case constant1: // case 后面的标号,也是个整数 group of statements 1; break; case constant2: group of statements 2; break; ... default: default group of statements }
jdk1.7后,整形,枚举类,字符串均可以。编程
public class TestString { static String string = "123"; public static void main(String[] args) { switch (string) { case "123": System.out.println("123"); break; case "abc": System.out.println("abc"); break; default: System.out.println("defauls"); break; } } }
为何jdk1.7后又能够用string类型做为switch参数呢?segmentfault
其实,jdk1.7并无新的指令来处理switch string,而是经过调用switch中string.hashCode,将string转换为int从而进行判断。
使用==比较原生类型如:boolean、int、char等等,使用equals()比较对象。
一、==是判断两个变量或实例是否是指向同一个内存空间。 equals是判断两个变量或实例所指向的内存空间的值是否是相同。
二、==是指对内存地址进行比较。 equals()是对字符串的内容进行比较。
三、==指引用是否相同。 equals()指的是值是否相同。
public static void main(String[] args) { String a = new String("ab"); // a 为一个引用 String b = new String("ab"); // b为另外一个引用,对象的内容同样 String aa = "ab"; // 放在常量池中 String bb = "ab"; // 从常量池中查找 System.out.println(aa == bb); // true System.out.println(a == b); // false,非同一对象 System.out.println(a.equals(b)); // true System.out.println(42 == 42.0); // true } public static void main(String[] args) { Object obj1 = new Object(); Object obj2 = new Object(); System.out.println(obj1.equals(obj2));//false System.out.println(obj1==obj2);//false obj1=obj2; System.out.println(obj1==obj2);//true System.out.println(obj2==obj1);//true }
自动装箱 在jdk?1.5以前,若是你想要定义一个value为100的Integer对象,则须要以下定义:
Integer i = new Integer(100); int intNum1 = 100; //普通变量 Integer intNum2 = intNum1; //自动装箱 int intNum3 = intNum2; //自动拆箱 Integer intNum4 = 100; //自动装箱
上面的代码中,intNum2为一个Integer类型的实例,intNum1为Java中的基础数据类型,将intNum1赋值给intNum2即是自动装箱;而将intNum2赋值给intNum3则是自动拆箱。
八种基本数据类型: boolean byte char shrot int long float double ,所生成的变量至关于常量。
基本类型包装类:Boolean Byte Character Short Integer Long Float Double。
自动拆箱和自动装箱定义:
自动装箱是将一个java定义的基本数据类型赋值给相应封装类的变量。 拆箱与装箱是相反的操做,自动拆箱则是将一个封装类的变量赋值给相应基本数据类型的变量。
Object是全部类的父类,任何类都默认继承Object
clone 保护方法,实现对象的浅复制,只有实现了Cloneable接口才能够调用该方法,不然抛出CloneNotSupportedException异常。
equals 在Object中与==是同样的,子类通常须要重写该方法。
hashCode 该方法用于哈希查找,重写了equals方法通常都要重写hashCode方法。这个方法在一些具备哈希功能的Collection中用到。
getClass final方法,得到运行时类型
wait 使当前线程等待该对象的锁,当前线程必须是该对象的拥有者,也就是具备该对象的锁。 wait() 方法一直等待,直到得到锁或者被中断。 wait(long timeout) 设定一个超时间隔,若是在规定时间内没有得到锁就返回。
调用该方法后当前线程进入睡眠状态,直到如下事件发生
一、其余线程调用了该对象的notify方法。 二、其余线程调用了该对象的notifyAll方法。 三、其余线程调用了interrupt中断该线程。 四、时间间隔到了。 五、此时该线程就能够被调度了,若是是被中断的话就抛出一个InterruptedException异常。
notify 唤醒在该对象上等待的某个线程。
notifyAll 唤醒在该对象上等待的全部线程。
toString 转换成字符串,通常子类都有重写,不然打印句柄。
从JDK1.2版本开始,把对象的引用分为四种级别,从而使程序能更加灵活的控制对象的生命周期。这四种级别由高到低依次为:强引用、软引用、弱引用和虚引用。
一、强引用
最广泛的一种引用方式,如String s = "abc",变量s就是字符串“abc”的强引用,只要强引用存在,则垃圾回收器就不会回收这个对象。
二、软引用(SoftReference)
用于描述还有用但非必须的对象,若是内存足够,不回收,若是内存不足,则回收。通常用于实现内存敏感的高速缓存,软引用能够和引用队列ReferenceQueue联合使用,若是软引用的对象被垃圾回收,JVM就会把这个软引用加入到与之关联的引用队列中。
三、弱引用(WeakReference)
弱引用和软引用大体相同,弱引用与软引用的区别在于:只具备弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程当中,一旦发现了只具备弱引用的对象,无论当前内存空间足够与否,都会回收它的内存。
四、虚引用(PhantomReference)
就是形同虚设,与其余几种引用都不一样,虚引用并不会决定对象的生命周期。若是一个对象仅持有虚引用,那么它就和没有任何引用同样,在任什么时候候均可能被垃圾回收器回收。 虚引用主要用来跟踪对象被垃圾回收器回收的活动。
虚引用与软引用和弱引用的一个区别在于:
虚引用必须和引用队列 (ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,若是发现它还有虚引,就会在回收对象的内存以前,把这个虚引用加入到与之关联的引用队列中。
http://blog.csdn.net/seu_calvin/article/details/52094115
一、HashCode的特性
(1)HashCode的存在主要是用于查找的快捷性,如Hashtable,HashMap等,HashCode常常用于肯定对象的存储地址。
(2)若是两个对象相同,?equals方法必定返回true,而且这两个对象的HashCode必定相同。
(3)两个对象的HashCode相同,并不必定表示两个对象就相同,即equals()不必定为true,只可以说明这两个对象在一个散列存储结构中。
(4)若是对象的equals方法被重写,那么对象的HashCode也尽可能重写。
二、HashCode做用
Java中的集合有两类,一类是List,再有一类是Set。前者集合内的元素是有序的,元素能够重复;后者元素无序,但元素不可重复。
equals方法可用于保证元素不重复,但若是每增长一个元素就检查一次,若集合中如今已经有1000个元素,那么第1001个元素加入集合时,就要调用1000次equals方法。这显然会大大下降效率。?因而,Java采用了哈希表的原理。
哈希算法也称为散列算法,是将数据依特定算法直接指定到一个地址上。
这样一来,当集合要添加新的元素时,先调用这个元素的HashCode方法,就一会儿能定位到它应该放置的物理位置上。
(1)若是这个位置上没有元素,它就能够直接存储在这个位置上,不用再进行任何比较了。
(2)若是这个位置上已经有元素了,就调用它的equals方法与新元素进行比较,相同的话就不存了。
(3)不相同的话,也就是发生了Hash key相同致使冲突的状况,那么就在这个Hash key的地方产生一个链表,将全部产生相同HashCode的对象放到这个单链表上去,串在一块儿(不多出现)。
这样一来实际调用equals方法的次数就大大下降了,几乎只须要一两次。
如何理解HashCode的做用:
从Object角度看,JVM每new一个Object,它都会将这个Object丢到一个Hash表中去,这样的话,下次作Object的比较或者取这个对象的时候(读取过程),它会根据对象的HashCode再从Hash表中取这个对象。这样作的目的是提升取对象的效率。若HashCode相同再去调用equal。
三、HashCode实践(如何用来查找)
HashCode是用于查找使用的,而equals是用于比较两个对象是否相等的。
(1)例如内存中有这样的位置
0 1 2 3 4 5 6 7
而我有个类,这个类有个字段叫ID,我要把这个类存放在以上8个位置之一,若是不用HashCode而任意存放,那么当查找时就须要到这八个位置里挨个去找,或者用二分法一类的算法。
但以上问题若是用HashCode就会使效率提升不少 定义咱们的HashCode为ID%8,好比咱们的ID为9,9除8的余数为1,那么咱们就把该类存在1这个位置,若是ID是13,求得的余数是5,那么咱们就把该类放在5这个位置。依此类推。
(2)可是若是两个类有相同的HashCode,例如9除以8和17除以8的余数都是1,也就是说,咱们先经过?HashCode来判断两个类是否存放某个桶里,但这个桶里可能有不少类,那么咱们就须要再经过equals在这个桶里找到咱们要的类。
请看下面这个例子
public class HashTest { private int i; public int getI() { return i; } public void setI(int i) { this.i = i; } public int hashCode() { return i % 10; } public final static void main(String[] args) { HashTest a = new HashTest(); HashTest b = new HashTest(); a.setI(1); b.setI(1); Set<HashTest> set = new HashSet<HashTest>(); set.add(a); set.add(b); System.out.println(a.hashCode() == b.hashCode()); System.out.println(a.equals(b)); System.out.println(set); } }
输出结果为:
true False [HashTest@1, HashTest@1]
以上这个示例,咱们只是重写了HashCode方法,从上面的结果能够看出,虽然两个对象的HashCode相等,可是实际上两个对象并非相等,由于咱们没有重写equals方法,那么就会调用Object默认的equals方法,显示这是两个不一样的对象。
这里咱们将生成的对象放到了HashSet中,而HashSet中只可以存放惟一的对象,也就是相同的(适用于equals方法)的对象只会存放一个,可是这里其实是两个对象ab都被放到了HashSet中,这样HashSet就失去了他自己的意义了。
下面咱们继续重写equals方法:
public class HashTest {
private int i;
public int getI() { return i; } public void setI(int i) { this.i = i; } public boolean equals(Object object) { if (object == null) { return false; } if (object == this) { return true; } if (!(object instanceof HashTest)) { return false; } HashTest other = (HashTest) object; if (other.getI() == this.getI()) { return true; } return false; } public int hashCode() { return i % 10; } public final static void main(String[] args) { HashTest a = new HashTest(); HashTest b = new HashTest(); a.setI(1); b.setI(1); Set<HashTest> set = new HashSet<HashTest>(); set.add(a); set.add(b); System.out.println(a.hashCode() == b.hashCode()); System.out.println(a.equals(b)); System.out.println(set); } }
输出结果以下所示。
从结果咱们能够看出,如今两个对象就彻底相等了,HashSet中也只存放了一份对象。
注意:
hashCode()只是简单示例写的,真正的生产换将不是这样的
true true [HashTest@1]
hashCode的存在主要是用于查找的快捷性,如Hashtable,HashMap等,hashCode是用来在散列存储结构中肯定对象的存储地址的。
若是两个对象相同,就是适用于equals(java.lang.Object) 方法,那么这两个对象的hashCode必定要相同。
若是对象的equals方法被重写,那么对象的hashCode也尽可能重写,而且产生hashCode使用的对象,必定要和equals方法中使用的一致,不然就会违反上面提到的第2点。
两个对象的hashCode相同,并不必定表示两个对象就相同,也就是不必定适用于equals(java.lang.Object) 方法,只可以说明这两个对象在散列存储结构中,如Hashtable,他们“存放在同一个篮子里”。
何时须要重写?
通常的地方不须要重载hashCode,只有当类须要放在HashTable、HashMap、HashSet等等hash结构的集合时才会重载hashCode,那么为何要重载hashCode呢?
要比较两个类的内容属性值,是否相同时候,根据hashCode 重写规则,重写类的 指定字段的hashCode(),equals()方法。
例如
public class EmpWorkCondition{ /** * 员工ID */ private Integer empId; /** * 员工服务总单数 */ private Integer orderSum; @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } EmpWorkCondition that = (EmpWorkCondition) o; return Objects.equals(empId, that.empId); } @Override public int hashCode() { return Objects.hash(empId); } // 省略 getter setter }
public static void main(String[] args) { List<EmpWorkCondition> list1 = new ArrayList<EmpWorkCondition>(); EmpWorkCondition emp1 = new EmpWorkCondition(); emp1.setEmpId(100); emp1.setOrderSum(90000); list1.add(emp1); List<EmpWorkCondition> list2 = new ArrayList<EmpWorkCondition>(); EmpWorkCondition emp2 = new EmpWorkCondition(); emp2.setEmpId(100); list2.add(emp2); System.out.println(list1.contains(emp2)); }
输出结果:true
上面的方法,作的事情就是,比较两个集合中的,实体类对象属性值,是否一致
OrderSum 不在比较范围内,由于没有重写它的,equals()和hashCode()方法
为何要重载equal方法?
由于Object的equal方法默认是两个对象的引用的比较,意思就是指向同一内存,地址则相等,不然不相等;若是你如今须要利用对象里面的值来判断是否相等,则重载equal方法。
通常的地方不须要重载hashCode,只有当类须要放在HashTable、HashMap、HashSet等等hash结构的集合时才会重载hashCode,那么为何要重载hashCode呢?
若是你重写了equals,好比说是基于对象的内容实现的,而保留hashCode的实现不变,那么极可能某两个对象明明是“相等”,而hashCode却不同。
这样,当你用其中的一个做为键保存到hashMap、hasoTable或hashSet中,再以“相等的”找另外一个做为键值去查找他们的时候,则根本找不到。
为何equals()相等,hashCode就必定要相等,而hashCode相等,却不要求equals相等?
一、由于是按照hashCode来访问小内存块,因此hashCode必须相等。 二、HashMap获取一个对象是比较key的hashCode相等和equal为true。
之因此hashCode相等,却能够equal不等,就好比ObjectA和ObjectB他们都有属性name,那么hashCode都以name计算,因此hashCode同样,可是两个对象属于不一样类型,因此equal为false。
为何须要hashCode?
一、经过hashCode能够很快的查到小内存块。 二、经过hashCode比较比equal方法快,当get时先比较hashCode,若是hashCode不一样,直接返回false。
List的三个子类的特色
ArrayList:
Vector:
LinkedList
Vector和ArrayList的区别
ArrayList和LinkedList的区别
共同点:都是线程不安全的
List有三个子类使用
String:适用于少许的字符串操做的状况。 StringBuilder:适用于单线程下在字符缓冲区进行大量操做的状况。 StringBuffer:适用多线程下在字符缓冲区进行大量操做的状况。 StringBuilder:是线程不安全的,而StringBuffer是线程安全的。
这三个类之间的区别主要是在两个方面,即运行速度和线程安全这两方面。 首先说运行速度,或者说是执行速度,在这方面运行速度快慢为:StringBuilder > StringBuffer > String。
String最慢的缘由
String为字符串常量,而StringBuilder和StringBuffer均为字符串变量,即String对象一旦建立以后该对象是不可更改的,但后二者的对象是变量,是能够更改的。
再来讲线程安全
在线程安全上,StringBuilder是线程不安全的,而StringBuffer是线程安全的。
若是一个StringBuffer对象在字符串缓冲区被多个线程使用时,StringBuffer中不少方法能够带有synchronized关键字,因此能够保证线程是安全的,但StringBuilder的方法则没有该关键字,因此不能保证线程安全,有可能会出现一些错误的操做。因此若是要进行的操做是多线程的,那么就要使用StringBuffer,可是在单线程的状况下,仍是建议使用速度比较快的StringBuilder。
Map
Set
List
Queue
Stack
用法
更为精炼的总结
Collection 是对象集合, Collection 有两个子接口 List 和 Set
List 能够经过下标 (1,2..) 来取得值,值能够重复。 Set 只能经过游标来取值,而且值是不能重复的。
ArrayList , Vector , LinkedList 是 List 的实现类
Map 是键值对集合
Stack类:继承自Vector,实现一个后进先出的栈。提供了几个基本方法,push、pop、peak、empty、search等。
Queue接口:提供了几个基本方法,offer、poll、peek等。已知实现类有LinkedList、PriorityQueue等。
https://segmentfault.com/a/1190000008101567
Hashtable是基于陈旧的Dictionary类的,HashMap是Java 1.2引进的Map接口的一个实现,它们都是集合中将数据无序存放的。
一、hashMap去掉了HashTable?的contains方法,可是加上了containsValue()和containsKey()方法
HashTable Synchronize同步的,线程安全,HashMap不容许空键值为空?,效率低。 HashMap 非Synchronize线程同步的,线程不安全,HashMap容许空键值为空?,效率高。 Hashtable是基于陈旧的Dictionary类的,HashMap是Java 1.2引进的Map接口的一个实现,它们都是集合中将数据无序存放的。
Hashtable的方法是同步的,HashMap未经同步,因此在多线程场合要手动同步HashMap这个区别就像Vector和ArrayList同样。
查看Hashtable的源代码就能够发现,除构造函数外,Hashtable的全部 public 方法声明中都有 synchronized 关键字,而HashMap的源代码中则连 synchronized 的影子都没有,固然,注释除外。
二、Hashtable不容许 null 值(key 和 value 都不能够),HashMap容许 null 值(key和value均可以)。
三、二者的遍历方式大同小异,Hashtable仅仅比HashMap多一个elements方法。
Hashtable table = new Hashtable(); table.put("key", "value"); Enumeration em = table.elements(); while (em.hasMoreElements()) { String obj = (String) em.nextElement(); System.out.println(obj); }
四、HashTable使用Enumeration,HashMap使用Iterator
从内部机制实现上的区别以下:
int hash = key.hashCode(); int index = (hash & 0x7FFFFFFF) % tab.length;
而HashMap从新计算hash值,并且用与代替求模:
int hash = hash(k); int i = indexFor(hash, table.length); static int hash(Object x) {   int h = x.hashCode();   h += ~(h << 9);   h ^= (h >>> 14);   h += (h << 4);   h ^= (h >>> 10);   return h; }
static int indexFor(int h, int length) {   return h & (length-1);
JDK7中的HashMap
HashMap底层维护一个数组,数组中的每一项都是一个Entry。
transient Entry<K,V>[] table;
咱们向 HashMap 中所放置的对象其实是存储在该数组当中。 而Map中的key,value则以Entry的形式存放在数组中。
static class Entry<K,V> implements Map.Entry<K,V> { final K key; V value; Entry<K,V> next; int hash;
总结一下map.put后的过程:
当向 HashMap 中 put 一对键值时,它会根据 key的 hashCode 值计算出一个位置, 该位置就是此对象准备往数组中存放的位置。
若是该位置没有对象存在,就将此对象直接放进数组当中;若是该位置已经有对象存在了,则顺着此存在的对象的链开始寻找(为了判断是不是否值相同,map不容许<key,value>键值对重复), 若是此链上有对象的话,再去使用 equals方法进行比较,若是对此链上的每一个对象的 equals 方法比较都为 false,则将该对象放到数组当中,而后将数组中该位置之前存在的那个对象连接到此对象的后面。
JDK8中的HashMap
JDK8中采用的是位桶+链表/红黑树(有关红黑树请查看红黑树)的方式,也是非线程安全的。当某个位桶的链表的长度达到某个阀值的时候,这个链表就将转换成红黑树。
JDK8中,当同一个hash值的节点数不小于8时,将再也不以单链表的形式存储了,会被调整成一颗红黑树(上图中null节点没画)。这就是JDK7与JDK8中HashMap实现的最大区别。
接下来,咱们来看下JDK8中HashMap的源码实现。
JDK中Entry的名字变成了Node,缘由是和红黑树的实现TreeNode相关联。
transient Node<K,V>[] table;
当冲突节点数不小于8-1时,转换成红黑树。
static final int TREEIFY_THRESHOLD = 8;
为了线程安全从ConcurrentHashMap代码中能够看出,它引入了一个“分段锁”的概念,具体能够理解为把一个大的Map拆分红N个小的HashTable,根据key.hashCode()来决定把key放到哪一个HashTable中。
Hashmap本质是数组加链表。根据key取得hash值,而后计算出数组下标,若是多个key对应到同一个下标,就用链表串起来,新插入的在前面。
ConcurrentHashMap:在hashMap的基础上,ConcurrentHashMap将数据分为多个segment,默认16个(concurrency level),而后每次操做对一个segment加锁,避免多线程锁的概率,提升并发效率。
总结
JDK6,7中的ConcurrentHashmap主要使用Segment来实现减少锁粒度,把HashMap分割成若干个Segment,在put的时候须要锁住Segment,get时候不加锁,使用volatile来保证可见性,当要统计全局时(好比size),首先会尝试屡次计算modcount来肯定,这几回尝试中,是否有其余线程进行了修改操做,若是没有,则直接返回size。若是有,则须要依次锁住全部的Segment来计算。
jdk7中ConcurrentHashmap中,当长度过长碰撞会很频繁,链表的增改删查操做都会消耗很长的时间,影响性能。
jdk8 中彻底重写了concurrentHashmap,代码量从原来的1000多行变成了 6000多 行,实现上也和原来的分段式存储有很大的区别。
JDK8中采用的是位桶+链表/红黑树(有关红黑树请查看红黑树)的方式,也是非线程安全的。当某个位桶的链表的长度达到某个阀值的时候,这个链表就将转换成红黑树。
JDK8中,当同一个hash值的节点数不小于8时,将再也不以单链表的形式存储了,会被调整成一颗红黑树(上图中null节点没画)。这就是JDK7与JDK8中HashMap实现的最大区别。
主要设计上的变化有如下几点
1.jdk8不采用segment而采用node,锁住node来实现减少锁粒度。 2.设计了MOVED状态 当resize的中过程当中 线程2还在put数据,线程2会帮助resize。 3.使用3个CAS操做来确保node的一些操做的原子性,这种方式代替了锁。 4.sizeCtl的不一样值来表明不一样含义,起到了控制的做用。
至于为何JDK8中使用synchronized而不是ReentrantLock,我猜是由于JDK8中对synchronized有了足够的优化吧。
hashTable虽然性能上不如ConcurrentHashMap,但并不能彻底被取代,二者的迭代器的一致性不一样的,hash table的迭代器是强一致性的,而concurrenthashmap是弱一致的。
ConcurrentHashMap的get,clear,iterator 都是弱一致性的。 Doug Lea 也将这个判断留给用户本身决定是否使用ConcurrentHashMap。
ConcurrentHashMap与HashTable均可以用于多线程的环境,可是当Hashtable的大小增长到必定的时候,性能会急剧降低,由于迭代时须要被锁定很长的时间。由于ConcurrentHashMap引入了分割(segmentation),不论它变得多么大,仅仅须要锁定map的某个部分,而其它的线程不须要等到迭代完成才能访问map。简而言之,在迭代的过程当中,ConcurrentHashMap仅仅锁定map的某个部分,而Hashtable则会锁定整个map。
那么既然ConcurrentHashMap那么优秀,为何还要有Hashtable的存在呢?ConcurrentHashMap能彻底替代HashTable吗?
HashTable虽然性能上不如ConcurrentHashMap,但并不能彻底被取代,二者的迭代器的一致性不一样的,HashTable的迭代器是强一致性的,而ConcurrentHashMap是弱一致的。 ConcurrentHashMap的get,clear,iterator 都是弱一致性的。 Doug Lea 也将这个判断留给用户本身决定是否使用ConcurrentHashMap。
那么什么是强一致性和弱一致性呢?
get方法是弱一致的,是什么含义?可能你指望往ConcurrentHashMap底层数据结构中加入一个元素后,立马能对get可见,但ConcurrentHashMap并不能如你所愿。换句话说,put操做将一个元素加入到底层数据结构后,get可能在某段时间内还看不到这个元素,若不考虑内存模型,单从代码逻辑上来看,倒是应该能够看获得的。
下面将结合代码和java内存模型相关内容来分析下put/get方法。put方法咱们只需关注Segment#put,get方法只需关注Segment#get,在继续以前,先要说明一下Segment里有两个volatile变量:count和table;HashEntry里有一个volatile变量:value。
总结
ConcurrentHashMap的弱一致性主要是为了提高效率,是一致性与效率之间的一种权衡。要成为强一致性,就获得处使用锁,甚至是全局锁,这就与Hashtable和同步的HashMap同样了。
HashMap 在并发执行 put 操做时会引发死循环,致使 CPU 利用率接近100%。由于多线程会致使 HashMap 的 Node 链表造成环形数据结构,一旦造成环形数据结构,Node 的 next 节点永远不为空,就会在获取 Node 时产生死循环。
了解了 HashMap 为何线程不安全,那如今看看如何线程安全的使用 HashMap。这个无非就是如下三种方式:
Hashtable ConcurrentHashMap Synchronized Map
Hashtable
例子
//Hashtable Map<String, String> hashtable = new Hashtable<>(); //synchronizedMap Map<String, String> synchronizedHashMap = Collections.synchronizedMap(new HashMap<String, String>()); //ConcurrentHashMap Map<String, String> concurrentHashMap = new ConcurrentHashMap<>(); Hashtable
先稍微吐槽一下,为啥命名不是 HashTable 啊,看着好难受无论了就装做它叫HashTable 吧。这货已经不经常使用了,就简单说说吧。HashTable 源码中是使用?synchronized?来保证线程安全的,好比下面的 get 方法和 put 方法:
public synchronized V get(Object key) { // 省略实现 } public synchronized V put(K key, V value) { // 省略实现 }
因此当一个线程访问 HashTable 的同步方法时,其余线程若是也要访问同步方法,会被阻塞住。举个例子,当一个线程使用 put 方法时,另外一个线程不但不可使用 put 方法,连 get 方法都不能够,好霸道啊!!!so~~,效率很低,如今基本不会选择它了。
ConcurrentHashMap
ConcurrentHashMap 于 Java 7 的,和8有区别,在8中 CHM 摒弃了 Segment(锁段)的概念,而是启用了一种全新的方式实现,利用 CAS 算法,有时间会从新总结一下。
SynchronizedMap
synchronizedMap() 方法后会返回一个 SynchronizedMap 类的对象,而在 SynchronizedMap 类中使用了 synchronized 同步关键字来保证对 Map 的操做是线程安全的。
性能对比
这是要靠数听说话的时代,因此不能只靠嘴说 CHM 快,它就快了。写个测试用例,实际的比较一下这三种方式的效率(源码来源),下面的代码分别经过三种方式建立 Map 对象,使用 ExecutorService 来并发运行5个线程,每一个线程添加/获取500K个元素。
Test started for: class java.util.Hashtable 2500K entried added/retrieved in 2018 ms 2500K entried added/retrieved in 1746 ms 2500K entried added/retrieved in 1806 ms 2500K entried added/retrieved in 1801 ms 2500K entried added/retrieved in 1804 ms For class java.util.Hashtable the average time is 1835 ms Test started for: class java.util.Collections$SynchronizedMap 2500K entried added/retrieved in 3041 ms 2500K entried added/retrieved in 1690 ms 2500K entried added/retrieved in 1740 ms 2500K entried added/retrieved in 1649 ms 2500K entried added/retrieved in 1696 ms For class java.util.Collections$SynchronizedMap the average time is 1963 ms Test started for: class java.util.concurrent.ConcurrentHashMap 2500K entried added/retrieved in 738 ms 2500K entried added/retrieved in 696 ms 2500K entried added/retrieved in 548 ms 2500K entried added/retrieved in 1447 ms 2500K entried added/retrieved in 531 ms For class java.util.concurrent.ConcurrentHashMap the average time is 792 ms
ConcurrentHashMap 性能是明显优于 Hashtable 和 SynchronizedMap 的,CHM 花费的时间比前两个的一半还少。
今天原本想看下了ConcurrentHashMap的源码,ConcurrentHashMap是Java 5中支持高并发、高吞吐量的线程安全HashMap实现。
在看不少博客在介绍ConcurrentHashMap以前,都说HashMap适用于单线程访问,这是由于HashMap的全部方法都没有进行锁同步,所以是线程不安全的,不只如此,当多线程访问的时候还容易产生死循环。
虽然本身在前几天的时候看过HashMap的源码,感受思路啥啥的都还清楚,对于多线程访问只知道HashMap是线程不安全的,可是不知道HashMap在多线程并发的状况下会产生死循环呢,为何会产生,何种状况下才会产生死循环呢???
《Java困惑》:多并发状况下HashMap是否还会产生死循环。
既然会产生死循环,为何并发状况下,仍是用ConcurrentHashMap。 jdk 好像有,可是Jdk8 已经修复了这个问题。
LinkedHashMap能够保证HashMap集合有序,存入的顺序和取出的顺序一致。
TreeMap实现SortMap接口,可以把它保存的记录根据键排序,默认是按键值的升序排序,也能够指定排序的比较器,当用Iterator遍历TreeMap时,获得的记录是排过序的。
HashMap不保证顺序,即为无序的,具备很快的访问速度。 HashMap最多只容许一条记录的键为Null;容许多条记录的值为 Null。 HashMap不支持线程的同步。
咱们在开发的过程当中使用HashMap比较多,在Map中在Map 中插入、删除和定位元素,HashMap 是最好的选择。
但若是您要按天然顺序或自定义顺序遍历键,那么TreeMap会更好。
若是须要输出的顺序和输入的相同,那么用LinkedHashMap 能够实现,它还能够按读取顺序来排列。
Collection 是集合类的上级接口,子接口主要有Set、List 、Map。
Collecions 是针对集合类的一个帮助类, 提供了操做集合的工具方法,一系列静态方法实现对各种集合的搜索、排序线性、线程安全化等操做。
例如
Map<String, Object> map4 = Collections.synchronizedMap(new HashMap<String, Object>()); 线程安全 的HashMap Collections.sort(List<T> list, Comparator<? super T> c); 排序 List
Collection
Collection 是单列集合
List
元素是有序的、可重复。 有序的 collection,能够对列表中每一个元素的插入位置进行精确地控制。 能够根据元素的整数索引(在列表中的位置)访问元素,并搜索列表中的元素。 可存放重复元素,元素存取是有序的。
List接口中经常使用类
Vector:线程安全,但速度慢,已被ArrayList替代。底层数据结构是数组结构。 ArrayList:线程不安全,查询速度快。底层数据结构是数组结构。 LinkedList:线程不安全。增删速度快。底层数据结构是列表结构。
Set
Set接口中经常使用的类
Set(集) 元素无序的、不可重复。 取出元素的方法只有迭代器。不能够存放重复元素,元素存取是无序的。
HashSet:线程不安全,存取速度快。它是如何保证元素惟一性的呢?依赖的是元素的hashCode方法和euqals方法。 TreeSet:线程不安全,能够对Set集合中的元素进行排序。它的排序是如何进行的呢?经过compareTo或者compare方法中的来保证元素的惟一性。元素是以二叉树的形式存放的。
Map
map是一个双列集合
Hashtable:线程安全,速度快。底层是哈希表数据结构。是同步的。不容许null做为键,null做为值。
Properties:用于配置文件的定义和操做,使用频率很是高,同时键和值都是字符串。是集合中能够和IO技术相结合的对象。
HashMap:线程不安全,速度慢。底层也是哈希表数据结构。是不一样步的。容许null做为键,null做为值,替代了Hashtable。
LinkedHashMap: 能够保证HashMap集合有序。存入的顺序和取出的顺序一致。
TreeMap:能够用来对Map集合中的键进行排序
确定会执行。finally{}块的代码。 只有在try{}块中包含遇到System.exit(0)。 之类的致使Java虚拟机直接退出的语句才会不执行。
当程序执行try{}遇到return时,程序会先执行return语句,但并不会当即返回——也就是把return语句要作的一切事情都准备好,也就是在将要返回、但并未返回的时候,程序把执行流程转去执行finally块,当finally块执行完成后就直接返回刚才return语句已经准备好的结果。
Throwable是 Java 语言中全部错误或异常的超类。 Throwable包含两个子类: Error 和 Exception 。它们一般用于指示发生了异常状况。 Throwable包含了其线程建立时线程执行堆栈的快照,它提供了printStackTrace()等接口用于获取堆栈跟踪数据等信息。
Java将可抛出(Throwable)的结构分为三种类型:
被检查的异常(Checked Exception)。 运行时异常(RuntimeException)。 错误(Error)。
运行时异常RuntimeException
定义 : RuntimeException及其子类都被称为运行时异常。 特色 : Java编译器不会检查它 也就是说,当程序中可能出现这类异常时,假若既"没有经过throws声明抛出它",也"没有用try-catch语句捕获它",仍是会编译经过。
例如,除数为零时产生的ArithmeticException异常,数组越界时产生的IndexOutOfBoundsException异常,fail-fail机制产生的ConcurrentModificationException异常等,都属于运行时异常。
堆内存溢出 OutOfMemoryError(OOM)
除了程序计数器外,虚拟机内存的其余几个运行时区域都有发生OutOfMemoryError(OOM)异常的可能。
Java Heap 溢出。 通常的异常信息:java.lang.OutOfMemoryError:Java heap spacess。 java堆用于存储对象实例,咱们只要不断的建立对象,而且保证GC Roots到对象之间有可达路径来避免垃圾回收机制清除这些对象,就会在对象数量达到最大堆容量限制后产生内存溢出异常。
堆栈溢出 StackOverflow (SOF)
StackOverflowError 的定义: 当应用程序递归太深而发生堆栈溢出时,抛出该错误。 由于栈通常默认为1-2m,一旦出现死循环或者是大量的递归调用,在不断的压栈过程当中,形成栈容量超过1m而致使溢出。
栈溢出的缘由:
递归调用。 大量循环或死循环。 全局变量是否过多。 数组、List、map数据过大。
封装(高内聚低耦合 -->解耦)
封装是指将某事物的属性和行为包装到对象中,这个对象只对外公布须要公开的属性和行为,而这个公布也是能够有选择性的公布给其它对象。在java中能使用private、protected、public三种修饰符或不用(即默认defalut)对外部对象访问该对象的属性和行为进行限制。
java的继承(重用父类的代码)
继承是子对象能够继承父对象的属性和行为,亦即父对象拥有的属性和行为,其子对象也就拥有了这些属性和行为。
java中的多态(父类引用指向子类对象)
多态是指父对象中的同一个行为能在其多个子对象中有不一样的表现。
有两种多态的机制:编译时多态、运行时多态。
一、方法的重载:重载是指同一类中有多个同名的方法,但这些方法有着不一样的参数。,所以在编译时就能够肯定到底调用哪一个方法,它是一种编译时多态。 二、方法的重写:子类能够覆盖父类的方法,所以一样的方法会在父类中与子类中有着不一样的表现形式。
重载 Overload方法名相同,参数列表不一样(个数、顺序、类型不一样)与返回类型无关。 重写 Override 覆盖。 将父类的方法覆盖。 重写方法重写:方法名相同,访问修饰符只能大于被重写的方法访问修饰符,方法签名个数,顺序个数类型相同。
Override(重写)
Overload(重载)
而重载的规则
一、必须具备不一样的参数列表。 二、能够有不一样的返回类型,只要参数列表不一样就能够了。 三、能够有不一样的访问修饰符。 四、能够抛出不一样的异常。
重写方法的规则
一、参数列表必须彻底与被重写的方法相同,不然不能称其为重写而是重载。 二、返回的类型必须一直与被重写的方法的返回类型相同,不然不能称其为重写而是重载。 三、访问修饰符的限制必定要大于被重写方法的访问修饰符(public>protected>default>private)。 四、重写方法必定不能抛出新的检查异常或者比被重写方法申明更加宽泛的检查型异常。
例如: 父类的一个方法申明了一个检查异常IOException,在重写这个方法是就不能抛出Exception,只能抛出IOException的子类异常,能够抛出非检查异常。
Interface 只能有成员常量,只能是方法的声明。 Abstract class能够有成员变量,能够声明普通方法和抽象方法。
interface是接口,全部的方法都是抽象方法,成员变量是默认的public static final 类型。接口不能实例化本身。
abstract class是抽象类,至少包含一个抽象方法的累叫抽象类,抽象类不能被自身实例化,并用abstract关键字来修饰。
static class(内部静态类)
一、用static修饰的是内部类,此时这个内部类变为静态内部类;对测试有用。 二、内部静态类不须要有指向外部类的引用。 三、静态类只能访问外部类的静态成员,不能访问外部类的非静态成员。
non static class(非静态内部类)
一、非静态内部类须要持有对外部类的引用。 二、非静态内部类可以访问外部类的静态和非静态成员。 三、一个非静态内部类不能脱离外部类实体被建立。 四、一个非静态内部类能够访问外部类的数据和方法。
用for循环arrayList 10万次花费时间:5毫秒。 用foreach循环arrayList 10万次花费时间:7毫秒。 用for循环linkList 10万次花费时间:4481毫秒。 用foreach循环linkList 10万次花费时间:5毫秒。
循环ArrayList时,普通for循环比foreach循环花费的时间要少一点。 循环LinkList时,普通for循环比foreach循环花费的时间要多不少。
当我将循环次数提高到一百万次的时候,循环ArrayList,普通for循环仍是比foreach要快一点;可是普通for循环在循环LinkList时,程序直接卡死。
ArrayList:ArrayList是采用数组的形式保存对象的,这种方式将对象放在连续的内存块中,因此插入和删除时比较麻烦,查询比较方便。
LinkList:LinkList是将对象放在独立的空间中,并且每一个空间中还保存下一个空间的索引,也就是数据结构中的链表结构,插入和删除比较方便,可是查找很麻烦,要从第一个开始遍历。
结论:
须要循环数组结构的数据时,建议使用普通for循环,由于for循环采用下标访问,对于数组结构的数据来讲,采用下标访问比较好。
须要循环链表结构的数据时,必定不要使用普通for循环,这种作法很糟糕,数据量大的时候有可能会致使系统崩溃。
NIO是为了弥补IO操做的不足而诞生的,NIO的一些新特性有:非阻塞I/O,选择器,缓冲以及管道。管道(Channel),缓冲(Buffer) ,选择器( Selector)是其主要特征。
概念解释
Channel——管道实际上就像传统IO中的流,到任何目的地(或来自任何地方)的全部数据都必须经过一个 Channel 对象。一个 Buffer 实质上是一个容器对象。
每一种基本 Java 类型都有一种缓冲区类型:
ByteBuffer——byte CharBuffer——char ShortBuffer——short IntBuffer——int LongBuffer——long FloatBuffer——float DoubleBuffer——double
Selector——选择器用于监听多个管道的事件,使用传统的阻塞IO时咱们能够方便的知道何时能够进行读写,而使用非阻塞通道,咱们须要一些方法来知道何时通道准备好了,选择器正是为这个须要而诞生的。
NIO和传统的IO有什么区别呢?
IO是面向流的,NIO是面向块(缓冲区)的。
IO面向流的操做一次一个字节地处理数据。一个输入流产生一个字节的数据,一个输出流消费一个字节的数据。,致使了数据的读取和写入效率不佳。
NIO面向块的操做在一步中产生或者消费一个数据块。按块处理数据比按(流式的)字节处理数据要快得多,同时数据读取到一个它稍后处理的缓冲区,须要时可在缓冲区中先后移动。这就增长了处理过程当中的灵活性。通俗来讲,NIO采起了“预读”的方式,当你读取某一部分数据时,他就会猜想你下一步可能会读取的数据而预先缓冲下来。
IO是阻塞的,NIO是非阻塞的
对于传统的IO,当一个线程调用read() 或 write()时,该线程被阻塞,直到有一些数据被读取,或数据彻底写入。该线程在此期间不能再干任何事情了。
而对于NIO,使用一个线程发送读取数据请求,没有获得响应以前,线程是空闲的,此时线程能够去执行别的任务,而不是像IO中那样只能等待响应完成。
NIO和IO适用场景
NIO是为弥补传统IO的不足而诞生的,可是尺有所短寸有所长,NIO也有缺点,由于NIO是面向缓冲区的操做,每一次的数据处理都是对缓冲区进行的,那么就会有一个问题,在数据处理以前必需要判断缓冲区的数据是否完整或者已经读取完毕,若是没有,假设数据只读取了一部分,那么对不完整的数据处理没有任何意义。因此每次数据处理以前都要检测缓冲区数据。
那么NIO和IO各适用的场景是什么呢?
若是须要管理同时打开的成千上万个链接,这些链接每次只是发送少许的数据,例如聊天服务器,这时候用NIO处理数据多是个很好的选择。
而若是只有少许的链接,而这些链接每次要发送大量的数据,这时候传统的IO更合适。使用哪一种处理数据,须要在数据的响应等待时间和检查缓冲区数据的时间上做比较来权衡选择。
通俗解释,最后,对于NIO和传统IO
有一个网友讲的生动的例子:
之前的流老是堵塞的,一个线程只要对它进行操做,其它操做就会被堵塞,也就至关于水管没有阀门,你伸手接水的时候,无论水到了没有,你就都只能耗在接水(流)上。
nio的Channel的加入,至关于增长了水龙头(有阀门),虽然一个时刻也只能接一个水管的水,但依赖轮换策略,在水量不大的时候,各个水管里流出来的水,均可以获得妥
善接纳,这个关键之处就是增长了一个接水工,也就是Selector,他负责协调,也就是看哪根水管有水了的话,在当前水管的水接到必定程度的时候,就切换一下:临时关上当
前水龙头,试着打开另外一个水龙头(看看有没有水)。
当其余人须要用水的时候,不是直接去接水,而是事前提了一个水桶给接水工,这个水桶就是Buffer。也就是,其余人虽然也可能要等,但不会在现场等,而是回家等,能够作
其它事去,水接满了,接水工会通知他们。
这其实也是很是接近当前社会分工细化的现实,也是统分利用现有资源达到并发效果的一种很经济的手段,而不是动不动就来个并行处理,虽然那样是最简单的,但也是最浪费资源的方式。
什么是Java的反射呢?
Java 反射是可让咱们在运行时,经过一个类的Class对象来获取它获取类的方法、属性、父类、接口等类的内部信息的机制。
这种动态获取信息以及动态调用对象的方法的功能称为JAVA的反射。
反射的做用?
反射就是:在任意一个方法里:
1.若是我知道一个类的名称/或者它的一个实例对象, 我就能把这个类的全部方法和变量的信息找出来(方法名,变量名,方法,修饰符,类型,方法参数等等全部信息)
2.若是我还明确知道这个类里某个变量的名称,我还能获得这个变量当前的值。
3.固然,若是我明确知道这个类里的某个方法名+参数个数类型,我还能经过传递参数来运行那个类里的那个方法。
反射机制主要提供了如下功能:
反射的原理?
JAVA语言编译以后会生成一个.class文件,反射就是经过字节码文件找到某一个类、类中的方法以及属性等。
反射的实现API有哪些?
反射的实现主要借助如下四个类:
Class:类的对象 Constructor:类的构造方法 Field:类中的属性对象 Method:类中的方法对象
反射的实例
List<String>可否转为List<Object>
不能够强转类型的
这个问题涉及到了,范型向上转型 和 范型向下转型问题。 List向上转换至List(等价于List)会丢失String类的身份(String类型的特有接口)。 当须要由List向下转型时,你的程序必须明确的知道将对象转换成何种具体类型,否则这将是不安全的操做。
若是要强转类型,Json 序列化转型
List<String> str = new ArrayList<String>(); List<Object> obj= JSONObject.parseArray(JSONObject.toJSONString(str));
或者遍历,或者克隆,可是取出来就是(Object)了,须要强转,String 由于类型丢了。
Android中三种经常使用解析XML的方式(DOM、SAX、PULL)简介及区别。
http://blog.csdn.net/cangchen/article/details/44034799
xml解析的两种基本方式:DOM和SAX的区别是?
DOM: document object model。
SAX: simple api for xml 。
dom一次性把xml文件所有加载到内存中简历一个结构一摸同样的树, 效率低。 SAX解析器的优势是解析速度快,占用内存少,效率高。
DOM在内存中以树形结构存放,所以检索和更新效率会更高。可是对于特别大的文档,解析和加载整个文档将会很耗资源。
DOM,它是生成一个树,有了树之后你搜索、查找均可以作。 SAX,它是基于流的,就是解析器从头至尾解析一遍xml文件,解析完了之后你不过想再查找从新解析。 sax解析器核心是事件处理机制。例如解析器发现一个标记的开始标记时,将所发现的数据会封装为一个标记开始事件,并把这个报告给事件处理器。
平时工做中,xml解析你是使用什么?
JDOM DOM4J
1.5
1.6
1.7
1.8
1.9
10
【示例】设计模式——单例模式、工厂模式、代理模式、观察者模式、装饰器模式
菜鸟教程-设计模式
什么是设计模式
设计模式是一种解决方案,用于解决在软件设计中广泛存在的问题,是前辈们对以前软件设计中反复出现的问题的一个总结。
咱们学设计模式,是为了学习如何合理的组织咱们的代码,如何解耦,如何真正的达到对修改封闭对扩展开放的效果,而不是去背诵那些类的继承模式,而后本身记不住,回过头来就骂设计模式把你的代码搞复杂了,要反设计模式。
设计模式的六大原则
一、开闭原则(Open Close Principle)
开闭原则的意思是:对扩展开放,对修改关闭。在程序须要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果。简言之,是为了使程序的扩展性好,易于维护和升级。想要达到这样的效果,咱们须要使用接口和抽象类,后面的具体设计中咱们会提到这点。
二、里氏代换原则(Liskov Substitution Principle)
里氏代换原则是面向对象设计的基本原则之一。 里氏代换原则中说,任何基类能够出现的地方,子类必定能够出现。LSP 是继承复用的基石,只有当派生类能够替换掉基类,且软件单位的功能不受到影响时,基类才能真正被复用,而派生类也可以在基类的基础上增长新的行为。里氏代换原则是对开闭原则的补充。实现开闭原则的关键步骤就是抽象化,而基类与子类的继承关系就是抽象化的具体实现,因此里氏代换原则是对实现抽象化的具体步骤的规范。
三、依赖倒转原则(Dependence Inversion Principle)
这个原则是开闭原则的基础,具体内容:针对接口编程,依赖于抽象而不依赖于具体。
四、接口隔离原则(Interface Segregation Principle)
这个原则的意思是:使用多个隔离的接口,比使用单个接口要好。它还有另一个意思是:下降类之间的耦合度。因而可知,其实设计模式就是从大型软件架构出发、便于升级和维护的软件设计思想,它强调下降依赖,下降耦合。
五、迪米特法则,又称最少知道原则(Demeter Principle)
最少知道原则是指:一个实体应当尽可能少地与其余实体之间发生相互做用,使得系统功能模块相对独立。
六、合成复用原则(Composite Reuse Principle)
合成复用原则是指:尽可能使用合成/聚合的方式,而不是使用继承。
JNI是 Java Native Interface 的缩写,它提供了若干的API实现了Java和其余语言的通讯(主要是C&C++)。从Java1.1开始,JNI标准成为java平台的一部分,它容许Java代码和其余语言写的代码进行交互。JNI一开始是为了本地已编译语言,尤为是C和C++而设计的,可是它并不妨碍你使用其余编程语言,只要调用约定受支持就能够了。使用java与本地已编译的代码交互,一般会丧失平台可移植性。
JNI步骤
JNI实例
public class HelloWorld { public native void displayHelloWorld();//全部native关键词修饰的都是对本地的声明 static { System.loadLibrary("hello");//载入本地库 } public static void main(String[] args) { new HelloWorld().displayHelloWorld(); } }
AOP(Aspect Oriented Programming) 面向切面编程,是目前软件开发中的一个热点,是Spring框架内容,利用AOP能够对业务逻辑的各个部分隔离,从而使的业务逻辑各部分的耦合性下降,提升程序的可重用性,踢开开发效率,主要功能:日志记录,性能统计,安全控制,事务处理,异常处理等。
AOP实现原理是java动态代理,可是jdk的动态代理必须实现接口,因此spring的aop是用cglib这个库实现的,cglis使用里asm这个直接操纵字节码的框架,因此能够作到不使用接口的状况下实现动态代理。
OOP面向对象编程,针对业务处理过程的实体及其属性和行为进行抽象封装,以得到更加清晰高效的逻辑单元划分。
OOP面向对象编程,针对业务处理过程的实体及其属性和行为进行抽象封装,以得到更加清晰高效的逻辑单元划分。而AOP则是针对业务处理过程当中的切面进行提取,它所面对的是处理过程的某个步骤或阶段,以得到逻辑过程的中各部分之间低耦合的隔离效果。这两种设计思想在目标上有着本质的差别。
举例:
对于“雇员”这样一个业务实体进行封装,天然是OOP的任务,咱们能够创建一个“Employee”类,并将“雇员”相关的属性和行为封装其中。而用AOP 设计思想对“雇员”进行封装则无从谈起。
一样,对于“权限检查”这一动做片断进行划分,则是AOP的目标领域。
OOP面向名次领域,AOP面向动词领域。
总之AOP能够经过预编译方式和运行期动态代理实如今不修改源码的状况下,给程序动态赞成添加功能的一项技术。