java笔记

2016-10-16  本文已影响332人  jmychou

java笔记第一天


==equals

  1. ==比较的比较的是两个变量的值是否相等,对于引用型变量表示的是两个变量在堆中存储的地址是否相同,即栈中存储的引用地址是否相同。
  2. equals方法
    1.Object的原生方法比较的是是否指向同一引用对象,即栈中的存储的引用地址是否相等,所以和==的结果一样。
    2.而String,Integer、Date等类重写了equals方法,比较的是当a,b是同一类对象,并且属性值相等,即堆中的内容相等,就返回true,并不一定是同一个对象。
Stirng s1="abc";
String s2=new String("abc");

s1 == s2 ;      //false 
s1.equals(s2);    //true
  1. 基本数据类型没有equals方法,byte,short,char,int,long,float,double,boolean

  2. Object本身的equals方法也是用双等号(==)进行比较的,所以比较后的结果跟双等号(==)的结果相同,而String,Integer等类重写了equals方法,所以==比较的结果不一样。

  3. 当使用String str2="abc"时,程序会首先在字符串池中寻找相同的对象,如果已经有一个str1创建, 则str2str1的引用相等。而只有引号内包含文本创建对象才会将创建的对象放入到字符串池,而String str=new String("abc")创建的对象是不放在字符串池中的。

    String str1 = "ab";
    String str2 = "cd";

    String str3 = str1+str2; //这种创建方式是不放入字符串池的,和str4的原理一样

    String str4 = str1+"cd"; //这种创建方式是不放入字符串池的。这里实际上创建一个StringBulider对象
    然后调用StringBulider.append()方法,然后调用StringBulider.toString()方法。所以和和str7不相等(==)



    String str5 = "ab"+str2; //这种创建方式是不放入字符串池的.

    String str6 = "ab"+"cd"; //这种创建方式是放入字符串池的,这种情况实际上是创建了1个对象,即"abcd"1个对象,编译阶段会直接合成一个字符串。

    String str7 = "abcd";
    //str6 与str7都指向了常量池中同一引用地址。

    System.out.println(str6==str7); //返回ture


    final String fstr="ab";
    final String fstr2 = "cd";
    String str8=fstr+"cd";
    Strins str9 = fstr + fstr2;       //此时str9 == str7 也返回true
  对于final字段,编译期直接进行了常量替换,而对于非final字段则是在运行期进行赋值处理的,所以str8和
  str7 指向常量池中相同位置,所以相等(==)

  final修饰的变量,只有定义时指定了初始值,并且被赋值时,只是简单的算术运算和字符串连接,没有访问普通变量,没有调用方法,此时该变量在编译期间就确定值了,类似于宏变量,使用时直接进行常量替换。

  下面这种情况就不相等

  final String str ;   //定义时未赋值,下面在赋值
  str = "ab";
  String str10 = str + "cd";   //str10 == str7 返回false;str此时不会进行常量替换

如果两个string对象,equals()相等,则他们的hashcode()也相等

String s1="abc";  String ss="abc"  //因为java常量池中不会存在两个相同的字符串,所以这两个相同

Sring s2=new String("abc");//或者Sring s2=new String(s1);
此时,s1,s2的堆中的值相等,但栈中的引用值不同,所以(s1==s2)==>false,(s1.equals(s2))==>true

使用new关键字一定会产生一个对象abc,和上面的"abc"不同,同时这个对象存储在堆中。所以上面产生两个对象
一个是存储在栈中的s2,一个是保存在堆中的abc,但是java不存在两个完全一样的字符串对象,所以对象abc
是引用常量字符串中的"abc",即s2-->abc对象--->常量池中的abc(见下方解释)


String s1=new String("abc");
String s2=new String("abc");
//new 出来的两个对象,==比较的是栈中存储的引用地址,肯定不等。
(s1==s2)==>false,(s1.equals(s2))==>true

String s1="abc";
String s2="abc";
(s1==s2)==>true,(s1.equals(s2))==>true

equals() 和hashcode()

  1. equals()相等的两个对象,hashcode()一定相等;
  2. equals()不相等的两个对象,hashcode()可能相等,也可能不等。
  3. 反过来:hashcode()不等,一定能推出equals()也不等;hashcode()相等,equals()可能相等,也可能不等。
  4. 两个对象相比较,hashcode()相当于字典中索引(比如A),而equals()相当于该索引下的单词,(比如AB,AC)
  5. 通过字面量赋值创建字符串时,会优先在常量池中查找是否已经存在相同的字符串,倘若已经存在,栈中的引用直接指向该字符串;倘若不存在,则在常量池中生成一个字符串,再将栈中的引用指向该字符串。
  6. Object的hashcode()方法是根据对象地址计算的(栈地址),所以两个对象的地址不同,hashcode也不同。equals()也比较的是栈地址
  7. String类的hashcode()方法重写了hashcode(),根据字符串的内容来返回hashcode(),所以相同的字符串有相同的hashcode
System.out.println("abc".hashCode());
System.out.println("abc".hashCode());
System.out.println(new String("abc").hashCode());
System.out.println(new String("abc").hashCode());

//以上四个的hashcode都相等
  1. 参考下面集合中判断两个对象相等。
    String s1 = "abc";  
    String s2 = new String("abc");
    s2 = s2.intern(); 此时`s1==s2`返回true,

    `intern()`会先检查字符串池,如果存在,就返回池里的字符串的引用;如果不存在,该方法会 把"abc"添加到字符串池中,然后再返回它的引用。

    当且仅当s1.equals(s2)返回true时, s1.intern()==s2.intern()才返回true
  1. String str=new String("abc") 创建了两个对象,String的构造器:public String(String original) { //other code ... } ,我们正是使用new调用了String类的上面那个构造器方法创建了一个对象,并将它的引用赋值给了str变量。同时我们注意到,被调用的构造器方法接受的参数也是一个String对象,这个对象正是"abc"。一个在常量池中,一个在堆中。

String对象

字符串常用方法

  1. charAt()----返回指定索引处的字符串
  2. concat()--将一个字符串追加到另一个字符串的末尾
  3. equalseIgnoseCase() 判断两个字符串的相等性,忽略大小写
  4. length() 返回字符串中的字符个数
  5. replace()用新字符代替指定的字符
  6. substring() 返回字符串的一部分
  7. toLowerCase()将字符串中的大写字符转换成小写字符返回
  8. toString() 返回字符串的值
  9. toUpperCase() 将字符串中的小写字符转换成大写字符返回
  10. trim() 删除字符串前后的空格
  11. splite() 将字符串按照指定的规则拆分成字符串数组
  12. toCharArray() 字符串转换为一个字符数组
  13. indexOf() 返回字符串第一次出现的索引
//字符串和字符数组互相转换
  String str1 = "hello" ;                  // 定义字符串
  char c[] = str1.toCharArray() ;      // 将一个字符串变为字符数组
 
  String str2 = new String(c) ;  // 将全部的字符数组变为String
  String str3 = new String(c,0,3) ;   // 将部分字符数组变为String
  1. valueOf() 基本类型转换为字符串
valueOf 是静态方法,源码如下:
   public static String valueOf(Object obj) { 
    return (obj == null) ? "null" : obj.toString();
 }

所以使用obj.toString()方法必须不为null,valueOf可以使用null,但返回的是字符串"null"
  1. s1.compareTo(s2),按照字典顺序比较两个字符串 区分大小写;compareToIgnoreCase不区分大小写
s1.compareTo(s2) 相等时返回 0 
不想等是有两种情况 长度不同和对应索引处字符不同 

1. 对应索引的值不同时,则返回此索引处的ascii码差值 即charAt(k) - charAt(k)

2. 长度不同时,则短字符是长字符的开头,返回长度差 即length() - length()
  1. Object toString()

获取系统当前时间 的毫秒数

StringBuffer (线程安全)

StringBuilder (线程不安全) (StringBuffer和Stringbuilder内部使用的是字符串数组存储)

字符串拼接 +, concat, append

虽然编译器对+进行了优化,它是使用StringBuilder的append()方法来进行处理的,但是使用append后使用了
toString(),即 str+='b',等价于 str=new StringBuilder(str).append(b).toString(),所以变慢的原因是
new StringBuilder()和 toString()
源码中最后使用了return new String() ,所以变慢
最后返回的是本身,没有创建新的对象

判断字符串是否为空

null不是对象,""空字符串是对象,所以null没有空间,空字符串有空间
equals()用于比较对象,而null不是对象,所以null用等号=
所以当 str=null,下面比较是错误的
if(str.equals("")||str==null){}             //null不能调用equals方法,所以会报空指针异常

正确的为if(str==null ||str.equals(""))

所以判断字符串为空,首先判断是否为null,所以高效的写法为
if(str==null || str.length()<=0){} 

不为空
if(str1!=null || str.length()!=0){} 

集合 Java的容器类主要由两个接口派生而出:Collection和Map。集合中的元素类型都是Object

Collection接口 Collection是容器层次结构中根接口。而Collections是一个提供一些处理容器类静态方法的类

Collection接口是 Set 、List 和 Queue 接口的父接口,提供了多数集合常用的方法声明,Iterator是Collection的父接口

AbstractCollection实现了Collection接口, AbstractList和AbstractSet都继承于AbstractCollection,并且AbstractList实现了List接口
AbstractSet实现了Set接口。具体的List实现类继承于AbstractList,而Set的实现类则继承于AbstractSet

  1. add(E e) 将指定对象添加到集合中,addAll(Collection c) 将指定集合的所有元素添加进来
  2. remove(Object o) 将指定的对象从集合中移除,移除成功返回true,不成功返回false
  3. contains(Object o) 查看该集合中是否包含指定的对象,包含返回true,不包含返回flase
  4. size()返回集合中存放的对象的个数。返回值为int
  5. clear()移除该集合中的所有对象,清空该集合。
  6. iterator()返回一个包含所有对象的iterator对象,用来循环遍历
  7. toArray()返回一个包含所有对象的数组,类型是Object
  8. toArray(T[] t)返回一个包含所有对象的指定类型的数组
  9. isEmpty() 判断集合是否有元素

Collections 工具类 提供的一些方法

将集合中不安全的集合包装成线程同步的集合

Collection c=Collections.synchronizedCollection(new ArrayList());

List list=Collections.synchronizedList(new ArrayList());

Set s=Collections.synchronizedSet(new HashSet());

Map m=Collections.synchronizedMap(new HashMap());

Iterator接口,Iterator是Collection的父接口,ListIterator专门用于遍历List

  1. boolean hasNext() //是否存在访问的元素
  2. next() //访问下一个元素
  3. void remove() //删除上次访问的元素,必须先访问后执行,如果没调用next(),则不合法
Iterator it=c.iterator();
while(it.hasNext()){
    it.next();
    it.remove();
}
void add(E e)    //将元素插入List
boolean hasPrevious()
E previous()      //返回前一个元素

List接口

List特有方法

List 一个有序的序列,元素可以重复,每一个元素都有一个索引,关心的是索引,与其他集合相比,List特有的就是和索引相关的一些方法:get(int index) 、
set(index,Object elem) 用elem替换指定位置元素,并返回旧元素
add(int index,Object o) 、 indexOf(Object o)第一次出现元素o的位置。

List判断对象相等的标准是 只要通过equals()方法返回true即可
  1. ArrayList 可以将它理解成一个可增长的数组(初始容量是10),顺序存储,它提供快速迭代和快速随机访问的能力。线程不安全,可以存null
继承于AbstractList,实现了List, RandomAccess, Cloneable, java.io.Serializable这些接口。
有个数组成员变量 Object[] elementData 用于保存ArrayList中的元素,当容量超过时,设置新的容量为 (原始容量*3)/2+1 

E set()     //设置位置上的元素,并且返回旧元素
void trimToSize()  //将容量设置为实际元素的个数
void clear()    //清空ArrayList,将全部的元素设为null
Object clone()   //克隆ArrayList,内部是将ArrayList拷贝到一个新的ArrayList中,并返回

//源码 

// 将e添加到ArrayList的指定位置
public void add(int index, E element) {
   if (index > size || index < 0)
       throw new IndexOutOfBoundsException(
       "Index: "+index+", Size: "+size);

   ensureCapacity(size+1);  // Increments modCount!!
   //将数组容量增大一位,然后等价于从index开始数组元素每个后移一位

   System.arraycopy(elementData, index, elementData, index + 1,size - index);    
   elementData[index] = element;    //然后设置index位置新值
   size++;
}

ArrayList遍历

Iterator iter=list.iterator();
while(iter.hasNext()){
  iter.next();
}
int size=list.size();
for(int i=0;i<size;++i){
  list.get(i);
}
for(Interger i : list){
  value=i;
}

ConcurrentModificationException异常 当遍历List的时候同时对其修改(增、删),就会抛出此异常

public class Test {
    public static void main(String[] args)  {
        ArrayList<Integer> list = new ArrayList<Integer>();
        list.add(2);
        Iterator<Integer> iterator = list.iterator();
        while(iterator.hasNext()){
            Integer integer = iterator.next();
            if(integer==2)
                list.remove(integer);
        } 
    }
}

Itr类的成员变量
1. cursor     //表示下一个要访问的元素的索引
2. lastRet   //上一个访问元素的索引

3. exceptedModcount     //表示对ArrayList修改的次数的期望值,初始值为modCount,
modCount是AbstactList的成员变量,ArrayList每次调用add()和remove()时,都会对modCount进行加1

//Iterator的hasNext()方法
public boolean hasNext(){
  return cursor !=size();       //判断下一个访问元素的下标是否等于ArrayList大小,不等则还有元素访问。
}


//Iterator的next()
public E next() {
    checkForComodification();
 try {
    E next = get(cursor);      //通过cursor获得下一个要访问元素的下标
    lastRet = cursor++;        //把cursor赋值给lastRet,然后自增,初始时cursor=0,lastRet=-1,调用一次后cursor=1,lastRet=0
    return next;
 } catch (IndexOutOfBoundsException e) {
    checkForComodification();            //此方法会判断modCount和exceptedModCount是否相等,不相等则抛出异常,
                                         //此时modCount=0,exceptedModCount=0 
    throw new NoSuchElementException();         
 }
}

然后调用ArrayList的remove(),此方法会对modCount加1,size减1,对以上测试程序,对于iterator,exceptedModcount=0,cursor=1,lastRet=0
对于list ,modCount=1,size=0。然后循环再调用hahNext(),此时cursor=1,size=0,所以不相等,继续执行。而再调用iterator的next,会调用
checkForComodification(),此时不相等,就抛出异常了。即调用list.remove()会导致modCount与exceptedModCount不一致。
而迭代器同样有remove()方法,虽然此方法内部调用的list的remove,但是多了一个exceptedModCount=modCount,所以不会报错。所以迭代器中删除元素
需要调用iter.remove()。

fail-fast原理

ArrayList 类似于顺序表,适合随机存取,set和get;而 LinkedList 类似于链表,适合插入和删除,add和remove,只需移动指针即可。

  1. LinkedList 中的元素之间是双链接的,当需要快速插入和删除时LinkedList成为List中的不二选择,元素可以为null。
AbstractSequentialList 实现了随机访问List的一些方法。get(int index)、set(int index,E element)、add(int index,E element)
和remove(int index)

LinkedList本质是双向循环链表 包含两个成员变量  header和size 
header是双向链表的表头,它是双向链表节点所对应的类Entry的实例。Entry是双向循环链表数据结构,Entry包含成员变量 previous,next,element。
previous是当前节点的上一个节点,next是当前节点的下一个节点,element是当前节点所包含的值。

List遍历

使用foreach结构的类对象必须实现了Iterable接口

Vector线程安全,因为都是同步方法

Set接口(内部是map实现的) ,三个实现类都是线程不安全的

Set关心唯一性,它不允许重复。

HashSet 当不希望集合中有重复值,并且不关心元素之间的顺序时可以使用此类。(内部是hashmap)
HashSet不是线程同步的。集合元素可以是null。HashSet判断两个元素相等的标准是equals()相等,并且
hashcode()返回值也相等。这两个有一个不相等,即是不重复。
如果两个对象的hashCode()返回值相等,但是equals()返回false,因为两个对象的hashcode值相同,HashSet将试图将他们保存在同一个位置,只能用链式结构保存对象,但是HashSet是根据hashcode定位元素,这样的话就会导致性能下降

HashSet内部有一个HashMap的成员变量,所有操作都是基于这个HashMap操作的。HashSet存储的实际是HashMap的key,所以不允许有重复元素,元素可以为null。

HashSet内部使用一个静态常量Object类变量PRESENT,作为HashMap的value,所以HashSet中存储的key的value值都为PRESENT

add(E e)方法
public boolean add(E e){
  return map.put(e,PRESENT) == null ;
}

添加成功返回true,否则为false

因为HashMap的put方法时,若已存在key,会用新value替换旧的value,但是key不会改变,并返回旧value,此时HashSet的add()方法便返回FALSE
而没有该key时,便添加该key,并返回null,而此时的add()返回true,添加成功

contains()方法 

public boolean contains(Object o) {
    return map.containsKey(o);        //调用HashMap的containsKey
    }


遍历方法
1. 迭代器遍历
for(Iterator iter=set.iterator();iter.hasNext();){
      iter.next();
}

2.toArray()转换为数组遍历
String[] strArr=(String[])set.toArray(new String[0]);
for(String str:strArr){
  syso(str);
} 

LinkedHashset 当不希望集合中有重复值,并且希望按照元素的插入顺序,有序进行迭代遍历时可采用此类。

TreeSet 使用树结构实现(红黑树)当不希望集合中有重复值,并且希望按照元素的自然顺序进行排序时可以采用此类。(自然顺序意思是某种和插入顺序无关,而是和元素本身的内容和特质有关的排序方式,譬如“abc”排在“abd”前面。)TreeSet判断对象相等标准是 两个对象通过compareTo(Object obj)方法比较是否返回0(内部是TreeMap)

Queue接口

 Queue接口继承Collection接口,用于保存将要执行的任务列表。一种队列则是双端队列,支持在头、尾两端插入和移除元素,主要包括:ArrayDeque、LinkedBlockingDeque、LinkedList。另一种是阻塞式队列,队列满了以后再插入元素则会抛出异常,主要包括ArrayBlockQueue、PriorityBlockingQueue、LinkedBlockingQueue。

LinkedList 同样实现了Queue接口,可以实现先进先出的队列。
PriorityQueue 用来创建自然排序的优先级队列。

Deque<E>接口

Deque接口是双端队列接口,继承自Queue接口。可以再两头插入和删除,所以相比Quene多了一些在两端操作的方法。比如addFirst(),addLast(),peekLast(),pollLast()等等

LinkedList 同样实现了Deque的接口

Map接口

Map是一个键值对的集合。也就是说,一个映射不能包含重复的键,每个键最多映射到一个值。关心的是唯一的标识符。他将唯一的键映射到某个元素。当然键和值都是对象。键不能重复,值可以重复。

HashMap,TreeMap继承于AbstractMap,AbstractMap实现了Map接口,Hashtable虽然继承于Dictionary,但实现了Map接口

HashMap 当需要键值对表示,又不关心顺序时可采用HashMap,HashMap中元素的排列顺序不固定,HashMap可以接受为null的键值(key)和值(value),而Hashtable则不行)。

Hashtable 注意Hashtable中的t是小写的,它是HashMap的线程安全版本,现在已经很少使用。

LinkedHashMap 是HashMap的子类,定义了当前对象的上一个和下一个引用,在hash表基础上形成双向循环链表,当需要键值对,并且关心插入顺序时可采用它。

LinkeedHashMap默认采用的按照put()顺序排序,新添加元素都会添加指链表末尾。
 public LinkedHashMap(int initialCapacity,float loadFactor,boolean accessOrder)此构造方法可以设置按照访问顺序排序,即最近访问的会
 从原位置删除,添加至链表末尾。所以第一个节点是最少访问的节点。

LinkedHashMap采用双向循环链表将所有Entry连接起来,这样遍历时,就不用循环table数组,只要遍历循环链表即可。

1.添加元素 

LinkedHashMap并没有重写HashMap的put方法,而是重写了若key存在时调用的recordAccess方法,和key不存在直接插入的addEntry()和createEntry()方法。当按照访问顺序排序时,这几个重写方法会使最近访问的元素移至链表末尾。所以就算不使用get(),再次put()已经put()过的元素,也会使顺序改变。并且如果实现LRU算法时,要重写removeEldestEntry()方法,该方法在addEntry()中被调用,意味着超过一定大小时,就将最近未访问的节点删除掉,即第一个节点

2. 访问元素 
LinkedHashMap重写了get()方法,调用该方法时,如果是按照访问顺序排序,也会调用recordAccess()方法,也会将元素移至链表末尾,改变顺序。

3. LinkedHashMap实现LRU
首先,当accessOrder为true时,才会开启按访问顺序排序的模式,才能用来实现LRU算法。我们可以看到,无论是put方法还是get方法,都会导致目标Entry成为最近访问的Entry,因此便把该Entry加入到了双向链表的末尾(get方法通过调用recordAccess方法来实现,put方法在覆盖已有key的情况下,也是通过调用recordAccess方法来实现,在插入新的Entry时,则是通过createEntry中的addBefore方法来实现),这样便把最近使用了的Entry放入到了双向链表的后面,多次操作后,双向链表前面的Entry便是最近没有使用的,这样当节点个数满的时候,删除的最前面的Entry(head后面的那个Entry)便是最近最少使用的Entry。

TreeMap 当需要键值对,并关心元素的自然排序时可采用它,继承于AbstractMap,且实现了NavigableMap接口因此,TreeMap中的内容是“有序的键值对。

在Map 中插入、删除和定位元素,HashMap是最好的选择。但如果您要按自然顺序或自定义顺序遍历键,那么TreeMap会更好。TreeMap的key必须实现Comparable接口,并且key是同一类的对象

Map接口的常用方法如下表所示:

put(K key, V value) 向集合中添加指定的键值对,若已存在则覆盖,并返回旧值,若键值之前不存在,则返回null
putAll(Map t) 把一个Map中的所有键值对添加到该集合
containsKey(Object key) 如果包含该键,则返回true
containsValue(Object value) 如果包含该值,则返回true,通过判断equals()返回true
get(Object key) 根据键,返回相应的值对象
keySet() 将该集合中的所有键以Set集合形式返回
values() 将该集合中所有的值以Collection形式返回
remove(Object key) 如果存在指定的键,则移除该键值对,返回键所对应的值,如果不存在则返回null
clear() 移除Map中的所有键值对,或者说就是清空集合
isEmpty() 查看Map中是否存在键值对
size() 查看集合中包含键值对的个数,返回int类型
Set entrySet() 返回键值对视图

entrySet 返回key-value的Set<Map.Entry<k,v>>集合
keySet 返回key的Set集合
values 返回value的Collection集合

HashMap

  1. HashMap key 和 value都可以为null
  2. HashMap有一个Entry的内部类,是hash表的数据结构,用来存储key-value对
  3. HashMap 有一个叫做table的Entry数组的成员变量,数组的索引作为hash插在的索引值,并且指向了存储key-value的链表
  4. key的hashcode()方法用来寻找Entry对象所在的bucket
  5. 如果两个key有相同的hash值,则他们会放在一个桶里,并且按照头插法插入链表
  6. key的equal()方法来判断两个就算key相同,是否为同一元素,从而确保key的唯一性
  7. value对象的hashcode()和equals()并没有什么作用
//成员变量 
1. table 一个Entry[]数组,Entry是一个单向链表,哈希表的key-value对都存储在Entry数组中
2. size  HashMap的大小,保存key-value键值对的数量
3. threshold  用于判断是否需要调整HashMap的容量。threshold的值="容量*加载因子",当HashMap中存储数据的数量达到threshold时,就需要将HashMap的容量加倍。
4. loadFactor就是加载因子。 
5. modCount是用来实现fail-fast机制的。

构造函数

  1. HashMap中有两个重要的参数,Capacity和负载因子(Load factor),Capacity就是table的大小,默认为16(2的倍数);loadFactor默认值为0.75;
    还有一个阈值(threshold),等于Capacity * loadFactor,所以默认为12,当存储容量超过阈值,就调整table大小为当前大小的2倍
HashMap有4个默认构造函数,此时会初始化table数组
1. 
// 默认构造函数。
 public HashMap() {
 }

2. 
// 指定“容量大小”和“加载因子”的构造函数
public HashMap(int initialCapacity, float loadFactor) {

//最大容量为2^30,超过这个容量,将被这个最大值替换。并且容量必须是2的倍数,就算不是,也会扩容至2的倍数
//比如设置初始值为17,会扩容32;初始值为15,会扩容16

3. public HashMap(Map<? extends K, ? extends V> m) {
  // 使用迭代法,将形参的元素加入到HashMap中

  for (Iterator<? extends Map.Entry<? extends K, ? extends V>> i = m.entrySet().iterator(); i.hasNext(); ) {
            Map.Entry<? extends K, ? extends V> e = i.next();
             putForCreate(e.getKey(), e.getValue());
         }
}

put(key,value)方法

1. 若key为null,则调用putForNullkey(value),遍历table,若存在key为null,则用 新value 替换 旧value,返回旧value。否则key为null的hash
值为0,存放再table[0]中,返回

2. 若key不为null,则计算key的hash值,通过hash值得到key在table中的索引

3. 遍历此索引下table[index]的链表,如果发现此链表中存在 键值的hash值和key的hash值相等,并且 key值相等(==比较相等,即栈地址相等) 或者key.equals(k)返回true,
即已存在key对应的值,则用value替换旧value,并返回旧value,保证key的唯一性

4. 若不存在key对应的值,在插入之前调用addEntry()先判断HashMap中size大小,如果size大于threshold,则扩容table.length*2两倍。
然后调用createEntry(),新建一个key-value节点,按照头插法插入链表。然后返回null

put()时涉及到的问题 hash算法 扩容

1. 先调用hash()方法,此方法根据key的hashcode进行二次hash,防止hash冲突

2. 根据hash()得到的hash值,然后调用indexFor()计算在table中的索引。
 static int indexFor(int h, int length) {
        return h & (length-1);
    }

此处的 h & (length-1) 等价于 h % length,但是 & 比 % 更高效。因为length总是等于2^n。
因为偶数的最低位为0,奇数的最低位为1。假设length不是偶数,假设为15,length-1=14;这样在和14进行&运算的时候,最低位永远是0,那么0001、0011、0101、0111、1001、1011、1101、1111位置处是不可能存储数据的,空间减少,进一步增加碰撞几率,这样就会导致查询速度慢 

而当lenth为偶数,假设为16,length-1=15=1111; 2^n-1 得到的数二进制低位全为1, 在进行&时,得到的值总是和原来的hash值相同,这样的话只有hash
值相同的,才会放入同一个链表中,不同的hash值发生碰撞的几率较小,提高查询效率。

所以对于任意对象,只要它的hashCode()值相等,则hash()方法计算出来的hash码值也相等,而在计算索引时,hash值相等的会存入同一个链表。
所以当两个对象(key)的hashCode()值相等,它们就会被存入同一个链表中。
1.8以下版本,1.8加入了红黑树 

1. 调用resize(newCapacity),传入新的容量,然后会初始化一个新的大Entry数组,Entry[] newTable = new Entry[newCapacity]; 

2. 调用transfer(newTable)方法,传入新数组。遍历旧的table数组,加入新的数组中

for (int j = 0; j < src.length; j++) { //遍历旧的Entry数组
   Entry<K,V> e = src[j];             //取得旧Entry数组的每个元素
   if (e != null) {
       src[j] = null;//释放旧Entry数组的对象引用(for循环后,旧的Entry数组不再引用任何对象)
       do {
           Entry<K,V> next = e.next;
           int i = indexFor(e.hash, newCapacity); //!!重新计算每个元素在数组中的位置
           e.next = newTable[i]; //头插法插入新的链表
           newTable[i] = e;      //将元素放在数组上
           e = next;             //访问下一个Entry链上的元素
       } while (e != null);
   }
}

因为重新放入新数组中也是使用头插法,所以之前顺序为s1->s2->s3,扩容之后就为s3->s2->s1 

3. 再将table指向新的newTable,修改阈值

//扩容的过程中,在多线程中链表出现循环链,可能造成死循环,详见印象笔记

get(key)方法

1. 若key为null,调用getForNullKey()方法,在table[0]处查找,找到则返回value,否则返回null

2. 若key不为null,则调用hash(key.hashcode())得到key的hash码,然后调用indexFor(),计算在table中的索引,然后遍历索引下的单链表,
若有元素和key的hash值相同,并且key值相等或者key.equals(k)返回true,则找到,返回value,否则返回null

remove(key) 删除键值为key的元素

1. 调用removeEntryForKey(key),若key为null,则key的hash值为0,否则调用hash()计算hash码,然后调用indexFor()计算索引,遍历该索引下的
链表。若为第一个元素,则table[i]指向下一个元素,否则就是执行单链表的删除,并返回该Entry对象。

2. 若该Entry对象为null,则未找到该元素,返回null,否则返回删除的value

使用自定义对象作为HashMap的key,一定要重写hashCode()和equals(),否则该key在外部被改变,hashCode()即被改变,再get()会返回null

1. 重写hashCode()是为了对同一个key,能得到相同的hashCode(),就可以定位到相应的key上

2. 重写equals()是为了向HashMap表明当前对象和key上所保存的对象是相等的,这样才真正地获得了这个key所对应的这个键值对。

containsKey(key)

1. public boolean containsKey(Object key) {
    return getEntry(key) != null;
}

2. 调用getEntry(),getEntry()作用就是返回返回“键为key”的键值对。HashMap将“key为null”的元素存储在table[0]位置,“key不为null”的则调用hash()计算哈希值,然后计算索引,如果存在key对应的元素,则返回该Entry对象,否则返回null

containsValue(value)

1. 若value为null,则调用containsNullValue(),containsNullValue()两层循环每个索引下的链表,判断是否有value=null的,若有则返回true,否则返回null。

2. 若value不为null,也是双重循环遍历,判断value.equals(e.value),若有则返回true,否则返回null。

Set<Map.Entry<k,v>> entryset values() keySet()三者类似

1. 内部有一个实现Iterator接口的类HashIterator(),当我们通过entrySet()获取到的Iterator的next()方法去遍历HashMap时,实际上调用的是 此类的nextEntry()方法。而nextEntry()的实现方式,先遍历Entry(根据Entry在table中的序号,从小到大的遍历);然后对每个Entry(即每个单向链表)逐个遍历

Map遍历
Map遍历有总计四种方式,方法有两种:(Entry是Map的静态内部类)

  1. 一类是基于map的Entry;map.entrySet();
  2. 一类是基于map的key;map.keySet()
    访问方式也有两种:
  3. 利用迭代器Iterator
  4. 利用foreach循环

HashMap和Hashtbale区别

集合中判断两个对象相等

  1. 判断两个对象的hashCode是否相等
    如果不相等,认为两个对象也不相等,完毕
    如果相等,转入2)
    (这一点只是为了提高存储效率而要求的,其实理论上没有也可以,但如果没有,实际使用时效率会大大降低,所以我们这里将其做为必需的。)
  2. 判断两个对象用equals运算是否相等
    如果不相等,认为两个对象也不相等
    如果相等,认为两个对象相等(equals()是判断两个对象是否相等的关键)


    hashCode方法的存在是为了减少equals方法的调用次数,从而提高程序效率

Comparable 与 Comparator

HashMap按照value排序

Map<String,Interger> map = new HashMap<>();
List<Map.entry<String,Interger>> li = new ArrayList<>(map.entrySet());

Collections.sort(l, new Comparator<Map.Entry<String, Integer>>() {    
    public int compare(Map.Entry<String, Integer> o1, Map.Entry<String, Integer> o2) {    
          return (o2.getValue() - o1.getValue());    
      }    
}); 

数组

  1. System.arraycopy(srcArray,srcPos,destArray,desPos,int length)数组拷贝
srcArray - 源数组。
srcPos - 源数组中的起始位置。
destArray - 目标数组。
destPos - 目标数据中的起始位置。
length - 要复制的数组元素的数量。 
  1. Arrays.copyOf() //拷贝数组
int []a={1,2,3,4,5};

int [] b=Arrays.copyOf(a,a.length);

如果第二个变量大于源数组的长度,则超出值默认为0
  1. Arrays.asList()
Arrays.asList() 返回一个List,但是该List不支持add和remove操作,其内部返回一个ArrayList对象
但是此ArrayList并不是 java.util.ArrayList, 而是Arrays的内部类。

Arrays.asList()最好传递的是引用类型,并且使用此方法后,数组就和链表链接在一起,修改其中
任何一个,另外一个也会变化。

Stack<Interger> sta=new Stack<Interger>();
1. public boolean empty()       //栈是否为空,因为栈实现了Collection接口,所以可用isEmpty()

2. public E pop()              //出栈

3. public E push(E item)       //进栈

4. public E peek()             //查看栈顶元素,但不移除

5. int search(Object o )       //返回对象在栈中的位置,以1为基数

java正则表达式

  1. String 类中的正则方法
1. boolean matches(reger)               //验证字符串是否匹配
str.matches("\/w+");

2. String replaceAll(regex,replacement)     //用replacement替换全部匹配的字符,replace没有正则参数

     //去掉前后空格方法一 
    String regex = "^\\[(.*)\\]$"; 
    String s1 = str.replaceAll(regex, "$1"); 
    //方法二,注:replace方法无正则匹配 
    String regex = "^\\[|\\]$"; 
    String s1 = str.replaceAll(regex, ""); 

3. String[] split()   //根据正则才拆分字符串,结果为一个字符串数组

 String str = "asfasf.sdfsaf.sdfsdfas.asdfasfdasfd.wrqwrwqer.asfsafasf.safgfdgdsg";
        String[] strs = str.split("\\.");
        for (String s : strs){
            System.out.println(s);
        } 
  1. 正则类

Pattern主要方法

boolean matches = Pattern.matches(pattern, text);    //静态方法
String patternString = "sep";

Pattern pattern = Pattern.compile(patternString);

String[] split = pattern.split(text);

System.out.println("split.length = " + split.length);

for(String element : split){
    System.out.println("element = " + element);
}

Mathcher用法

     String text = "This order was placed for QT3000! OK?";
      String pattern = "(.*)(\\d+)(.*)";

      // 创建 Pattern 对象
      Pattern pat = Pattern.compile(pattern);

      // 现在创建 matcher 对象
      Matcher mat = pat.matcher(text);
1. boolean matches = mat.matches();           //检测结果是否匹配,等同于以上两个方法


2. 查找所有匹配到的结果

String text    =
        "This is the text which is to be searched " +
        "for occurrences of the word 'is'.";
String patternString = "is";
Pattern pattern = Pattern.compile(patternString);      //先创建Pattern对象
Matcher matcher = pattern.matcher(text);               //用Pattern对象方法创建Matcher对象
int count = 0;
while(matcher.find()) {
    count++;
    System.out.println("found: " + count + " : "  + matcher.start() + " - " + matcher.end());
}

find() 方法返回第一个是否匹配,之后每次调用 find() 都会返回下一个。

start()和end()返回每次匹配的字串在整个文本中的开始和结束位置。end返回的是字符串末尾的后一位,多一位

3. group获得分组结果

String text    =  "John writes about this, and John writes about that," +
                        " and John writes about everything. "  ;
String patternString1 = "(John)";
Pattern pattern = Pattern.compile(patternString1);
Matcher matcher = pattern.matcher(text);
while(matcher.find()) {
    System.out.println("found: " + matcher.group());    //因为只有一个分组
}

//多个分组
String text    =
          "John writes about this, and John Doe writes about that," +
                  " and John Wayne writes about everything."
        ;
String patternString1 = "(John) (.+?) ";
Pattern pattern = Pattern.compile(patternString1);
Matcher matcher = pattern.matcher(text);
while(matcher.find()) {
    System.out.println("found: " + matcher.group(1) +
                       " "       + matcher.group(2));
}

3. 替换,等价于String类的replaceAll()方法

replaceAll(replacement) 和 replaceFirst() 方法可以用于替换Matcher搜索字符串中的一部分。replaceAll() 方法替换全部匹配的正则表达式,replaceFirst() 只替换第一个匹配的。

在处理之前,Matcher 会先重置。所以这里的匹配表达式从文本开头开始计算。即后面继续替换,也是从头开始

String text    =
          "John writes about this, and John Doe writes about that," +
                  " and John Wayne writes about everything."
        ;
String patternString1 = "((John) (.+?)) ";
Pattern pattern = Pattern.compile(patternString1);
Matcher matcher = pattern.matcher(text);

String replaceAll = matcher.replaceAll("Joe Blocks ");
System.out.println("replaceAll   = " + replaceAll);
String text="abcd";
String pattern="abc" ,此时使用以上三个方法,都为false,因为不是完全全部匹配
while(m.find()) { 
     System.out.println(m.group());   //m.group()等价于m.group(0)返回整个匹配结果 
     System.out.print("start:"+m.start()); 
     System.out.println(" end:"+m.end()); 
} 
String text="BBC ABCDAB ABCDABCDABDE";
String pattern="(AB)CD";
while(mat.find()) {
            System.out.println(mat.groupCount());   //返回1
            System.out.println(mat.group(1));
            System.out.println("found: " + count + " : "  + mat.start() + "-" + mat.end());
        }


mat.group()会返回 所有ABCD 
mat.group(1)返回所有AB

try..catch

异常

throws用来抛出异常类,用在方法上 void test() throws IOException{}。throws可以抛出多个异常类,用,分开
使用throws抛出异常就无需try..catch。然后一个方法throws异常,则调用该方法时,要么放在try..catch中,要么放在另一个throws方法中。

当有子类方法重写父类的throws异常时,子类抛出的异常必须是父类抛出异常的子类或者相同
void test(){
  throw new IOException();
}

如果throw抛出的异常,不是RuntimeException异常,则该throw语句必须要么处在try块里,显示捕获异常;要么放在一个带throws方法里,由调用者处理。

面向对象

  1. 类的初始化过程
class Father{
    int i=5;
     // 静态变量
     public static String p_StaticField = "父类--静态变量";
     // 变量
     public String p_Field = "父类--变量";
    
     // 静态初始化块
     static {
      System.out.println(p_StaticField);
      System.out.println("父类--静态初始化块");
     }
     // 初始化块
     {
      System.out.println(p_Field);
      System.out.println("父类--初始化块");
     }
     //父类构造方法
    Father(){
        System.out.println("Father"+this.i);
        test();
    }
    public void test(){
        System.out.println("father test"+this.i);
    }
}
public class Test extends Father {

    int i=50;
    
    // 静态变量
    public static String staticField = "子类静态变量";
    // 变量
    public String field = "子类变量";
    // 静态初始化块
    static {
    System.out.println(staticField);
    System.out.println("子类静态初始化块");
    }
    // 初始化块
    {
    System.out.println(field);
    System.out.println("初始化块");
    }
    
    //子类构造方法
    Test() {
    System.out.println("Son"+this.i);
    }
    public void test(){
        System.out.println("son test"+this.i);
    }

    public static void main(String[] args) {
       
        System.out.println("在我之前是啥");
        Test tt=new Test();
    }

}


//输出为

父类--静态变量
父类--静态初始化块
子类静态变量
子类静态初始化块

在我之前是啥
-----------
父类--变量
父类--初始化块
Father5
son test0               //此步为子类实例化父类,后面说明
子类变量
子类初始化块
Father fa=new Son();

当子类实例化父类对象时,该对象只能调用父类中定义的方法和属性。
因为属性不能重写,所以该对象访问的对象是父类的
因为静态方法也不能重写,所以访问的也是父类的,因为静态方法是类在加载时就被加载到内存中的方法,在整个运行过程中保持不变,因而不能重写。
非静态方法是在对象实例化时才单独申请内存空间,为每一个实例分配独立的运行内存,因而可以重写


因为非静态方法可以被重写(包括抽象方法),所以当调用方法时,原则是调用父类的方法,但是被子类重写了,就会调用子类的方法。产生多态或者动态绑定。 如果此方法没有被重写,则只会调用父类方法,不会调用子类独有方法。

重写原则 两同两小一大
1. 方法名相同,参数类型相同
2. 子类返回类型小于等于父类方法返回类型
3. 子类抛出异常小于等于父类抛出异常
4. 子类访问权限大于等于父类访问权限

class Father(){
    int i=5; 
    Father(){ 
        syso("father "+i);
        test();
    }

    pulic void test(){ 
        syso("father test"+this.i).
    }
}

class Son(){ 
    int i=50;

    Son(){ 
        syso("son "+this.i);
    }

    public void test(){ 
        syso("Son test"+this.i); 
    }

    pulic static void main(String[] args){ 
       Father fa=new Son();
       fa.i;
    }
}
// father 5
son test 0 
son 50 
5

//先调用父类构造方法,父类构造方法中,调用了test(),但是子类重写了此方法,所以调用的为子类的方法(多态),但是此时还没调用子类的构造方法,所以子类中i的值为0。
fa.i 调用父类的属性。

A) 接口没有提供构造方法(初始化块),抽象类中可以有构造方法(初始化块),接口是特殊的抽象类,接口和抽象类都不能实例化
B) 接口中的方法默认使用public、abstract修饰,因此不要企图使用proteced或是private修饰符去修饰方法,同时由于接口中的方法都是abstract的所以方法不能有具体实现,即方法后不能有{},抽象类中可以有实现过的方法。接口不能定义静态方法,抽象类可以定义静态方法。
C) 接口中的属性默认使用public、static、final修饰,因此不要企图使用proteced或是private修饰符去修饰属性,同时由于接口中的属性是public static final的那么定义属性时必须初始化,否则会报错。抽象类即可以定义普通成员变量也可以定义静态常量
D) 接口可以多继承

接口与实现类之间也存在多态关系,即可以用接口声明变量,然后实现类指向接口变量
多态三个条件 
1. 继承
2. 重写
3. 父类引用指向子类对象
Interger ina=2;
Interger inb=2;
syso(ina == inb)       //true 

Interger ina=128;
Interger inb=128;
syso(ina == inb)     //false 

单例类

class Singleton{
    private static Singleton instance;  //使用当前类变量来缓存是否已创建实例,已创建则直接返回

    private Singleton(){}     //构造器使用private修饰,隐藏该构造器

    //提供一个静态方法,用于返回实例,并且保证只返回一个对象

    public static Singleton getInstance(){
        //如果instance为null,说明还没创建过该对象
        //如果不为null,表明已经创建该对象,直接返回该对象
        if(instance == null){
            instance =new Singleton();
        }
        return instance;
    }
}

public class SingletTest{
    public static void main(){
        //创建对象不能通过构造器,只能通过类静态方法创建
        Singleton s1=Singleton.getInstance();

        Singleton s2=Singleton.getInstance();
        syso(s2 == s2);         //会输出true,因为只会产生一个对象
    }
}

//上面是懒汉式,多线程调用会产生多个实例。下面是饿汉式
class Singleton{
    //static final修饰,类加载时就初始化
  private static final Singleton instance=new Singleton();

  private Singleton(){}
  public static Singleton getInstance(){
    return instance;
  }
}

//静态内部类 懒加载       因为静态内部类不会在类加载时加载
public class Singleton{ 
    private Singleton(){}
    private static class SingletonHolder{ 
        private static Singleton instance = new Singleton();     
    }
    public static getInstance(){ 
        return SingletonHolder.instance;
    }
}

final final修饰的变量必须显式初始化

static 和 final

  1. final修饰局部变量 可以在定义是指定默认值,也可以随后赋值,但只能赋值一次,修饰形参时,不能在方法中修改
public void test(final int a){
    a=5;   //错误,不能修改final形参
}
  1. final修饰成员变量
  1. final 修饰基本类型和引用类型的区别
final int[] arr={1,2,3,4};

Arrays.sort(arr);           //可以进行排序
arr[3]=5;       //可以进行赋值

arr=null;       //重新赋值,非法

fianl Person p=new Person();

p.setName();    //可以重新设置属性值


final String str1="abc" + 123;

final String str2="abc"+String.valueOf(123);

str2 调用了String类的方法,所以无法在编译时确定值,所以str1不等于str2
final String str1="abc" + 123;

final String str2="abc"+String.valueOf(123);

str2 调用了String类的方法,所以无法在编译时确定值,所以str1不等于str2


String s1="abcd";
String s2="ab" + "cd";

s1 == s2  //true 

String s4="ab";
String s5="cd";

String s3=s4 + s5; 
s3==s1  //false      s3无法在编译时确定值,s4和s5只是普通变量,不回进行常亮替换,s4和s5加上final修饰符,s3即等于s1

缓存类

class CacheImmutale{
    private  static int MAX_SIZE=10;
    //使用数组缓存已有的实例
    private  static CacheImmutale[] cache =new CacheImmutale[MAX_SIZE];

    private static int pos=0;  //记录缓存实例在缓存中的位置,cache[pos-1]是最新缓存的实例
    private final String name;

    private CacheImmutale(Strin name){//构造方法私有
        this.name=name;
    } 

    public String getName(){
        return name;
    }

    public static CacheImmutale valueOf(String name){
        //遍历已缓存的对象
        for(int i=0;i<MAX_SIZE;++i){
            //如果已有实例,则直接返回
            if(cache[i]!=null && cache[i].getName().equals(name)){
                return cache[i];
            }
        }

        if(pos==MAX_SIZE){     //如果缓存池已满
            cache[0]=new CacheImmutale(name);   //覆盖第一个对象
            pos=1;
        }else{
            cache[pos++]=new CacheImmutale(name);     //新创建的对象缓存起来
        }
    }

    public boolean equals(Object obj){
        if(this==obj){
            return true;
        }

        if(obj!=null && obj.getClass() == CacheImmutale.class){
            CacheImmutale ci=(CacheImmutale)obj;
            return this.name.equals(ci.getName());
        }
        return false;
    }

    public int hashcode(){
        return name.hashcode();
    }
}

CacheImmutale c1=CacheImmutale.valueOf("hello");
CacheImmutale c2=CacheImmutale.valueOf("hello");

syso(c1 == c2);     //true

Interger类就采用额上述相同的策略,如果采用new 创建Integer对象每次都返回新的对象,而采用valueOf()方法,则会缓存该对象

Interger i1=new Interger(6);
Interger i2=Interger.valueOf(6);

Interger i3=Interger.valueOf(6);

syso(i1 == i2)    //false 
syso(i2 == i3)    //true 


Interger i2=Interger.valueOf(128);

Interger i3=Interger.valueOf(128);
syso(i2 == i3)    //false        由于Integer值缓存-128~127之间的值,所以超过此范围不缓存

抽象类

内部类

1. 静态内部类可以定义静态成员和非静态成员变量

2. 只能访问外部静态成员。外部类也不能直接访问静态内部类成员,必须使用静态内部类
类名或者内部类对象访问

3. 静态内部类 的外部类就像是一个包一样,在不是外部内的其他类中可以直接使用静态内部类定义对象
只要导入该静态内部类。或者使用 outClass.StaticInnerClass tt = new outClass.StaticInnerClass()

public class OutClass{ 
    //非静态内部类 
    public class InnerClass{} 

    //静态内部类 

    public static StaInnerClass{}
}

class Test{ 
    main(){ 
        OutClass.InnerClass obj1 = new OutClass().new InnerClass();   //非静态内部类对象

        OutClass.StaInnerClass obj1 = new OutClass.StaInnerClass();    //静态内部类
    }
}

匿名内部类

public class Test {
    public static void main(String[] args)  {
         
    }
     
    public void test(final int b) {
        final int a = 10;
        new Thread(){
            public void run() {
                System.out.println(a);
                System.out.println(b);
            };
        }.start();
    }
}

a的值在编译期间就可以确定,则直接在匿名内部类中创建一个拷贝;而b的值无法在编译期间确定,则通过构造函数传参的方式进行初始化。为了防止数据不一致性,所以加了final.

因为方法的生命周期和方法内部类对象的生命周期不一致。方法中的局部变量,方法结束后就释放掉,内部类就找不到了。加上final的话,内部类就会拷贝一份到内部类中,值就不会改变。

创建静态内部类对象的一般形式为:  外部类类名.内部类类名 xxx = new 外部类类名.内部类类名()

创建成员内部类对象的一般形式为:  外部类类名.内部类类名 xxx = 外部类对象名.new 内部类类名()

JVM

内存区域 包括程序计数器,Java虚拟机栈,本地方法栈,Java堆,方法区

  1. 程序计数器

每条线程都有一个程序计数器,各线程之间的计数器互不影响,因此该区域是私有的,并且不会有内存溢出异常。

  1. 虚拟机栈

2.1 局部变量表
存放方法参数和方法内部定义的局部变量。局部变量表所需的内存空间在编译期间完成分配,运行期间不会改变。

  1. Java堆 Java堆和方法区是线程共享的
  1. 方法区

对象实例化

每个class文件的前四个字节成为魔数,作用是确定这个文件能否被虚拟机接受

接下来的是文件版本号,5,6字节是次版本号,7,8字节是主版本号

类加载机制

  1. 类加载包括 加载 -> 链接(验证,准备,解析) ->初始化 ->使用 -> 卸载。
    其中加载、验证、准备和初始化四个阶段是顺序开始,不一定顺序完成。解析阶段顺序不确定,因为存在动态绑定,可能在初始化之前,也可能在初始化之后
绑定  指的是一个方法的调用与方法所在的类关联起来

静态绑定 编译器间绑定   final,static,private和构造方法是静态绑定 

动态绑定  运行时绑定,大部分方法都是后期绑定
  1. 加载
    将class文件加载到内存,在堆中生成一个java.lang.Class对象,作为方法区数据的访问入口。
类加载器从上到下分为 启动类加载器,扩展类加载器,应用程序类加载器和自定义类加载器。

即使两个类源于同一个class文件,只要类加载器不同,这两个类也不相等。

1. 启动类加载器  负责加载JDK\jre\lib,c++实现,其他几个类加载器都是Java实现 

2. 扩展类加载器  负责加载JDK\jre\ext目录,

3. 应用程序类加载器 负责加载ClassPath,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。

这些类加载器上面的都是下面的父类,但是并不是通过继承实现的,而是通过双亲委派模型实现的,即如果一个类收到了类加载请求,首先不会自己加载这个类,而是委托给父类加载,依次向上。只有当父类加载器无法完成加载时,子类才会去自己加载。这样可以保证加载的安全,比如你自己定义了一个String,Object类,是无法使用过,因为上层加载器会先加载系统的类,
自定义的类就不会被加载。
  1. 准备
public static int value = 3 ;

value在准备阶段的值为0,而不是3,赋值为3是调用类构造器<clinit>()所以赋值为3是在初始化阶段执行的。

public static final int value = 3; //编译期间值已经放入常量池中
而static final 修饰的变量,必须显示的赋值,在编译期间值已经确定,所以在准备阶段值也为3
  1. 解析 将常量池中的符号引用转换为直接引用得过程 包括四种解析
字段解析时会现在本类中寻找,会现在本类中寻找,若未找到,然后再在接口和父接口中寻找,然后再到父类,祖类中寻找。此处有子类直接引用父类定义的静态字段,只会触发父类的静态初始化块,不会触发子类的初始化块。

Super{ 
 static int m=100;
 static{ 

 }
}


Father extends Super{ 
 static int m=10;
 static{ 

 }
}

Son extends Father{ 
 static int m=1;
 static{ 

 }
}

Class{ 
  main(){ 
     Son.m    
  }
}

//Son不注释m,则三个静态块都会打印,注释Son的,会打印Super和Father的
//注释Father的,只会打印Super的 

并且实现的接口和父类中若定义相同的变量,编译器会出错
  1. 初始化
1. 静态字段只有直接定义这个字段的类才会被初始化,因此,如果父类直接引用父类特有,而自己没有的静态字段,只会触发父类的初始化而不会触发子类的初始化。

2. static final 字段在编译已经存入常量池中,所以也不会调用静态初始化块。
但是父类的还会调用。

3. 类数组定义,也不会触发初始化块。

main (){ 
 ClassName[] arr = new ClassName[10];       //此时数组中的元素只有一个引用,还未初始化

 for(ClassName a: arr
    )
    { 
       a = new ClassName();    //加上此句,就会触发初始化
    }
}
1. <clinit>() 执行时,静态块和静态字段是按照顺序执行的,静态语句块只能访问到它之前定义的静态变量,其之后定义的变量,只能赋值,不能访问,输出其值。

2. <clinit>() 会默认先执行父类的 <clinit>() 方法

3. 如果没有静态块和静态变量 <clinit>()可以不执行

4. 执行接口的<clinit>()方法不需要先执行父接口的<clinit>()方法,只有当父接口中定义的变量被使用时,父接口才会被初始化。另外,接口的实现类在初始化时也一样不会执行接口的<clinit>()方法。

垃圾回收

  1. 对象的三种状态
  1. 强制垃圾回收
强制系统回收有两种方式

1. System.gc()

2. Runtime.getRuntime.gc()

在垃圾回收之前,会调用finalize()进行清理资源,finalize()有如下特点
1. 永远不要主动调用某个对象的finalize()方法,该方法应该交给垃圾回收机制调用
2. finalize()何时被调用,是否被调用,具有不确定性,不一定会执行
3. 当JVM执行finalize(),可能使该对象或者系统中的其他对象变为可达状态
4. 当JVM执行finalize()出现异常时,垃圾回收机制不会报告异常,程序继续执行。

垃圾对象的判定

  1. 引用计数法
给对象添加引用计数器,有引用指向它,计数器加1,引用失效,计数器减1,当引用为0时,即被回收。但是无法处理对象相互循环引用的问题。

public Class{ 
    private Object obj;
    public void test(){ 
       Test t1 = new Test();
       Test t2 = new Test();

       //循环引用

       t1.obj = t2;
       t2.obj = t1;

        t1 = null;
        t2 = null ;     
        //将t1,t2置为null,即t1,t2指向的对象不能再访问,但是它们的引用计数器不为0,无法GC
    }
}
  1. 根搜素算法
将可以作为GC ROOT的对象作为起始点,从此点开始搜索,搜索和该节点有直接引用和间接引用的关系的对象,将这些对象以链的形式组合起来,形成一个图。然后不在这张关系网中的节点,就会被回收。即使这些回收节点之间有关联。

将可以作为GC ROOT的对象有: 
1. 虚拟机栈中引用的对象
2. 本地方法栈中引用的对象
3. 方法区中静态属性引用的对象
4. 方法区中常量引用的对象

在根搜索算法中,回收一个对象,会进行两次标记。第一次标记是不在关系网中的对象,第二次的就是判断该对象是否重写了finalize()方法,如果没有实现就直接回收,如果实现了就会先将该对象放入一个队列中,然后线程会创建一个低优先级的线程去执行finalize()方法,但是finalize()方法只能执行一次,如果在finalize()方法中,该对象重新加入关系网,就复活,否则就回收。

垃圾收集算法

  1. 标记--清除算法
此算法就是采用根搜索算法进行标记,标记完成后,再扫描整个空间中未被标记的对象,进行回收。

优点:不需要进行对象移动,只对不活动的对象进行处理

缺点:直接回收不存活的对象,所以会造成空间碎片,当再次需要分配一个较大的对象而需要连续的空间时,就会再次触发垃圾收集。
  1. 复制算法 适合于新生代
复制算法是标记-清除算法的改进。它将内存分为大小相等的两块,每次只使用其中的一块,当一块的用完后,就将还存活的对象复制到另一块内存上面,然后把使用过的内存一次性清理掉。

优点:每次只对一块内存进行回收,运行高效。不会存在碎片。

缺点: 每次只能分配一半的内存。
  1. 标记--整理算法 适合于老年代
此算法的标记过程同标记-清除的算法一样,只是对标记的垃圾对象处理不同。它不是直接对垃圾对象进行清理,而是将存活的对象向一端移动,然后清楚边界外的内存。

优点:解决了内存碎片的问题
缺点:多了对象的移动,成本加大。
  1. 分代收集算法 不同对象的生命周期不一样,所以采用分代收集算法

Java堆可以分为新生代,老年代,永久代

所有新生成的对象都放在新生代,新生代的生命周期比较短,新生代采用复制算法。

新生代内存按照8:1:1的比例分为一个Eden区和两个Survivior区。对象会在Eden区创建。
回收时先将Eden区的存活对象复制到一个Survivir0区,然后清空Eden区,当Survivir0区也存满了,则将Eden区和Survivir0区存活对象复制到另一个Survivir1区,然后清空Eden区和Survivir0区。

此时Survivir0区是空的,就将Survivir0区 和Survivir1区交换,即保持Survivir1区为空,如此反复。当Survivir1区不足以存放Eden区和Survivir0区的存活对象时,就将存活对象直接放到老年代。

新生代的对象在进行一次复制回收(Minor GC)的时候,年龄就会增加一岁,当岁数到一定年龄(默认15),就会进入老年区。
老年代都是生命周期比较长的对象。
老年代采用标记-整理方法收集,老年代默认是占用了68%就会触发Full GC 
用于存放静态文件,如Java类,方法等,对GC没有显著影响

四种引用方式

  1. 强引用(StrongReference)
new创建的对象就是强引用,就算内存不足,也不会回收该对象类解决内存不足。
  1. 软引用(SoftReference)
软引用通过SoftReference类实现,对于只有软引用的对象,当系统内存空间足够时,不会被系统回收,程序也可以使用该对象。当内存不足时,系统可能回收它。
软引用通常位于内存敏感程序中,适用于缓存
  1. 弱引用(WeakReference)
弱引用通过WeakReference类实现。对于只有弱引用的对象而言,不管内存是否足够,总会回收该对象。
  1. 虚引用(PhantomReference)
虚引用通过PhantomReference类实现, 虚引用和没用引用的效果大致相同。虚引用不能单独使用,必须和引用队列(ReferenceQueue)使用
虚引用无法获取它所引用的对象,所以get输出null

上面三个引用都有一个get()方法,获取被引用的对象

泛型

public class Test<T>{
  Test(){}

  Test(T info){      //构造函数还是原来的
  
  }
}

Test<String> t1=new Test<String>();
syso(li.getClass() == l2.getClass())     //返回true
静态方法,静态初始化块,静态变量的声明不能使用泛型

static T info ;    //错误
public static void test(T aa){}     //错误

类型通配符

  1. 如果Foo是Bar的子类型,G是一种带泛型的类型,则G<Foo>不是G<Bar>的子类型,所以当一个方法的形参是
    各种类型,就需要通配符。 比如List<String> 不是List<Object>的子类
    List<?> 表示是各种泛型List的父类。
List<?> c=new ArrayList<String>();  
c.add(new Object()); //compile time error,不管加入什么对象都出错,除了null外。  
c.add(null); //OK  
  1. 类型通配符的上限 只代表某一类型泛型的父类
  1. 类型通配符的下限 <? super Type> 代表必须是Type本身,或是Type的父类

  2. 如果你想从一个数据类型里获取数据,使用 ? extends 通配符
    如果你想把对象写入一个数据结构里,使用 ? super 通配符
    如果你既想存,又想取,那就别用通配符

线程 进程是资源分配的基本单位,线程是CPU调度的基本单位

  1. 线程创建
class Runner2 extends Thread{      //继承Thread类创建线程
    public void run(){
        for(int i=0;i<50;++i){
            System.out.println("Runner :  "+i);
        }
    }
}

Runner2 r=new Runner2();
r.start();
class Runner1 implements Runnable{      //实现Runnable接口创建线程
   private  int i=0;     //此时多个线程共享一个实例变量
    public void run(){    //线程执行体
        //int i=0;      //若是局部变量的话,每个线程都有一个局部变量的拷贝,所以互不影响
        for(;i<10000;++i){
            System.out.println(Thread.currentThread().getName()+" " +i);
        }
    }
}

Runner1 r=new Runner1();
Thread th=new Thread(r);
th.start();

Runnable对象仅仅作为Thread对象的target,Runnable实现类里包含的run()方法仅仅作为线程执行体,
而实际的线程对象依然是Thread实例,多个线程共享一个target对象

线程状态

线程结束的三个原因 
1. run()执行完毕,线程正常结束

2. 线程抛出一个未捕获的异常或者Errror

3.直接调用线程的Stop()方法(不建议使用,容易死锁)

控制线程

  1. join() 当在某个程序中调用join(),调用线程将被阻塞 直到被join()方法加入的join线程执行完毕
  1. sleep() 暂停线程执行,进入阻塞状态

  2. yield() 线程让步,让当前线程暂停一下,但是不会阻塞该进程,只是进入就绪状态。让系统的线程调度器重新调度一次
    所以当某个线程调用了yield(),有可能线程调度器又将其调度出来使用。当某个线程调用了yield方法,只有
    优先级与当前线程相同或者高的线程才会获得执行机会。


sleep()和yield()的区别

  1. 线程优先级
Thread类的优先级的静态常量  
MAX_PRIORITY   //值为10 
MIN_PRIORITY   //值为1
NORM_PRIORITY   //值为5  默认优先级

死锁

  1. 死锁的四个必要条件
1. 互斥条件   一个资源每次只能被一个进程使用
2. 请求与保持   一个进程隐请求资源而阻塞时,对已经获得的资源保持不放
3. 不剥夺   进程已获得资源,在未使用之前,不能强行剥夺
4. 循环等待   若干进程之间形成一种头尾相接的循环等待资源关系

只要有一个不满足,就不会死锁

线程同步

  1. synchronized
public synchronized void foo(){
  xxxxx
}
public void foo(){
  synchronized(this){
    xxxxxx
  }
}

synchronized代码块this指代调用此方法的当前对象,可以换成其他对象,就成为其他对象的同步锁

  1. synchronized 基本规则
class MyRunable implements Runnable{
  public void run(){
    synchronized(this){
      try{
            for(xxx){
              Thread.sleep(100);
              syso(xxx);
            }
        }catch(InterruptedException e){

        }
    }
  }
}

Runnable rn=new MyRunable();
Thread t1=new Thread(rn,"t1");
Thread t2=new Thread(rn,"t2");

t1.start();
t2.start();              //此时t1,t2 访问的都是rn对象的同步方法,所以会阻塞打印

-------------------------------
class MyThread extend Thread{
  MyThread(name){
      super(name);
  }

  public void run(){
    synchronized(this){
      xxxxx 
    }
  }
}

Thread t1=new MyThread(t1);
Thread t2=new MyThread(t2);

t1.start();
t2.start();                 //因为t1,t2是不同的对象,调用的方法是不同对象的同步方法,所以不会阻塞,会轮流打印
  1. 实例锁和全局锁
public class Test{
  synchronized void MethodA();
  synchronized void MethodB();

  static synchronized classMethodA();
  static synchronized classMethodB();

}

Lock

Lock lock = new ReentranLock();

与synchronzied区别

线程通信 三个方法都属于object,因为这三个方法都依赖于同步锁,而同步锁是是对象持有

volatile

  1. 并发编程三个概念
x = 10;         //语句1
y = x;          //语句2
x++;            //语句3
x = x + 1;      //语句4

只有语句1是原子操作,其他都不是
语句2 包括两个操作,首先读取x的值,然后讲x的值写入工作内存

语句3和4 包括两个操作 读取x的值,然后进行加1操作,写入新的值

java中对long、double类型的读分为两部分,先读前32位,然后读剩余32位
Voliate可以保证对long的读写是原子性的
  1. 主内存和工作内存
  1. volatile

线程安全集合 并发类

  1. List
实现了List接口,因此是个队列;包含了成员变量lock,实现对CopyOnWriteArrayList的互斥访问

CopyOnWriteArrayList 采用volatile数组来保持数据。执行读操作时,直接操作集合本身,无需加锁与阻塞。当执行写入操作,包括(add,remove,set)等方法,因为底层采用的是,复制一份新的数组,然后对数组操作,所以写操作比较麻烦,适合读操作大于写操作,比如缓存。修改时通过volatile数组使读到的
数据总是最新的;修改时会先获取互斥锁,修改完毕后,再将数据更新到volatile数组,然后再释放锁,实现线程安全

CopyOnWrite容器是一种读写分离的容器,读的时候不需要加锁,如果有多个线程同时向ArrayList添加数据,读的还是读的旧的数据,因为写的时候不会锁住旧的
ArrayList。只能保证数据的最终一致性,不能保证数据的实时一致性。

1.getArray()和 setArray(Object[] a)分别用来获取和设置volatile数组的值。CopyOnWriteArrayList的构造函数都会调用setArray()。

2. add(E e)
在添加操作开始前,获取互斥锁,此时若有其他线程要获取锁,则必须等待。然后新建一个数组,将原数组拷贝到新数组中,新数组length=原数组length+1,多出来
的为即存放添加的元素。然后调用setArray()将新数组的元素赋值给原volatile数组。然后释放互斥锁。

在执行写操作的时候,内存里会同时驻扎两个对象的内存,旧的对象和新的对象
(在复制的时候只是复制容器里的引用,只是在写的时候会创建新对象添加到新容器里,而旧容器的对象还在使用,所以有两份对象内存)

3. get(index)    获取volatile数组的index元素

4.先获取互斥锁,然后复制原数组,如果删除的是最后一个元素,则直接拷贝最后一个元素之前的元素 然后赋值到原数组;若不是最后一个元素,则将index之前和之
后的元素拷贝到新数组,然后赋值给volatile数组

5. 遍历
CopyOnWriteArrayList的iterator返回COWIterator对象,COWIterator实现了ListIterator接口,但是不支持修改元素的操作,对于remove(),set(),add()等操作,COWIterator都会抛出异常!并且迭代时不会抛出ConcurrentModificationException异常。
  1. Set
  1. Map
ConcurrentHashMap数据结构是由Segment数组和HashEntry数组,而HashEntry其实是一个链表。Segment是ConcurrentHashMap的内部类,此内部类里包含了一个HashEntry数组,HashEntry也是ConcurrentHashMap的内部类,存储的是键值对,类似HashMap的Entry。所以Segment类似于HashMap
ConcurrentHashMap类似于由Segment数组构成。

Segment继承与ReentrantLock,所以可以实现锁机制。包含了一个HashEntry数组table;count变量是table数组包含的HashEntry对象的个数。

HashEntry的key,hash,next域被申明为final,value被声明为volatile,所以HashEntry插入时只能头插入

- 初始化
默认情况下,Segment的数组长度为16,Segment中默认为2,且必须为2^n 

- get(key)
get过程不需要加锁,因为get使用到的变量都是volatile,比如用于统计当前Segment大小的count,以及HashEntry的value。因为volatie字段的写入操作
先于读操作,所以get读到的是最新的值。找到则返回value,否则返回null。 ConcurrentHashMap不允许key和value为null,因为get时读到value为null值
则可能是另一个线程修改了,此时需要加锁,重新获取。

- put(key,value) 只锁定某个segment,不是整个ConcurrentHashMap
value为null时,会抛出异常。加锁,定位到Segment,定位到HashEntry。如果key-value已存在则覆盖旧值,返回旧值;若不存在,则新建节点,添加到链表的头部
返回null。然后解锁。如果超过容量时,扩容是对当前Segment扩容。

- remove(key)
先加锁,将key之后的元素连接到新链表,然后讲key之前的元素克隆到新链表中,按照头插法插入,所以顺序相反。但是此时原链表并没有变化,所以读线程不会受到
干扰。

- 统计size
因为统计size的过程中,个数可能会变化。所以ConcurrentHashMap先尝试几次不加锁的方法统计每个Segment的大小,若在统计的过程中,数量发生变化,则再
采用加锁的方式统计。在putremove和clean方法里操作元素前都会将变量modCount进行加1,那么在统计size前后比较modCount是否发生变化,从而得知容器的大小是否发生变化。


ConcurrentHashMap 的高并发性主要来自于三个方面:

1.用分离锁实现多个线程间的更深层次的共享访问。
2.用 HashEntery 对象的不变性来降低执行读操作的线程在遍历链表期间对加锁的需求。
3.通过对同一个 Volatile 变量的写 / 读访问,协调不同线程间读 / 写操作的内存可见性。

ConcurrentHashMap 和 Hashtable区别

  1. Queue BlockingQueue阻塞队列,主要作为线程同步的工具,
    在队列为空时,消费者的线程会等待队列变为非空。当队列满时,生产者线程会等待队列可用。所以常用于生产者消费者场景

BlockingQueue有两个阻塞方法

队尾插入元素 add(e) offer(e) put(e) offer(e,time,unit)

队头删除元素 remove(e) poll() take() poll(time,unit)

获取、不删除元素 element() peek() --- ----

文件

  1. File
String path = '';
File f = new File(path);      //读取一个文件


//File 常用方法
f.createNewFile()   //创建文件

f.getAbsolutePath()   //文件绝对路径

f.exists()  //文件是否存在
f.isDirectory()  //是否为目录
f.isFile() //是否为文件
f.length() //文件长度
f.lastModified()  //最后修改时间 

// 以字符串数组的形式,返回当前文件夹下的所有文件(不包含子文件及子文件夹)
        f.list();
 
        // 以文件数组的形式,返回当前文件夹下的所有文件(不包含子文件及子文件夹)
        File[]fs= f.listFiles();
 
        // 以字符串形式返回获取所在文件夹
        f.getParent();
 
        // 以文件形式返回获取所在文件夹
        f.getParentFile();

        /a/b/c 

        // 创建文件夹,如果a b 文件夹不存在,创建失败
        f.mkdir();
 
        // 创建多级文件夹,如果a b 文件夹不存在,都会创建
        f.mkdirs();
 
        // 创建一个空文件,如果父文件夹不存在,就会抛出异常
        f.createNewFile();
        // 所以创建一个空文件之前,通常都会创建父目录
        f.getParentFile().mkdirs();
 
        // 列出所有的盘符c: d: e: 等等
        f.listRoots();
 
        // 刪除文件
        f.delete();
 
        // JVM结束的时候,刪除文件,常用于临时文件的删除
        f.deleteOnExit();
  1. 字节流 常用读取二进制文件,如图片、影像等
    File f = new File();
    FileInputStream fis = new FileInputStream(f);
    byte[] all = new byte[(int)f.length()];
    fis.read(all);   //从输入流中读取数据 ,一次读多个字节       
    for (byte bt:all
         ) {
        System.out.println(bt);
    }
    fis.close();


 try {  
            System.out.println("以字节为单位读取文件内容,一次读一个字节:");  
             
            in = new FileInputStream(file);  
            int tempbyte;  
            while ((tempbyte = in.read()) != -1) {    // 一次读一个字节 
                System.out.write(tempbyte);  
            }  
            in.close();  
        } catch (IOException e) {  
            e.printStackTrace();  
            return;  
        } 
byte[] data={};
File f = new File();
FileOutputStream fos = new FileOutputStream(f);       //输出流输出文件时,若文件不存在则会创建文件,文件存在会被覆盖,目录不存在会抛出异常

FileOutputStream fo = new FileOutputStream(f,true);  //第二个参数设置为true时,也表示为追加文件

fos.write(data);
File f = new File();

FileInputStream fis = null;  //流的声明在try外面,否则finally读取不到

try{
  fis = new FileInputStream(f);
  ...
}catch(IOException e){

}finally{
  if(null != fis){   //关闭之前,判断是否实例化
      try{
          fis.close();
      }catch(IOException e){
        e.prinStackTrace();
      }
  }
}

上面的关闭方式为try-with-resources。而所有的流都实现了一个接口叫做AutoCloseable,任何类实现了这个接口,都可以在try()中进行实例化。 并且在try, catch, finally结束的时候自动关闭,回收相关资源。

 try (FileInputStream fis = new FileInputStream(f)) {
            byte[] all = new byte[(int) f.length()];
            fis.read(all);
            for (byte b : all) {
                System.out.println(b);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

  1. 字符流 常用于读取普通文件,文本数字等
 try(FileReader fr = new FileReader(f)){  //字符流读入
            char[] res = new char[(int)f.length()];
            fr.read(res);  //读入一个字符数组
            for (char ch:res
                 ) {
                System.out.println(ch);
            }

        }catch (IOException e){
            e.printStackTrace();
        }
// 打开一个写文件器,构造函数中的第二个参数true表示以追加形式写文件      
FileWriter writer = new FileWriter(fileName, true);                     


 try(FileWriter fw = new FileWriter(f2)){
            String s = "abcde";
            fw.write(s);   //可以写入一个字符数组或者字符串
        }catch (IOException e){
            e.printStackTrace();
        }
  1. 缓存流

        try{
            FileReader fr = new FileReader(f);
            BufferedReader bf = new BufferedReader(fr);  //缓冲读入流,必须建立在一个已存在的流基础上
            String line;
            while((line = bf.readLine()) != null){ //一次读取一行
                System.out.println(line);
            }
            br.close();
            fr.close();     //先关闭缓存流,再关闭文件流
        }catch (IOException e){
            e.printStackTrace();
        }
  try{
            FileWriter fw = new FileWriter(f2);
            BufferedWriter bw = new BufferedWriter(fw);  //缓存流建立在一个已存在的流的基础上
            bw.write("aaa");
            bw.newLine();      //BufferedWriter 写入时不会自动换行,需要手动添加
            bw.write("bbbb");
            bw.close();
            fw.close();
        }catch (IOException e){
            e.printStackTrace();
        }


而PrintWriter可以自动换行
  try{
            FileWriter fw = new FileWriter(f2);
            PrintWriter pw = new PrintWriter(fw);
            pw.println("aaa");
            pw.close();
            fw.close();
        }catch (IOException e){
            e.printStackTrace();
        }
File f = new File();
FileInputStream fi = new FileInputStream(f);
InputStreamReader inReader = new InputStreamReader(fi);
BufferedReader br = new BufferedReader(inReader);


 BufferedWriter out = new BufferedWriter(new OutputStreamWriter(                        
                    new FileOutputStream(file, true)));

网络TCP

1. 指定服务器地址和端口,建立Socket
2. 获取输出流,把数据变成字节数组,通过输出流发送给服务端
3. 关闭输出流,获取输入流,获取反馈信息
4. 关闭资源

import java.io.*
import java.net.*

public void main(){
        Socket s = null;
        try{
            if(s == null){
                s = new Socket("xxx",13000);
                byte[] buf = "abcdef".getBytes();  //把数据转换成字节数组

                OutputStream out = s.getOutputStream();  //获取输出流
                out.write(buf);  //发送数据
                s.shutdownOutput(); //关闭发送流

                InputStream in = s.getInputStream();  //获取输入流,获取反馈信息
                byte[] buffer = new byte[1024];
                int len = in.read(buffer);

                System.out.println(new String(buffer,0,len)); //
            }
        }catch (Exception e){
                e.printStackTrace();
        }finally {
            if(s!=null){
                try{
                    s.close();
                }catch (Exception e){
                    
                }
            }
        }

}
1. 建立服务ServerSocket服务,然后,用ServerSocket的accept()方法得到socket服务
2. 获取输入流,然后可以得到数据
3. 反馈信息给客户端
4. 关闭资源

InetAddress ia = InetAddress.getByName("www.baidu.com");  //获得域名的IP
        String ip = ia.getHostAddress();
        System.out.println(ip);

        ia = InetAddress.getLocalHost();
        ip = ia.getHostAddress();
        System.out.println(ip);

        ServerSocket ss = new ServerSocket(13000);
        Socket s = ss.accept();
        String clientip = s.getInetAddress().getHostAddress();  //获取IP地址

        InputStream in = s.getInputStream();
        byte[] buf = new byte[1024];

        int len = 0;
        while((len = in.read(buf))!= -1){
            System.out.println(new String(buf,0,len));
        }
        s.shutdownInput();

        /*
        输入流转换为字符流
        BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream()));
        while((info = br.readLine()) != null){
          syso(info);
        }
        */
        
        OutputStream out = s.getOutputStream();   //发送反馈信息
        out.write("server has get".getBytes());

        /*
        输出流转字符流
        PrintWriter pw = new PrintWriter(s.getOutputStream,true);
        pw.println("hello world");
        */
        
        s.close();
        ss.close();

反射

  1. 获取类对象 以下三种方式都可以获取类对象 一中类只有一个类对象 此时类的静态属性也会被加载
  1. 反射创建一个对象
1. 先获取类对象
2. 通过类对象获取该类的构造器对象
3. 再通过一个构造器创建一个对象

        Class c1 = Class.forName(name);
  
        //反射机制创建对象
        Constructor c = c1.getConstructor();  //获取构造器,这中是无参的构造方法,可以传入类对象,调用其他构造方法
        Hello h = (Hello)c.newInstance();    //获取实例
        h.setI(1000);
        System.out.println(h.getI());
  1. 获取对象字段
Class c1 = Class.forName(name);
  
        //反射机制创建对象
        Constructor c = c1.getConstructor();  //获取构造器
        Hello h = (Hello)c.newInstance();    //获取实例
        
        Filed f1 = c1.getDeclaredField("name");  //获取name字段
 
        f1.set(h,"jmy"); //设置字段值
        System.out.println(h.getI());


getFiled()只能获取public字段,包括从父类继承来的字段

getDeclaredField()可以获取本类的所有字段,包括private的,但是不能获取继承来的字段,虽然可以获取private字段,但是无法访问该值,可以使用
f1.setAccessible(true)来暴力反射私有字段。
  1. 调用方法
Class c1 = Class.forName(name);
  
        //反射机制创建对象
        Constructor c = c1.getConstructor();  //获取构造器
        Hello h = (Hello)c.newInstance();    //获取实例
        
        //第一个参数为要调用的方法名,第二个是该方法的参数类型
        //第二类型参数是类对象,如果是对象就使用.class ,比如String.class, test.class 
        //如果是基本类型,就是用int.class  等价于Integer.TYPE, 其他基本类型类似
        Method m = c1.getMethod("setI",Integer.TYPE);   
        m.invoke(h,200);  //调用该方法
        System.out.println(h.getI());
  1. 反射作用
反射在Spring的IOC(控制反转,依赖注入)中有很大作用。如果不用反射,当调用业务方法一时,编写调用业务方法1的代码, 
当调用业务方法二时,编写调用业务方法1的代码。当使用反射,把类的名称和要调用的方法写入配置文件即可,当需要调用其他类时,只要修改
配置文件中的类名和方法名即可,而不用修改代码。

比如配置文件
class=reflection.Service1
method=doService1

读取配置文件,进行调用

        File config = new File("xx.txt");
        Properties pro = new Properties();
        pro.load(new FileInputStream(config));
        String className =(String)pro.get("class");
        String methodName = (String)pro.get("method");

        Class cas = Class.forName(className);  //根据类名称获取类对象
        Method mt = cas.getMethod(methodName); //根据方法名称,获取方法
        Constructor ct = cas.getConstructor(); //获取构造器

        Object obj = ct.newInstance();  //根据构造器,实例化对象
        mt.invoke(obj); //调用对象指定方法

上一篇下一篇

猜你喜欢

热点阅读