高级篇 复习:
多线程
线程-创建使用
//方式一 继承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();
}