12.3.3 LinkedHashMap
12.3.4. TreeMap
底层机制和源码剖析:
- 若按照compare方法比较key相同则无法加入value值
public class TreeMap_ { public static void main(String[] args) { //使用默认的构造器,创建TreeMap, 是字母排序 /* 要求:按照传入的 k(String) 的大小进行排序 */ // TreeMap treeMap = new TreeMap(); TreeMap treeMap = new TreeMap(new Comparator() { @Override public int compare(Object o1, Object o2) { //按照传入的 k(String) 的大小进行排序 //按照K(String) 的长度大小排序 //return ((String) o2).compareTo((String) o1); return ((String) o2).length() - ((String) o1).length(); } }); treeMap.put("jack", "杰克"); treeMap.put("tom", "汤姆"); treeMap.put("kristina", "克瑞斯提诺"); treeMap.put("smith", "斯密斯"); treeMap.put("hsp", "韩顺平");//加入不了 System.out.println("treemap=" + treeMap); /* 解读源码: 1. 构造器. 把传入的实现了 Comparator接口的匿名内部类(对象),传给给TreeMap的comparator public TreeMap(Comparator<? super K> comparator) { this.comparator = comparator; } 2. 调用put方法 2.1 第一次添加, 把k-v 封装到 Entry对象,放入root Entry<K,V> t = root; if (t == null) { compare(key, key); // type (and possibly null) check root = new Entry<>(key, value, null); size = 1; modCount++; return null; } 2.2 以后添加 Comparator<? super K> cpr = comparator; if (cpr != null) { do { //遍历所有的key , 给当前key找到适当位置 parent = t; cmp = cpr.compare(key, t.key);//动态绑定到我们的匿名内部类的compare if (cmp < 0) t = t.left; else if (cmp > 0) t = t.right; else //如果遍历过程中,发现准备添加Key 和当前已有的Key 相等,就不添加 return t.setValue(value); } while (t != null); } */ } }
12.3.5. Properties
注意事项和细节:
- Properties类继承Hashtable类,实现了Map接口,也是使用一种简直对的形式保存数据
- 使用特点和Hashtable类似
- Properties 还可以用于 从xxx.properties 文件中,加载数据到Properties类对象井进行读取和修改
- xxx.properties 文件通常作为配置文件
public class Properties_ { public static void main(String[] args) { //1. Properties 继承 Hashtable //2. 可以通过 k-v 存放数据,当然key 和 value 不能为 null //增加 Properties properties = new Properties(); //properties.put(null, "abc");//抛出 空指针异常 //properties.put("abc", null); //抛出 空指针异常 properties.put("john", 100);//k-v properties.put("lucy", 100); properties.put("lic", 100); properties.put("lic", 88);//如果有相同的key , value被替换 System.out.println("properties=" + properties); //通过k 获取对应值 System.out.println(properties.get("lic"));//88 //删除 properties.remove("lic"); System.out.println("properties=" + properties); //修改 properties.put("john", "约翰"); System.out.println("properties=" + properties); } }
12.4 Collections
基本介绍:
- Collections 是一个操作 Set.List 和 Map 等集合的工具类
- Collections 中提供了一系列静态的方法对集合元素进行排序、查询和修改等操作
常用方法:
- 排序操作:
- reverse (List):反转List中元素顺序
- shuffle(List):对 List 集合元素进行随机排序
- sort(List):根据元素的自然顺序对指定 List 集合元素按升序排序
- sort(List, Comparator):根据指定的 Comparator 产生的顺序对 List 集合元素进行排序
- swap (List, int,int):将指定 list 集合中的 i处元素和j处元素进行交换
- 查找替换:
- Object max(Collection):根据元素的自然顺序,返回给定集合中的最大元素
- Object max(Collection, Comparator):根据 Comparator 指定的顺序,返回给定集合中的最大元素
- Object min(Collection)
- Object min(Collection, Comparator)
- int frequency(Collection, Object):返回指定集合中指定元素的出现次数
- void copy(List dest, List src):将src中的内容复制到dest中
- boolean replaceAll(List list, Object oldVal, Object newVal):使用新值替换 List 对象的所有旧值
public class Collections_ { public static void main(String[] args) { //创建ArrayList 集合,用于测试. List list = new ArrayList(); list.add("tom"); list.add("smith"); list.add("king"); list.add("milan"); list.add("tom"); // reverse(List):反转 List 中元素的顺序 Collections.reverse(list); System.out.println("list=" + list); // shuffle(List):对 List 集合元素进行随机排序 // for (int i = 0; i < 5; i++) { // Collections.shuffle(list); // System.out.println("list=" + list); // } // sort(List):根据元素的自然顺序对指定 List 集合元素按升序排序 Collections.sort(list); System.out.println("自然排序后"); System.out.println("list=" + list); // sort(List,Comparator):根据指定的 Comparator 产生的顺序对 List 集合元素进行排序 //我们希望按照 字符串的长度大小排序 Collections.sort(list, new Comparator() { @Override public int compare(Object o1, Object o2) { //可以加入校验代码. return ((String) o2).length() - ((String) o1).length(); } }); System.out.println("字符串长度大小排序=" + list); // swap(List,int, int):将指定 list 集合中的 i 处元素和 j 处元素进行交换 //比如 Collections.swap(list, 0, 1); System.out.println("交换后的情况"); System.out.println("list=" + list); //Object max(Collection):根据元素的自然顺序,返回给定集合中的最大元素 System.out.println("自然顺序最大元素=" + Collections.max(list)); //Object max(Collection,Comparator):根据 Comparator 指定的顺序,返回给定集合中的最大元素 //比如,我们要返回长度最大的元素 Object maxObject = Collections.max(list, new Comparator() { @Override public int compare(Object o1, Object o2) { return ((String)o1).length() - ((String)o2).length(); } }); System.out.println("长度最大的元素=" + maxObject); //Object min(Collection) //Object min(Collection,Comparator) //上面的两个方法,参考max即可 //int frequency(Collection,Object):返回指定集合中指定元素的出现次数 System.out.println("tom出现的次数=" + Collections.frequency(list, "tom")); //void copy(List dest,List src):将src中的内容复制到dest中 ArrayList dest = new ArrayList(); //为了完成一个完整拷贝,我们需要先给dest 赋值,大小和list.size()一样 for(int i = 0; i < list.size(); i++) { dest.add(""); } //拷贝 Collections.copy(dest, list); System.out.println("dest=" + dest); //boolean replaceAll(List list,Object oldVal,Object newVal):使用新值替换 List 对象的所有旧值 //如果list中,有tom 就替换成 汤姆 Collections.replaceAll(list, "tom", "汤姆"); System.out.println("list替换后=" + list); } }
12.5 总结⭐️⭐️
选择集合:
- 先判断存储的类型(一组对象[单列]或一组键值对[双列])
- 一组对象[单列]: Collection接口
- 允许重复:List
增删多:LinkedList [底层维护双向链表]
改查多:ArrayList [底层維护 Object类型的可变数组] - 不允许重复:Set
无序:HashSet [底层是HashMap,维护了一个哈希表,即(数组+链表+红黑树)]
排序:Treeset []
插入和取出顺序一致:LinkedHashSet [底层维护数组+双向链表]
- 一组键[值对双列]:Map
- 键无序:HashMap [底层是:哈希表 jdk7:数组+链表,jdk8:数组+链表+红黑树]
- 键排序:TreeMap []
- 键插入和取出顺序一致:LinkedHashMap
- 读取文件 Propertie
13 泛型
13.1泛型
基本介绍:
- 泛型又称参数化类型(接收数据类型的数据类型),是Jdk5.0出现的新特性,解决数据类型的安全性问题
- 在类声明或实例化时只要指定好需要的具体的类型即可
- Java泛型可以保证如果程序在编译时没有发出警告,运行时就不会产生ClassCastException异常。同时,代码更加简洁、健壮
基本语法:
interface 接口 <T>{} class 类 <K,V>{} 在类名后指定类型参数的值(类型): List<String> strList = new ArrayList<String>(); lterator<Customer> iterator = customers.iterator();
注意事项和细节:
- 泛型只能是引用类型
- 在给泛型指定具体类型后,可以传入该类型或者其子类类型
- List list3 = new ArrayList():默认给它的 泛型是[<E> E就是 Object]
例:
public class GenericExercise02 { public static void main(String[] args) { ArrayList<Employee> employees = new ArrayList<Employee>(); employees.add(new Employee("jack", 2000, new MyDate(1, 1, 1))); employees.add(new Employee("tom", 2000, new MyDate(6, 1, 1))); employees.add(new Employee("tom", 3000, new MyDate(3, 1, 1))); employees.add(new Employee("jack", 2000, new MyDate(1, 1, 3))); employees.sort(new Comparator<Employee>() {//传入匿名内部类接口重写compare方法 @Override public int compare(Employee o1, Employee o2) { if (!(o1 instanceof Employee) && (o2 instanceof Employee)) { System.out.println("类型不正确"); return 0; } return o1.getBirthday().compareTo(o2.getBirthday());//在MyDate内重写compareTo方法 } }); for (Employee employee : employees) { System.out.println(employee); } } } class Employee { private String name; private int sal; private MyDate birthday; public Employee(String name, int sal, MyDate birthday) { this.name = name; this.sal = sal; this.birthday = birthday; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getSal() { return sal; } public void setSal(int sal) { this.sal = sal; } public MyDate getBirthday() { return birthday; } public void setBirthday(MyDate birthday) { this.birthday = birthday; } @Override public String toString() { return "Employee{" + "name='" + name + '\'' + ", sal=" + sal + ", birthday=" + birthday + '}'; } } class MyDate implements Comparable<MyDate> { private int year; private int month; private int day; public MyDate(int year, int month, int day) { this.year = year; this.month = month; this.day = day; } public int getYear() { return year; } public void setYear(int year) { this.year = year; } public int getMonth() { return month; } public void setMonth(int month) { this.month = month; } public int getDay() { return day; } public void setDay(int day) { this.day = day; } @Override public String toString() { return "MyDate{" + "year=" + year + ", month=" + month + ", day=" + day + '}'; } @Override public int compareTo(MyDate o) { int yearMinus = year - o.year; if (yearMinus != 0) { return yearMinus; } int monthMinus = month - o.month; if (monthMinus != 0) { return monthMinus; } return day - o.day; } }
13.2 自定义泛型
13.2.1泛型类
基本语法:
class 类名<T,R...>{
成员;
}
注意事项和细节:
- 普通成员可以使用泛型(属性、方法)
- 使用泛型的数组,不能初始化
- 静态方法中不能使用类的泛型
- 泛型类的类型,是在创建对象时确定的(因为创建对象时,需要指定确定类型)
- 如果在创建对象时,没有指定类型,默认为Object
public class CustomGeneric_ { public static void main(String[] args) { //T=Double, R=String, M=Integer Tiger<Double,String,Integer> g = new Tiger<>("john"); g.setT(10.9); //OK //g.setT("yy"); //错误,类型不对 System.out.println(g); Tiger g2 = new Tiger("john~~");//OK T=Object R=Object M=Object g2.setT("yy"); //OK ,因为 T=Object "yy"=String 是Object子类 System.out.println("g2=" + g2); } } //1. Tiger 后面泛型,所以我们把 Tiger 就称为自定义泛型类 //2, T, R, M 泛型的标识符, 一般是单个大写字母 //3. 泛型标识符可以有多个. //4. 普通成员可以使用泛型 (属性、方法) //5. 使用泛型的数组,不能初始化 //6. 静态方法中不能使用类的泛型 class Tiger<T, R, M> { String name; R r; //属性使用到泛型 M m; T t; //因为数组在new 不能确定T的类型,就无法在内存开空间 T[] ts; public Tiger(String name) { this.name = name; } public Tiger(R r, M m, T t) {//构造器使用泛型 this.r = r; this.m = m; this.t = t; } public Tiger(String name, R r, M m, T t) {//构造器使用泛型 this.name = name; this.r = r; this.m = m; this.t = t; } //因为静态是和类相关的,在类加载时,对象还没有创建 //所以,如果静态方法和静态属性使用了泛型,JVM就无法完成初始化 // static R r2; // public static void m1(M m) { // // } //方法使用泛型 public String getName() { return name; } public void setName(String name) { this.name = name; } public R getR() { return r; } public void setR(R r) {//方法使用到泛型 this.r = r; } public M getM() {//返回类型可以使用泛型. return m; } public void setM(M m) { this.m = m; } public T getT() { return t; } public void setT(T t) { this.t = t; } @Override public String toString() { return "Tiger{" + "name='" + name + '\'' + ", r=" + r + ", m=" + m + ", t=" + t + ", ts=" + Arrays.toString(ts) + '}'; } }
13.2.2泛型接口
基本语法:
interface 接口名<T,R...>{}
注意事项和细节:
- 静态成员不能使用泛型
- 泛型接口的类型,在继承接口或者实现接口时确定
- 没有指定类型,默认为Object
public class CustomInterfaceGeneric { public static void main(String[] args) { } } /** * 泛型接口使用的说明 * 1. 接口中,静态成员也不能使用泛型 * 2. 泛型接口的类型, 在继承接口或者实现接口时确定 * 3. 没有指定类型,默认为Object */ //在继承接口 指定泛型接口的类型 interface IA extends IUsb<String, Double> { } //当我们去实现IA接口时,因为IA在继承IUsu 接口时,指定了U 为String R为Double //,在实现IUsu接口的方法时,使用String替换U, 是Double替换R class AA implements IA { @Override public Double get(String s) { return null; } @Override public void hi(Double aDouble) { } @Override public void run(Double r1, Double r2, String u1, String u2) { } } //实现接口时,直接指定泛型接口的类型 //给U 指定Integer 给 R 指定了 Float //所以,当我们实现IUsb方法时,会使用Integer替换U, 使用Float替换R class BB implements IUsb<Integer, Float> { @Override public Float get(Integer integer) { return null; } @Override public void hi(Float aFloat) { } @Override public void run(Float r1, Float r2, Integer u1, Integer u2) { } } //没有指定类型,默认为Object //建议直接写成 IUsb<Object,Object> class CC implements IUsb { //等价 class CC implements IUsb<Object,Object> { @Override public Object get(Object o) { return null; } @Override public void hi(Object o) { } @Override public void run(Object r1, Object r2, Object u1, Object u2) { } } interface IUsb<U, R> { int n = 10; //U name; 不能这样使用 //普通方法中,可以使用接口泛型 R get(U u); void hi(R r); void run(R r1, R r2, U u1, U u2); //在jdk8 中,可以在接口中,使用默认方法, 也是可以使用泛型 default R method(U u) { return null; } }
13.2.3泛型方法
基本语法:
修饰符<T,R...>返回类型 方法名(参数列表){}
注意事项和细节:
- 泛型方法,可以定义在普通类中,也可以定义在泛型类中
- 当泛型方法被调用时,类型会确定
- public void eat(Ee) {},
修饰符后没有<T,R.> eat
方法不是泛型方法,而是使用了泛型
public class CustomMethodGeneric { public static void main(String[] args) { Car car = new Car(); car.fly("宝马", 100);//当调用方法时,传入参数,编译器,就会确定类型 System.out.println("======="); car.fly(300, 100.1);//当调用方法时,传入参数,编译器,就会确定类型 //测试 //T->String, R-> ArrayList Fish<String, ArrayList> fish = new Fish<>(); fish.hello(new ArrayList(), 11.3f); } } //泛型方法,可以定义在普通类中, 也可以定义在泛型类中 class Car {//普通类 public void run() {//普通方法 } //说明 泛型方法 //1. <T,R> 就是泛型 //2. 是提供给 fly使用的 public <T, R> void fly(T t, R r) {//泛型方法 System.out.println(t.getClass());//String System.out.println(r.getClass());//Integer } } class Fish<T, R> {//泛型类 public void run() {//普通方法 } public<U,M> void eat(U u, M m) {//泛型方法 } //说明 //1. 下面hi方法不是泛型方法 //2. 是hi方法使用了类声明的 泛型 public void hi(T t) { } //泛型方法,可以使用类声明的泛型,也可以使用自己声明泛型 public<K> void hello(R r, K k) { System.out.println(r.getClass());//ArrayList System.out.println(k.getClass());//Float } }
13.3 泛型继承和通配符
注意事项和细节:
- 泛型不具备继承性
- :支持任意泛型类型
- :支持A类以及A类的子类,规定了泛型的上限
- :支持A类以及A类的父类,不限于直接父类,规定了泛型的下限
public class GenericExtends { public static void main(String[] args) { Object o = new String("xx"); //泛型没有继承性 //List<Object> list = new ArrayList<String>(); //举例说明下面三个方法的使用 List<Object> list1 = new ArrayList<>(); List<String> list2 = new ArrayList<>(); List<AA> list3 = new ArrayList<>(); List<BB> list4 = new ArrayList<>(); List<CC> list5 = new ArrayList<>(); //如果是 List<?> c ,可以接受任意的泛型类型 printCollection1(list1); printCollection1(list2); printCollection1(list3); printCollection1(list4); printCollection1(list5); //List<? extends AA> c: 表示 上限,可以接受 AA或者AA子类 // printCollection2(list1);//× // printCollection2(list2);//× printCollection2(list3);//√ printCollection2(list4);//√ printCollection2(list5);//√ //List<? super AA> c: 支持AA类以及AA类的父类,不限于直接父类 printCollection3(list1);//√ //printCollection3(list2);//× printCollection3(list3);//√ //printCollection3(list4);//× //printCollection3(list5);//× //冒泡排序 //插入排序 //.... } // ? extends AA 表示 上限,可以接受 AA或者AA子类 public static void printCollection2(List<? extends AA> c) { for (Object object : c) { System.out.println(object); } } //说明: List<?> 表示 任意的泛型类型都可以接受 public static void printCollection1(List<?> c) { for (Object object : c) { // 通配符,取出时,就是Object System.out.println(object); } } // ? super 子类类名AA:支持AA类以及AA类的父类,不限于直接父类, //规定了泛型的下限 public static void printCollection3(List<? super AA> c) { for (Object object : c) { System.out.println(object); } } } class AA { } class BB extends AA { } class CC extends BB { }
例:
public class HomeWork01 { public static void main(String[] args) { } @Test public void testList() { DAO<User> dao = new DAO<User>(); dao.save("001",new User(1,10,"jack")); dao.save("002",new User(2,11,"tom")); dao.save("003",new User(3,12,"mike")); List<User> list = dao.list(); System.out.println(list); dao.update("002",new User(2,14,"faker")); List<User> list1 = dao.list(); System.out.println(list1); dao.delete("001"); List<User> list2 = dao.list(); System.out.println(list2); } }
14 GUI
14.1绘图
绘图原理:
- Component类提供了两个和绘图相关最重要的方法:
- paint(Graphics g)绘制组件的外观
- repaint()刷新组件的外观
- 当组件第一次在屏幕显示的时候,程序会自动的调用paint0方法来绘制组件,在以下情况paintQ将会被调用:
- 窗口最小化,再最大化
- 窗口的大小发生变化
- repaint函数被调用
Graphics类:
- 画直线 drawLine(int x1,int yl,int x2,int y2)
- 画矩形边框 drawRect(int x, int y, int width, int height)
- 画椭圆边框 drawOval(int x, int y, int width, int height)
- 填充矩形fillRect(int x, int y, int width, int height)
- 填充椭园 fillOval(int x, int y. int width, int height)
- 画图片 drawlmage(Image img, int x, int y, ...)
- 画字符串 drawString(String str, int x, inty)
- 设置画笔的字体 setFont (Font font)
- 设置画笔的颜色 setColor(Color c)
14.2 事件控制
java事件处理是采取“委派事件模型”。当事件发生时,产生事件的对象,会把此"信息"传递给"事件的监听者” 处理,这里所说的"信息"实际上就是 java.awt.event 事件类库里某个类所创建的对象,把它称为”事件的对象“
网络异常,图片无法展示|
基本介绍:
深入理解:
- 事件源:事件源是一个产生事件的对象,比如按钮,窗口等
- 事件:事件就是承载事件源状态改变时的对象,比如当键盘事件、鼠标事件、窗口事件等等,会生成一个事件对象,该对家保存着当前事件很多信息,比如KeyEvent 对象有含有被按下键的Gode值。java.awt.event包 和javax.swing.event包中定义了各种事
件类型 - 事件类型:
网络异常,图片无法展示| - 事件监听噐号接口:
(1)当事件源产生一个事件,可以传送给事件监听者处理Interfaces
(2) 事件监听者本质是一个类,该类实现了某个事件监听器接口比如前面我们案例中的MyPanle就是一个类,它实现了
KeyListener接口,它就可以作为一个事件监听者,对接受到的事件进行处理
(3)事件监听器接口有多种,不同的事件监听器接口可以监听不同的事件 一个类可以实现多个监听接口
(4)这些接口在java.awt.event包和javax.swing.event包中定义,列出常用的事件监听器接口,查看jdk 文档聚集了
15 线程(基础)
基本介绍:
- 线程由进程创建的,是进程的一个实体
- 一个进程可以拥有多个线程
- 单线程:同一个时刻,只允许执行一个线程
- 多线程:同一个时刻,可以执行多个线程
- 并发:同一个时刻,多个任务交替执行,造成一种“貌似同时”的错觉
- 并行:同一个时刻,多个任务同时执行,多核cpu可以实现并行
16.1 线程使用
16.1.1 继承Thread
public class Thread01 { public static void main(String[] args) throws InterruptedException { //创建Cat对象,可以当做线程使用 Cat cat = new Cat(); //源码 /* (1) public synchronized void start() { start0(); } (2) //start0() 是本地方法,是JVM调用, 底层是c/c++实现 //真正实现多线程的效果,是start0(), 而不是 run private native void start0(); */ cat.start();//启动线程-> 最终会执行cat的run方法 //cat.run();//run方法就是一个普通的方法, 没有真正的启动一个线程,就会把run方法执行完毕,才向下执行 //说明: 当main线程启动一个子线程 Thread-0, 主线程不会阻塞, 会继续执行 //这时 主线程和子线程是交替执行.. System.out.println("主线程继续执行" + Thread.currentThread().getName());//名字main for(int i = 0; i < 60; i++) { System.out.println("主线程 i=" + i); //让主线程休眠 Thread.sleep(1000); } } } //1. 当一个类继承了 Thread 类, 该类就可以当做线程使用 //2. 我们会重写 run方法,写上自己的业务代码 //3. run Thread 类 实现了 Runnable 接口的run方法 /* @Override public void run() { if (target != null) { target.run(); } } */ class Cat extends Thread { int times = 0; @Override public void run() {//重写run方法,写上自己的业务逻辑 while (true) { //该线程每隔1秒。在控制台输出 “喵喵, 我是小猫咪” System.out.println("喵喵, 我是小猫咪" + (++times) + " 线程名=" + Thread.currentThread().getName()); //让该线程休眠1秒 ctrl+alt+t try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } if(times == 80) { break;//当times 到80, 退出while, 这时线程也就退出.. } } } } 16.1.2 实现Runnable public class Thread02 { public static void main(String[] args) { Dog dog = new Dog(); //dog.start(); 这里不能调用start //创建了Thread对象,把 dog对象(实现Runnable),放入Thread Thread thread = new Thread(dog); thread.start(); // Tiger tiger = new Tiger();//实现了 Runnable // ThreadProxy threadProxy = new ThreadProxy(tiger); // threadProxy.start(); } } class Animal { } class Tiger extends Animal implements Runnable { @Override public void run() { System.out.println("老虎嗷嗷叫...."); } } //线程代理类 , 模拟了一个极简的Thread类 class ThreadProxy implements Runnable {//你可以把Proxy类当做 ThreadProxy private Runnable target = null;//属性,类型是 Runnable @Override public void run() { if (target != null) { target.run();//动态绑定(运行类型Tiger) } } public ThreadProxy(Runnable target) { this.target = target; } public void start() { start0();//这个方法时真正实现多线程方法 } public void start0() { run(); } } class Dog implements Runnable { //通过实现Runnable接口,开发线程 int count = 0; @Override public void run() { //普通方法 while (true) { System.out.println("小狗汪汪叫..hi" + (++count) + Thread.currentThread().getName()); //休眠1秒 try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } if (count == 10) { break; } } } }
对比:
- java的设计来看,通过继承Thread或者实现Runnable接口来创建线程本质上没有区别,从jidk帮助文档我们可以看到Thread类本身就实现了Runnable接口
- 实现Runnable接口方式更加适合多个线程共享一个资源的情况,并且避免了单继承的限制
线程退出:
public class ThreadExit_ { public static void main(String[] args) throws InterruptedException { T t1 = new T(); t1.start(); //如果希望main线程去控制t1 线程的终止, 必须可以修改 loop //让t1 退出run方法,从而终止 t1线程 -> 通知方式 //让主线程休眠 10 秒,再通知 t1线程退出 System.out.println("main线程休眠10s..."); Thread.sleep(10 * 1000); t1.setLoop(false); } } class T extends Thread { private int count = 0; //设置一个控制变量 private boolean loop = true; @Override public void run() { while (loop) { try { Thread.sleep(50);// 让当前线程休眠50ms } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("T 运行中...." + (++count)); } } public void setLoop(boolean loop) { this.loop = loop; } } • 代理模式模拟: public class Thread02 { public static void main(String[] args) { Dog dog = new Dog(); //dog.start(); 这里不能调用start //创建了Thread对象,把 dog对象(实现Runnable),放入Thread Thread thread = new Thread(dog); thread.start(); // Tiger tiger = new Tiger();//实现了 Runnable // ThreadProxy threadProxy = new ThreadProxy(tiger); // threadProxy.start(); } } class Animal { } class Tiger extends Animal implements Runnable { @Override public void run() { System.out.println("老虎嗷嗷叫...."); } } //线程代理类 , 模拟了一个极简的Thread类 class ThreadProxy implements Runnable {//你可以把Proxy类当做 ThreadProxy private Runnable target = null;//属性,类型是 Runnable @Override public void run() { if (target != null) { target.run();//动态绑定(运行类型Tiger) } } public ThreadProxy(Runnable target) { this.target = target; } public void start() { start0();//这个方法时真正实现多线程方法 } public void start0() { run(); } } class Dog implements Runnable { //通过实现Runnable接口,开发线程 int count = 0; @Override public void run() { //普通方法 while (true) { System.out.println("小狗汪汪叫..hi" + (++count) + Thread.currentThread().getName()); //休眠1秒 try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } if (count == 10) { break; } } } }
16.2 线程方法
常用方法1:
- setName:设置线程名称,使之与参数 name 相同
- getName:返回该线程的名称
- start:使该线程开始执行;Java 虚拟机底层调用该线程的 start0方法
- run :调用线程对象 run 方法
- setPriority:更改线程的优先级
- getPriority:获取线程的优先级
- sleep:在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)
- interrupt:中断线程
public class ThreadMethod01 { public static void main(String[] args) throws InterruptedException { //测试相关的方法 T t = new T(); t.setName("老韩"); t.setPriority(Thread.MIN_PRIORITY);//1 t.start();//启动子线程 //主线程打印5 hi ,然后我就中断 子线程的休眠 for(int i = 0; i < 5; i++) { Thread.sleep(1000); System.out.println("hi " + i); } System.out.println(t.getName() + " 线程的优先级 =" + t.getPriority());//1 t.interrupt();//当执行到这里,就会中断 t线程的休眠. } } class T extends Thread { //自定义的线程类 @Override public void run() { while (true) { for (int i = 0; i < 100; i++) { //Thread.currentThread().getName() 获取当前线程的名称 System.out.println(Thread.currentThread().getName() + " 吃包子~~~~" + i); } try { System.out.println(Thread.currentThread().getName() + " 休眠中~~~"); Thread.sleep(20000);//20秒 } catch (InterruptedException e) { //当该线程执行到一个interrupt 方法时,就会catch 一个 异常, 可以加入自己的业务代码 //InterruptedException 是捕获到一个中断异常. System.out.println(Thread.currentThread().getName() + "被 interrupt了"); } } } }
注意事项和细节:
- start:底层会创建新的线程,调用run,run 就是一个简单的方法调用,不会启动新线程
- interrupt:中断线程,但并没有真正的结束线程,所以一般用于中断正在休眠线程
- sleep:线程的静态方法,使当前线程休眠
常用方法2:
- yield:线程的礼让。让出Cpu,让其他线程执行,但礼让的时间不确定,所以也不一定礼让成功
- join:线程的插队。插队的线程一旦插队成功,则肯定先执行完插入的线程所有的任
public class ThreadMethod02 { public static void main(String[] args) throws InterruptedException { T2 t2 = new T2(); t2.start(); for(int i = 1; i <= 20; i++) { Thread.sleep(1000); System.out.println("主线程(小弟) 吃了 " + i + " 包子"); if(i == 5) { System.out.println("主线程(小弟) 让 子线程(老大) 先吃"); //join, 线程插队 //t2.join();// 这里相当于让t2 线程先执行完毕 Thread.yield();//礼让,不一定成功.. System.out.println("线程(老大) 吃完了 主线程(小弟) 接着吃.."); } } } } class T2 extends Thread { @Override public void run() { for (int i = 1; i <= 20; i++) { try { Thread.sleep(1000);//休眠1秒 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("子线程(老大) 吃了 " + i + " 包子"); } } }
用户线程和守护线程:
- 用户线程:也叫工作线程,当线程的任务执行完或通知方式结束
- 守护线程:一般是为工作线程服务的,当所有的用户线程结束,守护线程自动结束
setDaemon(True) - 常见的守护线程:垃圾回收机制
public class ThreadMethod03 { public static void main(String[] args) throws InterruptedException { MyDaemonThread myDaemonThread = new MyDaemonThread(); //如果我们希望当main线程结束后,子线程自动结束 //只需将子线程设为守护线程即可 myDaemonThread.setDaemon(true); myDaemonThread.start(); for( int i = 1; i <= 10; i++) {//main线程 System.out.println("辛苦的工作..."); Thread.sleep(1000); } } } class MyDaemonThread extends Thread { public void run() { for (; ; ) {//无限循环 try { Thread.sleep(1000);//休眠1000毫秒 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("快乐聊天,哈哈哈~~~"); } } }
16.3 线程生命周期
网络异常,图片无法展示|
周期图:
16.4 Synchronized⭐️
线程同步,即当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作,其他线程才能对该内存地址进行操作
线程同步机制:
基本语法:
//同步代码块 synchronized(对象) { // 得到对象的锁,才能操作同步代码 需要被同步代码; } //同步方法 public synchronized void m(String name){ 需要被同步代码; }
例:
public class SellTicket { public static void main(String[] args) { SellTicket03 sellTicket03 = new SellTicket03(); new Thread(sellTicket03).start();//第1个线程-窗口 new Thread(sellTicket03).start();//第2个线程-窗口 new Thread(sellTicket03).start();//第3个线程-窗口 } } //实现接口方式, 使用synchronized实现线程同步 class SellTicket03 implements Runnable { private int ticketNum = 100;//让多个线程共享 ticketNum private boolean loop = true;//控制run方法变量 Object object = new Object(); //同步方法(静态的)的锁为当前类本身 //老韩解读 //1. public synchronized static void m1() {} 锁是加在 SellTicket03.class //2. 如果在静态方法中,实现一个同步代码块. /* synchronized (SellTicket03.class) { System.out.println("m2"); } */ public synchronized static void m1() { } public static void m2() { synchronized (SellTicket03.class) { System.out.println("m2"); } } //老韩说明 //1. public synchronized void sell() {} 就是一个同步方法 //2. 这时锁在 this对象 //3. 也可以在代码块上写 synchronize ,同步代码块, 互斥锁还是在this对象 public /*synchronized*/ void sell() { //同步方法, 在同一时刻, 只能有一个线程来执行sell方法 synchronized (/*this*/ object) { if (ticketNum <= 0) { System.out.println("售票结束..."); loop = false; return; } //休眠50毫秒, 模拟 try { Thread.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("窗口 " + Thread.currentThread().getName() + " 售出一张票" + " 剩余票数=" + (--ticketNum));//1 - 0 - -1 - -2 } } @Override public void run() { while (loop) { sell();//sell方法是一共同步方法 } } }
16.5 互斥锁⭐️
基本介绍:
- Java语言中,引入了对象互斥锁的概念,来保证共享数据操作的完整性
- 每个对象都对应于一个可称为“互斥锁〞 的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象
- 关键字synchronized 来与对象的互斥锁联系。当某个对象用synchronized修饰时表明该对象在任一时刻只能由一个线程访问
- 同步的局限性:导致程序的执行效率要降低
- 同步方法(非静态的)的锁可以是this,也可以是其他对象(要求是同一个对象)
- 同步方法(静态的)的锁为当前类本身
注意事项和细节:
- 同步方法如果没有使用static修饰:默认锁对象为this
- 如果方法使用static修饰,默认锁对象:当前类.class
- 实现的落地步骤:
- 需要先分析上锁的代码
- 选择同步代码块或同步方法
- 要求多个线程的锁对象为同一个即可
public class DeadLock_ { public static void main(String[] args) { //模拟死锁现象 DeadLockDemo A = new DeadLockDemo(true); A.setName("A线程"); DeadLockDemo B = new DeadLockDemo(false); B.setName("B线程"); A.start(); B.start(); } } //线程 class DeadLockDemo extends Thread { static Object o1 = new Object();// 保证多线程,共享一个对象,这里使用static static Object o2 = new Object(); boolean flag; public DeadLockDemo(boolean flag) {//构造器 this.flag = flag; } @Override public void run() { //下面业务逻辑的分析 //1. 如果flag 为 T, 线程A 就会先得到/持有 o1 对象锁, 然后尝试去获取 o2 对象锁 //2. 如果线程A 得不到 o2 对象锁,就会Blocked //3. 如果flag 为 F, 线程B 就会先得到/持有 o2 对象锁, 然后尝试去获取 o1 对象锁 //4. 如果线程B 得不到 o1 对象锁,就会Blocked if (flag) { synchronized (o1) {//对象互斥锁, 下面就是同步代码 System.out.println(Thread.currentThread().getName() + " 进入1"); synchronized (o2) { // 这里获得li对象的监视权 System.out.println(Thread.currentThread().getName() + " 进入2"); } } } else { synchronized (o2) { System.out.println(Thread.currentThread().getName() + " 进入3"); synchronized (o1) { // 这里获得li对象的监视权 System.out.println(Thread.currentThread().getName() + " 进入4"); } } } } }
16.6 死锁
多个线程都占用了对方的锁资源,但不肯相让,导致了死锁,在编程是一定要避免死锁的发生
基本介绍:
释放锁:
- 当前线程的同步方法、同步代码块执行结束
- 当前线程在同步代码块、同步方法中遇到break、return
- 当前线程在同步代码块、同步方法中出现了未处理的Error或Exception,导致异常结束
- 当前线程在同步代码块、同步方法中执行了线程对象的wait0方法,当前线程暂停,井释放锁
不释放锁:
- 线程执行同步代码块或同步方法时,程序调用Thread.sleep()、Thread.yield()方法暂停当前线程的执行
- 线程执行同步代码块时,其他线程调用了该线程的suspendO方法将该线程挂起
16 IO流
16.0 文件
文件是保存数据的地方
基本介绍:
输入流:数据从数据源(文件)到程序(内存)的路径
输出流:数据从程序(内存)到数据源(文件)的路径
网络异常,图片无法展示|
文件流:
创建文件:
- new File(String pathname) //根据文件路径构建
- new File(File parent,String child) //根据父目录文件+子路径构建
- new File(String parent,String child) //根据父目录+子路径构建
public class FileCreate { public static void main(String[] args) { } //方式1 new File(String pathname) @Test public void create01() { String filePath = "/Users/henghengzhu/Downloads/代码/news1.txt"; File file = new File(filePath); try { file.createNewFile(); System.out.println("文件创建成功"); } catch (IOException e) { e.printStackTrace(); } } //方式2 new File(File parent,String child) //根据父目录文件+子路径构建 @Test public void create02() { File parentFile = new File("/Users/henghengzhu/Downloads/代码"); String fileName = "news2.txt"; //这里的file对象,在java程序中,只是一个对象 //只有执行了createNewFile 方法,才会真正的,在磁盘创建该文件 File file = new File(parentFile, fileName); try { file.createNewFile(); System.out.println("创建成功~"); } catch (IOException e) { e.printStackTrace(); } } //方式3 new File(String parent,String child) //根据父目录+子路径构建 @Test public void create03() { //String parentPath = "e:\\"; String parentPath = "/Users/henghengzhu/Downloads/"; String fileName = "news3.txt"; File file = new File(parentPath, fileName); try { file.createNewFile(); System.out.println("创建成功~"); } catch (IOException e) { e.printStackTrace(); } } }
常用方法:
- file.getName()
- file.getAbsolutePath()
- file.getParent()
- file.exists()
- file.isFile()
- file.isFile()
- isDirectory()
@Test public void info() { //先创建文件对象 File file = new File("e:\\news1.txt"); //调用相应的方法,得到对应信息 System.out.println("文件名字=" + file.getName()); //getName、getAbsolutePath、getParent、length、exists、isFile、isDirectory System.out.println("文件绝对路径=" + file.getAbsolutePath()); System.out.println("文件父级目录=" + file.getParent()); System.out.println("文件大小(字节)=" + file.length()); System.out.println("文件是否存在=" + file.exists());//T System.out.println("是不是一个文件=" + file.isFile());//T System.out.println("是不是一个目录=" + file.isDirectory());//F }
16.1 IO流原理及分类
基本介绍:
- I/O是Input/Output的缩写,I/O技术是非常实用的技术,用于处理数据传输
- Java程序中,对于数据的输入/输出操作以"流(stream)"的方式进行
- java.io包下提供了各种"流”类和接口,用以获取不同种类的数据,并通过方法输入或输出数据
- 输入input:读取外部数据(磁盛、光盘等存储设备的数据)到程序(内存)中
- 输出output:将程序(内存)数据输出到磁盘、光盘等存储设备中
流的分类:
- 按操作数据单位不同分为:字节流(8 bit),字符流(按字符)
- 按数据流的流向不同分为:输入流,输出流
- 按流的角色的不同分为:节点流,处理流/包装流
网络异常,图片无法展示|
网络异常,图片无法展示|
16.2 节点流和处理流
基本介绍:
- 节点流可以从一个特定的数据源读写数据,如FileReader、FileWriter
- 处理流(也叫包装流)是 “连接〞在已存在的流(节点流或处理流)之上,为程序提供更为强大的读写功能,如BufferedReader、BufferedWriter
网络异常,图片无法展示|
区别和联系:
- 节点流是底层流/低级流,直接跟数据源相接
- 处理流包装节点流,既可以消除不同节点流的实现差异,也可以提供重方便的方法来完成输入输出
- 处理流(也叫包装流)对节点流进行包装,使用了修饰器设计模式,不会直接与数据源相连
优点:
- 性能的提高:主要以增加缓冲的方式来提高输入输出的效率
- 操作的便捷:处理流可能提供了一系列便捷的方法来一次输入输出大批量的数据,使用重加灵活方便
模拟:
public class Test_ { public static void main(String[] args) { BufferedReader_ bufferedReader_ = new BufferedReader_(new FileReader_()); bufferedReader_.readFiles(10); BufferedReader_ bufferedReader_2 = new BufferedReader_(new StringReader_()); bufferedReader_2.readString(5); } }
16.3 输入流⭐️
基本介绍:
网络异常,图片无法展示
|
网络异常,图片无法展示
|
16.3.1 InputStream
16.3.1.1 FileInputStream
网络异常,图片无法展示|
常用方法:
使用方法:
public class FileInputStream_ { public static void main(String[] args) { } /** * 演示读取文件... * 单个字节的读取,效率比较低 * -> 使用 read(byte[] b) */ @Test public void readFile01() { String filePath = "e:\\hello.txt"; int readData = 0; FileInputStream fileInputStream = null; try { //创建 FileInputStream 对象,用于读取 文件 fileInputStream = new FileInputStream(filePath); //从该输入流读取一个字节的数据。 如果没有输入可用,此方法将阻止。 //如果返回-1 , 表示读取完毕 while ((readData = fileInputStream.read()) != -1) { System.out.print((char)readData);//转成char显示 } } catch (IOException e) { e.printStackTrace(); } finally { //关闭文件流,释放资源. try { fileInputStream.close(); } catch (IOException e) { e.printStackTrace(); } } } /** * 使用 read(byte[] b) 读取文件,提高效率 */ @Test public void readFile02() { String filePath = "e:\\hello.txt"; //字节数组 byte[] buf = new byte[8]; //一次读取8个字节. int readLen = 0; FileInputStream fileInputStream = null; try { //创建 FileInputStream 对象,用于读取 文件 fileInputStream = new FileInputStream(filePath); //从该输入流读取最多b.length字节的数据到字节数组。 此方法将阻塞,直到某些输入可用。 //如果返回-1 , 表示读取完毕 //如果读取正常, 返回实际读取的字节数 while ((readLen = fileInputStream.read(buf)) != -1) { System.out.print(new String(buf, 0, readLen));//显示 } } catch (IOException e) { e.printStackTrace(); } finally { //关闭文件流,释放资源. try { fileInputStream.close(); } catch (IOException e) { e.printStackTrace(); } } } }
16.3.1.2 BufferedInputStream
Bufferedlnputstream是字节流在创建 Bufferedlnputstream时,会创建一个内部缓沖区数组
基本介绍:
网络异常,图片无法展示|
常用方法:
例:
public class BufferedCopy02 { public static void main(String[] args) { // String srcFilePath = "e:\\Koala.jpg"; // String destFilePath = "e:\\hsp.jpg"; // String srcFilePath = "e:\\0245_韩顺平零基础学Java_引出this.avi"; // String destFilePath = "e:\\hsp.avi"; String srcFilePath = "e:\\a.java"; String destFilePath = "e:\\a3.java"; //创建BufferedOutputStream对象BufferedInputStream对象 BufferedInputStream bis = null; BufferedOutputStream bos = null; try { //因为 FileInputStream 是 InputStream 子类 bis = new BufferedInputStream(new FileInputStream(srcFilePath)); bos = new BufferedOutputStream(new FileOutputStream(destFilePath)); //循环的读取文件,并写入到 destFilePath byte[] buff = new byte[1024]; int readLen = 0; //当返回 -1 时,就表示文件读取完毕 while ((readLen = bis.read(buff)) != -1) { bos.write(buff, 0, readLen); } System.out.println("文件拷贝完毕~~~"); } catch (IOException e) { e.printStackTrace(); } finally { //关闭流 , 关闭外层的处理流即可,底层会去关闭节点流 try { if(bis != null) { bis.close(); } if(bos != null) { bos.close(); } } catch (IOException e) { e.printStackTrace(); } } } }
16.3.1.3 ObjectInputStream
基本介绍:
- Objectlnputstream 提供反序列化功能
- 反序列化就是在恢复数据时,恢复数据的值和数据类型
- 需要让某个对象支持序列化机制,则必须让其类是可序列化的,为了让某个类是可序列化的,该类必须实现如下两个接口之一:Serializable和Externalizable
例:
public class ObjectInputStream_ { public static void main(String[] args) throws IOException, ClassNotFoundException { //指定反序列化的文件 String filePath = "e:\\data.dat"; ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filePath)); //解读 //1. 读取(反序列化)的顺序需要和你保存数据(序列化)的顺序一致 //2. 否则会出现异常 System.out.println(ois.readInt()); System.out.println(ois.readBoolean()); System.out.println(ois.readChar()); System.out.println(ois.readDouble()); System.out.println(ois.readUTF()); //dog 的编译类型是 Object , dog 的运行类型是 Dog Object dog = ois.readObject(); System.out.println("运行类型=" + dog.getClass()); System.out.println("dog信息=" + dog);//底层 Object -> Dog //这里是特别重要的细节: //1. 如果我们希望调用Dog的方法, 需要向下转型 //2. 需要我们将Dog类的定义,放在到可以引用的位置 Dog dog2 = (Dog)dog; System.out.println(dog2.getName()); //旺财.. //关闭流, 关闭外层流即可,底层会关闭 FileInputStream 流 ois.close(); } }
16.3.2 Reader
16.3.2.1 FileReader
常用方法:
- read:每次读取单个字符,返回该字符,如果到文件末尾返回-1
- read(char):批量读取多个字符到数组,返回读取到的字符数,如果到文件末尾返回-1
方法使用:
public class FileReader_ { public static void main(String[] args) { } /** * 单个字符读取文件 */ @Test public void readFile01() { String filePath = "e:\\story.txt"; FileReader fileReader = null; int data = 0; //1. 创建FileReader对象 try { fileReader = new FileReader(filePath); //循环读取 使用read, 单个字符读取 while ((data = fileReader.read()) != -1) { System.out.print((char) data); } } catch (IOException e) { e.printStackTrace(); } finally { try { if (fileReader != null) { fileReader.close(); } } catch (IOException e) { e.printStackTrace(); } } } /** * 字符数组读取文件 */ @Test public void readFile02() { System.out.println("~~~readFile02 ~~~"); String filePath = "e:\\story.txt"; FileReader fileReader = null; int readLen = 0; char[] buf = new char[8]; //1. 创建FileReader对象 try { fileReader = new FileReader(filePath); //循环读取 使用read(buf), 返回的是实际读取到的字符数 //如果返回-1, 说明到文件结束 while ((readLen = fileReader.read(buf)) != -1) { System.out.print(new String(buf, 0, readLen)); } } catch (IOException e) { e.printStackTrace(); } finally { try { if (fileReader != null) { fileReader.close(); } } catch (IOException e) { e.printStackTrace(); } } } }