Java反射

Demo:

输出结果:

name=audi,color = red,price = 1,000

 

类装载器ClassLoader

类装载器工作机制

类装载器就是寻找类的节码文件并构造出类在JVM内部表示对象的组件。在Java中,类装载器把一个类装入JVM中,要经过以下步骤:

      [1.]装载:查找和导入Class文件;
      [2.]链接:执行校验、准备和解析步骤,其中解析步骤是可以选择的;
          [2.1]校验:检查载入Class文件数据的正确性;
          [2.2]准备:给类的静态变量分配存储空间;
          [2.3]解析:将符号引用转成直接引用;
    [3.]初始化:对类的静态变量、静态代码块执行初始化工作。

 

ClassLoader重要方法

在Java中,ClassLoader是一个抽象类,位于java.lang包中。下面对该类的一些重要接口方法进行介绍:

    •   Class loadClass(String name)
    • name参数指定类装载器需要装载类的名字,必须使用全限定类名,如com.baobaotao. beans.Car。该方法有一个重载方法loadClass(String name ,boolean resolve),resolve参数告诉类装载器是否需要解析该类。在初始化类之前,应考虑进行类解析的工作,但并不是所有的类都需要解析,如果JVM只需要知道该类是否存在或找出该类的超类,那么就不需要进行解析。

    • Class defineClass(String name, byte[] b, int off, int len)
    • 将类文件的字节数组转换成JVM内部的java.lang.Class对象。字节数组可以从本地文件系统、远程网络获取。name为字节数组对应的全限定类名。

    •   Class findSystemClass(String name)
    • 从本地文件系统载入Class文件,如果本地文件系统不存在该Class文件,将抛出ClassNotFoundException异常。该方法是JVM默认使用的装载机制。

    •   Class findLoadedClass(String name)
    • 调用该方法来查看ClassLoader是否已装入某个类。如果已装入,那么返回java.lang.Class对象,否则返回null。如果强行装载已存在的类,将会抛出链接错误。

  •   ClassLoader getParent()
  • 获取类装载器的父装载器,除根装载器外,所有的类装载器都有且仅有一个父装载器,ExtClassLoader的父装载器是根装载器,因为根装载器非Java编写,所以无法获得,将返回null。

 

Java反射机制

Class反射对象描述类语义结构,可以从Class对象中获取构造函数、成员变量、方法类等类元素的反射对象,并以编程的方式通过这些反射对象对目标类对象进行操作。这些反射对象类在java.reflect包中定义,下面是最主要的三个反射类:

    • Constructor:类的构造函数反射类,通过Class#getConstructors()方法可以获得类的所有构造函数反射对象数组。在JDK5.0中,还可以通过getConstructor(Class… parameterTypes)获取拥有特定入参的构造函数反射对象。Constructor的一个主要方法是new Instance(Object[] initargs),通过该方法可以创建一个对象类的实例,相当于new关键字。在JDK5.0中该方法演化为更为灵活的形式:newInstance (Object… initargs)。
    •  Method:类方法的反射类,通过Class#getDeclaredMethods()方法可以获取类的所有方法反射类对象数组Method[]。在JDK5.0中可以通过getDeclaredMethod(String name, Class… parameterTypes)获取特定签名的方法,name为方法名;Class…为方法入参类型列表。Method最主要的方法是invoke(Object obj, Object[] args),obj表示操作的目标对象;args为方法入参。在JDK 5.0中,该方法的形式调整为invoke(Object obj, Object… args)。此外,Method还有很多用于获取类方法更多信息的方法:
    • 1)Class getReturnType():获取方法的返回值类型;

            2)Class[] getParameterTypes():获取方法的入参类型数组;
            3)Class[] getExceptionTypes():获取方法的异常类型数组;
          4)Annotation[][] getParameterAnnotations():获取方法的注解信息,JDK 5.0中的新方法;

  •  Field:类的成员变量的反射类,通过Class#getDeclaredFields()方法可以获取类的成员变量反射对象数组,通过Class#getDeclaredField(String name)则可获取某个特定名称的成员变量反射对象。Field类最主要的方法是set(Object obj, Object value),obj表示操作的目标对象,通过value为目标对象的成员变量设置值。如果成员变量为基础类型,用户可以使用Field类中提供的带类型名的值设置方法,如setBoolean(Object obj, boolean value)、setInt(Object obj, int value)等。

摘录:http://www.iteye.com/topic/1123081

Singleton单例模式

Singleton(单例模式):

单例模式用来保证整个程序中某个实例有且只有一个。

单例模式有两种构建方式:

  • 饿汉方式:用static修饰,在类装载时构建。不适用于初始化时构造器非常耗时的类。
  • 懒汉方式:指全局的单例实例只有在第一次被使用时才构建,延迟初始化。

1.饿汉式单例类

public class TestSingleton
 
    private static TestSingleton instance = new TestSingleton();
 
    //把默认的构造方法设置为私有的,这样外界就无法通过构造方法来创建实例
    private TestSingleton() {}
 
    //静态方法,用来返回类的唯一实例
public static TestSingleton getInstance(){
return instance;
}
}
  • 饿汉模式因为是用static修饰,在类装载时构建,所以会导致加载类时比较慢,如果构造器内的方法比较耗时,则加载过程会比较长。
  • 但饿汉模式是线程安全的,运行时获取对象速度比较快。

2.懒汉式单例类

public class TestSingleton {
    //把默认的构造方法设置为私有的,这样外界就无法通过构造方法来创建实例
    private TestSingleton() {
    }

    private static TestSingleton instance = null;

    //静态方法,用来返回类的唯一实例,因为用synchronized加锁力度太大,所以使用double check的方式。
    public static TestSingleton getInstance() {
        if (instance == null) {
            synchronized (TestSingleton.class) {
                if (instance == null) {

                    //instance在初始化过程中,可能已经不为null,所以加一临时变量t,确保初始化完成后再赋值给instance。
                     TestSingleton t = new TestSingleton();
                     instance = t;
                 }
            }
        }
        return instance;
        }
}

其中instance = new TestSingleton()这句,这并非是一个原子操作,事实上在 JVM 中这句话大概做了下面 3 件事情:

  1. 给 instance 分配内存
  2. 调用TestSingleton 的构造函数来初始化成员变量,形成实例
  3. 将instance对象指向分配的内存空间(执行完这步 instance才是非 null 了)

但是在 JVM 的即时编译器中存在指令重排序的优化。也就是说上面的第二步和第三步的顺序是不能保证的,最终的执行顺序可能是 1-2-3 也可能是 1-3-2。如果是后者,则在 3 执行完毕、2 未执行之前,被线程二抢占了,这时 instance 已经是非 null 了(但却没有初始化),所以线程二会直接返回 instance,然后使用,就会报错,所以我们使用一个临时变量,确保初始化完成后再赋值给instance。

  • 懒汉模式的特点是加载类时比较快,但是在运行时获取对象的速度比较慢,线程不安全, 懒汉式如果在创建实例对象时不加上synchronized则会导致对象的访问不是线程安全的。

ArrayList和Vector ArrayList和LinkedList

ArrayList、Vector和LinkedList都实现了List类。

 

ArrayList和Vector:

  •  Vector的方法都是同步的(Synchronized),是线程安全的(thread-safe),而ArrayList的方法不是;
  • ArrayList性能比Vector好(因为Vector是线程安全的)
  • 两者都是采用的线性连续空间存储元素,但是当Vector或ArrayList中的元素超过它的初始大小时,Vector会将它的容量翻倍,而ArrayList只增加50%的大小。

ArrayList和LinkedList:

  • ArrayList的内部实现是基于内部数组Object[],所以从概念上讲,它更象数组,适合随机访问和遍历;
  • LinkedList是用链表结构存储数据的,很适合数据的动态插入和删除,随机访问和遍历速度比较慢。

Hash(散列)

  • 数组与链表数据结构存储数据的方式:
因为数组删除、插入操作时,同时需要操作后面的元素,所以增、删效率较低,而链表的结构导致了查找的效率较低,我们是否可以构造一个关系,使得待存储的集合{a,b,c,d,e,f}中的任意一个元素通过这个关系都能找到一个指定的索引值,所以现在假设有一种方法f,可以将各个元素和存储位置对应起来,那么这个方法f就是hash函数(散列函数)。

根据上图我们知道原有集合中的元素被散列之后所得到的有序对的集合为{(d,0),(b,2),(c,2),(a,3),(f,4),(e,5)}。其中,散列表中的1号槽为空,表示经过散列函数作用之后,原集合中没有任何元素被散列至该位置,而2号槽中则存在两个元素,将两个不同元素散列至相同位置的情形我们称为碰撞(Collision)。【hashMap中碰撞发生时,用链表进行存储,存储结构中包含key和value,当进行查找时,先查找hashCode找到对应槽(桶?bucket?),然后在链表中根据key进行查找value】

 

  • 常用的散列函数:
    1、除余法
    顾名思义,除余法就是用关键码x除以M(往往取散列表长度),并取余数作为散列地址。除余法几乎是最简单的散列方法,散列函数为: h(x) = x mod M。

 

 

  • 冲突解决的策略

尽管散列函数的目标是使得冲突最少,但实际上冲突是无法避免的。因此,我们必须研究冲突解决策略。冲突解决技术可以分为两类:开散列方法( open hashing,也称为拉链法,separate chaining )和闭散列方法( closed hashing,也称为开地址方法,open addressing )。这两种方法的不同之处在于:开散列法把发生冲突的关键码存储在散列表主表之外,而闭散列法把发生冲突的关键码存储在表中另一个槽内。

开散列方法:

1、拉链法

开散列方法的一种简单形式是把散列表中的每个槽定义为一个链表的表头。散列到一个特定槽的所有记录都放到这个槽的链表中。图9-5说明了一个开散列的散列表,这个表中每一个槽存储一个记录和一个指向链表其余部分的指针。这7个数存储在有11个槽的散列表中,使用的散列函数是h(K) = K mod 11。数的插入顺序是77、7、110、95、14、75和62。有2个值散列到第0个槽,1个值散列到第3个槽,3个值散列到第7个槽,1个值散列到第9个槽。

 

哈希表有多种不同的实现方法,最常用的一种方法就是 拉链法,可以理解为“链表的数组” ,如图:

从上图我们可以发现哈希表是由数组+链表组成的,一个长度为16的数组中,每个元素存储的是一个链表的头结点。那么这些元素是按照什么样的规则存储到数组中呢。一般情况是通过hash(key)%len获得,也就是元素的key的哈希值对数组长度取模得到。比如上述哈希表中,12%16=12,28%16=12,108%16=12,140%16=12。所以12、28、108以及140都存储在数组下标为12的位置。

HashMap其实也是一个线性的数组实现的,所以可以理解为其存储数据的容器就是一个线性数组。这可能让我们很不解,一个线性的数组怎么实现按键值对来存取数据呢?这里HashMap有做一些处理。

首先HashMap里面实现一个静态内部类Entry,其重要的属性有 key , value, next,从属性key,value我们就能很明显的看出来Entry就是HashMap键值对实现的一个基础bean,我们上面说到HashMap的基础就是一个线性数组,这个数组就是Entry[],Map里面的内容都保存在Entry[]里面。

 

2、桶式散列

桶式散列方法的基本思想是把一个文件的记录分为若干存储桶,每个存储桶包含一个或多个页块,一个存储桶内的各页块用指针连接起来,每个页块包含若干记录。散列函数h把关键码值K转换为存储桶号,即h(K)表示具有关键码值K的记录所在的存储桶号。 图9-6表示了一个具有B个存储桶的散列文件组织。有一个存储桶目录表,存放B个指针,每个存储桶一个,每个指针就是所对应存储桶的第一个页块的地址。

有些存储桶仅仅由一个页块组成,如下图中的1号存储桶。有的存储桶由多个页块组成,每一个页块的块头上有一个指向下一个页块的指针,例如,如下图中的第B-1号存储桶由b4,b5,b6三个页块组成,每个存储桶中最后一个页块的头上为空指针。

 

摘录自:http://blog.csdn.net/koself/article/details/7869453

http://blog.csdn.net/jn1158359135/article/details/7205688

http://blog.csdn.net/vking_wang/article/details/14166593

equals和==

  • ==用来比较两个对象的引用(地址)是否相同,用来比较基本数据类型时,用来比较值是否相等。
  • equals用来比较两个引用的对象的值是否相同。

 

  • 默认Object类的equals方法是比较两个对象的地址,跟==的结果一样。
  • 两个对象equals相等,则hashCode一定相等
  • 两个对象equasl不等,但hashCode有可能相等

 

在集合操作的时候有如下规则:

将对象放入到集合中时,首先判断要放入对象的hashcode值与集合中的任意一个元素的hashcode值是否相等,如果不相等直接将该对象放入集合中。如果hashcode值相等,然后再通过equals方法判断要放入对象与集合中的任意一个对象是否相等,如果equals判断不相等,直接将该元素放入到集合中,否则不放入。

 

  • 覆盖equals时总要覆盖hashCode 方法。

HashMap和HashTable

  • HashMap:

public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable

hashmap的键和值都是对象,并且不能包含重复键,但可以包含重复值。HashMap允许null key和null value,但是hashtable不允许。

HashMap是Hashtable的轻量级实现,但是HashMap不是线程安全的,由于非线程安全,效率上可能高于Hashtable。

 

hashMap工作原理:

  • 通过put()方法保存键和值时,先对key调用hashCode()方法,通过返回的hashCode用来找到bucket位置来存储Entry对象(Entry对象类似于链表的节点),而在bucket中存储的是keyvalue,作为map.Entry。
  • 当两个对象的hashCode相同时,bucket位置相同,这时就会发生碰撞,因为HashMap使用链表存储对象,所以会去找是否存在该key,如果存在则替换其value,不存在则将这个Entry存储在链表中。
  • 当用get查找时,会根据key计算hashCode找到对应bucket,然后根据key查找对应的Entry( 找到bucket位置之后,会调用keys.equals()方法去找到链表中正确的节点,最终找到要找的值对象),找到后将value返回。
  • 如果HashMap的大小超过了负载因子(load factor)定义的容量(默认的负载因子大小为0.75),也就是说,当一个map填满了75%的bucket时候,和其它集合类(如ArrayList等)一样,将会创建原来HashMap大小的两倍的bucket数组,来重新调整map的大小,并将原来的对象放入新的bucket数组中。这个过程叫作rehashing。
  • 当重新调整HashMap大小时,在多线程的情况下,可能产生条件竞争(race condition)。因为如果两个线程都发现HashMap需要重新调整大小了,它们会同时试着调整大小。在调整大小的过程中,存储在链表中的元素的次序会反过来,因为移动到新的bucket位置的时候,HashMap并不会将元素放在链表的尾部,而是放在头部,这是为了避免尾部遍历(tail traversing)。如果条件竞争发生了,那么就死循环了。

HashMap源代码:

//初始容量为16
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

//最大容量
static final int MAXIMUM_CAPACITY = 1 << 30;

//负载因子
static final float DEFAULT_LOAD_FACTOR = 0.75f;

因为负载因子为0.75,16*0.75=12,所以当保存第12个元素的时候会进行rehashing,会导致输出的结果顺序有变化。

例子:

for(int i = 0;i<20;i++){
    hashMap.put(i+"", i);
}

Iterator iterator = hashMap.entrySet().iterator();
while(iterator.hasNext()){
    System.out.println(iterator.next());
}

输出结果:

11=11
12=12
13=13
14=14
15=15
16=16
17=17
18=18
19=19
0=0
1=1
2=2
3=3
4=4
5=5
6=6
7=7
8=8
9=9
10=10

总结:Set对每个对象只接受一次,并使用自己内部的排序方法(通常,你只关心某个元素是否属于 Set,而不关心它的顺序–否则应该使用List)。Map同样对每个元素保存一份,但这是基于”键”的,Map也有内置的排序,因而不关心元素添加的 顺序。如果添加元素的顺序对你很重要,应该使用 LinkedHashSet或者LinkedHashMap.

HashMap遍历使用Demo:

HashMap<String,Integer> hashMap = new HashMap<>();
long startTime = System.currentTimeMillis();
for(int i = 0;i<TIME;i++){
    hashMap.put(i+"", i);
}
Iterator<Entry<String,Integer>> iterator = hashMap.entrySet().iterator();
while(iterator.hasNext()){
    Entry<String,Integer> entry = iterator.next();
    System.out.println("key is:"+entry.getKey());
    System.out.println("value is:"+entry.getValue());
}

 

  • HashTable:

public class Hashtable<K,V> extends Dictionary<K,V> implements Map<K,V>, Cloneable, Serializable

HashTable的许多方法都是用synchronized修饰的。

总结:

HashMap 线程不安全 运行有null的键和值 运行速度快 实现了Map接口
HashTable 线程安全 不允许有null的键和值 占用系统资源多,速度稍慢 Dictionary是过时的了,所以在后面的版本也实现了Map接口

 

 

MessageFormat类

java.text.MessageFormat类可以格式化字符串。

public class MessageFormat extends Format

常用方法:

public static String format(String pattern,Object… arguments)这个方法,后面接受可变参数。

其中可以用占位符表示变化部分,占位符的格式为{ ArgumentIndex , FormatType , FormatStyle }。

使用实例:

System.out.println(MessageFormat.format("My name is:{0},age is:{1,number,integer}",new Object[]{"hy",22}));

输出结果:My name is:hy,age is:22

正向代理和反向代理

正向代理

A同学在大众创业、万众创新的大时代背景下开启他的创业之路,目前他遇到的最大的一个问题就是启动资金,于是他决定去找马云爸爸借钱,可想而知,最后碰一鼻子灰回来了,情急之下,他想到一个办法,找关系开后门,经过一番消息打探,原来A同学的大学老师王老师是马云的同学,于是A同学找到王老师,托王老师帮忙去马云那借500万过来,当然最后事成了。不过马云并不知道这钱是A同学借的,马云是借给王老师的,最后由王老师转交给A同学。这里的王老师在这个过程中扮演了一个非常关键的角色,就是代理,也可以说是正向代理,王老师代替A同学办这件事,这个过程中,真正借钱的人是谁,马云是不知道的,这点非常关键。

我们常说的代理也就是只正向代理,正向代理的过程,它隐藏了真实的请求客户端,服务端不知道真实的客户端是谁,客户端请求的服务都被代理服务器代替来请求,科学上网工具 Shadowsocks 扮演的就是典型的正向代理角色。在天朝用浏览器访问 www.google.com 时会被无情的墙掉,要想翻阅这堵墙,你可以在国外用 Shadowsocks 来搭建一台代理服务器,让代理帮我们去请求 www.google.com,代理再把请求响应结果再返回给我。

proxy

反向代理

大家都有过这样的经历,拨打10086 客服电话,一个地区的 10086 客服有几个或者几十个,你永远都不需要关心在电话那头的是哪一个,叫什么,男的,还是女的,漂亮的还是帅气的,你都不关心,你关心的是你的问题能不能得到专业的解答,你只需要拨通了10086 的总机号码,电话那头总会有人会回答你,只是有时慢有时快而已。那么这里的 10086 总机号码就是我们说的反向代理。客户不知道真正提供服务的人是谁。

反向代理隐藏了真实的服务端,当我们访问 www.baidu.com 的时候,就像拨打 10086 一样,背后可能有成千上万台服务器为我们服务,但具体是哪一台,你不知道,也不需要知道,你只需要知道反向代理服务器是谁就好了,www.baidu.com 就是我们的反向代理服务器,反向代理服务器会帮我们把请求转发到提供真实计算的服务器那里去。Nginx 就是性能非常好的反向代理服务器,它可以用来做负载均衡。

reverse-proxy

两者的区别在于代理的对象不一样,「正向代理」代理的对象是客户端,「反向代理」代理的对象是服务端

转载请注明来源:正向代理与反向代理

java序列化与持久化

序列化:

序列化 (Serialization)将对象的状态信息转换为可以存储或传输的形式的过程。

transient

transient是类型修饰符,只能用来修饰字段。在对象序列化的过程中,标记为transient的变量不会被序列化。

当某个字段被声明为transient后,默认序列化机制就会忽略该字段。
public class Person implements Serializable {
private String name;
transient private int age;
private String sex;
}
(保存时没有该字段)
只有实现了Serializable和Externalizable接口的类的对象才能被序列化。Externalizable接口继承自 Serializable接口,实现Externalizable接口的类完全由自身来控制序列化的行为,而仅实现Serializable接口的类可以 采用默认的序列化方式 。

持久化:

持久化是将程序数据在持久状态和瞬时状态间转换的机制。通俗的讲,就是瞬时数据(比如内存中的数据,是不能永久保存的)持久化为持久数据(比如持久化至数据库中,能够长久保存)。
volatile
volatile也是变量修饰符,只能用来修饰变量。volatile修饰的成员变量在每次被线程访问时,都强迫从共享内存中重读该成员变量的值。而且,当成员变量发生变化时,强迫线程将变化值回写到共享内存。这样在任何时刻,两个不同的线程总是看到某个成员变量的同一个值。

Java的内存机制:

Java使用一个主内存来保存变量当前值,而每个线程则有其独立的工作内存。线程访问变量的时候会将变量的值拷贝到自己的工作内存中,这样,当线程对自己工作内存中的变量进行操作之后,就造成了工作内存中的变量拷贝的值与主内存中的变量值不同。

Java语言规范中指出:为了获得最佳速度,允许线程保存共享成员变量的私有拷贝,而且只当线程进入或者离开同步代码块时才与共享成员变量的原始值对比。

这样当多个线程同时与某个对象交互时,就必须要注意到要让线程及时的得到共享成员变量的变化。

而volatile关键字就是提示VM:对于这个成员变量不能保存它的私有拷贝,而应直接与共享成员变量交互。

使用建议:在两个或者更多的线程访问的成员变量上使用volatile。当要访问的变量已在synchronized代码块中,或者为常量时,不必使用。

由于使用volatile屏蔽掉了VM中必要的代码优化,所以在效率上比较低,因此一定在必要时才使用此关键字。