java高级篇

高级篇 复习:

多线程

线程-创建使用

//方式一 继承Thread 
class A extends Thread{
	 @Override
    public void run() {
        System.out.println("继承Thread 重写Run");
    }
}
//方式二 实现 Runnable 接口类
class B implements Runnable{
 @Override
    public void run() {
        System.out.println("实现 Runnable 重写Run");
    }
} 

//运行
public static void main(Strin[] args){
	//继承Thread
	A a = new A();
	a.start();
	//实现Runable
	new Thread(new B()).start();
} 
/**
说明:
Thread 源码中 也是实现的Runnable 接口,并且内部有个属性存储构造函数传入的Runnable 对象
上面的两种方式推荐使用Runnable接口实现的方式 因为 一般场景中多线程的使用往往伴随着线程安全,
就会需要共享锁,Thread 需要单独维护一个静态变量来 持有锁,Runable 缺不需要下面会写个例子来说明。
**/

方法说明

  • start() -对象方法 调用线程进入就绪状态,等待获取 cup 执行权
  • run() -重写该方法,内容是线程具体需要执行 任务或者业务
  • currentThread() -静态方法 获取当前线程
  • sleep(long time) -静态方法 当前线程进入阻塞,时间结束后自动恢复就绪状态
  • yield() -对象方法,释放当前线程对cup的执行权
  • join() -对象方法,指定某线程作为当前线程首要完成的线程,举例: 线程A 的run()方法中 调用B.join() 这个时候就必须等B 执行完成才能继续执行。
  • get/setName() -对象方法 设置获取/设置线程昵称
  • isAlive() -判断线程是否存货,源码根据stat字段属性来判定
  • setDaemon() -设置当前线程为守护线程
    提示:如果直接调用run 那么java 并不会创建一个线程来执行,而是当作普通的方法调用来执行。

线程-守护线程

简单来说,一个java 程序的运行,最少有三个线程:main、异常、GC 。这三个就是守护线程,开发人员之际编写的线程默认就是用户线程,当程序中没有用户线程的时候守护线程就会消亡 “兔死狗烹,鸟尽弓藏”

线程-生命周期

查看源码中会发现 Thread 有一个枚举属性 State,记录这当前线程的生命状态:NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED

  • NEW -创建新线程
  • RUNNABLE -调用start()方法进入就绪状态
  • BLOCKED -阻塞
  • WAITING -等待,等待cup 的执行权
  • TIMED_WAITING -超时等待,等待的同时设置了超时时间
  • TERMINATED -死亡。

线程寿命周期流程图(:标记了 线程进/出 阻塞状态条件)
在这里插入图片描述

线程-同步

最早期解决线程同步的方式有两种(synchronized):同步代码块、同步方法。在jdk5之后又新出了一种方式:Lock

下面用 ‘车票’ 举例

synchronized(同步代码块、同步方法)、Lock

//实现Runnable 
class A implements Runnable{
	private static int asum = 100; 
	private ReentrantLock lock = new ReentrantLock();
	public void run(){
		while(asum  > 0){ 
			//方式一:同步代码块
			synchronized(this){//因为是多个线程公用一个runner所以,this对象本身可以作为线程的共享锁。 
            Thread.sleep(10);//线程随眠10毫秒,这段代码只是为了提高数据出错的机率
            if (asum > 0)
                System.out.println(Thread.currentThread().getName() + ":票:" + (asum --));
           } 
           //方式二:同步方法
           show();
	
		   //方式三:Lock 锁
		    try {
                lock.lock();//手动持有锁
                Thread.sleep(10);//这段代码只是为了提高数据出错的机率
                if (sum > 0)
                    System.out.println(Thread.currentThread().getName() + ":票:" + (sum--));
            } catch (Exception e) {

            } finally {
                lock.unlock();//手动关闭锁
            }
		}
	}
	private synchronized void show(){//默认this
	  		Thread.sleep(10);//线程随眠10毫秒,这段代码只是为了提高数据出错的机率
            if (asum > 0)
                System.out.println(Thread.currentThread().getName() + ":票:" + (asum --)); 
	}
}

//继承Thread 
class B extends Thread{
	private static int bsum = 100;
	//private static Object o = new Object();
	private static  ReentrantLock lock = new ReentrantLock(); //需要设置成static 
	public void run(){
		while(bsum > 0){
		    //方式一:同步代码块
			//与Runnable 不同,继承Thread 需要new 多个线程,所以不能使用This 作为锁,需要单独指定静态变量或者A.class 这种唯一的数据才能作为锁
			//synchronized(Object ){
			synchronized(A.class){ 
            Thread.sleep(10);//线程随眠10毫秒,这段代码只是为了提高数据出错的机率
            if (bsum > 0)
                System.out.println(Thread.currentThread().getName() + ":票:" + (bsum --));
           } 

 		   //方式二:同步方法
           show();
           
             //方式三:Lock 锁
		    try {
                lock.lock();//手动持有锁
                Thread.sleep(10);//这段代码只是为了提高数据出错的机率
                if (sum > 0)
                    System.out.println(Thread.currentThread().getName() + ":票:" + (sum--));
            } catch (Exception e) {

            } finally {
                lock.unlock();//手动关闭锁
            }
		}
	}
	private static synchronized void show(){//同理Thread 不能使用隐氏的this。所以需要方法设置成static
	  		Thread.sleep(10);//线程随眠10毫秒,这段代码只是为了提高数据出错的机率
            if (bsum > 0)
                System.out.println(Thread.currentThread().getName() + ":票:" + (bsum --)); 
	}
}

public void main(String []args){
  //实现Runnable 方式
  	A a = new A();
  	Thread t1 = new Thread(a);
 	Thread t2 = new Thread(a);
   	Thread t3 = new Thread(a);
  	t1.setName("a1");
  	t2.setName("a2");
  	t3.setName("a3");
  	t1.start();
 	t2.start();
  	t3.start();

  	B t1 = new B();
	B t2 = new B();
 	B t3 = new B();
 	t1.setName("T1");
 	t2.setName("T2");
	t3.setName("T3");
 	t1.start();
 	t2.start();
 	t3.start(); 
}

补充-单例模式

分析:单例模式分为懒汉与饿汉,饿汉是线程安全的静态类只会加载一次。懒汉是非线程安全的。

class A {
	private static A a = null;
	public A(){}

	//基本写法
	public A getA(){
		//分析:如果是多线程那么可能会同时多个线程进入if 然后出现a 被多次new 赋值,所以需要线程同步
	  	if(a == null ){
			a = new A();
		}
		return a;
	}
	
	//线程同步
	 public A getA(){
		//分析: 最开始会有很多线程进入第一层if 然后 锁确保同步,然后再进第二层if 就不会出现重复 赋值new 对象,后续其他线程再来访问就会被第一层if 阻止 就没有必要再去获得锁再去二次if判断
	  	if(a == null ){
	  		synchronized(A.class){
				if(a == null ){
				   a = new A();
				}
			} 
		}
		return a;
	}
}

sleep() 与 wait()

比较 -相同

  • 都是阻塞方法

比较 -不同

  • sleep() 是Thread 静态方法,wait()是对象方法。
  • sleep()任何地方都可以通过Thread.sleep()使用,wait()只能在同步代码块或者同步方法中使用,下面的代码只是想说明,wait、notify、notifyAll的调用者 必须是锁(同步监视器) 。例子如下:
	synchronized(this){ //切记:下面方法的 隐式的使用了this.xxx()来调用方法
		notify(); //唤醒优先级最高的 进入阻塞的线程
		notifyAll();//唤醒所有
		wait(); //当前线程阻塞,并且释放同步监视器
	}
	public static Object o = new Object();
	synchronized(o){ //切记:换一种写法
		o.notify(); //唤醒优先级最高的 进入阻塞的线程
		o.notifyAll();//唤醒所有
		o.wait(); //当前线程阻塞,并且释放同步监视器
	} 
  • 在同步代码块/同步方法中 同时使用sleep()与wait ,前者不会时方同步监视器,后者会释放。简单理解(白话):调用sleep()我要睡觉了(阻塞)睡多久甭管!锁我是不会拿出来的,它是我的。 调用wait()我也要去睡了(阻塞)睡多久甭管!但是锁我交出来!你们随便该咋咋地,调用notify() 优先级最高的兄弟!别睡了(wait()阻塞状态的其他线程)准备拿锁了虽然你级别高但是也要看cpu老大的安排。同理 notifyAll()对所有wait()阻塞的线程

例子:线程通信- 1~100 多个线程轮流打印

public class SleepAndWaitRunnable implements Runnable {
    private int sum = 100;

    @Override
    public void run() {
        while (sum > 0) {
            synchronized (this) {
                try {
                    this.notifyAll();//唤醒其他线程
                    if (sum > 0)
                        System.out.println(Thread.currentThread().getName() + ":票:" + (sum--));

                    this.wait(); //当前线程释放锁,并且进入阻塞,等待其他线程唤醒
                } catch (Exception e) {

                }
            }
        }
    }
}

public void main(String[] args){
   SleepAndWaitRunnable sleepAndWaitRunnable = new SleepAndWaitRunnable();
        Thread ts1 = new Thread(sleepAndWaitRunnable);
        Thread ts2 = new Thread(sleepAndWaitRunnable);
        Thread ts3 = new Thread(sleepAndWaitRunnable);
        Thread ts4 = new Thread(sleepAndWaitRunnable);
        ts1.setName("ts1");
        ts2.setName("ts2");
        ts3.setName("ts3");
        ts4.setName("ts4");
        ts1.start();
        ts2.start();
        ts3.start();
        ts4.start();
        } 

JDK5 新增线程创建方式

Callable

Callable jdk5新增的一种线程实现的接口,还有相关的Future 表示 ‘未来’, FutureTask实现Runnable 与Future 。Callable 的使用也依赖于 FutureTask的对象,下面代码展示

//示例
class A implements Callable<Integer>{
	public Integer call() throws Exception {
	    System.out.println(Thread.currentThread().getName()+"这里是jdk5 创建线程之一 实现 Callable");
        return 200;
	}
}

public static void main(String [] arge){
	FutureTask<Integer> f = new FutureTask(new A());
	new Thread(f).start(); //因为FutureTask 实际上是Runnable 的实现类,所以可以配合Thread 使用。
	 try {
            Integer req =  f.get(); //可以拿到Callable实现类对应call的返回值,这就是FutureTask 的重要体现
            System.out.println("***jdk5之Callable创建线程回调值"+req);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
}

Callable 相对 Runnable 的优势

  • 1.获取线程回调值
  • 2.可以捕获异常
  • 3.支持泛型

线程池

jdk5新增,Executors线程池工具类、ExecutorService线程池接口。在之后的比如jdbc 连接池就是借鉴了线程池的的理念。

class A implements Callable<Integer>{
	public Integer call() throws Exception {
	    System.out.println(Thread.currentThread().getName()+"这里是jdk5 创建线程之一 实现 Callable");
        return 200;
	}
}
		//通过线程池工具类 创建线程池接口类 
        ExecutorService service =  Executors.newFixedThreadPool(2);//默认2个线程
        service.execute(new Runnable() { //如果使用Runable 
            @Override
            public void run() {
                System.out.println("**线程池方式调用execute执行");
            }
        });
        Future<Integer> futureTask1 = service.submit(aCallable); //使用Callable
        try {
            Integer req =   futureTask1.get(); //返回值
            System.out.println("***jdk5之线程池 创建线程装入Callable 回调值"+req);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }

使用线程池好处

  • 1.提高响应(避免重复创建销毁线程)
  • 2.降低资源消耗
  • 3.便于管理(1.线程数 、2.线程池最大线程数、3.线程最大闲置逗留时间,超过时间销毁)

常用类

String、StringBuffer、StringBuilder

String、StringBuffer、StringBuilder以上三种都是对字符串的操作对象类型。一般初学者最常用的就是String,但是并不知道在效率方面它却是最差的

特性:(可通过源码查看)

  • String: 不可变字符串、底层char[]、线程不安全、效率最低
  • StringBuffer:可变、底层char[]、线程安全、效率低
  • StringBuilder:可变、底层cahr[]、线程不安全、效率高

下面举例子说明

public static void main(String [] args){
  /**
         * String 字符串,不可变(即时是作为参数也不会发生改变),可排序,不可被继承,可序列化,底层char[]数组实现
         * 字符串赋值规则: 1.只有常量,会先去 常量池中创建常量,然后地址返回给变量
         *              2.只要有一个变量那么就不会去常量池,而是直接在堆中创建内存存放
         *              3.字符串变量调用intern()那么就会去常量池创建对应的字符常量,并返回地址
         * 				4.使用final 修饰的变量就 等于是常量
         *
         * String、StringBuffer、StringBuilder 比较
         * String :不可变,底层char[]
         * StringBuffer:可变、底层char[] 线程安全,效率低
         * StringBuilder:可变、地城char[] 线程不安全,效率高 jdk5新出的类
         *
         * 线程安全上来说:
         *      StringBuffer 与 StringBuilder 几乎相同但是,StringBuffer 的方法都是同步方法,所以安全,同时效率低
         * 效率上来说:StringBuilder -> StringBuffer -> String
         * 扩容原理: StringBuffer与StringBuilder 的扩容原理相同:
         *          源码中每次append(String str)都会先做一次长度的判断,不够就会进行扩容
         *          当前长度<<1 + 2 的长度,如果还是不够那么就会直接创建当前改变后char[]需要的长度
         *          同时使用Array.copyOf()将char[]复制到扩容后的char[]作为value
         *          所以从上可以得出结论:String 是不可变,所以每次添加实际上底层都是销毁新建反复操作,
         *          而相对于StringBuffer和StringBuilder 只有char[]长度不够才会扩容(创建新char[])
         *    优化:根据实际的业务常见选用字符串,线程安全使用 StringBuffer ,非线程安全使用StringBuilder
         *         还有如果在   知道最终char[]长度的情况下,可以通过一开始创建指定长度(一步到位)那么扩容的机制也就省了
         * **/

 		String a = "a";
        String a2 = "ab";

        String a3 = a + a2;
        String a4 = "aab";
        String a5 = "a" + "ab";

        String a6 = "a" + a2;
        String a7 = a + "ab";

        String a8 = a3.intern();//将a3中的字符串作为常量去常量池存储并返回地址,a3自身不会做出任何改变
        System.out.println(a5 == a4);//true   常量池
        System.out.println(a3 == a4);//false  堆内存
        System.out.println(a3 == a5);//false  堆内存
        System.out.println(a4 == a6);//false  堆内存
        System.out.println(a4 == a7);//false  堆内存
        System.out.println(a4 == a8);//true   常量池
        System.out.println(a3 == a8);//false  堆内存

        //举例子
        String s = new String("cctv");
        char[] s2 = {'t', 'e', 's', 't'};
        update(s, s2);
        System.out.println("***因为String不可变的特性,所以既是方法内做了修改也不会生效:" + s);
        System.out.print("***char[]却没有final 的特性:" );
        System.out.print(s2);
    }
    private static void update(String s, char[] z) { 
        s = "zsdasdsa";
        z[0] = 'p';
    }
	
	    //效率测试
        System.out.println("**********Stirng、StringBuffer、StringBuilder 效率测试*************");

        StringBuilder sb1 = new StringBuilder();
        StringBuffer sb2 = new StringBuffer();
        String sb3 = new String();
        StringBuilder sb4 = new StringBuilder(1024*1024*100);
        long startTime = System.currentTimeMillis();
        for (int i = 0; i < 20000; i++) {
            sb3 += "cctv" + String.valueOf(i);
        }
        long end1 = System.currentTimeMillis() - startTime;
        System.out.println("String 当前时间消耗:" + end1);


        long startTime2 = System.currentTimeMillis();
        for (int i = 0; i < 20000; i++) {
            sb2.append("cctv" + String.valueOf(i));
        }

        long end2 = System.currentTimeMillis() - startTime2;
        System.out.println("StringBuffer 当前时间消耗:" + end2);

        long startTime3 = System.currentTimeMillis();
        for (int i = 0; i < 20000; i++) {
            sb1.append("cctv" + String.valueOf(i));
        }
        long end3 = System.currentTimeMillis() - startTime3;
        System.out.println("StringBuilder 当前时间消耗:" + end3);

        //测试如果一开始就指定大小
        long startTime4 = System.currentTimeMillis();
        for (int i = 0; i < 20000; i++) {
            sb4.append("cctv" + String.valueOf(i));
        }
        long end4 = System.currentTimeMillis() - startTime4;
        System.out.println("StringBuilder 指定大小 当前时间消耗:" + end4);

		//打印结果: 
		String 当前时间消耗:3097
		StringBuffer 当前时间消耗:6
		StringBuilder 当前时间消耗:6
		StringBuilder 指定大小 当前时间消耗:5

枚举(简单提一下)

枚举的意义在于 定义 :确定、并且不会再修改的 数据对象。简单举例子:Thread 中的状态属性 State 就是一个 枚举类,定义了 线程只会存在的集中状态。在jkd5之前 枚举需要自己通过 全局静态的方式来创建,但是jdk5之后 出现了enum 关键字

举例子:

//
public enum A{
	A1,B1,C1;
}

//带属性的枚举
public enum B{
	B1(12"王飒"),
	B2(15"王飒2"),
	B3(11"王飒3");
	
	private int age;
	private String name;
	
	B(int age,String name){
		this.age = age;
		this.name = name;
	}
}

public static void main(String[] args){
	System.out.print(A.A1);
	System.out.print(B.B1);
	//具体更多的APi 自行百度
}

注解(Annotation)

注解 最开始使用并不大,但是在后续的开发当中,框架都是基于注解代替文件配置。所以注解是有必要学习的。想想 spring 的aop 对吧,技术就是动态代码生成,其实现方式就是注解+反射

什么是注解(神马是快乐星球!!!!!)

注解就是 ! 解释某个东西 那么他就是注解,这是咱们代码角度来解释。回想下以前语文 古诗下面的 注释! 你懂了嘛!意思差不多。注解我们平时看到的就有很多例如:@Override、@Deprecated、 @SupperssWarnings等等。当然我们也可以自定义注解!

例子:

public @interface MyAnnotation{} //没有任何参数的 自定义注解

元注解!

又来!元注解就是:解释 注解的注解。这样说可能不太容易理解,那么我们这么想,我们自定义的注解能在什么地方使用,生命周期是多久等等。对 元注解就是来 注明 我们的自定义注解 声明周期、作用范围等等

java 自身的注解例如:@SupperssWarnings

@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings { 
    String[] value();
}

元注解:就只有四个:两个相对常用、两个相对不常用

  • 常用- @Target:范围 ,修饰 该注解能使用的范围例如:类、方法、变量、形参…
  • 常用- @Retention:生命周期,修饰 该注解存货范围:SOURCE(java 文件)、CLASS(编译成字节码)、RUNTIME(运行时)。简单来说 预编译阶段,编译,jvm 加载
  • 不常用-@Documented:生成javadoc 时候保留该注解
  • 不常用-@Inherited: 继承、该注解允许被其他注解继承

例子: 自定义

@Retention(RetentionPolicy.CLASS) //生命周期
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})//注解使用范围
@Documented //javadoc 生成文档保存 自定义注解
@Inherited  //自定义注解 允许被其他注解继承
public @interface MyAnnotation {
   String[] value();
}

//使用 对属性进行使用
@MyAnnotation(value={"c","a"})
String a = "c";

提示:jdk8 新增一个元注解 @Repeatable 重复类型,以及@Target 新增范围 泛型 相关详细内容请自行百度查阅。

集合(Collection、Map)

集合框架:最初数据处理使用Object[] 数组,但是发现比较繁琐相对的api也很少,所以集合诞生了,主要分两种:Collection、Map 。他们分别处理不同的数据
Collection: 单列集合,有两个子接口:List、Set
Map:双列集合(key,value)有五个实现:HashMap、LinkedHashMap、TreeMap、Hashtable、Properties

常用方法

/**
* 常用方法:
* 
    int size(); //当前集合内容数量
    boolean isEmpty();//size() == 0 
    Iterator<E> iterator(); //获取一个迭代器
    Object[] toArray();//集合转数组 
    boolean add(E e);//添加 
    boolean remove(Object o);//删除 
    boolean containsAll(Collection<?> c);//this 是否包含 c 
    boolean addAll(Collection<? extends E> c);//添加c 到 this
    boolean removeAll(Collection<?> c); //删除 this 种的c的交集对象
    void clear();//清空 size == 0
*/

 		Collection<Integer> collection = new ArrayList<>();
        Collection<Integer> collection2 = new ArrayList<>();
        collection.add(1);                                   //新增
        int size = collection.size();                        //获取对象个数
        collection2.addAll(collection);                      //添加所有
        boolean isSize = collection.isEmpty();               //是否有内容
        collection2.clear();                                 //清空内容
        collection.contains(1);                              //判断是否包含某个指定 对象
        collection.containsAll(collection2);                 //判断是否包含某个指定 集合内的所有对象
        collection.remove(123);                           //移除指定对象
        collection.removeAll(Arrays.asList(3333, 111));      //移除多个对象
        collection.retainAll(Arrays.asList(1));              //取 交集

Iterator(迭代器)

在介绍Collection的具体实现类之前,先介绍迭代器,简单来说就说帮助遍历Collection的一个工具类。

常用方法

    // iterator(迭代器)
        Collection collection1 = new ArrayList();
        List list = Arrays.asList("1", 3333, 31, 111, 123123);
        collection1.addAll((Collection) list); 
        Iterator iterable = collection1.iterator();
//        System.out.println(iterable.next());
//        System.out.println(iterable.next());
//        System.out.println(iterable.next());
        //遍历 通过next 移动指针 返回内容
        System.out.println("**********hasNext()配next()遍历");
        while (iterable.hasNext()) {
            Object o = iterable.next();
            System.out.println(o);
            if ("1".equals(o)) {
                iterable.remove();
            }
        }
        System.out.println("collection:size():" + collection1.size()); 

理解图:
在这里插入图片描述

List(有序,可重复集合)

上面提到过List 作为Collection 的子接口。特点就是有序、可重复性。具体的实现类:ArrayList、LinkedList、Vector 三个类。

  • 有序:有顺序可言,遍历出现的顺序,与根据编码时add()的顺序一致
  • 可重复:同一个对象可以存在多个。

ArrayList、LinkedList、Vector

ArrayList 最直白的说,就是代替数组的集合。特点:object[]数据结构、查询遍历快,插入慢,线程不安全。
LinkedList 特点:双向链表(java.util.LinkedList,Node) 数据结构、查询遍历慢、插入快、线程不安全。
Vector(快入土) 特点:object[] 数据结构、查询遍历快、插入慢、线程安全(因为是线程安全所以 查询遍历 或 插入 都比ArrayList 慢一点点),提一嘴为什么说他线程安全,因为方法都是同步方法

结构图:
提示:Vector 快入土了且结构与ArrayList 一样所以没画
在这里插入图片描述
在这里插入图片描述

提示:图中只做了 add(int index,E Object);方法的过程,remove(E object);的过程却没做,这个自行脑部,与add的区别不大,上面图只是为了表达 数组的数据结构与双向链表数据结构的不同

代码举例子:

  System.out.println("****有序,可重复 List 实现类:ArrayList、LinkedList、Vector**********:");
        ArrayList arrayList = new ArrayList();
        arrayList.add(1);
        arrayList.add(1);
        arrayList.add(1);
        arrayList.add("cctv");
        arrayList.add('a');
        System.out.println("****ArrayList:"+ arrayList.toString());

        LinkedList linkedList = new LinkedList();
        linkedList.add(1);
        linkedList.add(2);
        linkedList.add('a');
        linkedList.add(new Object());

        System.out.println("****LinkedList:"+ linkedList.toString());


        Vector vector = new Vector();
        vector.add(1);
        vector.add(2);
        vector.add('a');
        vector.add(3);
        System.out.println("****Vector:"+ vector.toString());

扩容机制

  • ArrayList:

jdk7:构造函数指定new Object[10],add()时候判断大小不够,扩容 size + (szie >> 1) 。
jdk8: 构造函数并未指定大小 Object[] object = {}。add()判断是否是第一次添加,第一次添加,当前添加元素size 与 object.length做判断 Math.max 取最大 然后初始化数组,然后 Arrays.copyOf。之后的添加按照正常的流程,容量够添加,不够 扩容 size + (szie >> 1)

  • LinkedList:

双向链表 不需要提前指定大小。prev 为null 表示当前数据结构为 ‘头’,next为null 表示为 ‘尾’。每次的添加都会遍历全部。

  • Vectro:

与ArrayList 一样的扩容机制,不同点在于 扩容大小是 原来的两倍大小

Set(无序、不可重复)

Set 作为 Collection 的另一个子接口:特点是 无序并且不可重复。它的实现类:HashSet、LinkedHashSet、TreeSet。

  • 无序:遍历显示的数据顺序,与编码时add()的顺序不同。并不是真的没有顺序,在添加的时候会 根据唯一值hsah(具体怎么确保唯一需要重写 hashCode()),然后再通过计算直接拿到下标,然后循环遍历找到需要插入位置的单项链表 插入数据。
  • 不可重复:同上 当插入的时候会根据 hash 值来判定,如果有数据判定!相等插入失败,不相等,就会以单项链表的结构存储在,当前位置已经存在的对象的‘prev’ 的下面。

HashSet、LinkedHashSet、TreeSet

HashSet 特点:数组+单项链表(java.util.Map) 数据结构 插入快、遍历慢、线程不安全。
LinkedHashSet 特点: 数组+单项链表(java.util.Map) 数据结构 插入快、遍历快、线程不安全。
TreeSet 特点:二叉树 数据结构 线程不安全、可以排序 支持自然排序、自定义排序。

  System.out.println("****无序、不可重复 Set 实现类:HashSet、LinkedHashSet、TreeSet**********:");
        HashSet set = new HashSet();
        set.add(new UserHashSet("cctv",2));
        set.add(new UserHashSet("ictv",4));
        set.add(new UserHashSet("zctv",22));
        set.add(new UserHashSet("cftv",1));
        set.add(new UserHashSet("qctv",41));
        set.add(new UserHashSet("mctv",8));
        set.add(new UserHashSet("cctv",23));//不同 age
        set.add(new UserHashSet("ictv",4)); //相同
        System.out.println("HashSet 无序插入 原样展示:"+set + "");


        LinkedHashSet linkedHashSet = new LinkedHashSet();
        linkedHashSet.add(new UserHashSet("cctv",2));
        linkedHashSet.add(new UserHashSet("ictv",4));
        linkedHashSet.add(new UserHashSet("zctv",22));
        linkedHashSet.add(new UserHashSet("cftv",1));
        linkedHashSet.add(new UserHashSet("qctv",41));
        linkedHashSet.add(new UserHashSet("mctv",8));
        linkedHashSet.add(new UserHashSet("cctv",23));//不同 age
        linkedHashSet.add(new UserHashSet("ictv",4)); //相同
        System.out.println("LinkedHashSet 有序插入 有序展示:"+linkedHashSet + "");





        Comparator comparator = new Comparator<UserHashSet>() {
            @Override
            public int compare(UserHashSet o1, UserHashSet o2) {
                return Double.compare(o1.getAge(),o2.getAge());
            }
        };
        TreeSet treeSet = new TreeSet<UserHashSet>(comparator);
        treeSet.add(new UserHashSet("cctv",2));
        treeSet.add(new UserHashSet("ictv",4));
        treeSet.add(new UserHashSet("zctv",22));
        treeSet.add(new UserHashSet("cftv",1));
        treeSet.add(new UserHashSet("qctv",41));
        treeSet.add(new UserHashSet("mctv",8));
        treeSet.add(new UserHashSet("cctv",23));//不同 age
        treeSet.add(new UserHashSet("ictv",4)); //相同
        System.out.println("TreeSet 无序插入 自定义排序 并展示:"+treeSet + "");

Map

再次强调 双列数据。主要实现类:HashMap、LinkedHashMap、TreeMap、Hashtable、Properties。
HashMap:主要实现类,线程不安全、允许key/value 为空
LinkedHashMap: HashMap 的子类,线程不安全、在遍历方面效率高
TreeMap:主要用于排序,自然排序与 自定义排序 线程不安全
Hashtable: 古老的主要类,线程安全,但是不用了
Properties: Hashtable 的子类,key/value 都是String 类型的数据。主要用来处理配置文件

  • HashMap

key 是一个Set 集合,不可重复,无序
value 是Collection ,可重复,无序
一组 key-value 组成一个 HashMap.Noed(jdk7之前叫Entry)
所以 一个HashMap = Set<HashMap.Node>;
先介绍几个HashMap源码中属性:
* table-链表数组 存储数据 HashMap.Node<K,V>[] table
* entrySet-
* size-存储数据的个数
* modCount-计数器
* threshold-临界值,触发扩容机制的临界值
* loadFactor- 装载因子,临界值 = loadFactor* table.length
原理:
* jdk8: 构造函数初始化loadFactor然后并没有初始化table。
* put(k,v)添加数据的时候,会先拿到key,调用key的hashCode()方法
* 拿到key的哈希值然后参与hash(Object key) 计算拿到一个 hash 然后进行添加
* 第一次添加数据会先初始化通过resize()拿到一个table[16]进行判断
* 会通过hash 计算拿到table的下标然后去对应位置 判断是否能直接插入
* 情况1:table[index] == null ,没有数据可以直接插入newNode(hash, key, value, null)
* 情况2:table[index] != null,需要hash进行 == 的判断与 kye进行equals(table[index].key)判断
* 情况2.1: true , 那么进行value 的覆盖,table[index].value = value
* 情况2.2: false , 判断是否是红黑树结构,
* 情况2.2.1: 是红黑树。
* 情况2.2.2: 不是红黑树,并且 也不符合2.1 的情况,那么遍历 table[index].next
* 先判断next 是否为空,为空表示可以直接添加在链表最尾部。
* 不是空,按照情况2 的判断方式是否能够覆盖,不能覆盖就跳过当前循环。
* 最后在每次完成一次添加之后会 size 与 modCount都会+1 ,这个时候会触发resize()判断是否需要扩容,
* jdk7:与8之间的区别在于,jdk7在构造函数的时候就会指定大小16,而不是第一次添加数据才会初始化大小。
* jdk7叫Entry[],jdk8叫Node[]
* jdk8新增了红黑树,在 扩容时候触发,链表深度 >8并且size > 64 就会将单项链表的地方转换成 红黑树。
*

  • LinkedHashMap

与HashMap 相同,再此基础上,对链表做了结构上的修改,增加了两个属性记录,上一个、下一个
原理
* 与父类HashMap 一样在此基础上实现了自身的Entry结构,
* 继承HashMap.Node 新增了Entry[] before, Entry[] after,用来记录上一个,下一个
* 查看源码中可以发现 LinkedHashMap重写了newNode()方法,所以在调用putVal中添加数据实际上走的是
* LinkedHashMap 自身的重写的newNode()方法 实际上添加的就是 LinkedHashMap.Entry[] 的双向链表
* 这也就为什么 LinkedHashMap 在遍历方面比HashMap效率高

提示:TreeMap 、Hashtable、Properties了解就行了。

代码示例:

  System.out.println("**********Map:HashMap,LinkedHashMap、TreeMap、Hashtable、Properties******************");
        //HashMap
        HashMap hashMap = new HashMap();
        hashMap.put(3, "cctv");
        hashMap.put("cctv", "cctv");
        hashMap.put(1, "cctv");
        hashMap.put(2, "cctv");
        hashMap.put(3, "cctv");
        hashMap.put("a", "cctv");
        System.out.println("HashMap 无序 不可重复:" + hashMap);


        LinkedHashMap linkedHashMap = new LinkedHashMap();
        linkedHashMap.put("a", "cctv");
        linkedHashMap.put("cctv", "cctv");
        linkedHashMap.put(1, "cctv");
        linkedHashMap.put(2, "cctv");
        linkedHashMap.put(3, "cctv");
        linkedHashMap.put("a", "cctv");
        System.out.println("LinkedHashMap 有序 不可重复:" + linkedHashMap);


        TreeMap treeMap = new TreeMap();
        treeMap.put("cctv", "cctv");
        treeMap.put("1", "cctv");
        treeMap.put("2", "cctv");
        treeMap.put("3", "cctv");
        treeMap.put("a", "cctv");
        treeMap.put("2", "cctv");
        System.out.println("TreeMap: 无序 不可重复" + treeMap);


        Properties properties = new Properties();
        FileInputStream fileInputStream = null;
        try {
        //这里需要自行创建文件,修改路径
            fileInputStream = new FileInputStream("src/main/resources/jdbc.properties");
            properties.load(fileInputStream);
            String name = properties.getProperty("name");
            System.out.println("Properties: 获取文件内容:" + name);
        } catch (Exception e) {
            e.printStackTrace();
        }
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值