zoukankan      html  css  js  c++  java
  • 传智 刘意 2015年Java基础视频-深入浅出精华版 笔记 day24~day26(2016年4月26日13:09:39)

    day24
    1.多线程(JDK5之后的Lock锁的概述和使用)
     
    Lock:
              void lock(): 获取锁。
              void unlock():释放锁。  
      ReentrantLock是Lock的实现类.
     
    Re---entrant---Lock
     
    SellTicket类
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
     
    public class SellTicket implements Runnable {
     
        // 定义票
        private int tickets = 100;
     
        // 定义锁对象
        private Lock lock = new ReentrantLock();
     
        @Override
        public void run() {
            while (true) {
                try {//没有catch的try……finally块
                    // 加锁
                    lock.lock();
                    if (tickets > 0) {
                        try {
                            Thread.sleep(100);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println(Thread.currentThread().getName()
                                + "正在出售第" + (tickets--) + "张票");
                    }
                } finally {
                    // 释放锁
                    lock.unlock();
                }
            }
        }
     
    }
     
    测试类略,与前一天的一样。
     
    2.死锁问题概述和使用
    同步的弊端:
              A:效率低
              B:容易产生死锁
      
      死锁:
              两个或两个以上的线程在争夺资源的过程中,发生的一种相互等待的现象。
      
      举例:
              中国人,美国人吃饭案例。
              正常情况:
                  中国人:筷子两支
                  美国人:刀和叉
              现在:
                  中国人:筷子1支,刀一把(在等待美国人的筷子1支)
                  美国人:筷子1支,叉一把(在等待中国人的叉一把)
                    互相等待,但是互相不会放弃手中的筷子和刀(叉)
     
    下面为示例
    ============================================================
    首先,新建一个MyLock 类(作用是创建两个永不变的锁,为DieLock类提供锁,静态是为了调用方便)
    public class MyLock {
        // 创建两把锁对象
        public static final Object objA = new Object();
        public static final Object objB = new Object();
        //注意,是public而不是private,否则同一个包的不同类调用不了这两把锁
    }
     
    ==========================================================
    接着是核心---DieLock类(选其中一种方法:继承Thread类)
    public class DieLock extends Thread {
     
        private boolean flag;//作用是能在测试类中手动规定是执行if语句的内容还是else语句的内容,
    //从而方便做出死锁的效果(两个线程,一个设为true,一个设为false,刚好两条不同的路)
     
        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");
                    }
                }
            }
        }
    }
    再次提醒一下关键代码
     
    //其实,synchronized嵌套使用一不小心会有死锁现象
    ======================================================
    最后是测试类
    public class DieLockDemo {
        public static void main(String[] args) {
            DieLock dl1 = new DieLock(true);
            DieLock dl2 = new DieLock(false);
     
            dl1.start();
            dl2.start();
        }
    }
     
    运行示例
     
    停不下来(其实是卡住了),成功出现了死锁现象
     
    3.生产者消费者问题描述图
     
    4.生产者消费者问题代码1
     
    分析:
              资源类:Student    
              设置学生数据:SetThread(生产者)
              获取学生数据:GetThread(消费者)
              测试类:StudentDemo
     
    初步想法
    先建Student类
     
    然后SetThread类
     
    然后GetThread类
     
    测试类
     
    如上图所示,这样的初始思路是有问题的
     
      问题1:按照思路写代码,发现数据每次都是:null---0
      原因:我们在每个线程中都创建了新的资源,而我们要求的时候设置和获取线程的资源应该是同一个
      如何实现呢?
              在外界把这个数据创建出来,通过构造方法传递给其他的类。
    改进后代码如下
    ===========================
    Student类(不变)
    public class Student {
        String name;
        int age;
    }
    ==============================
    SetThread类
    public class SetThread implements Runnable {
     
        private Student s;
     
        public SetThread(Student s) {
            this.s = s;
        }
     
        @Override
        public void run() {
            // Student s = new Student();
            s.name = "林青霞";
            s.age = 27;
        }
     
    }
     
    =================================
    GetThread类
    public class GetThread implements Runnable {
        private Student s;
     
        public GetThread(Student s) {
            this.s = s;
        }
     
        @Override
        public void run() {
            // Student s = new Student();
            System.out.println(s.name + "---" + s.age);
        }
     
    }
    ====================================
    测试类StudentDemo 
    public class StudentDemo {
        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();
        }
    }
     
    5.生产者消费者题代码2并解决线程安全问题
    上一次的代码有潜在的问题
     
    错乱的姓名年龄匹配
     
    原因
     
    下面总结问题:
     
      问题1:按照思路写代码,发现数据每次都是:null---0
      原因:我们在每个线程中都创建了新的资源,而我们要求的时候设置和获取线程的资源应该是同一个
      如何实现呢?
              在外界把这个数据创建出来,通过构造方法传递给其他的类。
      
      问题2:为了数据的效果好一些,我加入了循环和判断,给出不同的值,这个时候产生了新的问题
              A:同一个数据出现多次
              B:姓名和年龄不匹配
      原因:
              A:同一个数据出现多次
                  CPU的一点点时间片的执行权,就足够你执行很多次。
             B:姓名和年龄不匹配
                  线程运行的随机性
      线程安全问题:
              A:是否是多线程环境        是
              B:是否有共享数据        是
              C:是否有多条语句操作共享数据    是
      解决方案:
              加锁。
               注意:
                 A:不同种类的线程都要加锁。
                  B:不同种类的线程加的锁必须是同一把
     
    Student类,测试类一样,没变
    加锁要注意的问题
    当set和get的锁都是new Object()的时候
    由于独立都new 了一次,因此锁是不一样的
     
     
    这样还不能达到效果。锁必须是同一把才有意义
     
    上图的s实际上是指通过构造方法传进来的s对象。
     
    6.生产者消费者之等待唤醒机制思路图解以及生产者消费者之等待唤醒机制代码实现,分析
    上一节的代码其实不太好
     
      问题3:虽然数据安全了,但是呢,一次一大片不好看(获取同一个数据一次而输出多次,也就是set一次却get了多次),我就想依次的一次一个输出(也就是set一次get一次)。
      如何实现呢?
              通过Java提供的等待唤醒机制解决。
     
    等待唤醒:
              Object类中提供了三个方法:
                  wait():等待
                  notify():唤醒单个线程
                  notifyAll():唤醒所有线程
                这三个方法都必须在同步代码块中执行(例如synchronized块)
            为什么这些方法不定义在Thread类中呢?
                  这些方法的调用必须通过锁对象调用,而我们刚才使用的锁对象是任意锁对象
                  所以,这些方法必须定义在Object类中。
     
    以下是生产者消费者之等待唤醒机制代码实现
    =====================================
    Student类(增加了一个成员变量)
    public class Student {
        String name;
        int age;
        boolean flag; // 默认情况是没有数据(false),如果是true,说明有数据
    }
    ======================================
    SetThread 类
    public class SetThread implements Runnable {
     
        private Student s;
        private int x = 0;
     
        public SetThread(Student s) {
            this.s = s;
        }
     
        @Override
        public void run() {
            while (true) {
                synchronized (s) {
                    //判断有没有
                    if(s.flag){//如果有数据的话,就wait着
                        try {
                            s.wait(); //t1等着,释放锁
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
     
                    if (x % 2 == 0) {
                        s.name = "林青霞";
                        s.age = 27;
                    } else {
                        s.name = "刘意";
                        s.age = 30;
                    }
                    x++; //x=1
     
                    //修改标记
                    s.flag = true;
                    //唤醒线程
                    s.notify(); //唤醒t2,唤醒并不表示你立马可以执行,必须还得抢CPU的执行权。
                }
                //t1有,或者t2有
            }
        }
    }
    =====================================================
    GetThread 类
    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){//如果没有数据的话,就等着(没有数据没法get,只能等着)
                        try {
                            s.wait(); //t2就等待了。立即释放锁。将来醒过来的时候,是从这里醒过来的时候
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
     
                    System.out.println(s.name + "---" + s.age);
                    //林青霞---27
                    //刘意---30
     
                    //修改标记
                    s.flag = false;
                    //唤醒线程
                    s.notify(); //唤醒t1
                }
            }
        }
    }
     
    ======================================================
    测试类 StudentDemo 
    public class StudentDemo {
        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();
        }
    }
    =====================================================
    运行如下
     
    注意:如果没有以下代码
                    s.flag = true;
                    s.notify(); 
    的话,会进入死锁状态
     
    等待唤醒机制代码分析
    ==================================================================

     

     

    7.线程的状态转换图及常见执行情况

    8.线程组的概述和使用

     

    线程组: 把多个线程组合到一起。
      它可以对一批线程进行分类管理,Java允许程序直接对线程组进行控制。
    (MyRunnable类省略)
    默认情况下
    MyRunnable my = new MyRunnable();
            Thread t1 = new Thread(my, "林青霞");
            Thread t2 = new Thread(my, "刘意");
            // 我不知道他们属于那个线程组,我想知道,怎么办
            // 线程类里面的方法:public final ThreadGroup getThreadGroup()
            ThreadGroup tg1 = t1.getThreadGroup();//查看这个线程属于哪个线程组
            ThreadGroup tg2 = t2.getThreadGroup();
            // 线程组里面的方法:public final String getName()
            String name1 = tg1.getName();//get这个线程组的名称
            String name2 = tg2.getName();
            System.out.println(name1);
            System.out.println(name2);
            // 通过结果我们知道了:线程默认情况下属于main线程组
     
      // 通过下面的测试,你应该能够看到,默任情况下,所有的线程都属于同一个组
            System.out.println(Thread.currentThread().getThreadGroup().getName());
    那么,我们如何修改线程所在的组呢?
            // 创建一个线程组
            // 创建其他线程的时候,把其他线程的组指定为我们自己新建线程组
    // ThreadGroup(String name)
            ThreadGroup tg = new ThreadGroup("这是一个新的组");
     
            MyRunnable my = new MyRunnable();
            // Thread(ThreadGroup group, Runnable target, String name)
            Thread t1 = new Thread(tg, my, "林青霞");
            Thread t2 = new Thread(tg, my, "刘意");
     
            System.out.println(t1.getThreadGroup().getName());
            System.out.println(t2.getThreadGroup().getName());
     
            //通过组名称设置后台线程,表示该组的线程都是后台线程
            tg.setDaemon(true);
    ==================================================================
    注意:Thread(ThreadGroup group, Runnable target, String name)----三个参数
    Thread t1 = new Thread(tg, my, "林青霞");
            Thread t2 = new Thread(tg, my, "刘意");
    Thread三个构造方法
     
    9.生产者消费者之等待唤醒机制代码优化
    也就是,最终版代码(在Student类中有大改动,然后GetThread类和SetThread类简洁很多)
    =======================================
    Student类
    public class Student {
        private String name;
        private int age;
        private boolean flag; // 默认情况是没有数据,如果是true,说明有数据
     
        public synchronized void set(String name, int age) {
            // 如果有数据,就等待
            if (this.flag) {//锁是this对象
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
     
            // 设置数据
            this.name = name;
            this.age = age;
     
            // 修改标记
            this.flag = true;
            this.notify();
        }
     
        public synchronized void get() {
            // 如果没有数据,就等待
            if (!this.flag) {
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
     
            // 获取数据
            System.out.println(this.name + "---" + this.age);
     
            // 修改标记
            this.flag = false;
            this.notify();
        }
    }
     
    ========================================
    SetThread 类
    public class SetThread implements Runnable {
     
        private Student s;
        private int x = 0;
     
        public SetThread(Student s) {
            this.s = s;
        }
     
        @Override
        public void run() {
            while (true) {
                if (x % 2 == 0) {
                    s.set("林青霞", 27);//这里直接调用方法而不用考虑同步的问题了----因为Student类搞定一切同步问题
                } else {
                    s.set("刘意", 30);//同上
                }
                x++;
            }
        }
    }
     
    =========================================================
    GetThread 类
    public class GetThread implements Runnable {
        private Student s;
     
        public GetThread(Student s) {
            this.s = s;
        }
     
        @Override
        public void run() {
            while (true) {
                s.get();//直接调用方法,无需考虑同步问题
            }
        }
    }
    ======================================================
    测试类 StudentDemo (没变)
    public class StudentDemo {
        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();
        }
    }
    =========================================================
     最终版代码中:
              把Student的成员变量给私有的了。
              把设置和获取的操作给封装成了功能,并加了同步。
              设置或者获取的线程里面只需要调用方法即可。
     
    10.线程池的概述和使用
     
     
     

    public static ExecutorServicenewCachedThreadPool()

      创建一个具有缓存功能的线程池

      缓存:百度浏览过的信息再次访问

    public static ExecutorServicenewFixedThreadPool(intnThreads)

      创建一个可重用的,具有固定线程数的线程池

    public static ExecutorServicenewSingleThreadExecutor()

      创建一个只有单线的线程池,相当于上个方法的参数是1 

     

    ==============================================
      线程池的好处:线程池里的每一个线程代码结束后,并不会死亡,而是再次回到线程池中成为空闲状态,等待下一个对象来使用。
      
      如何实现线程的代码呢?
              A:创建一个线程池对象,控制要创建几个线程对象。
                  public static ExecutorService newFixedThreadPool(int nThreads)
              B:这种线程池的线程可以执行:
                 可以执行Runnable对象或者Callable对象代表的线程
                  做一个类实现Runnable接口。
              C:调用如下方法即可
                   Future<?> submit(Runnable task)
                <T> Future<T> submit(Callable<T> task)
             D:我就要结束,可以吗?
                 可以。
    =====================================
    代码区
    首先,MyRunnable类
    public class MyRunnable implements Runnable {
     
        @Override
        public void run() {
            for (int x = 0; x < 100; x++) {
                System.out.println(Thread.currentThread().getName() + ":" + x);
            }
        }
     
    }
    ======================================
    ExecutorsDemo 类
    public class ExecutorsDemo {
        public static void main(String[] args) {
            // 创建一个线程池对象,控制要创建几个线程对象
            // public static ExecutorService newFixedThreadPool(int nThreads)
            ExecutorService pool = Executors.newFixedThreadPool(2);
     
            // 可以执行Runnable对象或者Callable对象代表的线程
            pool.submit(new MyRunnable());
            pool.submit(new MyRunnable());
     
            //结束线程池
            pool.shutdown();
        }
    }
    ===========================================
    如果没有   pool.shutdown(); ----根本停不下来
     
    如果有  pool.shutdown();---可以停下来
     
    11.多线程方式3的思路及代码实现(先忽略泛型)
      多线程实现的方式3:
           A:创建一个线程池对象,控制要创建几个线程对象。
                  public static ExecutorService newFixedThreadPool(int nThreads)
              B:这种线程池的线程可以执行:
                  可以执行Runnable对象或者Callable对象代表的线程
                  做一个类实现Runnable接口。
              C:调用如下方法即可
                  Future<?> submit(Runnable task)
                 <T> Future<T> submit(Callable<T> task)
             D:我就要结束,可以吗?
                 可以。
     ==============================================
    callable---是接口,有返回值,而且依赖于线程池才能使用,一般很少用
     
    Callable:是带泛型的接口。
    这里指定的泛型其实是call()方法的返回值类型。
    MyCallable 类
    public class MyCallable implements Callable {
     
        @Override
        public Object call() throws Exception {
            for (int x = 0; x < 100; x++) {
                System.out.println(Thread.currentThread().getName() + ":" + x);
            }
            return null;
        }
     
    }
    ================================================
    测试类
     
    public class CallableDemo {
        public static void main(String[] args) {
            //创建线程池对象
            ExecutorService pool = Executors.newFixedThreadPool(2);
     
            //可以执行Runnable对象或者Callable对象代表的线程
            pool.submit(new MyCallable());
            pool.submit(new MyCallable());
     
            //结束
            pool.shutdown();
        }
    }
    =================================================
    12.多线程方式3的求和案例(用上泛型和返回值)
     
    MyCallable 类
    新建界面修改一下T
     
    public class MyCallable implements Callable<Integer> {
     
        private int number;//限定范围的变量
     
        public MyCallable(int number) {
            this.number = number;
        }
     
        @Override
        public Integer call() throws Exception {
            int sum = 0;
            for (int x = 1; x <= number; x++) {
                sum += x;
            }
            return sum;
        }
     
    }
    ====================================
    测试类

    public class CallableDemo {
        public static void main(String[] args) throws InterruptedException, ExecutionException {
            // 创建线程池对象
            ExecutorService pool = Executors.newFixedThreadPool(2);
     
            // 可以执行Runnable对象或者Callable对象代表的线程
            Future<Integer> f1 = pool.submit(new MyCallable(100));
            Future<Integer> f2 = pool.submit(new MyCallable(200));
     
            // V get()
            Integer i1 = f1.get();
            Integer i2 = f2.get();
     
            System.out.println(i1);
            System.out.println(i2);
     
            // 结束
            pool.shutdown();
        }
    }
    =============================================================
    13.匿名内部类的方式实现多线程程序
     
    匿名内部类的格式:
              new 类名或者接口名( ) {
                  重写方法;
              };
              本质:是该类或者接口的子类对象
    ===============================================
    public class ThreadDemo {
        public static void main(String[] args) {
            // 继承Thread类来实现多线程
            new Thread() {
                public void run() {
                    for (int x = 0; x < 100; x++) {
                        System.out.println(Thread.currentThread().getName() + ":"
                                + x);
                    }
                }
            }.start();
     
            // 实现Runnable接口来实现多线程
            new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int x = 0; x < 100; x++) {
                        System.out.println(Thread.currentThread().getName() + ":"
                                + x);
                    }
                }
            }) {
            }.start();
     
            // 更有难度的
            new Thread(new Runnable() {//实际上是匿名内部类的嵌套??
                @Override
                public void run() {
                    for (int x = 0; x < 100; x++) {
                        System.out.println("hello" + ":" + x);
                    }
                }
            }) {
                public void run() {
                    for (int x = 0; x < 100; x++) {
                        System.out.println("world" + ":" + x);
                    }
                }
            }.start();
        }
    }
    =======================================================
    以下是解析
    可以先写以下框架在扩充代码
     
    匿名内部类的嵌套???我也不清楚,视频里也没说,不过应该是
    子类重写run方法
    以下是一个复杂的,只有面试题才会出的恶性写法
    这个子类对象继承了Thread类而且重写父类的run()方法,同时还实现了Runnable接口同时重写了接口中的run()方法
     
    那么问题来了,究竟执行哪个呢?答案是"world"这个run方法(用单词hello和world是为了好区分)
    14.定时器的概述和使用
     
      定时器:可以让我们在指定的时间做某件事情,还可以重复的做某件事情。
      依赖Timer和TimerTask这两个类:
      Timer:定时
              public Timer()
              public void schedule(TimerTask task,long delay)
              public void schedule(TimerTask task,long delay,long period)
              public void cancel()
      TimerTask:任务
    在开发中一般不会用Timer,因为太弱,开发中一般用框架(见下图)
     
    ===================================
    public class TimerDemo {
        public static void main(String[] args) {
            // 创建定时器对象
            Timer t = new Timer();
            // 3秒后执行爆炸任务
            // t.schedule(new MyTask(), 3000);
            //结束任务
            t.schedule(new MyTask(t), 3000);
        }
    }
     
    // 做一个任务
    class MyTask extends TimerTask {
     
        private Timer t;
     
        public MyTask(){}
     
        public MyTask(Timer t){
            this.t = t;
        }
     
     
     
        @Override
        public void run() {
            System.out.println("beng,爆炸了");
            t.cancel();
        }
     
    }
    =============================================
    注意,cancel方法放在run方法执行
     
    如果要不止炸一次怎么办??
    下面--------
    定时任务的多次执行代码体现
    public class TimerDemo2 {
        public static void main(String[] args) {
            // 创建定时器对象
            Timer t = new Timer();
            // 3秒后执行爆炸任务第一次,如果不成功,每隔2秒再继续炸
            t.schedule(new MyTask2(), 30002000);
        }
    }
     
    // 做一个任务
    class MyTask2 extends TimerTask {//同一个包下MyTask类只有一个,所以改为MyTask2
        @Override
        public void run() {
            System.out.println("beng,爆炸了");
        }
    }
    15.案例---定时删除指定的带内容目录
    需求:在指定的时间删除我们的指定目录(你可以指定c盘,但是我不建议,我使用项目路径下的demo)
    =======================================================
    class DeleteFolder extends TimerTask {
     
        @Override
        public void run() {
            File srcFolder = new File("demo");
            deleteFolder(srcFolder);
        }
     
        // 递归删除目录
        public void deleteFolder(File srcFolder) {
            File[] fileArray = srcFolder.listFiles();
            if (fileArray != null) {//使用for循环必须考虑这个问题
                for (File file : fileArray) {
                    if (file.isDirectory()) {
                        deleteFolder(file);//递归
                    } else {
                        System.out.println(file.getName() + ":" + file.delete());//其实这里可以直接 file.delete();输出是为了方便查看结果
                    }
                }
                System.out.println(srcFolder.getName() + ":" + srcFolder.delete());//输出原因同上
            }
        }
    }
     
    public class TimerTest {
        public static void main(String[] args) throws ParseException {
            Timer t = new Timer();
     
            String s = "2014-11-27 15:45:00";
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            Date d = sdf.parse(s);
     
            t.schedule(new DeleteFolder(), d);
        }
    }
    ===========================================================
    16.多线程常见的面试题
        1:多线程有几种实现方案,分别是哪几种?
        两种。
     
        继承Thread类
        实现Runnable接口
     
        扩展一种:实现Callable接口。这个得和线程池结合。(一般可以不答)
     
      2:同步有几种方式,分别是什么?
        两种。
     
        同步代码块
        同步方法
     
      3:启动一个线程是run()还是start()?它们的区别?
        start();
     
        run():封装了被线程执行的代码,直接调用仅仅是普通方法的调用
        start():启动线程,并由JVM自动调用run()方法
     
      4:sleep()和wait()方法的区别
        sleep():必须指时间;不释放锁
        wait():可以不指定时间,也可以指定时间;释放锁
     
      5:为什么wait(),notify(),notifyAll()等方法都定义在Object类中
        因为这些方法的调用是依赖于锁对象的,而同步代码块的锁对象是任意锁。
        而Object代码任意的对象,所以,定义在这里面。
     
      6:线程的生命周期图
        新建 -- 就绪 -- 运行 -- 死亡
        新建 -- 就绪 -- 运行 -- 阻塞 -- 就绪 -- 运行 -- 死亡
        建议:画图解释。
     
    17.面向对象的常见设计原则概述
     
    面向对象思想设计原则
    •在实际的开发中,我们要想更深入的了解面向对象思想,就必须熟悉前人总结过的面向对象的思想的设计原则
    •单一职责原则
    •开闭原则
    •里氏替换原则
    •依赖注入原则
    •接口分离原则
    •迪米特原则
     
    l单一职责原则
    •其实就是开发人员经常说的”高内聚,低耦合”
    •也就是说,每个类应该只有一个职责,对外只能提供一种功能,而引起类变化的原因应该只有一个。在设计模式中,所有的设计模式都遵循这一原则。
     
    l开闭原则
    •核心思想是:一个对象对扩展开放,对修改关闭。
    •其实开闭原则的意思就是:对类的改动是通过增加代码进行的,而不是修改现有代码。
    •也就是说软件开发人员一旦写出了可以运行的代码,就不应该去改动它,而是要保证它能一直运行下去,如何能够做到这一点呢?这就需要借助于抽象和多态,即把可能变化的内容抽象出来,从而使抽象的部分是相对稳定的,而具体的实现则是可以改变和扩展的。
    l里氏替换原则
    •核心思想:在任何父类出现的地方都可以用它的子类来替代。
    •其实就是说:同一个继承体系中的对象应该有共同的行为特征。
     
    l依赖注入原则
    •核心思想:要依赖于抽象,不要依赖于具体实现。
    •其实就是说:在应用程序中,所有的类如果使用或依赖于其他的类,则应该依赖这些其他类的抽象类,而不是这些其他类的具体类。为了实现这一原则,就要求我们在编程的时候针对抽象类或者接口编程,而不是针对具体实现编程。
    l接口分离原则
    •核心思想:不应该强迫程序依赖它们不需要使用的方法。
    •其实就是说:一个接口不需要提供太多的行为,一个接口应该只提供一种对外的功能,不应该把所有的操作都封装到一个接口中。
    l迪米特原则
    •核心思想:一个对象应当对其他对象尽可能少的了解
    •其实就是说:降低各个对象之间的耦合,提高系统的可维护性。在模块之间应该只通过接口编程,而不理会模块的内部工作原理,它可以使各个模块耦合度降到最低,促进软件的复用
    18.设计模式的概述和分类
    l设计模式概述
    •设计模式(Design pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性。
    •设计模式不是一种方法和技术,而是一种思想
    •设计模式和具体的语言无关,学习设计模式就是要建立面向对象的思想,尽可能的面向接口编程,低耦合,高内聚,使设计的程序可复用
    •学习设计模式能够促进对面向对象思想的理解,反之亦然。它们相辅相成
     
    l设计模式的几个要素
    •名字 必须有一个简单,有意义的名字
    •问题 描述在何时使用模式
    •解决方案 描述设计的组成部分以及如何解决问题
    •效果 描述模式的效果以及优缺点
    l设计模式的分类
    创建型模式 对象的创建
    结构型模式 对象的组成(结构)
    行为型模式 对象的行为
     
    常见23个设计模式

    创建型模式:简单工厂模式,工厂方法模式,抽象工厂模式,建造者模式,原型模式,单例模式。(6个)

    结构型模式:外观模式、适配器模式、代理模式、装饰模式、桥接模式、组合模式、享元模式。(7个)

    行为型模式:模版方法模式、观察者模式、状态模式、职责链模式、命令模式、访问者模式、策略模式、备忘录模式、迭代器模式、解释器模式。(10个)

    19.简单工厂模式概述和使用
    猫狗举例
    ==================
    先不考虑设计模式的话,
    先建一个Animal抽象类
    public abstract class Animal {
        public abstract void eat();
    }
    ==================
    Dog类
    public class Dog extends Animal {
     
        @Override
        public void eat() {
            System.out.println("狗吃肉");
        }
     
    }
    ======================
    Cat类
    public class Cat extends Animal {
     
        @Override
        public void eat() {
            System.out.println("猫吃鱼");
        }
     
    }
    ===========================
    测试类
    public class AnimalDemo {
        public static void main(String[] args) {
            // 具体类调用
            Dog d = new Dog();
            d.eat();
            Cat c = new Cat();
            c.eat();
            System.out.println("------------");
    }
    =======================================
    但是在实际开发中,创建一个对象可能没那么容易,所以new一个对象的过程交给一个单独的类来做
    即在同一个包下新建一个工厂类专门生产动物对象
    public class AnimalFactory {
     
        private AnimalFactory() {
        }
     
         public static Dog createDog() {
         return new Dog();
         }
        
         public static Cat createCat() {
         return new Cat();
         }
    }
    =========================
    测试类测试代码
     
            // 工厂有了后,通过工厂给造
             Dog dd = AnimalFactory.createDog();
             Cat cc = AnimalFactory.createCat();
             dd.eat();
             cc.eat();
             System.out.println("------------");
    ==================================
    对工厂类进行改进
    public class AnimalFactory {
     
        private AnimalFactory() {
        }
     
        public static Animal createAnimal(String type) {
            if ("dog".equals(type)) {
                return new Dog();
            } else if ("cat".equals(type)) {
                return new Cat();
            } else {
                return null;//不能漏
            }
        }
    }
    =================================
    测试类测试代码
    // 工厂改进后
            Animal a = AnimalFactory.createAnimal("dog");//只能用Animal接收
            a.eat();
            a = AnimalFactory.createAnimal("cat");
            a.eat();
     
            // NullPointerException
            a = AnimalFactory.createAnimal("pig");
            if (a != null) {//防止空指针异常
                a.eat();
            } else {
                System.out.println("对不起,暂时不提供这种动物");
            }
     
    =======================================
     
    20.工厂方法模式的概述和使用
     
    新建一个包
    然后先写Animal类(抽象类)
    public abstract class Animal {
        public abstract void eat();
    }
    ====================================
    然后写一个总的工厂接口Factory
    public interface Factory {
        public abstract Animal createAnimal();
    }
    ===================================
    接着,新建一个空的测试类,假如有需求:我要买只狗
    那么,首先需要有Dog类
    public class Dog extends Animal {
     
        @Override
        public void eat() {
            System.out.println("狗吃肉");
        }
     
    }
    ====================================
    然后,建一个生产狗的工厂类
    public class DogFactory implements Factory {
     
        @Override
        public Animal createAnimal() {//多态
            return new Dog();
        }
     
    }
    =======================================
    在测试类中
    public static void main(String[] args) {
            // 需求:我要买只狗
            Factory f = new DogFactory();
            Animal a = f.createAnimal();
            a.eat();
            System.out.println("-------");
        }
    ========================================
    接着,我要买只猫,同理
    猫类
    public class Cat extends Animal {
     
        @Override
        public void eat() {
            System.out.println("猫吃鱼");
        }
     
    }
     
    ====================================
    猫工厂
    public class CatFactory implements Factory {
     
        @Override
        public Animal createAnimal() {
            return new Cat();
        }
     
    }
     
    =================================
    测试类
    public class AnimalDemo {
        public static void main(String[] args) {
            // 需求:我要买只狗
            Factory f = new DogFactory();
            Animal a = f.createAnimal();
            a.eat();
            System.out.println("-------");
     
            //需求:我要买只猫
            f = new CatFactory();
            a = f.createAnimal();
            a.eat();
        }
    }
     
    ==========================================================
    21.单例模式之饿汉式
    饿汉的意思:类一加载就造对象
    引入:先新建一个空的Student类
    测试
    单例模式:保证类在内存中只有一个对象。
      
      如何保证类在内存中只有一个对象呢?
              A:把构造方法私有
              B:在成员位置自己创建一个对象
              C:通过一个公共的方法提供访问
    ===============================
    Student类
    public class Student {
        // 构造私有
        private Student() {
        }
     
        // 自己造一个
        // 静态方法只能访问静态成员变量,加静态
        // 为了不让外界直接访问修改这个值,加private
        private static Student s = new Student();
     
        // 提供公共的访问方式
        // 为了保证外界能够直接使用该方法,加静态
        public static Student getStudent() {
            return s;
        }
    }
    ===============================
    测试类
    public class StudentDemo {
        public static void main(String[] args) {
            // Student s1 = new Student();
            // Student s2 = new Student();
            // System.out.println(s1 == s2); // false
     
            // 通过单例如何得到对象呢?
     
            // Student.s = null;
     
            Student s1 = Student.getStudent();
            Student s2 = Student.getStudent();
            System.out.println(s1 == s2);
     
            System.out.println(s1); // null,cn.itcast_03.Student@175078b
            System.out.println(s2);// null,cn.itcast_03.Student@175078b
        }
    }
    ============================================
     
    注意这行代码
     private static Student s = new Student();
    没有private会很危险,会导致同一个包的其它类修改s的值
    而static则保证个体Student()方法的执行(因为这个方法是静态的)
    成功运行单例模式
    21.单例模式之懒汉式
     
      单例模式:
              饿汉式:类一加载就创建对象
              懒汉式:用的时候,才去创建对象
    ==============================
    先创一个Teacher类
    public class Teacher {
        private Teacher() {
        }
     
        private static Teacher t = null;
     
        public synchronized static Teacher getTeacher() {
            if (t == null) {//当t为null时才去创建
                t = new Teacher();
            }
            return t;
        }
    }
     
    =========================================
    测试类
    public class TeacherDemo {
        public static void main(String[] args) {
            Teacher t1 = Teacher.getTeacher();//t为null,因此创建对象
            Teacher t2 = Teacher.getTeacher();//前面已经创建对象了,这一次直接用那个对象
            System.out.println(t1 == t2);
            System.out.println(t1); // cn.itcast_03.Teacher@175078b
            System.out.println(t2);// cn.itcast_03.Teacher@175078b
        }
    }
    =========================================
    注意:
     
      面试题:单例模式的思想是什么?请写一个代码体现
      
              开发:饿汉式(是不会出问题的单例模式)
              面试:懒汉式(可能会出问题的单例模式)
                  A:懒加载(延迟加载)    
                  B:线程安全问题
                      a:是否多线程环境    是
                      b:是否有共享数据    是
                      c:是否有多条语句操作共享数据     是
     
    下图表示Teacher类的方法符合出现线程安全问题的条件
     
    改进关键:synchronized
     
    22.单例模式的Java代码体现Runtime类
    =====================================
    Runtime源码
    /*
     * class Runtime {
     *         private Runtime() {}
     *         private static Runtime currentRuntime = new Runtime();
     *         public static Runtime getRuntime() {
     *           return currentRuntime;
     *       }
     * }
     */
    ===========================================
    Runtime:每个 Java 应用程序都有一个 Runtime 类实例,使应用程序能够与其运行的环境相连接。
      exec(String command)
    =================================================
    public class RuntimeDemo {
        public static void main(String[] args) throws IOException {
            Runtime r = Runtime.getRuntime();
            // r.exec("notepad");
            // r.exec("calc");
    //        r.exec("shutdown -s -t 10000");
            r.exec("shutdown -a");
        }
    }
    ================================================
    一些解析
     
     
    一运行
     
    shutdown -a 取消关机
     

    其实简单来讲就是以下作用
     
    day24笔记补充
    等待唤醒机制改进该程序,让数据能够实现依次的出现
                    wait()
                    notify()
                    notifyAll() (多生产多消费)
    特别注意单例模式  
     单例模式(掌握)
                a:饿汉式
                b:懒汉式
     
    Runtime
            JDK提供的一个单例模式应用的类。
            还可以调用dos命令
     
     
     
     
    day25
     
    1.GUI概述和GUI与CLI的区别
     
    2.java.awt和javax.swing包的区别

    Java为GUI提供的对象都存在java.Awt和javax.Swing两个包中。

     

     

    java.awt:Abstract Window ToolKit (抽象窗口工具包),需要调用本地系统方法实现功能。属重量级控件。
     
    javax.swing:在AWT的基础上,建立的一套图形界面系统,其中提供了更多的组件,而且完全由Java实现。增强了移植性,属轻量级控件。

     

    javax--- java扩展包
     
    轻量级(移植性好),重量级(移植性差)说的是与系统的关联性的大小
     
    3.GUI中组件继承图
     
    那么,从哪里学起呢??
    Component??Container??
    都不是,应该从Frame学起。
     
     
    Frame继承的方法太多了,,,,但是Frame本身的方法不多。
     
    4.HelloWorld窗体案例
    ===============================================
    public class FrameDemo {
        public static void main(String[] args) {
            // 创建窗体对象
            // Frame f = new Frame();
            // Frame(String title)
            Frame f = new Frame("林青霞");//设置窗体标题可以用构造方法
     
            // 设置窗体标题
            f.setTitle("HelloWorld");//设置窗口标题的第二个方法
            // 设置窗体大小
            f.setSize(400, 300); // 单位:像素
            // 设置窗体位置(就是设置程序一运行就弹出窗口的位置,默认左上角)
            f.setLocation(400, 200);
     
            // 调用一个方法,设置让窗体可见
            // f.show();//已过时
            f.setVisible(true);//setVisible一般放在最后写,因为设置好窗体才看到窗体比较好(就好像穿好衣服化好妆出门才好)
     
            // System.out.println("helloworld");
        }
    }
    ==================================================
    以下是一些解析以及注意的问题
    窗体直接点右上角(如图红色关闭区域)关闭不了,而是要强制关闭程序才可以。
     
    窗体太小,调大一点
     
    改变打开时窗体弹出的位置
     
    5.优化HelloWorld窗体案例代码
    public class FrameDemo2 {
        public static void main(String[] args) {
            // 创建对象
            Frame f = new Frame("方法调用的前后关系");
     
            // f.setVisible(true);
            // try {
            // Thread.sleep(3000);//让线程睡一会的目的就是说setVisible要放到最后而不要放在前面,也就是先设置窗体位置大小,再显示窗体,就好像先穿衣服,打扮再出门。
            // } catch (InterruptedException e) {
            // e.printStackTrace();
            // }
     
            // // f.setSize(400, 300);
            // // Dimension(int width, int height)
            // Dimension d = new Dimension(400, 300);
            // f.setSize(d);
            // // f.setLocation(400, 200);
            // // Point(int x, int y)
            // Point p = new Point(400, 200);
            // f.setLocation(p);
            // 一个方法搞定
            f.setBounds(400, 200, 400, 300);
     
            f.setVisible(true);
        }
    }
    ============================================
    以下是解析
     
     f.setSize(400, 300);
    等价于
    // Dimension(int width, int height)
            // Dimension d = new Dimension(400, 300);
            // f.setSize(d);
    =================================
    f.setLocation(400, 200);
    等价于
    // Point(int x, int y)//point是点的意思
            // Point = new Point(400, 200);
            // f.setLocation(p);
    ====================================
    其实,
    // 设置窗体大小
            f.setSize(400, 300); // 单位:像素
            // 设置窗体位置
            f.setLocation(400, 200);
    以上两行代码直接可以用一句来代替
    // 一个方法搞定
            f.setBounds(400, 200, 400, 300);
     
    也就是,最终三行代码搞掂
     
    ============================================================
    6.事件监听机制原理概述及举例
    事件监听机制:
        A:事件源    事件发生的地方
        B:事件    就是要发生的事情
        C:事件处理    就是针对发生的事情做出的处理方案
        D:事件监听 就是把事件源和事件关联起来
     
    举例:人受伤事件。
     
        事件源:人(具体的对象)
            Person p1 = new Person("张三");
            Person p2 = new Person("李四");
        事件:受伤
            interface 受伤接口 {
                一拳();
                一脚();
                一板砖();
            }
        事件处理:
            受伤处理类 implements 受伤接口 {
                一拳(){ System.out.println("鼻子流血了,送到卫生间洗洗"); }
                一脚(){ System.out.println("晕倒了,送到通风处"); }
                一板砖(){ System.out.println("头破血流,送到太平间"); }
            }
        事件监听:
            p1.注册监听(受伤接口)
     
    7.窗体关闭案例
    public class FrameDemo {
        public static void main(String[] args) {
            // 创建窗体对象
            Frame f = new Frame("窗体关闭案例");
     
            // 设置窗体属性
            f.setBounds(400, 200, 400, 300);
     
            // 让窗体关闭
            //事件源---f为事件源
            //事件:对窗体的处理
            //事件处理:关闭窗口(System.exit(0));//退出虚拟机
            //事件监听
            f.addWindowListener(new WindowListener() {//匿名内部类
                @Override
                public void windowOpened(WindowEvent e) {
                }
                
                @Override
                public void windowIconified(WindowEvent e) {
                }
                
                @Override
                public void windowDeiconified(WindowEvent e) {
                }
                
                @Override
                public void windowDeactivated(WindowEvent e) {
               }
              
               @Override
               public void windowClosing(WindowEvent e) {
                   System.exit(0);//退出虚拟机
                }
               
                @Override
                public void windowClosed(WindowEvent e) {
               }
                
               @Override
                public void windowActivated(WindowEvent e) {
                }
            });
     
            // 设置窗体可见
            f.setVisible(true);
        }
    }
    添加事件监听后窗体就可以通过点右上角红色的关闭按钮关闭了
     
    8.适配器设计模式讲解
    如上面代码可以看见,代码非常多且累赘,那些并不需要(甚至说不应该或者说不能)实现的方法也强迫要实现
     
    问题:
     *         接口(方法比较多) -- 实现类(仅仅使用一个,也得把其他的实现给提供了,哪怕是空实现)
     *         太麻烦了。
     * 解决方案:
     *         接口(方法比较多) -- 适配器类(实现接口,仅仅空实现) -- 实现类(用哪个重写哪个)//适配器类实现了分离
    ================================================
    首先新建一个接口
    /*
     * 针对用户操作的四种功能
     */
    public interface UserDao {
        public abstract void add();
     
        public abstract void delete();
     
        public abstract void update();
     
        public abstract void find();
    }
    =========================================
    然后实现这个接口//这个实现类需要实现全部方法比较麻烦
    public class UserDaoImpl implements UserDao {
     
        @Override
        public void add() {
            System.out.println("添加功能");
        }
     
        @Override
        public void delete() {
            System.out.println("删除功能");
        }
     
        @Override
        public void update() {
            System.out.println("修改功能");
        }
     
        @Override
        public void find() {
            System.out.println("查找功能");
        }
     
    }
    ====================================================
    测试类中
    public class UserDaoDemo {
        public static void main(String[] args) {
            UserDao ud = new UserDaoImpl();
            ud.add();//为了用add方法必须把其余三个方法也实现了,不太合理
        }
    }
    =====================================================
     
    下面,加入适配器
     
    先建一个抽象类(适配器)实现UserDao接口(空实现)
    public abstract class UserAdapter implements UserDao {
     
        @Override
        public void add() {
        }
     
        @Override
        public void delete() {
        }
     
        @Override
        public void update() {
        }
     
        @Override
        public void find() {
        }
     
    }
    ===========================================================
    然后,新建一个类实现UserDao接口,但是不直接实现UserDao,而是继承UserAdapter (因为它实现了那个接口)
    public class UserDaoImpl2 extends UserAdapter {
        @Override
        public void add() {
            System.out.println("添加功能");
        }
    }
    //UserDaoImpl2 比UserDapImpl1简洁多了
    ================================================
    测试类中
    public class UserDaoDemo {
        public static void main(String[] args) {
           // UserDao ud = new UserDaoImpl();
           // ud.add();
            // 我没有说我们需要四种功能都实现啊。
            UserDao ud2 = new UserDaoImpl2();
            ud2.add();
        }
    }
    ============================================
    附上新建截图
     
     
     
    9.适配器类改进窗体关闭案例
    原来代码是这样的
     
    用适配器改进后
     
            //用适配器类改进
            f.addWindowListener(new WindowAdapter() {
                @Override
                public void windowClosing(WindowEvent e) {
                    System.exit(0);
                }
            });
     
    简洁了很多
    ==================================================
     
    10窗体添加按钮并对按钮添加事件案例
    需求:把按钮添加到窗体,并对按钮添加一个点击事件。
      A:创建窗体对象
      B:创建按钮对象
      C:把按钮添加到窗体
      D:窗体显示
    ==============================================
    public class FrameDemo {
        public static void main(String[] args) {
            // 创建窗体对象
            Frame f = new Frame("添加按钮");
            // 设置属性
            f.setBounds(400, 200, 400, 300);
            // 设置布局为流式布局
            f.setLayout(new FlowLayout());
     
            // 创建按钮对象
            Button bu = new Button("点我啊");
            // bu.setSize(20, 10);
     
            // 把按钮添加到窗体
            f.add(bu);
     
            // 设置窗体可以关闭
            f.addWindowListener(new WindowAdapter() {
                @Override
                public void windowClosing(WindowEvent e) {
                    System.exit(0);
                }
            });
     
            bu.addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    System.out.println("你再点试试");
                }
            });
     
            // 窗体显示
            f.setVisible(true);
        }
    }
    =================================================
    下面是一些解析
    首先讲一下为什么要f.setLayout(new FlowLayout());
    这个涉及窗体布局方案的问题
     
    如果不设置布局的话,默认是边界布局(BoderLayout),但是可以看到,这个按钮填充了整个窗体
     
    因为边界布局分东,南,西,北,中按钮默认在中间,而其它位置有没有任何东西,所以它填充满了
    整个窗体
    还有名字忘了不要紧,会查就行---setLayout传的参数就是LayoutManager(接口)
     
     
    还有适配器的问题,为什么ActionListener不用适配器呢?---原因是,整个接口本来就只需要实现一个方法,没必要动用到适配器类
     
    运行示例
     
    11.把文本框的值转移到文本域案例
    案例预览
    需求:如下图
    把文本框数据转移到文本域
     
    =======================================================
    public class FrameDemo {
        public static void main(String[] args) {
            // 创建窗体对象
            Frame f = new Frame("数据转移");
            // 设置窗体属性和布局
            f.setBounds(400, 200, 400, 300);
            f.setLayout(new FlowLayout());
     
            // 创建文本框
            final TextField tf = new TextField(20);
            // 创建按钮
            Button bu = new Button("数据转移");
            // 创建文本域
            final TextArea ta = new TextArea(10, 40);
     
            // 把组件添加到窗体
            f.add(tf);
            f.add(bu);
            f.add(ta);
     
            // 设置窗体关闭
            f.addWindowListener(new WindowAdapter() {
                @Override
                public void windowClosing(WindowEvent e) {
                    System.exit(0);
                }
            });
     
            // 对按钮添加事件
            bu.addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    // 获取文本框的值
                    String tf_str = tf.getText().trim();//trim防止多余空格的输入
                    // 清空数据
                    tf.setText("");
     
                    // 设置给文本域
                    // ta.setText(tf_str);
                    // 追加和换行
                    ta.append(tf_str + " ");// 起到换行的作用,但是直接 也可以??
     
                    //获取光标
                    tf.requestFocus();//点击按钮后光标自动回到TextField中
                }
            });
     
            // 设置窗体显示
            f.setVisible(true);
        }
    }
    =================================================
    以下为解析以及一些注意事项
    TextField一般用列数那个构造方法
     
    以下的问题在eclipse3.7(JDK1.7)报错,但是在eclipse4.4(JDK1.8)却不报错,我也不知道为什么
    附上网上解析:
    局部内部类,只能访问被final修饰的局部变量
    内部类绝对不能访问非final的成员变量
      因为内部类 也是类,他们实例化后存储在堆中局部变量存储在栈中,所以一般情况而言内部类的声明周期长于局部变量。
    试想一个活着的内部类去访问一个声明周期已经结束的局部变量~ 
    所以,极有可能的原因是: jdk1.8版本内部类成员变量不用final修饰也能访问
     
    以前关于局部内部类的笔记
     
    解决后
    // 创建文本框
            final TextField tf = new TextField(20);
           
            // 创建文本域
            final TextArea ta = new TextArea(10, 40);
    =============================================
    获取光标的意图
     
    12.通过鼠标移动到按钮上更改背景色案例
    需求:
     
    public class FrameDemo {
        public static void main(String[] args) {
            // 创建窗体对象
            final Frame f = new Frame("更改背景色");
            // 设置窗体属性和布局
            f.setBounds(400, 200, 400, 300);
            f.setLayout(new FlowLayout());
     
            // 创建四个按钮
            Button redButton = new Button("红色");
            Button greenButton = new Button("绿色");
            Button buleButton = new Button("蓝色");
     
            // 添加按钮
            f.add(redButton);
            f.add(greenButton);
            f.add(buleButton);
     
            // 设置窗体关闭
            f.addWindowListener(new WindowAdapter() {
                @Override
                public void windowClosing(WindowEvent e) {
                    System.exit(0);
                }
            });
     
            // 对按钮添加动作事件
            // redButton.addActionListener(new ActionListener() {
            // @Override
            // public void actionPerformed(ActionEvent e) {
            // f.setBackground(Color.RED);
            // }
            // });
     
            // 对按钮添加鼠标点击事件
            // redButton.addMouseListener(new MouseAdapter() {
            // @Override
            // public void mouseClicked(MouseEvent e) {
            // f.setBackground(Color.RED);
            // }
            // });
     
            // 对按钮添加鼠标的进入事件
            redButton.addMouseListener(new MouseAdapter() {
                @Override
                public void mouseEntered(MouseEvent e) {
                    f.setBackground(Color.RED);
                }
            });
     
            redButton.addMouseListener(new MouseAdapter() {
                @Override
                public void mouseExited(MouseEvent e) {
                    f.setBackground(Color.WHITE);
                }
            });
     
            greenButton.addMouseListener(new MouseAdapter() {
                @Override
                public void mouseEntered(MouseEvent e) {
                    f.setBackground(Color.GREEN);
                }
            });
     
            greenButton.addMouseListener(new MouseAdapter() {
                @Override
                public void mouseExited(MouseEvent e) {
                    f.setBackground(Color.WHITE);
                }
            });
     
            buleButton.addMouseListener(new MouseAdapter() {
                @Override
                public void mouseEntered(MouseEvent e) {
                    f.setBackground(Color.BLUE);
                }
            });
     
            buleButton.addMouseListener(new MouseAdapter() {
                @Override
                public void mouseExited(MouseEvent e) {
                    f.setBackground(Color.WHITE);
                }
            });
     
            // 设置窗体显示
            f.setVisible(true);
        }
    }
    ==============================================
    解析
    代码看着很长,其实很多重复的内容
    动作事件和鼠标事件
     
    redButton.addMouseListener(new MouseAdapter() {
                @Override
                public void mouseEntered(MouseEvent e) {
                    f.setBackground(Color.RED);
                }
            });
    鼠标事件用了适配器,因为原监听器接口要实现的方法挺多
     mouseEntered: 鼠标移动到按钮区域就变颜色(移动到但是没有点按钮)
    mouseExited: 鼠标离开按钮区域就变颜色(离开但是没有点按钮)
    ====================================================
    13.如何控制在文本框里面只能输入数字字符案例
    需求:
     
    /*
     * 你输入的如果是非数字字符,就取消你键盘录入的效果
     */
    public class FrameDemo {
        public static void main(String[] args) {
            // 创建窗体对象并设置属性
            Frame f = new Frame("不能输入非数字字符");
            f.setBounds(400, 200, 400, 300);
            f.setLayout(new FlowLayout());
     
            // 创建Label标签对象
            Label label = new Label("请输入你的QQ号码,不能是非数字,不信你试试");
            TextField tf = new TextField(40);
     
            // 添加到窗体上
            f.add(label);
            f.add(tf);
     
            // 设置窗体关闭
            f.addWindowListener(new WindowAdapter() {
                @Override
                public void windowClosing(WindowEvent e) {
                    System.exit(0);
                }
            });
     
            // 给文本框添加事件
            tf.addKeyListener(new KeyAdapter() {
                @Override
                public void keyPressed(KeyEvent e) {
                    // 如果你取得的字符不是数字字符就取消事件
                    // 思路:先获取字符,判断字符,取消事件
                    // char getKeyChar()  
                    char ch = e.getKeyChar();
    //                System.out.println(ch);
                    if(!(ch>='0' && ch<='9')){
                        e.consume();//取消该键盘录入事件
                    }
                }
            });
     
            // 设置窗体可见
            f.setVisible(true);
        }
    }
    ===============================================
    以下为解析
    keyPressed---keyAdapter接口中
     
    可以看见,有三个方法,所以适配器替代了原来的监听器
     
     
    还有,注意几个方法,getKeyChar(),consume() ----都在KeyEvent 类中
     
    consume在代码中意思是取消该事件,让事件不再发生。在本案例中是当检测到非法字符时不输入(忽略输入非法字符)

     

    14.一级菜单案例

    需求:

    /*
     * 一级菜单
     */
    public class FrameDemo {
        public static void main(String[] args) {
            // 创建窗体对象并设置属性
            Frame f = new Frame("一级菜单");
            f.setBounds(400, 200, 400, 300);
            f.setLayout(new FlowLayout());
     
            // 创建菜单栏
            MenuBar mb = new MenuBar();
            // 创建菜单
            Menu m = new Menu("文件");
            // 创建菜单项
            MenuItem mi = new MenuItem("退出系统");
     
            // 谁添加谁呢
            m.add(mi);
            mb.add(m);
     
            // 设置菜单栏
            f.setMenuBar(mb);
     
            // 设置窗体关闭
            f.addWindowListener(new WindowAdapter() {
                @Override
                public void windowClosing(WindowEvent e) {
                    System.exit(0);
                }
            });
     
            mi.addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    System.exit(0);
                }
            });
     
            // 设置窗体可见
            f.setVisible(true);
        }
    }
    ===============================================
    一个注意的问题
    setMenuBar而不是add
    15.多级菜单案例
    需求
    *
     * 多级菜单
     */
    public class FrameDemo {
        public static void main(String[] args) {
            // 创建窗体对象并设置属性
            final Frame f = new Frame("多级菜单");
            f.setBounds(400, 200, 400, 300);
            f.setLayout(new FlowLayout());
     
            final String name = f.getTitle();
     
            // 创建菜单栏
            MenuBar mb = new MenuBar();
            // 创建菜单
            Menu m1 = new Menu("文件");
            Menu m2 = new Menu("更改名称");
            // 创建菜单项
            final MenuItem mi1 = new MenuItem("好好学习");
            final MenuItem mi2 = new MenuItem("天天向上");
            MenuItem mi3 = new MenuItem("恢复标题");
            MenuItem mi4 = new MenuItem("打开记事本");
            MenuItem mi5 = new MenuItem("退出系统");
     
            // 谁添加谁呢
            m2.add(mi1);
            m2.add(mi2);
            m2.add(mi3);
     
            m1.add(m2);
            m1.add(mi4);
            m1.add(mi5);
     
            mb.add(m1);
     
            // 设置菜单栏
            f.setMenuBar(mb);
     
            // 设置窗体关闭
            f.addWindowListener(new WindowAdapter() {
                @Override
                public void windowClosing(WindowEvent e) {
                    System.exit(0);
                }
            });
     
            mi1.addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    f.setTitle(mi1.getLabel());//getLabel表示这个Item的名称是什么就把它设置为Frame的标题
                }
            });
     
            mi2.addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    f.setTitle(mi2.getLabel());
                }
            });
     
            mi3.addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    f.setTitle(name);//name为最初的标题
                }
            });
     
            mi4.addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    Runtime r = Runtime.getRuntime();//打开记事本,单例模式
                    try {
                        r.exec("notepad");
                    } catch (IOException e1) {
                        e1.printStackTrace();
                    }
                }
            });
     
            mi5.addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    System.exit(0);
                }
            });
     
            // 设置窗体可见
            f.setVisible(true);
        }
    }
    =======================================================
    一些注意事项
    前面写final String name = f.getTitle();的作用(原因)
     
    初始默认值为"多级菜单",如上图所示,窗体标题已经被改为"天天向上",所以要恢复初始值不能直接getTitle
     
    16.Netbeans的概述和使用
    首先解决安装问题
    安装成功的前提是有JDK
    配好环境变量
    然后,发现还是安装不了
    用命令行
     
     
     
    然后就可以安装了
     
    NetBeans主要用来做GUI方便,其它并不常用
     
    仅有的优点
     
    拖放使用非常方便
    常用快捷键(不用记)
    完成代码:ctrl+ //任何地方按下此组合键,均会提示相应的参考字段; 
    导入所需包:ctrl+shift+i 
    格式化代码:alt+shift+F 
    psvm+Tab 生成Main方法 
    sout + Tab //生成输出语句
     
    ================================
    17.模拟四则运算案例
    具体内容看视频
     
     
    在NetBeans中需要手动改动的代码
    默认是没有标题的,所以编写初始化标题的代码
    public NewJFrameDemo() {
            initComponents();
            init();
        }
     
        private void init() {
            this.setTitle("模拟四则运算");
        }
    // 红字为手写的方法,初始化标题
    ====================================================
    事件监听的代码(计算器四则运算)
    private void jButton1ActionPerformed(java.awt.event.ActionEvent evt) {                                         
            //获取第一个操作数
            String firstNumberString = this.firstNumber.getText().trim();
            //获取运算符
            String selectOperator = this.selectOperator.getSelectedItem().toString();
            //获取第二个操作数
            String secondNumberString = this.secondNumber.getText().trim();
            //        System.out.println(firstNumberString);
            //        System.out.println(selectOperator);
            //        System.out.println(secondNumberString);
     
            int firstNumber = Integer.parseInt(firstNumberString);
            int secondNumber = Integer.parseInt(secondNumberString);
     
            int resultNumber = 0;
     
            switch (selectOperator) {
                case "+":
                    resultNumber = firstNumber + secondNumber;
                    break;
                case "-":
                    resultNumber = firstNumber - secondNumber;
                    break;
                case "*":
                    resultNumber = firstNumber * secondNumber;
                    break;
                case "/":
                    resultNumber = firstNumber / secondNumber;
                    break;
            }
     
            //把结果赋值给结果框
            this.resultNumber.setText(String.valueOf(resultNumber));
     
     
        }                                        
     
     
    然后修改窗体的图标代码
    看图就好
    首先建一个cn.itcast.util包
    Uiutil类
    然后建一个resource包,把图标图片放进包里
    Toolkit是抽象类
    在NewJFrame类中改代码
    this代表当前对象(窗体)
    修改后运行
    再设置一下初始运行时窗体的默认位置为居中
    在UiUtil类中
    //设置窗体居中
        public static void setFrameCenter(JFrame jf) {
            /*
             思路:
             A:获取屏幕的宽和高
             B:获取窗体的宽和高
             C:(用屏幕的宽-窗体的宽)/2,(用屏幕的高-窗体的高)/2作为窗体的新坐标。
             */
            //获取工具对象
            Toolkit tk = Toolkit.getDefaultToolkit();
     
            //获取屏幕的宽和高
            Dimension d = tk.getScreenSize();
            double srceenWidth = d.getWidth();
            double srceenHeigth = d.getHeight();
     
            //获取窗体的宽和高
            int frameWidth = jf.getWidth();
            int frameHeight = jf.getHeight();
     
            //获取新的宽和高
            int width = (int) (srceenWidth - frameWidth) / 2;
            int height = (int) (srceenHeigth - frameHeight) / 2;
     
            //设置窗体坐标
            jf.setLocation(width, height);
        }
    =======================================
    在NewJFrame类中
    public NewJFrame() {
            initComponents();
            init();
        }
     
        private void init() {
            this.setTitle("模拟四则运算");
            UiUtil.setFrameImage(this,"jjcc.jpg");
            UiUtil.setFrameCenter(this);
        }
     
     
    附上UiUtil类
    /**
     * 专门做界面效果的类
     *
     * @author Administrator
     */
    public class UiUtil {
     
        private UiUtil() {
        }
     
        //修改窗体的图标
        public static void setFrameImage(JFrame jf) {
            //获取工具类对象
            //public static Toolkit getDefaultToolkit():获取默认工具包。 
            Toolkit tk = Toolkit.getDefaultToolkit();
     
            //根据路径获取图片
            Image i = tk.getImage("src\cn\itcast\resource\user.jpg");
     
            //给窗体设置图片
            jf.setIconImage(i);
        }
     
            public static void setFrameImage(JFrame jf,String imageName) {
            //获取工具类对象
            //public static Toolkit getDefaultToolkit():获取默认工具包。 
            Toolkit tk = Toolkit.getDefaultToolkit();
     
            //根据路径获取图片
            Image i = tk.getImage("src\cn\itcast\resource\"+imageName);
     
            //给窗体设置图片
            jf.setIconImage(i);
        }
     
        //设置窗体居中
        public static void setFrameCenter(JFrame jf) {
            /*
             思路:
             A:获取屏幕的宽和高
             B:获取窗体的宽和高
             C:(用屏幕的宽-窗体的宽)/2,(用屏幕的高-窗体的高)/2作为窗体的新坐标。
             */
            //获取工具对象
            Toolkit tk = Toolkit.getDefaultToolkit();
     
            //获取屏幕的宽和高
            Dimension d = tk.getScreenSize();
            double srceenWidth = d.getWidth();
            double srceenHeigth = d.getHeight();
     
            //获取窗体的宽和高
            int frameWidth = jf.getWidth();
            int frameHeight = jf.getHeight();
     
            //获取新的宽和高
            int width = (int) (srceenWidth - frameWidth) / 2;
            int height = (int) (srceenHeigth - frameHeight) / 2;
     
            //设置窗体坐标
            jf.setLocation(width, height);
        }
    }
    ================================================
    修改UI----皮肤包(略)
    如何修改窗体的皮肤
    附上MyLookAndFeel 类
    =========================================================
    //这里面定义了常见的要使用的皮肤的字符串路径。
    public abstract class MyLookAndFeel {
        // 系统自带皮肤,5种都能用
        public static String SYS_METAL = "javax.swing.plaf.metal.MetalLookAndFeel";
        public static String SYS_NIMBUS = "com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel";
        // 有个性
        public static String SYS_CDE_MOTIF = "com.sun.java.swing.plaf.motif.MotifLookAndFeel";
        public static String SYS_WINDOWS = "com.sun.java.swing.plaf.windows.WindowsLookAndFeel";
        public static String SYS_WINDOWS_CLASSIC = "com.sun.java.swing.plaf.windows.WindowsClassicLookAndFeel";
        // JIattoo jar包资源
        public static String JTATTOO_ACRYL = "com.jtattoo.plaf.acryl.AcrylLookAndFeel";
        public static String JTATTOO_AERO = "com.jtattoo.plaf.aero.AeroLookAndFeel";
        // 还可以
        public static String JTATTOO_ALUMINUM = "com.jtattoo.plaf.aluminium.AluminiumLookAndFeel";
        // 很喜欢
        public static String JTATTOO_BERNSTEIN = "com.jtattoo.plaf.bernstein.BernsteinLookAndFeel";
        public static String JTATTOO_FAST = "com.jtattoo.plaf.fast.FastLookAndFeel";
        // 有个性
        public static String JTATTOO_HIFI = "com.jtattoo.plaf.hifi.HiFiLookAndFeel";
        public static String JTATTOO_LUNA = "com.jtattoo.plaf.luna.LunaLookAndFeel";
        // 很喜欢
        public static String JTATTOO_MCWIN = "com.jtattoo.plaf.mcwin.McWinLookAndFeel";
        public static String JTATTOO_MINT = "com.jtattoo.plaf.mint.MintLookAndFeel";
        // 有个性
        public static String JTATTOO_NOIRE = "com.jtattoo.plaf.noire.NoireLookAndFeel";
        public static String JTATTOO_SMART = "com.jtattoo.plaf.smart.SmartLookAndFeel";
        // liquidlnf.jar包资源
        // 很喜欢
        public static String LIQUIDINF = "com.birosoft.liquid.LiquidLookAndFeel";
    }
    ========================================================
    测试几个看看效果
     
     
    最后,对四则运算案例加入校验和提示
    代码(绿字为新增代码)
    ………………………………………………………………………………………………………………………………
    ………………………………………………省略前面代码……………………………………………………
    //获取第二个操作数
            String secondNumberString = this.secondNumber.getText().trim();
            //数据校验,必须是数字字符串
            String regex = "\d+";
     
            //校验第一个数
            if (!firstNumberString.matches(regex)) {
    //            System.out.println("数据不匹配");
                //public static void showMessageDialog(Component parentComponent,Object message)
                JOptionPane.showMessageDialog(this, "第一个操作数不满足要求必须是数字");
                this.firstNumber.setText("");
                this.firstNumber.requestFocus();
                return;//回去吧
            }
     
            if (!secondNumberString.matches(regex)) {
    //            System.out.println("数据不匹配");
                //public static void showMessageDialog(Component parentComponent,Object message)
                JOptionPane.showMessageDialog(this, "第二个操作数不满足要求必须是数字");
                 this.secondNumber.setText("");
                this.secondNumber.requestFocus();
                return;//回去吧
            }
     //把字符串数据转换为整数
            int firstNumber = Integer.parseInt(firstNumberString);
            int secondNumber = Integer.parseInt(secondNumberString);
    …………………………………………省略后面代码…………………………………………………………
    …………………………………………………………………………………………………………………………
    附:JOptionPane类方法
     
    运行示例
     
     
    18.用户登录注册案例
    包结构
     
    pojo包下
    User类
    public class User {
        private String username;
        private String password;
     
        public User(){}
     
        /**
         * @return the username
         */
        public String getUsername() {
            return username;
        }
     
        /**
         * @param username the username to set
         */
        public void setUsername(String username) {
            this.username = username;
        }
     
        /**
         * @return the password
         */
        public String getPassword() {
            return password;
        }
     
        /**
         * @param password the password to set
         */
        public void setPassword(String password) {
            this.password = password;
        }
     
     
    }
    注意:
     
    ============================================================
    dao包下
    UserDao接口
     */
    public interface UserDao {
     
        /**
         * 这是用户登录功能
         *
         * @param username 用户名
         * @param password 密码
         * @return 登录是否成功
         */
        public abstract boolean login(String username, String password);
     
        /**
         * 这是用户注册功能
         *
         * @param user 被注册的用户信息
         */
        public abstract void regist(User user);
    }
    =======================================================
    dao.impl包下UserDaoImpl类(与IO流的是一样的)
    public class UserDaoImpl implements UserDao {
     
        //定义文件
        private static File file = new File("user.txt");
     
        //类加载的时候就把文件创建
        static {
            try {
                file.createNewFile();
            } catch (IOException ex) {
                ex.printStackTrace();
            }
        }
     
        @Override
        public boolean login(String username, String password) {
            boolean flag = false;
     
            BufferedReader br = null;
            try {
                br = new BufferedReader(new FileReader(file));
                String line = null;
                while ((line = br.readLine()) != null) {
                    String[] datas = line.split("=");
                    if (datas[0].equals(username) && datas[1].equals(password)) {
                        flag = true;
                        break;
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                try {
                    br.close();
                } catch (IOException ex) {
                    ex.printStackTrace();
                }
            }
     
            return flag;
        }
     
        @Override
        public void regist(User user) {
            BufferedWriter bw = null;
            try {
                bw = new BufferedWriter(new FileWriter(file, true));
                bw.write(user.getUsername() + "=" + user.getPassword());
                bw.newLine();
                bw.flush();
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                try {
                    bw.close();
                } catch (IOException ex) {
                    ex.printStackTrace();
                }
            }
        }
    }
    =============================================================
    19.用户登录注册的界面实现,界面跳转
    借助于NetBeans实现
    再修改一下代码
     public LoginFrame() {
            initComponents();
            init();
        }
     
        private void init() {
            this.setTitle("登录界面");
            UiUtil.setFrameCenter(this);
            UiUtil.setFrameImage(this,"user.jpg");
        }
     
    注册界面同理,略
     
    接着是界面跳转
    dispose----使当前窗体关闭
     
     
    this.setResizable(false);
     
    还有就是让窗体大小不可改变
     
     private void init() {
            this.setTitle("登录界面");
            this.setResizable(false);//窗体大小不可改变
            UiUtil.setFrameCenter(this);
            UiUtil.setFrameImage(this,"user.jpg");
        }
    在注册按钮添加事件,跳转到注册界面
     
        private void jButton3ActionPerformed(java.awt.event.ActionEvent evt) {                                         
            RegistFrame rf = new RegistFrame();
    //        this.setVisible(false);
            rf.setVisible(true);//让注册界面可见
            this.dispose();//让登录界面不可见
        }  
     
    private void jButton1ActionPerformed(java.awt.event.ActionEvent evt) {                                         
            LoginFrame lf = new LoginFrame();
            lf.setVisible(true);
            this.dispose();
        } 
    ======================================================
    20. 用户登录注册的最终版
    LoginFrame类中
     private void jButton2ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButton2ActionPerformed
            this.jtfUsername.setText("");
            this.jpfPassword.setText("");
        }//GEN-LAST:event_jButton2ActionPerformed
     
        private void jButton1ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButton1ActionPerformed
            /*
            思路:
            A:获取用户名和密码
            B:正则表达式校验用户名和密码
            C:创建对象调用功能,返回一个boolean值
            D:根据boolean值给出提示
            */
              //获取用户名和密码
            String username = this.jtfUsername.getText().trim();
            String password = this.jpfPassword.getText().trim();
     
            //用正则表达式做数据校验
            //定义规则
            //用户名规则
            String usernameRegex = "[a-zA-z]{5}";
            //密码规则
            String passwordRegex = "\w{6,12}";
     
            //校验
            if(!username.matches(usernameRegex)) {
                JOptionPane.showMessageDialog(this, "用户名不满足条件(5个英文字母组成)");
                this.jtfUsername.setText("");
                this.jtfUsername.requestFocus();
                return;
            }
     
             if(!password.matches(passwordRegex)) {
                JOptionPane.showMessageDialog(this, "密码不满足条件(6-12个任意单词字符)");
                this.jpfPassword.setText("");
                this.jpfPassword.requestFocus();
                return;
            }
     
             //创建对象调用功能,返回一个boolean值
             UserDao ud = new UserDaoImpl();
             boolean flag =  ud.login(username, password);
     
             if(flag){
                  JOptionPane.showMessageDialog(this, "恭喜你登录成功");
    //              NewJFrame njf = new NewJFrame();
                  NewJFrame njf = new NewJFrame(username);
                  njf.setVisible(true);
                  this.dispose();
             }else {
                   JOptionPane.showMessageDialog(this, "用户名或者密码有误");
                   this.jtfUsername.setText("");
                   this.jpfPassword.setText("");
                   this.jtfUsername.requestFocus();
             }
        }
    =============================================================
    RegistFrame类
     
    private void jButton3ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButton3ActionPerformed
            /*
            分析:
            A:获取用户名和密码
            B:用正则表达式做数据校验
            C:封装成用户对象
            D:调用用户操作的功能进行注册
            E:回到登录界面
            */
            //获取用户名和密码
            String username = this.jtfUsername.getText().trim();
            String password = this.jpfPassword.getText().trim();
     
            //用正则表达式做数据校验
            //定义规则
            //用户名规则
            String usernameRegex = "[a-zA-z]{5}";
            //密码规则
            String passwordRegex = "\w{6,12}";
     
            //校验
            if(!username.matches(usernameRegex)) {
                JOptionPane.showMessageDialog(this, "用户名不满足条件(5个英文字母组成)");
                this.jtfUsername.setText("");
                this.jtfUsername.requestFocus();
                return;
            }
     
             if(!password.matches(passwordRegex)) {
                JOptionPane.showMessageDialog(this, "密码不满足条件(6-12个任意单词字符)");
                this.jpfPassword.setText("");
                this.jpfPassword.requestFocus();
                return;
            }
     
             //封装成用户对象
             User user = new User();
             user.setUsername(username);
             user.setPassword(password);
     
             //调用用户操作的功能进行注册
             UserDao ud = new UserDaoImpl();
             ud.regist(user);
     
             //给出提示
              JOptionPane.showMessageDialog(this, "用户注册成功,回到登录界面");
     
              goLogin();
        }
     
     private void goLogin() {    //跳转功能
            LoginFrame lf = new LoginFrame();
           lf.setVisible(true);
           this.dispose();
        }
     
    补充:四则运算修改
     
    ==============================================
     
    day25补充笔记
    注意一个工具"exe4j" jar转exe可执行文件
     
    如何让Netbeans的东西Eclipse能访问。
        在Eclipse中创建项目,把Netbeans项目的src下的东西给拿过来即可。
        注意:修改项目编码为UTF-8(NetBeans默认编码是UTF-8,而eclipse是GBK)
     
    事件监听机制(理解)
            A:事件源
            B:事件
            C:事件处理
            D:事件监听
        适配器模式(理解)
            A:接口
            B:抽象适配器类
            C:实现类
     
    day26
     
    1.网络模型概述和图解
    l网络模型一般是指

      OSI(Open System Interconnection开放系统互连)参考模型

      TCP/IP参考模型

    网络模型7层概述:

    1.物理层:主要定义物理设备标准,如网线的接口类型、光纤的接口类型、各种传输介质的传输速率等。它的主要作用是传输比特流(就是由1、0转化为电流强弱来进行传输,到达目的地后在转化为1、0,也就是我们常说的数模转换与模数转换)。这一层的数据叫做比特。

    2. 数据链路层:主要将从物理层接收的数据进行MAC地址(网卡的地址)的封装与解封装。常把这一层的数据叫做帧。在这一层工作的设备是交换机,数据通过交换机来传输。

    3. 网络层:主要将从下层接收到的数据进行IP地址(例192.168.0.1)的封装与解封装。在这一层工作的设备是路由器,常把这一层的数据叫做数据包。

    4. 传输层:定义了一些传输数据的协议和端口号(WWW端口80等),如:TCP(传输控制协议,传输效率低,可靠性强,用于传输可靠性要求高,数据量大的数据),UDP(用户数据报协议,与TCP特性恰恰相反,用于传输可靠性要求不高,数据量小的数据,如QQ聊天数据就是通过这种方式传输的)。 主要是将从下层接收的数据进行分段和传输,到达目的地址后再进行重组。常常把这一层数据叫做段。

    5.会话层:通过传输层(端口号:传输端口与接收端口)建立数据传输的通路。主要在你的系统之间发起会话或者接受会话请求(设备之间需要互相认识可以是IP也可以是MAC或者是主机名)

    6.表示层:主要是进行对接收的数据进行解释、加密与解密、压缩与解压缩等(也就是把计算机能够识别的东西转换成人能够能识别的东西(如图片、声音等)。

    7.应用层: 主要是一些终端的应用,比如说FTP(各种文件下载),WEB(IE浏览),QQ之类的(可以把它理解成我们在电脑屏幕上可以看到的东西.就是终端应用)。

    2.网络编程三要素-----IP地址
    网络编程三要素:
        A:IP地址
        B:端口
        C:协议
     

    我们要进行通讯,需要哪些要素呢?

    比如说:我要跟你说话.

    第一个条件:我要先找到你 (IP)

    第二个条件:你得有接收数据的地方  耳朵 (端口)

    第三个条件:我跟你说话,你能接收到,咱按什么方式接收啊,我说英文你懂吗,说韩文你懂吗,不懂是吧,所以我还是说中文把.(协议)

     
    举例:
        我想和林青霞说话了。肿么办?
        A:我要找到林青霞。
        B:对她说话,要对耳朵说。
        C:我说什么呢?"I Love You"
          但是,她没学过英语,听不懂。
          我没必要说英语,说汉语就可以了:我爱你
     
    IP地址:
        网络中计算机的唯一标识。
     
        计算机只能识别二进制的数据,所以我们的IP地址应该是一个二进制的数据。
        但是呢,我们配置的IP地址确不是二进制的,为什么呢?
            IP:192.168.1.100
            换算:11000000 10101000 00000001 01100100
        假如真是:11000000 10101000 00000001 01100100的话。
        我们如果每次再上课的时候要配置该IP地址,记忆起来就比较的麻烦。
        所以,为了方便表示IP地址,我们就把IP地址的每一个字节上的数据换算成十进制,然后用.分开来表示:
            "点分十进制"
     
        IP地址的组成:网络号段+主机号段
            A类:第一号段为网络号段+后三段的主机号段
                一个网络号:256*256*256 = 16777216
            B类:前二号段为网络号段+后二段的主机号段
                一个网络号:256*256 = 65536
            C类:前三号段为网络号段+后一段的主机号段
                一个网络号:256
     
        IP地址的分类:
            A类    1.0.0.1---127.255.255.254    (1)10.X.X.X是私有地址(私有地址就是在互联网上不使用,而被用在局域网络中的地址)                            (2)127.X.X.X是保留地址,用做循环测试用的。
            B类    128.0.0.1---191.255.255.254    172.16.0.0---172.31.255.255是私有地址。169.254.X.X是保留地址。
            C类    192.0.0.1---223.255.255.254    192.168.X.X是私有地址
            D类    224.0.0.1---239.255.255.254     
            E类    240.0.0.1---247.255.255.254
     
        两个DOS命令:
            ipconfig 查看本机ip地址
            ping 后面跟ip地址。测试本机与指定的ip地址间的通信是否有问题
     
        特殊的IP地址:
            127.0.0.1 回环地址(表示本机)//也就是说,ping本机的IP地址相当于ping 127.0.0.1
            x.x.x.255 广播地址
            x.x.x.0 网络地址
     
    3.InetAddress类的概述和使用
    如何获取和操作IP地址呢?

      为了方便我们对IP地址的获取和操作,java提供了一个类InetAddress供我们使用。

    /*
     * 如果一个类没有构造方法
     * A:成员全部是静态的(Math,Arrays,Collections)
     * B:单例设计模式(Runtime)
     * C:类中有静态方法返回该类的对象(InetAddress)
     *         class Demo {
     *             private Demo(){}
     * 
     *             public static Demo getXxx() {
     *                 return new Demo();
     *             }
     *         }
     * 
     * 看InetAddress的成员方法:
     * public static InetAddress getByName(String host):根据主机名或者IP地址的字符串表示得到IP地址对象
     */
    public class InetAddressDemo {
        public static void main(String[] args) throws UnknownHostException {
            // public static InetAddress getByName(String host)
            // InetAddress address = InetAddress.getByName("liuyi");
            // InetAddress address = InetAddress.getByName("192.168.12.92");
            InetAddress address = InetAddress.getByName("192.168.12.63");
     
            // 获取两个东西:主机名,IP地址
            // public String getHostName()
            String name = address.getHostName();
            // public String getHostAddress()
            String ip = address.getHostAddress();
            System.out.println(name + "---" + ip);
        }
    }
     
    =====================================
    一个奇怪的小现象
     
    以太网适配器 本地连接:
     
       连接特定的 DNS 后缀 . . . . . . . :
       IPv6 地址 . . . . . . . . . . . . : 2001:250:3c00:2335:c4a5:1d7f:b668:
       临时 IPv6 地址. . . . . . . . . . : 2001:250:3c00:2335:ddad:5f45:3400:
       本地链接 IPv6 地址. . . . . . . . : fe80::c4a5:1d7f:b668:f26c%12
       IPv4 地址 . . . . . . . . . . . . : 172.29.111.93
       子网掩码  . . . . . . . . . . . . : 255.255.255.0
       默认网关. . . . . . . . . . . . . : fe80::72f9:6dff:fe70:348f%12
                                           172.29.111.254
     
    以太网适配器 VirtualBox Host-Only Network:
     
       连接特定的 DNS 后缀 . . . . . . . :
       本地链接 IPv6 地址. . . . . . . . : fe80::d506:ceb4:be6:69b5%51
       IPv4 地址 . . . . . . . . . . . . : 192.168.56.1
       子网掩码  . . . . . . . . . . . . : 255.255.255.0
       默认网关. . . . . . . . . . . . . :
     
     
    4.网络编程三要素之端口,协议
     
    端口号:
        正在运行的程序的标识。
        有效端口:0~65535,其中0~1024系统使用或保留端口。

    协议:
        通信的规则
     
        UDP:
            把数据打包
            数据有限制
            不建立连接
            速度快
            不可靠
     
        TCP:
            建立连接通道
            数据无限制
            速度慢
            可靠
     
        举例:
            UDP:发短信
            TCP:打电话
     
    5.Socket通信原理图解
     
    Socket包含了:IP地址+端口
    6.UDP协议发送数据
    UDP协议发送数据:
      A:创建发送端Socket对象
      B:创建数据,并把数据打包
      C:调用Socket对象的发送方法发送数据包
      D:释放资源
    要注意的是Socket对象在UDP中指的不是Socket类,而是DatagramSocket,Socket是TCP用的
    public static void main(String[] args) throws IOException {
            // 创建发送端Socket对象
            // DatagramSocket()
            DatagramSocket ds = new DatagramSocket();
     
            // 创建数据,并把数据打包
            // DatagramPacket(byte[] buf, int length, InetAddress address, int port)
            // 创建数据
            byte[] bys = "hello,udp,我来了".getBytes();//巧妙把字符串转换为字节数组
            // 长度
            int length = bys.length;
            // IP地址对象
            InetAddress address = InetAddress.getByName("192.168.12.92");
            // 端口
            int port = 10086;//端口号的选择:一万号开外
            DatagramPacket dp = new DatagramPacket(bys, length, address, port);
     
            // 调用Socket对象的发送方法发送数据包
            // public void send(DatagramPacket p)
            ds.send(dp);
     
            // 释放资源
            ds.close();//底层依赖IO流,所以要释放资源
        }
    ======================================================
     
    7.UDP协议接收数据
     
      UDP协议接收数据:
      A:创建接收端Socket对象
      B:创建一个数据包(接收容器)
      C:调用Socket对象的接收方法接收数据
      D:解析数据包,并显示在控制台
      E:释放资源
     
    public static void main(String[] args) throws IOException {
            // 创建接收端Socket对象
            // DatagramSocket(int port)
            DatagramSocket ds = new DatagramSocket(10086);//端口号的选择:一万号开外
     
            // 创建一个数据包(接收容器)
            // DatagramPacket(byte[] buf, int length)
            byte[] bys = new byte[1024];
            int length = bys.length;
            DatagramPacket dp = new DatagramPacket(bys, length);
     
            // 调用Socket对象的接收方法接收数据
            // public void receive(DatagramPacket p)
            ds.receive(dp); // 阻塞式(在没有接收到数据之前等待)
     
            // 解析数据包,并显示在控制台
            // 获取对方的ip
            // public InetAddress getAddress()
            InetAddress address = dp.getAddress();
            String ip = address.getHostAddress();
            // public byte[] getData():获取数据缓冲区
            // public int getLength():获取数据的实际长度
            byte[] bys2 = dp.getData();
            int len = dp.getLength();
            String s = new String(bys2, 0, len);
            System.out.println(ip + "传递的数据是:" + s);
     
            // 释放资源
            ds.close();
        }
    小的注意:
    ===================================================
    UDP协议发送和接收数据图解
    8.UDP协议发送和接收数据代码优化)
    利用链式编程进行优化
    先写ReceiveDemo----接收端
    public class ReceiveDemo {
        public static void main(String[] args) throws IOException {
            // 创建接收端的Socket对象
            DatagramSocket ds = new DatagramSocket(12345);
     
            // 创建一个包裹
            byte[] bys = new byte[1024];
            DatagramPacket dp = new DatagramPacket(bys, bys.length);
     
            // 接收数据
            ds.receive(dp);
     
            // 解析数据
            String ip = dp.getAddress().getHostAddress();
            String s = new String(dp.getData(), 0, dp.getLength());
            System.out.println("from " + ip + " data is : " + s);
     
            // 释放资源
            ds.close();
        }
    }
     
    一个注意的问题:接收端不可以连续运行两次
     
      多次启动接收端:
              java.net.BindException: Address already in use: Cannot bind
              端口被占用
     
    ==============================================================
    再写发送端
    public class SendDemo {
        public static void main(String[] args) throws IOException {
            // 创建发送端的Socket对象
            DatagramSocket ds = new DatagramSocket();
     
            // 创建数据并打包
            byte[] bys = "helloworld".getBytes();
            DatagramPacket dp = new DatagramPacket(bys, bys.length,
                    InetAddress.getByName("192.168.12.92"), 12345);
     
            // 发送数据
            ds.send(dp);
     
            // 释放资源
            ds.close();
        }
    }
     
     
    ====================================================
    几个注意问题
     
     
    9.DOS窗口演示简易聊天小程序
    首先把上一次的那两个程序单独拿出来放到同一个文件夹中、
     
     
    然后,编译运行
    10.多线程实现聊天室程序
    首先新建一个ChatRoom类
    /*
     * 通过多线程改进刚才的聊天程序,这样我就可以实现在一个窗口发送和接收数据了
     */
    public class ChatRoom {
        public static void main(String[] args) throws IOException {
            DatagramSocket dsSend = new DatagramSocket();
            DatagramSocket dsReceive = new DatagramSocket(12306);
     
            SendThread st = new SendThread(dsSend);
            ReceiveThread rt = new ReceiveThread(dsReceive);
     
            Thread t1 = new Thread(st);
            Thread t2 = new Thread(rt);
     
            t1.start();
            t2.start();
        }
    }
    ================================================================
    然后慢慢填坑
    SendThread 类
    public class SendThread implements Runnable {
     
        private DatagramSocket ds;
     
        public SendThread(DatagramSocket ds) {//通过带参构造方法传参数
            this.ds = ds;
        }
     
        @Override
        public void run() {
            try {
                // 封装键盘录入数据
                BufferedReader br = new BufferedReader(new InputStreamReader(
                        System.in));
                String line = null;
                while ((line = br.readLine()) != null) {
                    if ("886".equals(line)) {
                        break;
                    }
     
                    // 创建数据并打包
                    byte[] bys = line.getBytes();
                    // DatagramPacket dp = new DatagramPacket(bys, bys.length,
                    // InetAddress.getByName("192.168.12.92"), 12345);
                    DatagramPacket dp = new DatagramPacket(bys, bys.length,
                            InetAddress.getByName("192.168.12.255"), 12306);
     
                    // 发送数据
                    ds.send(dp);
                }
     
                // 释放资源
                ds.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
     
    }
    =================================================
    ReceiveThread 类
    public class ReceiveThread implements Runnable {
        private DatagramSocket ds;
     
        public ReceiveThread(DatagramSocket ds) {
            this.ds = ds;
        }
     
        @Override
        public void run() {
            try {
                while (true) {
                    // 创建一个包裹
                    byte[] bys = new byte[1024];
                    DatagramPacket dp = new DatagramPacket(bys, bys.length);
     
                    // 接收数据
                    ds.receive(dp);
     
                    // 解析数据
                    String ip = dp.getAddress().getHostAddress();
                    String s = new String(dp.getData(), 0, dp.getLength());
                    System.out.println("from " + ip + " data is : " + s);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
     
    }
    ==============================================
    运行效果
     
    11.TCP协议发送数据(发送端为Client)
    TCP协议发送数据:
      A:创建发送端的Socket对象
              这一步如果成功,就说明连接已经建立成功了。
      B:获取输出流,写数据
      C:释放资源
      
      连接被拒绝。TCP协议一定要先看服务器。(必须要有服务器,这一点与UDP不同)
      java.net.ConnectException: Connection refused: connect
     
    public class ClientDemo {
        public static void main(String[] args) throws IOException {
            // 创建发送端的Socket对象
            // Socket(InetAddress address, int port)//不用这种,为了书写简便
            // Socket(String host, int port)
            // Socket s = new Socket(InetAddress.getByName("192.168.12.92"), 8888);
            Socket s = new Socket("192.168.12.92", 8888);
     
            // 获取输出流,写数据
            // public OutputStream getOutputStream()
            OutputStream os = s.getOutputStream();
            os.write("hello,tcp,我来了".getBytes());
     
            // 释放资源
            s.close();
        }
    }
    ======================================
     
    12.TCP协议接收数据(Server端)
     
    TCP协议接收数据:
      A:创建接收端的Socket对象
      B:监听客户端连接。返回一个对应的Socket对象
      C:获取输入流,读取数据显示在控制台
      D:释放资源
     
    public class ServerDemo {
        public static void main(String[] args) throws IOException {
            // 创建接收端的Socket对象
            // ServerSocket(int port)
            ServerSocket ss = new ServerSocket(8888);
     
            // 监听客户端连接。返回一个对应的Socket对象
            // public Socket accept()
            Socket s = ss.accept(); // 侦听并接受到此套接字的连接。此方法在连接传入之前一直阻塞。
     
            // 获取输入流,读取数据显示在控制台
            InputStream is = s.getInputStream();
     
            byte[] bys = new byte[1024];
            int len = is.read(bys); // 阻塞式方法
            String str = new String(bys, 0, len);
     
            String ip = s.getInetAddress().getHostAddress();
     
            System.out.println(ip + "---" + str);
     
            // 释放资源
            s.close();
            // ss.close(); //这个不应该关闭,服务器不关闭
        }
    }
    ==============================================
    13.TCP协议发送和接收数据图解
    下面是针对这张图的解析
    客户端发送数据
     
    服务器接收数据
    服务器返回数据
    14.服务器给客户端一个反馈案例
    先写客户端
    public class ServerDemo {
        public static void main(String[] args) throws IOException {
            // 创建服务器Socket对象
            ServerSocket ss = new ServerSocket(9999);
     
            // 监听客户端的连接
            Socket s = ss.accept(); // 阻塞
     
            // 获取输入流(接收来自客户端的数据)
            InputStream is = s.getInputStream();
            byte[] bys = new byte[1024];
            int len = is.read(bys); // 阻塞
            String server = new String(bys, 0, len);
            System.out.println("server:" + server);
     
            // 获取输出流(对客户端做出应答)
            OutputStream os = s.getOutputStream();
            os.write("数据已经收到".getBytes());
     
            // 释放资源
            s.close();
            // ss.close();
        }
    }
    ============================================================
    再写客户端
    public class ClientDemo {
        public static void main(String[] args) throws IOException {
            // 创建客户端Socket对象
            Socket s = new Socket("192.168.12.92", 9999);
     
            // 获取输出流
            OutputStream os = s.getOutputStream();
            os.write("今天天气很好,适合睡觉".getBytes());
     
            // 获取输入流
            InputStream is = s.getInputStream();
            byte[] bys = new byte[1024];
            int len = is.read(bys);// 阻塞
            String client = new String(bys, 0, len);
            System.out.println("client:" + client);
     
            // 释放资源
            s.close();
        }
    }
    =========================================================
    注意:两个文件在同一个包下,运行的时候应该先运行Server的程序,再运行Client的程序
     
    15.客户端键盘录入服务器控制台输出
    需求:客户端键盘录入,服务器输出到控制台
    为方便描述先写客户端
    ================================================
    /*
     * 客户端键盘录入,服务器输出到控制台
     */
    public class ClientDemo {
        public static void main(String[] args) throws IOException {
            // 创建客户端Socket对象
            Socket s = new Socket("192.168.12.92", 22222);
     
            // 键盘录入数据
            BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
            // 把通道内的流给包装一下
            BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(
                    s.getOutputStream()));
     
            String line = null;
            while ((line = br.readLine()) != null) {
                // 键盘录入数据要自定义结束标记
                if ("886".equals(line)) {
                    break;
                }
                bw.write(line);
                bw.newLine();
                bw.flush();
            }
     
            // 释放资源
            // bw.close(); //s关了,bw 自然关了
            // br.close();  //br通过886引发break语句关闭,不需要此句
            s.close();
        }
    }
     
    =============================================================

    public class ServerDemo {
        public static void main(String[] args) throws IOException {
            // 创建服务器Socket对象
            ServerSocket ss = new ServerSocket(22222);
     
            // 监听客户端连接
            Socket s = ss.accept();
     
            // 包装通道内容的流
            BufferedReader br = new BufferedReader(new InputStreamReader(
                    s.getInputStream()));
            String line = null;
            while ((line = br.readLine()) != null) {
                System.out.println(line);
            }
     
            // br.close();//s关了,br自然关了
            s.close();
            // ss.close();//服务器不关闭
        }
    }
    ======================================================
    一个要注意的问题
    16.客户端键盘录入服务器写到文本文件(而不是上一节的控制台)
    /*
     * 客户端键盘录入,服务器输出文本文件
     */
    public class ClientDemo {
        public static void main(String[] args) throws IOException {
            // 创建客户端Socket对象
            Socket s = new Socket("192.168.12.92", 23456);
     
            // 封装键盘录入
            BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
            // 封装通道内的数据
            BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(
                    s.getOutputStream()));
     
            String line = null;
            while ((line = br.readLine()) != null) {
                if ("over".equals(line)) {
                    break;
                }
     
                bw.write(line);
                bw.newLine();
                bw.flush();
            }
     
            // bw.close();
            // br.close();
            s.close();
        }
    }
     
     
    =============================================================
    public class ServerDemo {
        public static void main(String[] args) throws IOException {
            // 创建服务器Socket对象
            ServerSocket ss = new ServerSocket(23456);
     
            // 监听客户端连接
            Socket s = ss.accept();
     
            // 封装通道内的数据
            BufferedReader br = new BufferedReader(new InputStreamReader(
                    s.getInputStream()));
            // 封装文本文件
            BufferedWriter bw = new BufferedWriter(new FileWriter("a.txt"));
     
            String line = null;
            while ((line = br.readLine()) != null) {
                bw.write(line);
                bw.newLine();
                bw.flush();
            }
     
            bw.close();
            // br.close();
            s.close();
            // ss.close();
        }
    }
    其实以上程序只要明确两点:数据从哪里来,数据要到哪里去
     
    17.客户端读取文本文件服务器控制台输出案例
     
    客户端文本文件,服务器输出到控制台
     
    public class ClientDemo {
        public static void main(String[] args) throws IOException {
            // 创建Socket对象
            Socket s = new Socket("192.168.12.92", 34567);
     
            // 封装文本文件
            BufferedReader br = new BufferedReader(new FileReader(
                    "InetAddressDemo.java"));
            // 封装通道内的流
            BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(
                    s.getOutputStream()));
     
            String line = null;
            while ((line = br.readLine()) != null) {
                bw.write(line);
                bw.newLine();
                bw.flush();
            }
     
            br.close();
            s.close();
        }
    }
    ======================================================
    public class ServerDemo {
        public static void main(String[] args) throws IOException {
            // 创建服务器Socket对象
            ServerSocket ss = new ServerSocket(34567);
     
            // 监听客户端连接
            Socket s = ss.accept();
     
            // 封装通道内的流
            BufferedReader br = new BufferedReader(new InputStreamReader(
                    s.getInputStream()));
     
            String line = null;
            while ((line = br.readLine()) != null) {
                System.out.println(line);//这里要手动加换行符
            }
     
     
            s.close();
        }
    }
    ==================================================
     
    18.TCP协议上传文本文件
    客户端文本文件,服务器输出文本文件----其实就是客户端上传文件到服务器端
    public class UploadClient {
        public static void main(String[] args) throws IOException {
            // 创建客户端Socket对象
            Socket s = new Socket("192.168.12.92", 11111);
     
            // 封装文本文件
            BufferedReader br = new BufferedReader(new FileReader(
                    "InetAddressDemo.java"));
            // 封装通道内流
            BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(
                    s.getOutputStream()));
     
            String line = null;
            while ((line = br.readLine()) != null) {
                bw.write(line);
                bw.newLine();
                bw.flush();
            }
     
            // 释放资源
            br.close();
            s.close();
        }
    }
     
     ===================================================
    public class UploadServer {
        public static void main(String[] args) throws IOException {
            // 创建服务器端的Socket对象
            ServerSocket ss = new ServerSocket(11111);
     
            // 监听客户端连接
            Socket s = ss.accept();
     
            // 封装通道内的流
            BufferedReader br = new BufferedReader(new InputStreamReader(
                    s.getInputStream()));
            // 封装文本文件
            BufferedWriter bw = new BufferedWriter(new FileWriter("Copy.java"));
     
            String line = null;
            while ((line = br.readLine()) != null) {
                bw.write(line);
                bw.newLine();//别忘记加换行
                bw.flush();
            }
     
            bw.close();
            s.close();
        }
    }
    =========================================================
    19.TCP上传文本文件并给出反馈
    首先,按照正常思路写
     
    服务器接收完文件后给出反馈
    然后客户端接收这个反馈信息
    然后,发现,错了。。。。。。
    会出现相互等待的现象,,,,虽然文件可以顺利上传,但是两个程序都没有正常终止
     
    原因??
    按照我们正常的思路加入反馈信息,结果却没反应。为什么呢?
      读取文本文件是可以以null作为结束信息的,但是呢,通道内是不能这样结束信息的。
      所以,服务器根本就不知道你结束了。而你还想服务器给你反馈。所以,就相互等待了。
     
    如何解决呢?
        再多写一条数据,告诉服务器,读取到这条数据说明我就结束,你也结束吧。
              这样做可以解决问题,但是不好。(原因稍后会解析)
     
    客户端
    然而这样做并不好!!!文件中恰好含有终止符就有问题
     

    接下来,查API寻求更好办法
    Socket对象提供了一种解决方案
              public void shutdownOutput()
     
     
    上图中正常连接终止序列也是数据的一部分,会传输过去但是接收端不会显示出来
     
    最终版
    public class UploadClient {
        public static void main(String[] args) throws IOException {
            // 创建客户端Socket对象
            Socket s = new Socket("192.168.12.92", 11111);
     
            // 封装文本文件
            BufferedReader br = new BufferedReader(new FileReader(
                    "InetAddressDemo.java"));
            // 封装通道内流
            BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(
                    s.getOutputStream()));
     
            String line = null;
            while ((line = br.readLine()) != null) { // 阻塞
                bw.write(line);
                bw.newLine();
                bw.flush();
            }
     
            //自定义一个结束标记
    //        bw.write("over");
    //        bw.newLine();
    //        bw.flush();
     
            //Socket提供了一个终止,它会通知服务器你别等了,我没有数据过来了
            s.shutdownOutput();
     
            // 接收反馈
            BufferedReader brClient = new BufferedReader(new InputStreamReader(
                    s.getInputStream()));
            String client = brClient.readLine(); // 阻塞
            System.out.println(client);
     
            // 释放资源
            br.close();
            s.close();
        }
    }
    ============================================================
    public class UploadServer {
        public static void main(String[] args) throws IOException {
            // 创建服务器端的Socket对象
            ServerSocket ss = new ServerSocket(11111);
     
            // 监听客户端连接
            Socket s = ss.accept();// 阻塞
     
            // 封装通道内的流
            BufferedReader br = new BufferedReader(new InputStreamReader(
                    s.getInputStream()));
            // 封装文本文件
            BufferedWriter bw = new BufferedWriter(new FileWriter("Copy.java"));
     
            String line = null;
            while ((line = br.readLine()) != null) { // 阻塞
            // if("over".equals(line)){
            // break;
            // }
                bw.write(line);
                bw.newLine();
                bw.flush();
            }
     
            // 给出反馈
            BufferedWriter bwServer = new BufferedWriter(new OutputStreamWriter(
                    s.getOutputStream()));
            bwServer.write("文件上传成功");
            bwServer.newLine();
            bwServer.flush();
     
            // 释放资源
            bw.close();
            s.close();
        }
    }
    =======================================================
    20.TCP协议上传图片并给出反馈
    上传图片与上传文件的区别:上传图片只能用字节流
    public class UploadServer {
        public static void main(String[] args) throws IOException {
            // 创建服务器Socket对象
            ServerSocket ss = new ServerSocket(19191);
     
            // 监听客户端连接
            Socket s = ss.accept();
     
            // 封装通道内流
            BufferedInputStream bis = new BufferedInputStream(s.getInputStream());
            // 封装图片文件
            BufferedOutputStream bos = new BufferedOutputStream(
                    new FileOutputStream("mn.jpg"));
     
            byte[] bys = new byte[1024];
            int len = 0;
            while ((len = bis.read(bys)) != -1) {
                bos.write(bys, 0, len);
                bos.flush();
            }
     
            // 给一个反馈
            OutputStream os = s.getOutputStream();
            os.write("图片上传成功".getBytes());
     
            bos.close();
            s.close();
        }
    }
    ===============================================
    public class UploadClient {
        public static void main(String[] args) throws IOException {
            // 创建客户端Socket对象
            Socket s = new Socket("192.168.12.92", 19191);
     
            // 封装图片文件
            BufferedInputStream bis = new BufferedInputStream(new FileInputStream(
                    "林青霞.jpg"));
            // 封装通道内的流
            BufferedOutputStream bos = new BufferedOutputStream(s.getOutputStream());
     
            byte[] bys = new byte[1024];
            int len = 0;
            while ((len = bis.read(bys)) != -1) {
                bos.write(bys, 0, len);
                bos.flush();
            }
     
            s.shutdownOutput();
     
            // 读取反馈
            InputStream is = s.getInputStream();
            byte[] bys2 = new byte[1024];
            int len2 = is.read(bys2);
            String client = new String(bys2, 0, len2);
            System.out.println(client);
     
            // 释放资源
            bis.close();
            s.close();
        }
    }
    ===================================================
    注意:一定要注意---字节流也可以flush!!而且在本案例中,不flush的话会有问题
    不加flush就会出现以下现象
    查API
     
    改进后
     
    21.关于多个客户端上传到一个服务器的思考
    之前的程序都是一对一的,假如有多个客户端怎么办??
    正常思路:在服务器端用循环改进
     
    但是
    通过while循环可以改进一个服务器接收多个客户端。
     但是这个是有问题的。
     如果是这种情况,假设我还有张三,李四,王五这三个人分别执行客户端
     张三:好好学习.avi(100M)            256k(带宽,下面同理)
     李四:天天向上.mp3(3M)                1M
     王五:ILoveJava.txt(1k)            100M
     
    带宽不同,会导致第一个张三上传了一年文件然后才可以让李四上传
    改进:利用多线程(下一节)
    ==========================================
    22.多线程改进多个客户端上传文件案例
    客户端
    public class UploadClient {
        public static void main(String[] args) throws IOException {
            // 创建客户端Socket对象
            Socket s = new Socket("192.168.12.92", 11111);
     
            // 封装文本文件
            // BufferedReader br = new BufferedReader(new FileReader(
            // "InetAddressDemo.java"));
            BufferedReader br = new BufferedReader(new FileReader(
                    "ReceiveDemo.java"));
            // 封装通道内流
            BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(
                    s.getOutputStream()));
     
            String line = null;
            while ((line = br.readLine()) != null) { // 阻塞
                bw.write(line);
                bw.newLine();
                bw.flush();
            }
     
            // Socket提供了一个终止,它会通知服务器你别等了,我没有数据过来了
            s.shutdownOutput();
     
            // 接收反馈
            BufferedReader brClient = new BufferedReader(new InputStreamReader(
                    s.getInputStream()));
            String client = brClient.readLine(); // 阻塞
            System.out.println(client);
     
            // 释放资源
            br.close();
            s.close();
        }
    }
    =================================================
    新的线程(其实就是把大部分的服务器代码粘贴过来)
    public class UserThread implements Runnable {
        private Socket s;
     
        public UserThread(Socket s) {
            this.s = s;
        }
     
        @Override
        public void run() {
            try {
                // 封装通道内的流
                BufferedReader br = new BufferedReader(new InputStreamReader(
                        s.getInputStream()));
                // 封装文本文件
                // BufferedWriter bw = new BufferedWriter(new
                // FileWriter("Copy.java"));
     
                // 为了防止名称冲突
                String newName = System.currentTimeMillis() + ".java";
                BufferedWriter bw = new BufferedWriter(new FileWriter(newName));
     
                String line = null;
                while ((line = br.readLine()) != null) { // 阻塞
                    bw.write(line);
                    bw.newLine();
                    bw.flush();
                }
     
                // 给出反馈
                BufferedWriter bwServer = new BufferedWriter(
                        new OutputStreamWriter(s.getOutputStream()));
                bwServer.write("文件上传成功");
                bwServer.newLine();
                bwServer.flush();
     
                // 释放资源
                bw.close();
                s.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    =====================================================
    服务器端测试
    public class UploadServer {
        public static void main(String[] args) throws IOException {
            // 创建服务器Socket对象
            ServerSocket ss = new ServerSocket(11111);
     
            while (true) {
                Socket s = ss.accept();
                new Thread(new UserThread(s)).start();
            }
        }
    }
    =================================================
    其实这个案例只是简单模拟,并不很理想
    以下解析:
    while true允许多个客户端同时访问
     
    时间值保证命名的唯一性(其实也不太靠谱,只是简单模拟,因为很可能同一毫秒挤进很多线程)
     
    day26笔记补充
          端口
                是应用程序的标识。范围:0-65535。其中0-1024不建议使用。
           协议
                UDP:数据打包,有限制,不连接,效率高,不可靠
                TCP:建立数据通道,无限制,效率低,可靠
      Socket机制
            A:通信两端都应该有Socket对象
            B:所有的通信都是通过Socket间的IO进行操作的
    ======================================================
      UDP协议发送和接收数据(掌握 自己补齐代码)
            发送
                创建UDP发送端的Socket对象
                创建数据并把数据打包
                发送数据
                释放资源
     
            接收
                创建UDP接收端的Socket对象
                创建数据包用于接收数据
                接收数据
                解析数据包
                释放资源
    =======================================
        TCP协议发送和接收数据(掌握 自己补齐代码)
            发送
                创建TCP客户端的Socket对象
                获取输出流,写数据
                释放资源
     
            接收
                创建TCP服务器端的Socket对象
                监听客户端连接
                获取输入流,读取数据
                释放资源
    ============================================
     
  • 相关阅读:
    关于Flutter引用image_picker插件报错
    关于Flutter页面布局
    关于Flutter和Android混合开发引入flutter_boost插件-有更新,见底部
    关于uni-app的scroll-into-view
    关于全站http切换到https
    关于canvas画原子运动模型
    [BS-00] const限定常量或者变量(初次赋值后),其值不允许被改变
    认识C和内存管理
    C语言的内存管理
    堆heap和栈Stack(百科)
  • 原文地址:https://www.cnblogs.com/huangtao1996/p/5393773.html
Copyright © 2011-2022 走看看