java随笔
static变量和static方法
static变量
1.static修饰的变量:静态变量,静态变量在内存中只有一个拷贝,jvm只为静态变量分配一次内存,在加载类的过程中完成静态变量的内存分配。可以类名直接访问。一般在对象之间共享值时和方便访问变量时使用静态变量。
2.实例变量,每创建一个实例就会为实例变量分配一次内存,实例变量可以有多个拷贝,互不影响。
静态方法
静态方法可以直接通过类名调用,实例也可调用。静态方法中不能使用this和super关键字,不能直接访问所属类的实例变量和实例方法,只能访问所属类的静态成员变量和成员方法。
static代码块
public class Test5 {
private static int a;
private int b;
static{
Test5.a=3;
System.out.println(a);
Test5 t=new Test5();
t.f();
t.b=1000;
System.out.println(t.b);
}
在类中独立于类成员的static语句块,可以有多个,jvm加载类时会按顺序执行静态代码块
static final
static final修饰的变量,表示一旦赋值就不可修改,并且可以通过类名访问
static final修饰的方法,不可覆盖,可通过类名直接访问
java支持的数据类型有?何为自动拆装箱?
1.byte
2.short
3.int
4.long
5.float
6.double
7.boolean
8.char
自动装箱时java编译器在基本数据类型和对应的对象包装类型之间做的一个转化,比如int转成Integer,double转double等,反之就是自动拆箱
java不支持多继承。每个类只能继承一个类,但可以实现多个接口
抽象类和抽象接口
java提供和创建抽象类和接口,不同点
1.接口中所有的方法隐含的都是抽象的。而抽象类则可以同时包含抽象和非抽象的方法。
2.类可以实现很多个接口,但是只能继承一个抽象类
3.类如果要实现一个接口,它必须要实现接口声明的所有方法。但是,类可以不实现抽象类声明的所有方法,当然,在这种情况下,类也必须得声明成是抽象的。
4.抽象类可以在不提供接口方法实现的情况下实现接口。
5.Java接口中声明的变量默认都是final的。抽象类可以包含非final的变量。
6.Java接口中的成员函数默认是public的。抽象类的成员函数可以是private,protected或者是public。
7。接口是绝对抽象的,不可以被实例化。抽象类也不可以被实例化,但是,如果它包含main方法的话是可以被调用的。
创建线程的几种方式*
- 继承thread类
- 实现Runnable接口
- 使用Executor框架来创建线程池
java不支持多继承,实现接口的方式更受欢迎
synchronized获取锁,同步*
在监视器内部,如何做线程同步?程序应做何种级别的同步
监视器和锁在Java虚拟机中是一块使用的。监视器监视一块同步代码块,确保一次只有一个线程执行同步代码块。每一个监视器
hashMap的原理
hashMap以key-value的形式进行数据存储,本质上是数组和链表的结合。
initialCapacity(初始容量)和loadFactor(加载因子)是影响hashMap性能的重要参数。默认初始容量16,加载因子是0.75。为了保证HashMap的效率,系统必须要在某个临界点进行扩容处理,临界点:当HashMap中元素的数量=数据长度length*加载因子(loadFactor).扩容是一个非常耗时的过程,需要重新计算数据在数组中的位置并进行复制。
实验表明length=2的n次方时,数组中元素分布较均匀
过程:
- 利用key的hashCode重新hash计算出当前对象的元素在数组中的下标,然后找到在数组中的位置。
- 如果hash值相同且key值也相同,则覆盖原始值;如果hash相同key不同(出现冲突),则将当前的key-value放入链表中
hashMap和hashTable、ConcurrentHashMap和synchronized Map的原理和区别(出处:http://www.importnew.com/21396.html)
HashMap中key可以为null,HashTable中key不可以为null,ConcurrentHashMap中key和value都不能为null
HashMap是非线程安全的
如何线程安全的使用hashMap
Map<String,String> hashTable = new HashTable<String,String>()
Map<String,String> synchronizedMap = Collections.synchronizedMap(new HashMap<String,String>)
Map<String,String> concurrentHashMap = new ConcurrentHashMap<String,String>();
HashMap何时会产生死循环?
HashTable
HashTable源码中使用synchronized来保证线程安全,如get方法和put方法
public synchronized V get(Object key){
//省略
}
public synchronized V put(Object key){
//省略
}
当一个线程使用put方法时别的线程不但不可以使用put,连get方法都不可以使用,效率低!现已基本不使用
ConcurrentHashMap
ConcurrentHashMap线程安全的,适用于读者数量大于写者数量的场景
- 允许并发读和线程安全的更新操作
- 在执行写操作时,只锁住部分map
- 高的并发级别会造成时间和空间的浪费,低的并发级别在写线程多时会引起线程间的竞争
- 返回的迭代器是弱一致性,fail-safe并且不会抛出ConcurrentModificationException异常
- 不允许null的键值
- 可代替HashTable,但CHM不会锁住整个map
java7
采用锁分离技术,使用多个锁来控制hash表的不同部分,每一部分相当于一个hashTable,有自己的锁。只要多个修改操作发生在不同的segment上,就可以并发执行。
有些方法需要跨段,如size()和containsValue(),他们可能需要锁定整个表而不仅仅是段,这需要按顺序锁定所有段,操作完毕后,按顺序释放所有段的锁
java8
synchronizedHashMap
源码
//synchronizedMap方法
public static <K,V> Map<K,V>synchronizedMap(Map<K,V> m){
return new SynchronizedMap<>(m);
}
//SynchronizedMap类
private static class SynchronizedMap<K,V> implements Map<K,V> Serializable{
private static final long serialVersionUID = 1978198479659022715L;
private final Map<K,V> m; // Backing Map
final Object mutex; // Object on which to synchronize
SynchronizedMap(Map<K,V> m) {
this.m = Objects.requireNonNull(m);
mutex = this;
}
SynchronizedMap(Map<K,V> m, Object mutex) {
this.m = m;
this.mutex = mutex;
}
public int size() {
synchronized (mutex) {return m.size();}
}
public boolean isEmpty() {
synchronized (mutex) {return m.isEmpty();}
}
public boolean containsKey(Object key) {
synchronized (mutex) {return m.containsKey(key);}
}
public boolean containsValue(Object value) {
synchronized (mutex) {return m.containsValue(value);}
}
public V get(Object key) {
synchronized (mutex) {return m.get(key);}
}
public V put(K key, V value) {
synchronized (mutex) {return m.put(key, value);}
}
public V remove(Object key) {
synchronized (mutex) {return m.remove(key);}
}
// 省略其他方法
}
从源码中可以看出,synchronizedMap()方法返回一个SynchronizedMap类的对象,而在SynchronizedMap类中使用了synchronized同步关键字来保证对Map的操作是线程安全的
HashMap为什么是非线程安全?
void addEntry(int hash,K key,V value,int bucketIndex){
Entry<K,V> e = table[bucketIndex];
table[bucketIndex] = new Entry<K,V>(hash,key,value,e);
if(size++ >= threshold){
resize(2*table.length);
}
}
原因一:hashMap做put操作的时候调用addEntry方法,现在假如A线程和B线程同时对同一个数据位置调用该方法,两个线程会同时得到头节点,A写入头节点以后B也写入新的头节点,那B的写入操作造成A的写入操作丢失。
addEntry中当加入新的键值对后键值对总数超过门限值的时候会调用一个resize操作,代码如下:
void resize(int newCapacity){
Entry[] oldTable = table;
int oldCapacity = oldTable.length;
if(oldCapacity == MAXIMUM_CAPACITY){
threshold = Integer.MAX_VALUE;
return;
}
Entry[] newTable = new Entry[newCapacity];
transfer(newTable);
table = newTable;
threshold = (int)(newCapacity * loadFactor);
这个操作会生成一个新的容量的数组,会对原数组的所有键值对重新进行计算和写入新的数组,之后指向新的数组。
原因二:当多个线程同时检测需要进行resize()操作,各自生成新的数组并rehash后赋给该map底层的数组table,结果最后只有一个线程生成的新数组被赋给table变量,其他线程的均丢失。
Map testmap = Collection.synchronizedMap(new HashMap())
equals()方法和hashCode()方法
java.lang.Object类中有两个非常重要的方法
public boolean equals(Object obj)
public int hashCode()
Object是类继承结构的基础,是所有类的父类
equals()
public boolean equals(Object obj){
return (this == obj)
}
是对两个对象的地址值进行比较。但String、Math、Integer、Double等这些封装类在使用equals()方法时,已经覆盖了object类的equals()方法
如在String类中如下:
public boolean equals(Object anObject){
if(this == anObject){
return true;
}
if(anObject instanceof String){
String anotherString = (String)anObject;
int n = count;
if(n == anotherString.count){
char v1[] = value;
char v2[] = anotherString.value;
int i = offset;
int j = anotherString.offset;
while(n-- != 0)//对每一位置逐一比较
{
if(v1[i++] != v2[j++]
return false;
}
}
return true;
}
}
return false;
}
类推Math、Integer、Double等类都重写了equals()方法,还有基本类型也是进行内容的比较
注意:当equals方法被override时,hashCode()也要被override。按照一般hashCode()方法的实现来说,相等的对象,它们的hash code一定相等
hashCode
- 在一个java应用的执行期间,如果一个对象提供给equals做比较的信息没有被修改的话,该对象多次调用hashCode方法,始终返回同一个integer。
- 如果两个对象根据equals(Object)方法是相等的,那调用二者各自的hashCode()方法必须产生同一个integer结果。
在Object类中,hashCode定义如下:
public native int hashCode();
说明是本地方法,实现跟本地机器有关,如String、Integer、Double等这些类都覆盖了hashCode方法,String中定义的hashCode()方法如下:
public int hashCode(){
int h = hash;
if(h == 0){
int off = offset;
char val[] = value;
int len = count;
for(int i=0;i<len;i++){
h = 31*h+val[off++];
}
hash = h;
}
return h;
}
ArrayList 和 linkedList
- ArrayList实现了基于动态数组的数据结构,LinkedList基于链表的数据结构。
- 对于随机访问,ArrayList优于LinkedList
- 对于新增和删除操作,LinkedList优于ArrayList
ArrayList
ArrayList,在声明对象时并不需要指定它的长度,对象的大小是按照其中存储的数据来动态扩充和收缩的。
数组扩容是对ArrayList效率影响较大的一个元素。
每当执行Add、AddRange、insert、insertRange等添加元素的方法,都会检查内部数组的容量是否够用。若不够,以当前容量的两倍来重新构建数组,将旧元素COPY到数组中,然后丢弃旧数组
特定类型(Object除外)的数组的性能优于ArrayList的性能,因为ArrayList的元素属于Object类型,所以在存储或检索值类型时通常发生装箱和取消装箱的操作。
map、list、set
list 有序可重复
set 无序不可重复
map 按键值对存储,无放入顺序
List接口有三个实现类:LinkedList、ArrayList、Vector
Set接口有两个实现类:HashSet(底层由HashMap实现),LinkedHashSet
Map接口有三个实现类:HashMap、HashTable、LinkedHashMap
HashMap allows one null key and any number of null values.,而Hashtable则不行
HashTable是synchronized的,是线程安全的,而HashMap不是