TreeSet比较器排序
//自定义类实现Comparator接口,重写compare方法
public class MyComparator implements Comparator<Student> {
@Override
public int compare(Student s1, Student s2) {
//主要条件:
// 按照学生的年龄从小到大排序
int num = s1.getAge() - s2.getAge() ;
//次要条件:年龄相同的人,按照姓名进行比较:(字典顺序比较) //自定义类使用该compareTo方法需重写equals、hashCode方法
int num2 = (num==0)?(s1.getName().compareTo(s2.getName())):num ;
return num2;
}
}
//使用比较器排序完成
//public TreeSet(Comparator<E> comparator)创建TreeSet集合使用比较器进行排序
//需要存储的类型实现Comparator接口中的int compare(T o1,T o2),T--Type类型,需要Compartor中的泛型一致
public class TreeSetDemo {
public static void main(String[] args) {
//创建TreeSet集合对象
方案1:自定义一个类 实现Comparator接口
//接口多态/创建具体的子实现类对象
MyComparator my = new MyComparator() ;
TreeSet<Student> ts = new TreeSet<>(my) ;
方案2:参数就是接口匿名内部类
TreeSet<Student> ts = new TreeSet<>(new Comparator<Student>() {
@Override
public int compare(Student s1, Student s2) {
int num = s1.getAge() - s2.getAge() ;
//次要条件:年龄相同的人,按照姓名进行比较:(字典顺序比较)
int num2 = (num==0)?(s1.getName().compareTo(s2.getName())):num ;
return num2;
}
}) ;
//创建学生对象,测试
Student s1 = new Student("gaoyuanyuan",44) ;
Student s2 = new Student("zhangjianing",44) ;
Student s3 = new Student("wenzhang",35) ;
ts.add(s1) ;
ts.add(s2) ;
ts.add(s3) ;
//遍历
for(Student s:ts){
System.out.println(s) ;
}
}
}
//学生类
public class Student {
private String name ;
private int age ;
// ......
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
if (age != student.age) return false;
return name.equals(student.name);
}
@Override
public int hashCode() {
int result = name.hashCode();
result = 31 * result + age;
return result;
}
}
Map集合
Map<K,V> :一个键对应一个值(键值映射),Map集合针对键必须唯一,值是可以重复的
K Value
1 张三
2 王五
Map集合的基本功能:
//创建Map实现子类
Map<String,String> map = new HashMap<>() ;
1、添加:V put(K key,V value) :添加方法,通过返回值是否为null,判断键是否重复,如果不重复,返回值永远是null;如果键重复,将第一次存储键对应的值返回,将后面键对应的值进行存储
map.put("邓超","孙俪") ;
map.put("文章","马伊琍") ;//被替换
map.put("文章","姚笛") ;//返回“马伊琍”,将“姚笛”加入
2、 void clear():清空Map集合的所有键值对
map.clear();
3、 V remove(Object key):删除Map集合的指定键,然后返回被删除的值
map.remove("文章");//删除该键,返回“yaod”
4、 boolean containsKey(Object key):是否包含指定的键
map.containsKey("邓超"); //true,存在
5、boolean containsValue(Object value):是否包含指定的值
map.containsValue("孙俪"); //true,存在
6、 boolean isEmpty():判断Map集合是否为空
map.isEmpty();// true
Map集合遍历方式
Map<String,String> map = new HashMap<>();
map.put("杨过","小龙女") ;
map.put("郭靖","黄蓉") ;
1、Set<K> keySet()获取Map集合的所有的键的集合
//V get(Object key):通过键获取值
Set<String> keySet = map.keySet();
for(String key : keySet){
String value = map.get(key);
System.out.println(key + ":" +get(value);
}
2、 Set<Map.Entry<K,V>> entrySet() 获取所有键值对对象
//K getKey()获取键
//V getValue()
Set<Map.Entry<String, String>> entrySet = map.entrySet();
for(Map.Entry<String,String> en:entrySet){
String key = en.getKey();
String value = en.getValue();
System.out.println(key+"="+value) ;
}
面试题:Map集合和Collection集合的区别?
1)集合类型的区别
Collection单例集合,只能存储一种引用类型数据
Map,双列集合,可以存储键值对Key,Value,两种引用类型数据
2)遍历方式的区别
Collection--->使用自己的专有遍历方式:迭代器---增强for来代替迭代器
Map:1、Set<K> keySet()--->增强for遍历所有的键---->V get(Object key)
2、Set<Map.entry<K,V>> etnrySet()--->遍历键值对对象--->K getKey(), V getValue()
3)间接有一定关系
Collection里面Set接口的两个子实现类,HashSet和TreeSet都依赖于Map接口中两个子实现类
HashMap和TreeMap---put方法有关系
HashMap嵌套ArrayList,及嵌套遍历
HashMap<String, ArrayList<String>> hm = new HashMap<>() ;
//第一个子集合
ArrayList<String> array1 = new ArrayList<>() ;
array1.add("宋江") ;
array1.add("晁盖") ;
//将第一个ArrayList集合作为值添加到HashMap集合中
hm.put("水浒传",array1) ;
//第二个子集合
ArrayList<String> array2 = new ArrayList<>() ;
array2.add("刘备") ;
array2.add("曹操") ;
hm.put("三国演义",array2) ;
//遍历大集合
//获取所有的键
Set<String> keySet = hm.keySet();
//遍历所有键
for(String key:keySet){
System.out.println(key+"\t") ;
//通过键获取值---ArrayList<String>
ArrayList<String> keyValue = hm.get(key);
//嵌套遍历ArrayList
for(String value:keyValue){
System.out.println("\t"+value);
}
}
HashMap
hashMap--->底层哈希表实现,put方法依赖hashCode和equals方法,
Map针对键有效,要针对键的类型自定类型,自定义类必须重写equals和hashCode(),保证键唯一
HashMap<Student,String> hm = new HashMap<>() ;
Student s1 = new Student("唐伯虎",35) ;
hm.put(s1,"明朝") ;
//遍历
Set<Student> keySet = hm.keySet();
for(Student s:keySet){
String value = hm.get(s);
System.out.println(s.getName()+s.getAge()+value);
}
TreeMap
TreeMap<K,V><存储键值对元素,保证键唯一的同时,还有排序
使用哪种排序,取决于使用构造方法:
一、自然排序,无参构造方法:public TreeMap():当前键的类型必须实现Compareable接口,重写comeable方法
二、选择器排序:有参构造方法,传入比较器
//使用比较器排序实现
TreeMap<Student,String> tm = new TreeMap<>(new Comparator<Student>() { //匿名内部类
@Override
public int compare(Student s1, Student s2) {
// 按照学生姓名的长度,从小到排序
int num = s1.getName().length() - s2.getName().length() ;
//次要条件:长度相同的,不一定姓名内容相同
int num2 = (num==0)?(s1.getName().compareTo(s2.getName())):num ;
return num2 ;
}
}) ;
Collections:针对集合操作工具类
java.util.Collections:针对集合操作工具类。提供一些常用的方法:
List<Integer> list = new ArrayList<>();
list.add(15);
list.add(10);
list.add(3);
1、binarySearch 二分搜索在List中查询元素 (不管集合还是Arrays---->binaraySearch方法,(集合或者数组必须有序))
public static <T> int binarySearch(List<? extends Comparable<? super T>> list,T key):
int index2 = Collections.binarySearch(list, 15);
2、max 获取Colection中的最大值 public static <T extends Object & Comparable<? super T>> T max(Collection<? extends T> coll)
Integer max = Collections.max(list);
3、min 获取Colection中的最小值 public static <T extends Object & Comparable<? super T>> T min(Collection<? extends T> coll)
Integer max = Collections.min(list);
4、sort 针对List集合进行自然排序 public static <T extends Comparable<? super T>> void sort(List<T> list):针对List集合进行自然排序
Collections.sort(list);
5、 public static <T> void sort(List<T> list,Comparator<? super T> c):针对List集合比较器排序。
6、public static void shuffle(List<?> list):针对List集合随机置换
7、 public static <T> List<T> synchronizedList(List<T> list):将线程不安全ArrayList,变成线程安全的列表
集合应用:使用集合"模拟斗地主"
使用集合模拟斗地主,创建牌盒,洗牌,发牌,看牌,保证每个人手上的牌有序
public class PokerTest {
public static void main(String[] args) {
//1)创建牌盒HashMap<Integer,String>
HashMap<Integer,String> hm = new HashMap<>() ;
//创建ArrayList集合单独存储的牌的编号
ArrayList<Integer> array = new ArrayList<>() ;
//2)装牌
//点数数组
String[] numbers = {"3","4","5","6","7","8","9","10","J","Q","K","A","2"} ;
//花色数组
String[] colors = {"♥","♠","♣","♦"} ;
//定义一个索引值,记录牌的编号
int index = 0 ;
for(String number:numbers){
for(String color:colors){
String poker = color.concat(number);
//将编号和牌添加大集合中
hm.put(index,poker) ;
//将index添加ArrayList集合中
array.add(index) ;
index++ ;
}
}
//添加小王,大王
hm.put(index,"小王");
array.add(index) ;
index++ ;
//添加大王
hm.put(index,"大王") ;
array.add(index) ;
//洗牌:洗的是ArrayList那个编号
Collections.shuffle(array) ;
//发牌:发的也是编号,为了保证每个人上手的牌有序,使用TreeSet
TreeSet<Integer> player1 = new TreeSet<>() ;
TreeSet<Integer> player2 = new TreeSet<>() ;
TreeSet<Integer> player3 = new TreeSet<>() ;
TreeSet<Integer> diPai = new TreeSet<>() ;
//循环:获取所有的牌的编号
for(int x = 0 ; x < array.size(); x++){
if(x >= array.size()-3){
//底牌
diPai.add(array.get(x)) ;
}else if(x % 3 == 0){
//玩家1的
player1.add(array.get(x)) ;
}else if(x % 3 == 1){
//玩家2
player2.add(array.get(x)) ;
}else if(x % 3 == 2){
//玩家3
player3.add(array.get(x)) ;
}
}
//调用看牌的功能
lookPoker("李国栋",player1,hm) ;
lookPoker("刘一帆",player2,hm) ;
lookPoker("梁飞虎",player3,hm) ;
lookPoker("底牌",diPai,hm) ;
}
//定义看牌的功能
public static void lookPoker(String name,TreeSet<Integer> ts,HashMap<Integer,String > hm){
System.out.print(name+"的牌是:\t") ;
//遍历TreeSet集合:获取到每一个玩家的编号
for(Integer key:ts){
//在hm大集合中去通过键获取值
String value = hm.get(key);
System.out.print(value+" ");
}
System.out.println();
}
线程
进程和线程的概念
线程是依赖于进程的,没有进程就没有线程
1、进程
在计算机的任务管理查看到此电脑上所有的客户端的进行
概念:
调用"系统资源"的独立单位
开启一个进程----创建 "系统资源"
所有的计算机,包括我们"jvm",都支持多进程,多进程的意义?
一句话:提高CPU的使用率
在打游戏的同时,听音乐,---同时开启了两个进程,是同时进行的吗?
并不是同时的,CPU(时间片),一点点时间片可以高效在两个进制来回切换
2、线程:
是进程中的最小执行单元(理解为,进程中的某个任务)
举例:360进程开启之后,可以杀毒/清理/电脑瘦身等等
多线程:在一个程序的过程中,其执行路径有多条,线程的执行具有"随机性"
多线程的意义?
多个线程"共享同一个资源",互相抢占CPU执行权,从而达到线程的执行速度
举例 1 v 3 打篮球,3个人抢占的篮球的几率大,可能这1个人也能抢占大篮球
面试题:jvm是多线程的吗、Java能创建线程吗
1、jvm是多线程的吗?
是多线程;至少有两条件线程
1)main---"用户线程"
2)垃圾回收线程,回收没有更多的引用对象,从而释放空间;如果没有
垃圾回收线程,Java中不断的去new,不断的堆内存空间---->导致OOM(Out Of Memory:内存溢出)
2、Java能创建线程吗?
Java不能够直接创建资源的,间接的提供了一个Thread
创建线程的方式
创建线程的方式1:
1)自定义一个类 继承Thread类
2)重写Thread类的run方法
3)在main()用户线程中,创建当前线程了对象
4)启动线程--->start()不是run方法,run方法是一个普通方法
//1、自定义一个类 继承Thread类
public class MyThread extends Thread{
@Override
public void run() {//重写run方法
for(int x = 0 ; x < 200 ; x ++){
//public final String getName()获取线程的名称
System.out.println(getName()+":"+x) ;
}}}
//2、创建当前线程了对象、启动线程
public class ThreadDemo {
public static void main(String[] args) {
//在main()用户线程中,创建当前线程了对象
MyThread my = new MyThread() ;//第一个线程对象
MyThread my2 = new MyThread() ;//第二个线程对象
my.setName("高圆圆") ;//setName()方法,设置线程名称
my2.setName("李纪奔");
//启动线程
//my.run();用线程对象将普通run方法,并不会出现线程的执行随机性
my.start();
//my.start() ;//IllegalThreadStateException:非法线程状态异常,一个线程只能启动一次
my2.start();
}}
线程的创建方式2:
1)自定义一个类,实现Runnable接口
2)重写Runnable接口的run方法
3)在用户线程main中去创建当前类对象--->"资源类"
4)创建Thread类对象,将3)资源类作为Thread类对象的参数进行传递
public class ThreadDemo {
public static void main(String[] args) {
//创建MyRunnable类对象
MyRunnable my = new MyRunnable() ;
//创建线程Thread类对象,将资源类作为参数传递
//public Thread(Runnable target,String name)
Thread t1 = new Thread(my,"t1") ;
Thread t2 = new Thread(my,"t2") ;
//启动线程
t1.start() ;
t2.start() ;
}}
//自定义一个类,实现Runnable接口
public class MyRunnable implements Runnable{
@Override
public void run() {
//完成耗时的操作
//两个线程都要进来
for(int x = 0 ; x < 200 ; x ++){
//Thread类静态方法
//public static Thread currentThread():获取正在执行的线程
System.out.println(Thread.currentThread().getName()+":"+x) ;
}}}
线程案例:龟兔赛跑
乌龟和兔子使用同一个赛道---"体现数据共享"
public class Race implements Runnable{
//定义一个静态的String类型变量:代表胜利者
private static String winner ;
@Override
public void run() {
//for循环0-199---x:代表步数
for(int x = 0 ; x < 100 ; x++){
//如果当前线程名称是"兔子",并且每10步睡眠一下 让他睡眠
if("兔子".equals(Thread.currentThread().getName()) && (x%10==0)){
try {
Thread.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+"跑了---->"+x+"步");
boolean flag = isGameOver(x) ;//x就是的步数
if(flag){
//true:表示比赛结束
break;
}
}
}
//定义一个方法:比赛是否结束的方法
public boolean isGameOver(int step){//上面的步数
if(winner!=null){
//已经存在胜利者
return true ;
}//局部代码块
{
if(step >=99){
//跑完了
winner = Thread.currentThread().getName() ;
System.out.println("winner is"+winner) ;
return true ;
}
}
return false ;
}
public static void main(String[] args) {
//创建赛道---资源类对象
Race race = new Race() ;
//创建两个线程
Thread t1 = new Thread(race,"兔子") ;
Thread t2 = new Thread(race,"乌龟") ;
//启动线程
t1.start() ;
t2.start() ;
}
}
线程礼让yield、线程 插队join()
public static void yield():线程礼让(暂停当前正在执行的线程,执行其他线程)
案例:
public class YieldThread extends Thread{
@Override
public void run() {
for(int x = 0 ; x < 100 ;x++){
System.out.println(getName()+":"+x);
yield() ;//每一个线程在执行这个方法的时候,都要暂停当前自己的线程,执行其他线程
}
}
}
public final void join() throws InterruptedException
因为线程的执行具有随机性,哪个线程启动之后,调用join(),正常理想情况,等待该线程执行完毕,执行其他线程
案例:
jt1.start();
try {
jt1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
jt2.start();
try {
jt2.join();//等待该线程终止
} catch (InterruptedException e) {
e.printStackTrace();
}
jt3.start();
join()和yield()的区别
join()
当前线程终止(线程调用join()方法会让当前线程执行完毕)
yield()
线程礼让,暂停正在执行的线程,并执行其他线程
解决多线程安全问题-synchronized-买票案例
校验多线程安全问题的标准 (解决方案)
1)检查程序是否是多线程环境 :是多线程卖票
2)是否存在共享数据 : 也存在的而且存在100张票
3)是否存在多条语句对共享数据的操作:也存在,判断tickets以及完成--
Java提供了同步代码块,解决上面3),使用同步代码块将多条语句对象共享数据的包起来
Java的同步机制
synchronized(锁对象){ //锁对象可以是任意的Java类对象,每一个线程都是用"同一把锁"
多条语句对共享数据的操作
}
什么是同步方法?
如果一个成员方法中进来就是一个synchronized(锁对象){}----同步代码块,将synchronized提到方法声明上
默认的锁对象this
如果是静态的同步方法,锁对象是 "当前类名.class"--跟"反射"有关系
public class SellTicket implements Runnable{
private static int tickets = 100 ;
//创建一把锁
private Object obj = new Object() ;
//定义一个统计变量
int x = 0 ;
@Override
public void run() {
//模拟一直有票
while(true){
if(x% 2==0){
//synchronized (obj) {
//synchronized (this) {//当前类对象的地址值引用
synchronized (SellTicket.class) {//当前类名.class:获取到当前类的字节码文件对象
if (tickets > 0) {
//模拟网络延迟
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
} System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets--) + "张票");
}
}
}else {
//x变量不能被2整除
sell() ; //同步方法
}
x++ ;
}
}
//同步方法:如果一个成员方法中进来就是一个synchronized(锁对象){}----同步代码块,将synchronized提到方法声明上
//同步方法的锁对象:是this---当前类对象的地址值引用
//如果是静态的同步方法,锁对象是 "当前类名.class"
// public synchronized void sell(){
public static synchronized void sell(){//静态是随着类的加载而加载 ,静态的同步方法的锁对象是"类名.class"
if (tickets > 0) {
//模拟网络延迟
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets--) + "张票");
}
}
}
死锁
public class MyLock {
// 锁对象:可以是任意Java类对象
public static Object objA = new Object() ;
public static Object objB = new Object() ;
}
public class DieLock implements Runnable {
private boolean flag ;
public DieLock(boolean flag){
this.flag = flag ;
}
@Override
public void run() {
if(flag){
synchronized (MyLock.objA){
System.out.println("if ObjA...");
synchronized (MyLock.objB){
System.out.println("if ObjB...");
}
}
}else {
synchronized (MyLock.objB){
System.out.println("else ObjB...");
synchronized (MyLock.objA){
System.out.println("else objA...");
} } }}
生产者-消费者模式-解决线程死锁问题
生产者不断产生数据,当他没有数据了,等待产生数据之后,自己等待wait,notify通知(唤醒)消费者所在的线程来使用数据
消费者不断使用数据,当他有数据了,等待先使用数据,使用完毕没有数据了,自己等待wait,notify通知(唤醒)生产者所在的线程来产生数据
//测试类
public class ThreadDemo {
public static void main(String[] args) {
//创建学生对象,锁对象
Student s = new Student() ;
//创建资源类
SetThread st = new SetThread(s) ;
GetThread gt = new GetThread(s) ;
Thread t1 = new Thread(st) ;
Thread t2 = new Thread(gt) ;
t1.start() ;
t2.start() ;
}
//学生类(锁对象)
public class Student {
String name ; //姓名
int age ; //年龄
boolean flag ; //false,没有数据,true,有数据!
}
//生产者所在的资源类
public class SetThread implements Runnable{
private Student s ;
public SetThread(Student s){
this.s = s ;
}
private int x = 0 ;//统计变量
@Override
public void run() {
while(true){
synchronized (s){ //锁对象,任意Java类对象
if(s.flag){
//没有数据,先产生数据,等待消费使用数据
//线程等待--Object类---->wait()
try {
s.wait(); //wait方法调用,立即释放锁
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if(x % 2==0){
s.name = "高圆圆" ;
s.age = 44 ;
}else {
s.name = "李国栋";
s.age = 23 ;
}
//如果有数据了,通知(唤醒)消费者资源来,赶紧来消费(改变信号)
s.flag = true ;
s.notify() ;
}
x ++ ;
}}}
//消费者资源类
public class GetThread implements Runnable{
//声明学生变量
private Student s ;
public GetThread(Student s){
this.s = s ;
}
@Override
public void run() {
while(true){
synchronized (s){
if(!s.flag){
//有数据了,等待先使用这个数据
try {
s.wait();//wait方法调用,立即释放锁
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(s.name.concat(""+s.age));
//当flag是一个false,没有数据了,通知(唤醒)生产者所在的线程,来产生数据!
s.flag = false ;
s.notify();
}}}}
面试题: sleep和wait方法区别?
1)使用区别:
sleep()单独使用,里面传入超时时间,
wait()不能单独用,必须在同步代码块/同步方法中使用,否则,就会出现 java.lang.IllegalMonitorStateException
2)来源不同
sleep(long time)线程睡眠,来源于Thread类中,wait()来源于Object类中
3)导致线程状态不同
sleep(long time)参数出入的超时时间,时间到达,线程睡眠接收,线程会继续执行,在休眠过程中,
线程处于的状态是TIMED_WAITTING(超时等待)
wait()
线程进入等待状态,如果没有锁对象调用notify()或则notifyAll(),线程状态就进入到WAITTING(死死等待)
4)是否主动唤醒
sleep(long time):时间到达之后,自动唤醒(主动唤醒),而wait(),被动唤醒,需要使用notify()或者notifyAll进行唤醒
5)是否会释放锁
sleep()方法不会去释放锁,wait()方法调用会立即释放锁
线程池
线程的好处:
1)提高了线程使用率
2)大大减少资源开销
4)可以通过一些参数(7大参数)进行线程池的调优,到达一些效果
弊端:
维护成本大
可以通过线程池---->
Executors工厂类提供很多静态方法
public static ExecutorService newFixedThreadPool(int nThreads):创建固定的可重用的线程数的线程池
ExecutorService子实现类=--- TreadPoolExecutor完成线程池初始化
如果涉及到定期让线程进行活动(public static ScheduledExecutorService newScheduledThreadPool)
面试题 wait()/notify()并非Thread类中为什么Object的?
wait()线程等待--->底层域wait(long time),Java的本地方法(底层语言操作),使用monitor(监视器锁)来调用wait方法(本质就是synchronized同步锁)
notify()/notifyAll():唤醒单个线程以及唤醒所有线程,都是锁对象的方法,而锁对象可以是任意Java类对象,不会定义在Thread类中,而是在Object类中定义的
Lock锁
jdk5以后提供了具体的"锁定操作"
java.util.concurrent.locks.Lock接口---->可重入的互斥锁ReentrantLock
提供一些方法,可以获取锁,可以去释放锁
void lock() 获取锁 void unlock()释放锁
//使用案例
//对象可以是任意Java类对象,现在jdk提供Lock具体的锁----ReetrantLock子实现类
private Lock lock = new ReentrantLock() ;
@Override
public void run() {
//模拟一直有票
while(true){
try{
//获取锁
// void lock() 获取锁
// synchronized (自定义一个锁){}
lock.lock();
if(tickets>0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在出售第"+(tickets--)+"张票");
}
}finally {
//finally除非一种特例:在执行finally语句之前,jvm退出 System.exit(0) ; ,任何情况下,finally语句一定会执行!
//使用完,释放锁
lock.unlock();
}}}}
设计模式-代理模式-静态代理
静态代理:
最大特点:代理角色和真实角色必须实现同一个接口
代理角色:帮助真实角色完成一些事情
真实角色:专注于自己的事情
public class StaticProxyDemo {
public static void main(String[] args) {
//测试真实角色
//接口多态/创建具体的子类
//创建WeddingCompany类对象
You you2 = new You() ;
WeddingCompany weddingCompany = new WeddingCompany(you2) ; //类似 ---Thread t1 = new Thread(MyRunnable对象,线程名称);
weddingCompany.mary() ; //类似----t1.start() ; jvm调用run
}
}
//结婚的接口
interface Mary{
void mary() ;
}
//真实角色
class You implements Mary{
@Override
public void mary() {
System.out.println("要结婚了,很开心...");
}
}
//婚庆公司:代理角色
class WeddingCompany implements Mary{
//声明Mary接口变量
private Mary mary ; //---类似Thread类的private Runnable target;
public WeddingCompany(Mary mary){ //类似 Thread(Runnable target,String name)
this.mary = mary ;
}
@Override
public void mary() {
if(mary!=null){
System.out.println("婚庆公司,要布置婚礼现场!");
//真实角色完成它自己的事情
mary.mary();
System.out.println("婚庆公司接收尾款!");
}
}
}
线程的生命周期
Thread类中有内部枚举类:
enum State{
NEW,新建状态
RUNNABLE,运行状态
BLOCKED,线程阻塞状态
WAITTING,死死等待,
TIMED_WAITTING,超时等待
TERMINATED,线程终止/线程死亡
}
线程池
线程池:
可以通过Executors工厂类(当前类构造方法私有化,对外提供静态方法--->简单工厂模式)提供一些创建线程池的功能
可以创建固定的可重用的线程数的线程池
public static ExecutorService newFixedThreadPool(int nThreads):参数为当前的线程数量
上面的返回值ExecutorService接口,提供一些方法
Future<?> submit(Runnable task)提交异步任务
<T> Future<T> submit(Callable<T> task):提交异步任务
void shutdown():关闭线程池
public class ExecutorsDemo {
public static void main(String[] args) {
//创建线程池---创建固定的可重用的线程数的线程池
// public static ExecutorService newFixedThreadPool(int nThreads):参数为当前的线程数量
ExecutorService threadPool = Executors.newFixedThreadPool(2);
//提交异步任务:展示两个线程并发执行的,不做计算,submit不需要返回值结果
// submit(Runnable task)提交异步任务
// MyRunnable my = new MyRunnable() ;
// threadPool.submit(my) ;
// threadPool.submit(my) ;
// <T> Future<T> submit(Callable<T> task):提交异步任务 ,不做异步任务的计算,不需要返回结果
MyCallable myCallable = new MyCallable() ;
threadPool.submit(myCallable) ;
threadPool.submit(myCallable) ;
// void shutdown():关闭线程池
threadPool.shutdown();
}
}
自定义类实现Callable:完成异步计算
public class MyCallable implements Callable<Object> {
@Override
public Object call() throws Exception {
for(int x = 0 ; x < 200 ; x++){
System.out.println(Thread.currentThread().getName()+":"+x);
}
return null;
}
}
public class MyRunnable implements Runnable{
@Override
public void run() {
for(int x = 0 ; x < 100 ; x++){
System.out.println(Thread.currentThread().getName()+":"+x) ;
}
}
}
使用线程池的方式完成多个线程的异步计算
需求:使用线程池的方式完成多个线程的异步计算:
1个线程求1-100的和
另一个线程求1-200的和
1)创建线程池:固定的可重用的线程数的线程池
2)提交异步任务<T> Future<T> submit(Callable<T> task):提交异步任务
返回值:Future接口:代表异步计算的结果
V get():获取计算的结果
public class Test {
public static void main(String[] args) throws
ExecutionException, InterruptedException {
//1)创建线程池:固定的可重用的线程数的线程池
ExecutorService threadPool = Executors.newFixedThreadPool(2);
//2)提交异步任务
//<T> Future<T> submit(Callable<T> task):
Future<Integer> f1 = threadPool.submit(new MySelfCallable(100));
Future<Integer> f2 = threadPool.submit(new MySelfCallable(200));
System.out.println(f1.get());
System.out.println(f2.get());
//3)关闭线程池
threadPool.shutdown();
}
}
异步任务
public class MySelfCallable implements Callable<Integer> {
private int number ;
public MySelfCallable(int number){ //100或者200
this.number = number ;
}
@Override
public Integer call() throws Exception {
//定义一个最终结果变量
int sum = 0 ;
for(int x = 1; x <= number ;x++){
sum += x ;
}
return sum;
}
}
线程池七大参数
ThreadPoolExecutor它的构造方法涉及的相关参数
线程池相关参数
1.corePoolSize
核心线程数:是指线程池中长期存活的线程数
2.maximumPoolSize
最大线程数:线程池允许创建的最大线程数量,当线程池的任务队列满了之后,可以创建的最大线程数。
3.keepAliveTime
空闲线程存活时间,当线程池中没有任务时,会销毁一些线程,销毁的线程数=maximumPoolSize(最大线程数)-corePoolSize(核心线程数)
4.TimeUnit
时间单位:空闲线程存活时间的描述单位
5.BlockingQueue
阻塞队列:线程池存放任务的队列,用来存储线程池的所有待执行任务。
它可以设置以下几个值:
ArrayBlockingQueue:一个由数组结构组成的有界阻塞队列。
LinkedBlockingQueue:一个由链表结构组成的有界阻塞队列。
SynchronousQueue:一个不存储元素的阻塞队列,即直接提交给线程不保持它们。
PriorityBlockingQueue:一个支持优先级排序的无界阻塞队列。
DelayQueue:一个使用优先级队列实现的无界阻塞队列,只有在延迟期满时才能从中提取元素
LinkedTransferQueue:一个由链表结构组成的无界阻塞队列。与SynchronousQueue类似,还含有非阻塞方法。
LinkedBlockingDeque:一个由链表结构组成的双向阻塞队列。
6.ThreadFactory
线程工厂:线程池创建线程时调用的工厂方法,通过此方法可以设置线程的优先级、线程命名规则以及线程类型(用户线程还是守护线程)等。
7.RejectedExecutionHandler
拒绝策略:当线程池的任务超出线程池队列可以存储的最大值之后,执行的策略.
默认的拒绝策略有以下 4 种:
AbortPolicy:拒绝并抛出异常。
CallerRunsPolicy:使用当前调用的线程来执行此任务。
DiscardOldestPolicy:抛弃队列头部(最旧)的一个任务,并执行当前任务。
DiscardPolicy:忽略并抛弃当前任务。
线程池的默认策略是 AbortPolicy 拒绝并抛出异常
异常
异常概念
程序中会出现异常,开发人员必须解决异常
异常的父类:Throwable
-->提供一些方法:将异常的信息直接打印在控制上:具体(跟踪堆栈-->底层原码)
public void printStackTrace()
1、 Error(严重问题) Exception
严重问题:内存溢出(借助于其他硬件解决这个问题,加内存条)
2、 Exception:
1)编译时期异常:只要不是RuntimeException的子类,都属于编译时期异常。必须让开发人员必须处理,不处理,代码编译通过不了
2)运行时期异常:RuntimeException。大部分逻辑不够严谨,导致出现的问题
例如:NullPointerEception:在获取到某个类的对象的时候,并没有非空判断,可能对象是null,使用这个对象调用它里面的方法,出现空指针了
处理异常方案
一、throws:向方法调用者,(向父类抛出异常,最终会抛出给jvm)
二、捕获异常 try...catch
1、标准格式try...catch...finally
try{
可能出现问题的代码
}catch(异常类名 对象名){
处理异常
}finally{
//释放资源
}
2、变形格式try...catch...
try{
可能出现问题的代码
}catch(异常类名 对象名){
处理异常
}
3、变形格式try...catch...catch...
try{
可能出现问题的代码
}catch(异常类名 对象名){
处理异常
}catch(异常类名2 对象名2){
处理异常...
}
案例:
//try代码如果出现问题,---执行catch语句---->处理异常---->内存中,jvm会创建异常的对象,new XXException,
//执行的catch语句代码
try{
//可能出现的代码
int i = 10 ;
System.out.println(i/0) ;
}catch(Exception e){ //捕获的异常Exception
//自己处理
//System.out.println("除数不能为0");
e.printStackTrace();
}
自定义异常
自定义异常: 自定义一个类,继承自Exception/RuntimeException类
//自定义一个类
public class MyException extends Exception{
public MyException(){
}
//有参构造:message: 异常信息提示
public MyException(String message){
super(message);
}}
public class Teacher {
public void getSocre(int socre) throws MyException{//向调用者抛出异常
if(socre <0 || socre>100){
throw new MyException("成绩非法!") ; //产生异常
}else {
System.out.println("当前学生成绩是:"+socre) ;
}}}
public class ExceptionTest {
方式一://向上抛出异常
public static void main(String[] args) throws MyException {//向上抛出异常
Scanner sc = new Scanner(System.in) ;
System.out.println("请您输入学生成绩:") ;
int socre = sc.nextInt() ;
Teacher t = new Teacher() ;
t.getSocre(socre) ;
方式二://捕获异常
public static void main(String[] args) {
try {
Scanner sc = new Scanner(System.in) ;
System.out.println("请您输入学生成绩:") ;
int socre = sc.nextInt() ;
Teacher t = new Teacher() ;
t.getSocre(socre) ;
} catch (MyException e) {
e.printStackTrace();
}
异常的使用的注意事项
1、子类继承父类的时候,如果子类重写父类的方法,父类的方法如果本身没有throws抛出异常
子类的方法中出现异常,子类只能自己处理,只能try...catch...
2、子类继承父类,子类重写父类方法的时候,如果父类的方法抛出异常,那么子类的该方法最起码要根父类的方法异常保存一致(要么子类该放的异常是父类方法的异常中的子类)
面试题:throw和throws的区别
区别:
1)书写位置的区别
throws:用在方法声明上
throw:用在方法体中
2)后面跟的类型不一样的
throws后面跟的异常类名,可以多个,中间逗号隔开
throw:后面只能一个异常对象,new XXException()
3)是否出现异常的时机
throws:可能出现问题,可能性
throw:执行某个逻辑代码,一定会出现这个异常!(肯定性)
4)处理异常:
throws:抛出给调用者,谁调用这个方法(带有throws的方法),谁必须对这个已处理(throws/try...catch...)
throw:异常的处理:交给方法体中逻辑判断,if(xxx){throw new XXException(...);}
单例设计模式
单例设计模式---属于创建型设计模式(创建对象)
概念:始终在内存中有且仅有一个当期类的实例(有一个对象)
单例设计模式:分为饿汉式、懒汉式
1)饿汉式
构造器私有化,对外不能new
静态实例:Student一加载,就创建当前类实例
对外提供静态的方法,返回值是当前类本身
public class Student {
private static Student s = new Student() ;
private Student(){}
public static Student getStudent(){
return s;
2)懒汉式
声明静态变量
构造器私有化,对外不能new
对外提供静态方法,返回值是当期类本身
public class Teacher {
//声明静态变量,t的类型Teacher
private static Teacher t ;
private Teacher(){}
//多个线程同时去访问这个方法,
//线程1,线程2,线程3--导致出现线程安全问题
/*public static Teacher getInstance(){
synchronized (Teacher.class){ //静态方法的锁对象:跟类有关系 类名.class
if(t ==null){
t = new Teacher() ;
}
return t;
}
}*/
public static synchronized Teacher getInstance(){//变成了静态的同步方法 :锁对象:类名.class
if(t ==null){
t = new Teacher() ;
}
return t;
饿汉式:不会出现安全问题的单例设计模式
1)当前类是具体类
2)类一加载就创建当前类实例
3)构造私有化,对外隐藏,不能new实例
4)对外提供静态方法,返回值当前类本身
懒汉式(可能存在安全问题的一种单例模式)
1)当前类是个具体类
2)当前类构造方法私有化
3)当前类的成员位置:声明当前类的一个静态变量
4)对外提供静态方法,返回值是当前类本身:需要判断当前变量是否为null
Io流
1)按流的方向划分
输入流/输出流
2)按流的类型划分:
字节流
字节输入流:InputStream
具体的子类:FileInputStream
高效字节输入流:BufferedIntputStream
System.out----->PrintStream:字节打印流
提供功能println(换行任何类型的内容)
print(任何类型的内容)
字节输出流:OutputStream
具体的子类:FileOutputStream
高效字节输入流:BufferedOutputStream
字符流
字符输入流:Reader
具体的子类:InputStreamReader:字符转换入流
便捷类:FileReader
高效字符输入流:BufferedReader
字符输出流:Writer
具体的子类:OutputStreamWriter:字符转换输出流
便捷类:FileWriter
高效的字符输出流:BufferedWriter
File
java.io.File--->表示文件或者文件夹(目录)一种抽象路径形式
Fiel构造方法:
1、File(File parent, String child)从父抽象路径名和子路径名字符串创建新的 File实例。
File file2 = new File("D:\\demo") ;
File file3 = new File(file2,"a.txt") ;
2、 File(String pathname)通过将给定的路径名字符串转换为抽象路径名来创建新的 File实例。
File file4 = new File("D:\\EE_2302\\day26\\code\\a.txt") ;
3、File(String parent, String child) 父路径名字符串和子路径名字符串创建新的 File实例
File file = new File("D:\\demo","a.txt") ;
File的基本功能以及特有功能
1、 public boolean createNewFile() throws IOException:创建文件
File file3 = new File("a.txt") ;
file3.createNewFile();
2、public boolean mkdir():创建文件夹
File file = new File("D:\\EE_2302\\day26\\code\\demo") ;
file.mkdir();
3、public boolean mkdirs():创建文件夹,当父目录不存在,自动创建
创建aaa\bbb\ccc 多级目录(直接指定具体文件夹或者文件,相对路径创建)
File file2 = new File("aaa\\bbb\\ccc") ;
file2.mkdirs();
4、public boolean delete():删除文件夹或则文件路径,如果是文件夹,必须是空目录
5、public boolean exists():判断此路径名表示的文件或者目录是否存在
6、public boolean isDirectory():此抽象路径名表示的文件是否是目录
7、public boolean isFile():是否是文件
8、public boolean isHidden():是否隐藏
9、public boolean canRead():是否可读
10、public boolean canWrite():是否可写
11、public String getName():获取当前File对象所表示的文件夹以及文件的名称
特有功能:高级功能
12、public String[] list():获取指定抽象路径名表示的文件或者目录的名称(返回字符串数组)
//描述D盘---所有的文件夹以及文件的名称 获取到
File myFile = new File("d:\\") ;
String[] strs = myFile.list() ;
if(strs!=null){
for (String str : strs) {
System.out.println(str);
}
//System.out.println(Arrays.toString(strs));
}
13、public File[] listFiles():获取指定路径名表示的文件或者目录的File数组
File[] files = myFile.listFiles();
//防止空指针
if(files!=null){
for (File f : files) {
//获取当前File对象所表示的文件夹以及文件的名称 --public String getName()
System.out.println(f.getName());
}
}
获取以磁盘下以"xxx"结尾的文件
获取D盘下的所有的以".jpg"结尾的文件
"需要将文件名打印到控制台上"
方式一:
public class FileTest {
public static void main(String[] args) {
//1)描述D盘---使用File对象
File file = new File("D:\\") ;
//2)通过 public File[] listFiles() :获取指定路径下所有的文件以及文件夹的File数组
File[] files = file.listFiles();
if(files!=null){
for (File f : files) {
//f:代表File对象
//判断是否是文件
if(f.isFile()){
//满足是文件
//获取文件名称,以".jpg"结尾
if(f.getName().endsWith(".jpg")){
//输出文件名称
System.out.println(f.getName());
}}}}}}
方式二:文件名称过滤器
参数都是接口---FilenameFilter:文件名称过滤器
public boolean accept(File dir,String name)
将指定的文件存储在文件列表中dir,返回值true(存储在文件列表中),否则false,不存储
public File[] listFiles(FilenameFilter filter)
public String[] list(FilenameFilter filter)
public class FileTest2 {
public static void main(String[] args) {
//1)描述D盘:使用File对象
File file = new File("D:\\") ;
//2)public String[] list(FilenameFilter filter) 直接获取满足条件的文件,存储在String数组中
String[] strs = file.list(new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
//同时满足条件:
//创建File对象--描述指定目录的文件列表
File f = new File(dir, name);
//分步完成
//f是表示文件形式
//boolean flag = f.isFile();
//f是文件,文件名称是以".jpg"结尾
//boolean flag2 = f.getName().endsWith(".jpg");
//return flag && flag2;
//一步走
return f.isFile() && f.getName().endsWith(".jpg") ;
}
});
if(strs!=null){
for (String str : strs) {
//满足文件列表的内容输出
System.out.println(str);
}}}}
字节输入输出
字节输出流
字节输出流
OutputStream---抽象类--不能new,具体的子类:针对文件操作:FileOutputStream
操作步骤:
1)创建字节输出流对象
2)写数据
3)释放资源
public class FileOutputStreamDemo {
public static void main(String[] args) throws IOException {
//public FileOutputStream(String name)throws FileNotFoundException
//OutputStream out = new FileOutputStream("fos.txt") ;//多态
FileOutputStream fos = new FileOutputStream("fos.txt") ;//具体类new --->这个过程:需要创建系统资源(C语言)
//写数据
//void write(byte[] b):写字节数组
//void write(byte[] b, int off, int len):写字节数组一部分
//abstract void write(int b) 写一个字节
//fos.write(97) ;//写一个字节
byte[] bytes = {98,99,100,101,102,103,104} ;
fos.write(bytes) ;
fos.write(bytes,0,3);
//释放资源:将流对象指向的文件地址的系统资源进行释放
fos.close() ;
//fos.write(108); 当流对象关闭,释放系统资源,不能在写数据的,否则,IOException: Stream Closed
}
}
FileOutputStream实现文件的字节数追加
public FileOutputStream(String name,
boolean append)
throws FileNotFoundException
第二个参数是true:自动追加内容
IO流写数据,实现换行效果 widows系统 "\r\n"
public class FileOutputStreamDemo2 {
public static void main(String[] args) throws IOException {
//创建FileOutputStream流对象
FileOutputStream fos2 = new FileOutputStream("fos2.txt",true) ;
//fos2.write("world".getBytes()) ;
for(int x = 0 ; x < 10 ; x ++){
fos2.write(("hello"+x).getBytes());
//写入换行符号
fos2.write("\r\n".getBytes()); //str.grtBytes获得字符串的字节数组
}
//关闭资源
fos2.close();
}
}
字节输入流
InputStream:字节输入流--->抽象类--->具体的子类FileInputStream:文件字节输入流
1)创建文件字节输入流对象FileInputStream(String name) :读取指定的文件 name:文件地址
2)读数据
方式一:继承它父类的方法:public abstract int read()throws IOException:一次读取一个字节
方式二:public int read(byte[] b) throws IOException:一次读取一个字节数组
使用字节输入流将当前项目下的"fis.txt"文件内容读取出来打印在控制台上
针对中文---会出现乱码 (char)字节数,平台默认编码格式utf-8:一个中文对应三个字节
英文字母和中文-拼接一块,一个英文字母对应一个字节,这个时候拼接不上,导致文乱码---Java提供"字符流",解决乱码
一次读取一个字节的方式:
public class FileInputStreamDemo {
public static void main(String[] args) {
FileInputStream fis = null ;
try {
//创建文件字节输入流对象FileInputStream(String name)
//fis = new FileInputStream("fis.txt") ;
//读取当前项目下的FileOutputStreamDemo3.java文件,把它内容打印控制台上
fis = new FileInputStream("FileOutputStreamDemo3.java") ;
//public abstract int read()throws IOException:一次读取一个字节
//返回值:读取的实际字节数值
//文件内容未知---使用while循环
//循环条件中---赋值,判断,一块使用
//定义一个字节数:从0开始
int by = 0 ;
while((by=fis.read())!=-1){
//将字节数by---强转char字符
System.out.print((char)by) ;
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if(fis!=null){
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
//读取当前项目下的FileOutputStreamDemo3.java
FileInputStream fis2 = new FileInputStream("FileOutputStreamDemo3.java") ;
使用字节输入流一次读取一个字节数组的方式
public int read(byte[] b) throws IOException:一次读取一个字节数组,返回:读取的字节数组的长度
public class FileInputStreamDemo2 {
public static void main(String[] args) throws IOException {
byte[] bytes = new byte[1024] ;
//实际字节数从0开始
int len = 0 ;
while((len=fis2.read(bytes))!=-1){//赋值,判断 -- read(byte[] bytes):阻塞式方法,没有到-1,就一直读
String str = new String(bytes,0,len) ;
System.out.println(str);
}
字节输入+字节输出
将当前项目下的myavi.mp4文件复制到D:\EE_2302\day26\code\copy.mp4
public class CopyMp4 {
public static void main(String[] args) {
long start = System.currentTimeMillis() ;
//共耗时:222564毫秒,
//copyMp4("myavi.mp4","D:\\EE_2302\\day26\\code\\copy.mp4");
//共耗时:356毫秒
copyMp4_2("myavi.mp4","D:\\EE_2302\\day26\\code\\copy.mp4");
long end = System.currentTimeMillis() ;
System.out.println("共耗时:"+(end-start)+"毫秒");
}
//一次读取一个字节数组
/**
* 使用字节输入流一次读取一个字节数组
* @param srcFile 源文件
* @param destFile 目标文件
*/
public static void copyMp4_2(String srcFile,String destFile){
FileInputStream fis = null ;
FileOutputStream fos = null ;
try {
fis = new FileInputStream(srcFile) ;
fos = new FileOutputStream(destFile) ;
//一次读取一个字节数组
byte[] bytes = new byte[1024] ;
//实际字节数
int len = 0 ;
while((len=fis.read(bytes))!=-1){
//一次读取一个字节数组,写字节数组(每次0开始,写入实际字节数)
fos.write(bytes,0,len);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if(fos!=null){
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(fis!=null){
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
//一读取一个字节
/**
* 使用字节输入流一读取一个字节的方式,进行文件复制
* @param srcFile 源文件地址
* @param destFile 目标文件地址
*/
public static void copyMp4(String srcFile, String destFile) {
FileInputStream fis = null ;
FileOutputStream fos = null ;
try {
//创建字节输入流操作srcFile
fis = new FileInputStream(srcFile) ;
//创建字节输出流对象操作destFile
fos = new FileOutputStream(destFile) ;
//一次读取一个字节
int by = 0 ;
while((by=fis.read())!=-1){
//使用字节输入流一次一个字节,使用fos输出流对象写一个字节
fos.write(by) ; //不需要强转,要的字节
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
//释放资源
if(fos!=null){
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(fis!=null){
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
字节缓冲流
Java之IO流提供了字节缓冲流:让读取的速度更快一些,提供对应的类
字节缓冲输入流/字节缓冲输出流 (高效的字节流)---仅仅是内部提供缓冲区字节数组长度:8192长度,
文件的读写复制还的依赖于基本字节流(InputStream/OutputStream)
BufferedInputStream(InputStream in):字节缓冲输入流
BufferedOutputStream(OutputStream out):字节缓冲输出流
需求:
将当前项目下的myavi.mp4--->复制到 D:\EE_2302\day27\code\copy.mp4
public class CopyFileTest {
public static void main(String[] args) throws IOException {
long start = System.currentTimeMillis();
// copyFile("myavi.mp4","D:\\EE_2302\\day27\\code\\copy.mp4") ;
copyFile2("myavi.mp4","D:\\EE_2302\\day27\\code\\copy.mp4") ;
long end = System.currentTimeMillis() ;
System.out.println("共耗时:"+(end-start)+"毫秒");
}
1、使用字节缓冲输入流一次读取一个字节数组
/**
* 使用字节缓冲输入流一次读取一个字节数组
* @param srcFile 源文件地址
* @param destFile 目标文件地址
*/
public static void copyFile2(String srcFile, String destFile) throws IOException {
//BufferedInputStream(InputStream in):字节缓冲输入流
//创建字节缓冲输入流对象
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(srcFile)) ;
// BufferedOutputStream(OutputStream out):字节缓冲输出流
//创建字节缓冲输出流对象
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(destFile)) ;
//一次读取一个字节数组
byte[] bytes = new byte[1024] ;
int len = 0 ;//实际字节数
while((len=bis.read(bytes))!=-1){
//每次从0开始写入实际字节数
bos.write(bytes,0,len);
//使用字节缓冲输出流的时候,防止一些字节数没有交给OutputStream字节输出流
//强制刷新缓冲输出流(尤其图片,图片文件带有缓冲数据:可能字节数没有写入到底层输出流中,导致图片文件缺失)
bos.flush() ;
}
//释放资源
bos.close();
bis.close();
}
2、使用字节缓冲输入流一次读取一个字节
/**
* 使用字节缓冲输入流一次读取一个字节
* @param srcFile 源文件地址
* @param destFile 目标文件地址
*/
public static void copyFile(String srcFile, String destFile) throws IOException {
//BufferedInputStream(InputStream in):字节缓冲输入流
//创建字节缓冲输入流对象
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(srcFile)) ;
// BufferedOutputStream(OutputStream out):字节缓冲输出流
//创建字节缓冲输出流对象
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(destFile)) ;
//一次读取一个字节
int by = 0 ;
while((by=bis.read())!=-1){
//读取一个字节,写出一个字节
bos.write(by) ;
}
//释放资源
bos.close();
bis.close();
}
}
字符输入输出
字符输入流-转换流InputStreamReader
字符输入流---->Reader(抽象类)
字符转换输入流
具体的子类:字符转换输入流InputStreamReader --- 将字节输入流---->转换--->字符输入流
public InputStreamReader(InputStream in):使用平台默认字符集进行解码---读取数据
public InputStreamReader(InputStream in,String charsetName)
使用指定字符集解码---读取数据
读数据:
int read() :一次读取一个字符 --->返回结果:实际的字符数
int read(char[] cbuf) :一次读取一个字符数组
int read(char[] cbuf,int off, int len):读取字符数组的一部分
public class InputStreamReaderDemo {
public static void main(String[] args) throws Exception {
//创建字符转换输入流对象
//解码:gbk(中国编码表): 一个中文对应两个字节
/* InputStreamReader isr = new InputStreamReader(
new FileInputStream("osw.txt"),"gbk") ;*/
//public InputStreamReader(InputStream in)
InputStreamReader isr = new InputStreamReader(
new FileInputStream("osw.txt")) ;
//读数据
//一次读取一个字符
int by = 0 ;//实际字符数
while((by=isr.read())!=-1){
//展示控制台上 读取的时候将文件内容的里面--->int类型----->转换成字符
System.out.print((char)by);
}
}
}
字符输出流-转换流OutputStreamWriter
字符流:
字符输出流:Writer(抽象类)--->
具体的子类 OutputStreamWriter:字符转换输出流,"可以将字节输出流---转换---字符输出流"
构造方法:
public OutputStreamWriter(OutputStream out):使用平台默认字符集进行编码--输出数据
public OutputStreamWriter(OutputStream out,String charsetName):
使用指定的字符集进行编码---输出数据
写数据:
1、void write(char[] cbuf) :写入字符数组
2、void write(char[] cbuf,int off,int len ) :写入字符数组的一部分 3、void write(int c) :写一个字符
4、void write(String str) :字符串
5、void write(String str,int off,int len):写入字符串的一部分
public class OutputStreamWriterDemo {
public static void main(String[] args) throws Exception {
//创建一个字符转换输出流对象
//utf-8:一个中文对应三个字节
/* OutputStreamWriter osw = new OutputStreamWriter(
new FileOutputStream("osw.txt"),"utf-8") ;*/
OutputStreamWriter osw = new OutputStreamWriter(
new FileOutputStream("osw.txt"));
//写字符串
osw.write("helloworld");
osw.write("高圆圆");
osw.write(97) ;//写入字符,传入参数int--->ASCII码表对应的字符值
//释放资源
osw.close();
}
}
字符输入输出流FileReader、FileWriter
public class CopyFileTest {
public static void main(String[] args) throws IOException {
//创建字符输入流对象---public FileReader(String fileName)
FileReader fr = new FileReader("D:\\EE_2302\\day27\\code\\DiGuiTest.java");//默认的字符集(解码)
//创建字符输出流对象---FileWriter(String fileName):直接操作目标文件。
//FileWriter(String fileName, boolean append) :直接操作文件,实现追加写入
FileWriter fw = new FileWriter("copy.java") ; //默认的字符集(编码)
int by = 0 ;
while((by=fr.read())!=-1){
//写一个字符
fw.write(by) ;
fw.flush();
}
//释放资源
fw.close();
fr.close();
}
字符缓冲流
字符缓冲输入流
BufferedReader:字符缓冲输入流---可以读取一行内容
构造方法:
public BufferedReader(Reader in):提供默认缓冲区大小的字符缓冲输入流 (8192个长度)
这个流如果直接操作文件--->参数里面使用FileReader
public String readLine()throws IOException
特有功能:
public String readLine()throws IOException:一次读取一行
返回值是读取到这一行的内容,当流已经到达末尾,则返回null
键盘录入两种方式:
1)Scanner(InputStream in)---->Scanner sc = new Scanner(System.in) ;
2)new BufferedReader(new InputStreamReader(System.in))
//InputStreamReader(InputStream in)
public class BufferedReaderDemo {
public static void main(String[] args) throws IOException {
//创建一个字符缓冲输入流对象
BufferedReader br = new BufferedReader(new FileReader("copy.java")) ;
String line = null ;
while((line=br.readLine())!=null){ //赋值,判断 readLine() --->阻塞式方法
System.out.println(line);
}
br.close();
字符缓冲输出流
字符缓冲输出流:提供字符数组/字符/字符串的一种高效写入
BufferedWriter构造方法
public BufferedWriter(Writer out):提供默认缓冲区大小的字符缓冲输出流,默认缓冲区足够大 8192长度
写的功能:
write(字符/字符数组/字符串...)
特有功能:不需要在使用"\r\n"换行符号, public void newLine()写入行的分隔符
public class BufferedWriterDemo {
public static void main(String[] args) throws IOException {
//创建字符缓冲输出流对象
//FileWriter(Stirng pathname,boolean append):第二个参数true,自动追加(字符追加到末尾)
BufferedWriter bw = new BufferedWriter(new FileWriter("bw.txt",true)) ;
//写数据
for(int x = 1 ; x <=10 ;x ++){
bw.write("hello"+x);
//换行
bw.newLine();
bw.flush();
}
//释放资源
bw.close();
字符缓冲输入+输出
将当前项目下的某个.java文件复制到某个磁盘上的路径中
public class Test {
public static void main(String[] args) throws IOException {
//封装源文件
BufferedReader br = new BufferedReader(
new FileReader("copy.java")) ;
//封装目的地文件
BufferedWriter bw = new BufferedWriter(
new FileWriter("D:\\EE_2302\\day27\\code\\my.java")) ;
//一次读取一行
String line = null ;
while((line=br.readLine())!=null){
//写一行
bw.write(line) ;
bw.newLine();
bw.flush();
}
//释放资源
bw.close();
br.close();
}
}
属性集合列表-Properties
java.util.Properties:属性集合列表---extends Hashtabl<K,V>-->
本质是Map<K,V>集合
添加功能:put(K key,V value)
遍历方式:Set<K> keySet()
遍历键-->通过键获取值 V get(K key)
自己的特有功能:
java.util.Properties:没有泛型,键和值都只能String----> 作用:用来读取配置文件xx .properties配置文件(src下面)
构造方法:
public Properties()
添加属性列表的键和值:
public Object setProperty(String key,String value)
特有的遍历方式:
public Set<String> stringPropertyNames():获取属性列表中的所有的键(属性名称)
public String getProperty(String key):通过属性列表中的属性名称获取对应的值
public class PropertiesDemo {
public static void main(String[] args) {
//创建属性集合列表
Properties prop = new Properties() ;
System.out.println(prop);
//public Object setProperty(String key,String value)
prop.setProperty("张三","30") ;
prop.setProperty("李四","25") ;
prop.setProperty("王五","35") ;
System.out.println(prop);
//遍历
// public Set<String> stringPropertyNames():获取属性列表中的所有的键(属性名称)
// public String getProperty(String key):通过属性列表中的属性名称获取对应的值
Set<String> keySet = prop.stringPropertyNames();
for(String key:keySet){
String value = prop.getProperty(key);
System.out.println(key+"---"+value);
}
}
}
将文件的内容加载到属性集合列表中
java.util.Properties可以保存到流中或从流中加载
将文件的内容加载到属性集合列表中:
public void load(InputStream inStream/Reader)
将属性列表中的内容保存到指定文件中
第二个参数:描述属性列表
public void store(OutputStream out/Writer,String comments)
private static void myStore() throws IOException {
//将属性列表中的内容---保存指定文件中
Properties prop = new Properties() ;
//存储用户名和密码
prop.setProperty("文章","123") ;
prop.setProperty("高圆圆","123456") ;
prop.setProperty("admin","admin") ;
System.out.println(prop) ;
//public void store(OutputStream out/Writer,String comments)
prop.store(new FileWriter("D:\\EE_2302\\day27\\code\\day27\\src\\my.properties"),"name'list") ;
}
如何读取src下面的xx.properties文件
private static void myLoad() throws IOException {
//创建属性集合列表
Properties prop = new Properties() ;
//1)获取当前类的字节码文件对象--->类名.class 属性
Class c = PropertiesDemo2.class ;
//2)通过获取到的字节码文件对象获取 当前类的加载器
// public ClassLoader getClassLoader()
ClassLoader classLoader = c.getClassLoader();
//3)ClassLoader---类加载器--->读取到src下面配置文件的内容--->存储到字节输入流中
/public InputStream getResourceAsStream(String name) 返回用于读取指定资源的输入流
InputStream inputStream = classLoader.getResourceAsStream("name.properties");
//一步走
InputStream inputStream = PropertiesDemo2.class.getClassLoader().
getResourceAsStream("name.properties");
//public void load(InputStream inStream/Reader)
prop.load(inputStream);
System.out.println(prop);
}
}
网络图片下载到本地
Java提供了类:URL-->统一资源定位符号
1)创建URL对象public URL(String spec)
2)使用URL建立http请求---建立连接
public URLConnection openConnection()throws IOException
URLConnnection:和http建立的通信协议(不能new)
HttpURLConnection--子类提供一些方法:
public InputStream getInputStream()throws IOException 获取到字节输入流
3)创建基本字节输出流FileOutputStream
4)一次读取一个字节数组的方式,将图片复制到本地磁盘上
public class Test2 {
public static void main(String[] args) {
HttpURLConnection httpURLConnection = null ;
InputStream inputStream = null ;
FileOutputStream fos = null ;
try {
//1)创建URL地址对象
URL url = new URL("http://rs0k1dy20.hd-bkt.clouddn.com/bug.jpg");
//2)建立连接通信
//public URLConnection openConnection()throws IOException
// URLConnnection:和http建立的通信协议(不能new)
httpURLConnection = (HttpURLConnection) url.openConnection();
//3)获取输入流对象-读取网络地址的内容
inputStream = httpURLConnection.getInputStream();
//4)创建字节输出流对象
fos = new FileOutputStream("gaoyuanyuan.jpg") ;
//一次读取一个字节数组
byte[] bytes = new byte[1024] ;
int len = 0 ;
while((len=inputStream.read(bytes))!=-1){
//写
fos.write(bytes,0,len) ;
}
} catch (IOException e) {
e.printStackTrace();
}finally {
if(fos!=null){
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(inputStream!=null){
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(httpURLConnection!=null){
//让连接失效public abstract void disconnect()
httpURLConnection.disconnect();
}
}
}
}
键盘录入的方式
键盘录入
0)main方法里面:早期录入 String[] args
1)Scanner(InputStream in)---->Scanner sc = new Scanner(System.in) ;
2)new BufferedReader(new InputStreamReader(System.in))
//InputStreamReader(InputStream in)
public class Demo {
public static void main(String[] args) throws IOException {
//字符缓冲输入流需要--->包装字节输入流
// InputStream inputStream = System.in ;
//需要Reader字符流
//Reader reader = new InputStreamReader(inputStream);
//InputStreamReader(InputStream in)
//创建字符缓冲输入流对象
// BufferedReader br = new BufferedReader(reader) ;
//一步走
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
System.out.println("请您输入一个数据:");
String line = br.readLine();
//数字字符串--->
int num = Integer.parseInt(line);
System.out.println("您输入的内容是:"+num);
}
}
方法递归
方法递归:方法本身调用方法的一种现象
前提条件:
1)定义一个方法
2)这个方法的逻辑是规律的
3)这个方法是有结束条件,否则---成了"死递归"
注意:构造没有递归
需求:
求5的阶乘
1)定义一个方法
2)有规律:
分解法:
将问题1
拆分问题11
5!=5*4!
4! = 4 * 3!
3! = 3 * 2!
2! = 2 * 1!
1!永远是1
public class DiGuiDemo {
public static void main(String[] args) {
getJc(5):
}
public static int getJc(int n){ //5
if(n==1){
return 1 ;
}else {
//5*getJc(4)
//4*getJc(3)
//3*getJc(2)
//2*getJc(1)
return n * getJc(n-1) ;
}
}
}
递归应用
需求:删除某个磁盘上的带内容的目录
"D:\EE_2302\day27\code\demo"
public class DiGuiTest {
public static void main(String[] args) {
//描述磁盘上的路径
File srcFile = new File("D:\\EE_2302\\day27\\code\\demo") ;
//定义递归删除带内容的目录的方法
deleteDirectory(srcFile) ;
}
public static void deleteDirectory(File destFile) { //传入的的路径
//获取目标文件夹 中所有的文件以及文件夹的File数组
File[] files = destFile.listFiles();
if(files!=null){
for(File file:files){
//获取每一个file对象
//去判断
if(file.isDirectory()){
//是文件夹
//调用递归删除的方法
deleteDirectory(file);
}else {
//是文件,删除的同时获取它的父目录的名称
//public File getParentFile()
//System.out.println(file.getParentFile().getName()+"目录名称:"+"---->"+file.delete());
System.out.println(file.getName()+"目录名称:"+"---->"+file.delete());
}
}
System.out.println(destFile.getName()+"----"+destFile.delete());
}
}
}
网络编程
网络编程三要素
网络三要素:
1、ip地址/端口号/协议
ip地址:A/B/C三大类
A类--->前面第一个号段:网络号段 后面的号段:主机号段 (国家部门--机房)
B类--->前面两个号段:网络号段 后面号段:主机号段 (大学校园/公司内网等等)
C类---->属于私人地址 前面三个号段:网络号段 后面号段:主机号段
10.35.165.17 --- "点分十进法"
2、端口号:
范围 0~65535 这里面 0-1024(属于保留端口号)
自己指定端口号--->1025-65535
(不要和系统端口号冲突---->Address already in use:BindException:地址被占用)
mysql端口号:3306
tomcat:web服务器:8080
3、协议:
UDP/TCP协议区别 (底层网络协议)
UDP协议:
1)属于不可靠连接,不需要建立连接通道(不安全) ----以"数据报包"的方式进行数据传输的
2)不同步的,执行效率相对TCP协议,效率高
3)发送数据大小有限制的
TCP协议:
1)属于可靠连接,需要建立连接通道 ----以 "流"的方式发送数据
2)同步的,执行效率低,安全性相对UDP协议(安全性能高)
3)发送数据大小无限制
InetAddress
java.net.InetAddress:代表互联网ip地址对象
获取ip地址对象---->获取ip地址字符串形式/获取主机名称
参数:要么传入主机名称/要么ip地址字符串
public static InetAddress getByName(String host)throws UnknownHostException :获取互联ip地址对象
InetAddress的成员方法
public String getHostAddress():获取ip地址字符串形式
public String getHostName():获取ip地址的主机名
public class InetAddressDemo {
public static void main(String[] args) throws UnknownHostException {
// public static InetAddress getByName(String host)throws UnknownHostException
InetAddress inetAddress = InetAddress.getByName("DESKTOP-Q62EUJH");
InetAddress inetAddress = InetAddress.getByName("10.35.165.34");
//获取ip地址字符串形式
String ip = inetAddress.getHostAddress() ;
//获取主机名称
String name = inetAddress.getHostName();
}
}
使用UDP协议发送、接收数据
Udp接收端
1)创建接收端的Socket对象
2)创建一个"数据报包"在里面自定义缓冲区:将发送端是数据存储到缓冲区中(字节数组,长度:1024或者1024的整数倍)
3)使用2)接收容器(缓冲区),接收发送端的数据
4)解析接收容器中的实际内容
5)展示数据即可
6)释放接收端的资源
注意:
接收端开启一次就可以;否则
Exception in thread "main" java.net.BindException: Address already in use: Cannot bind
public class UdpReceive {
public static void main(String[] args) throws Exception {
//1)创建接收端的Socket对象
//public DatagramSocket(int port)throws SocketException
DatagramSocket ds = new DatagramSocket(6666) ;
//2)创建一个"数据报包"在里面自定义缓冲区:将发送端是数据存储到缓冲区中(字节数组,长度:1024或者1024的整数倍)
//参数1:用于保存传入"数据报"的缓冲区。
//参数2:要读取的字节数
//public DatagramPacket(byte[] buf,int length)
byte[] buffer = new byte[1024] ;
DatagramPacket dp = new DatagramPacket(buffer,buffer.length) ;
//3)使用2)接收容器(缓冲区),接收发送端的数据
//public void receive(DatagramPacket p)throws IOException从此套接字接收数据报包
ds.receive(dp) ;
//4)解析接收容器中的实际内容
//public byte[] getData() :解析数据报包中DatagramPacket
// 缓冲的实际的字节数组
byte[] bytes = dp.getData();
//public int getLength():解析数据报包中DatagramPacket中的实际字节数组长度
int length = dp.getLength();
//展示数据
String message = new String(bytes,0,length) ;
//谁发送的数据---获取ip地址字符串形式
//数据报包DatagramPacket--->public InetAddress getAddress()
//InetAddress:ip地址--->String getHostAddress()
String ip = dp.getAddress().getHostAddress() ;
System.out.println("data from is--->"+ip+",data is--->"+message) ;
//释放资源
ds.close();
}
}
使用UDP协议发送数据
1)创建发送端的socket对象
2)创建"数据报包"--里面包括:数据内容,ip地址,端口号
3)使用发送端的Socket对象发送"数据报包"
4)释放资源
public class UdpSend {
public static void main(String[] args) throws Exception {
//1)创建发送端的socket对象 ---java.net.DatagramSocket:此类表示用于发送和接收数据报数据包的套接字
//public DatagramSocket()throws SocketException
DatagramSocket ds = new DatagramSocket() ;
//2)创建"数据报包"--里面包括:数据内容,ip地址,端口号
//java.net DatagramPacket
//参数1:分组数据(字节数组)
//参数2:包的长度
//参数3:ip地址对象
//参数4:端口号
//public DatagramPacket(byte[] buf,int length,InetAddress address,int port)
byte[] bytes = "hello,udp我来了".getBytes() ;
int length = bytes.length ;
InetAddress inetAddress = InetAddress.getByName("10.35.165.17") ;
int port = 6666 ;
DatagramPacket dp = new DatagramPacket(bytes,length,inetAddress,port) ;
//使用发送端的Socket对象发送"数据报包"
//public void send(DatagramPacket p)throws IOException从此套接字发送数据报包
ds.send(dp) ;
//释放资源
ds.close();
}
使用UDP协议不断发送、接收数据
UDP协议这种方式:发送端不断键盘录入数据,接收端不断的展示数据(接收不关闭,一直开启)
public class Send {
public static void main(String[] args) {
//创建发送端的socket对象
DatagramSocket ds = null ;
try {
ds = new DatagramSocket() ;
//键盘录入数据--->BufferedReader
BufferedReader br = new BufferedReader(
new InputStreamReader(System.in)) ;
System.out.println("请您输入数据 ") ;
String line = null ;
while((line=br.readLine())!=null){
//数据报包:将数据发送接收端
DatagramPacket dp = new DatagramPacket(
line.getBytes(),
line.getBytes().length,
InetAddress.getByName("10.35.165.17"),
12306) ;
//发送数据
ds.send(dp) ;
}
} catch (SocketException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
if(ds!=null){
ds.close();
}
}
}
}
接收端不断的展示数据(接收不关闭,一直开启)
public class Receive {
public static void main(String[] args) {
//创建接收端的Socket
try {
DatagramSocket ds = new DatagramSocket(12306) ;
//不断的展示数据
while(true){
//接收数据:接收容器
byte[] buffer = new byte[1024] ;
DatagramPacket dp = new DatagramPacket(buffer,buffer.length) ;
//接收
ds.receive(dp) ;
//解析缓冲区(接收容器)的数据
String message = new String(dp.getData(),0,dp.getLength()) ;
//获取ip地址
String ip = dp.getAddress().getHostAddress() ;
System.out.println("data from --->"+ip+",content is--->"+message);
}
} catch (SocketException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
//接收端不关闭,模拟真实场景
}
}
}
线程方式使用UDP
//创建线程
public class ChatRoom {
public static void main(String[] args) {
try {
//创建发送端Socket
DatagramSocket sendDs = new DatagramSocket() ;
//创建接收端的Socket
DatagramSocket receiveDs = new DatagramSocket(10086) ;
//创建发送端的线程所在的资源类对象
SendTread st = new SendTread(sendDs) ;
//创建接收端的线程所在资源类对象
ReceiveThread rt = new ReceiveThread(receiveDs) ;
//创建线程
Thread t1 = new Thread(st) ;
Thread t2 = new Thread(rt) ;
//启动线程
t1.start();
t2.start();
} catch (SocketException e) {
e.printStackTrace();
}
}
}
接收端的资源类
public class ReceiveThread implements Runnable{
private DatagramSocket ds ;
public ReceiveThread(DatagramSocket ds){
this.ds = ds ;
}
@Override
public void run() {
try {
//不断的展示数据
while(true){
//接收数据:接收容器
byte[] buffer = new byte[1024] ;
DatagramPacket dp = new DatagramPacket(buffer,buffer.length) ;
//接收
ds.receive(dp) ;
//解析缓冲区(接收容器)的数据
String message = new String(dp.getData(),0,dp.getLength()) ;
//获取ip地址
String ip = dp.getAddress().getHostAddress() ;
System.out.println("data from --->"+ip+",content is--->"+message);
}
} catch (IOException e) {
e.printStackTrace();
}
//接收端不需要释放资源,一直开启状态
}
}
发送端的资源类
public class SendTread implements Runnable{
private DatagramSocket ds ;
public SendTread(DatagramSocket ds){
this.ds = ds ;
}
@Override
public void run() {
try {
//键盘录入数据--->BufferedReader
BufferedReader br = new BufferedReader(
new InputStreamReader(System.in)) ;
String line = null ;
while((line=br.readLine())!=null){
//数据报包:将数据发送接收端
DatagramPacket dp = new DatagramPacket(
line.getBytes(),
line.getBytes().length,
InetAddress.getByName("10.35.165.17"),
10086) ;
//发送数据
ds.send(dp) ;
}
} catch (IOException e) {
e.printStackTrace();
} finally {
ds.close();
}
}
}