• 20145231第六周学习笔记


    20145231 《Java程序设计》第6周学习总结

    教材学习内容总结

    第十章:输入/输出

    InputStream与Outputstream

    • 串流设计的概念

    从应用程序角度看,将数据从来源取出,可以使用输入串流,将数据写入目的地,可以使用输出串流;在Java中,输入串流代表对象为java.io.InputStream实例,输出串流代表对象为java.io.OutputStream实例;

    • 串流继承框架

    System.inSystem.out分别代表标准输入和标准输出;

    可以使用System的setIn()方法指定InputStream实例,用setOut()方法指定printStream;代码如下:

    System.err为printStream实例,称为标准输出串流,用于立即显示错误信息;

    FileInputStream:是InputStream的子类,可以指定文件名创建实例,一旦创建文档就开启,接着就可以用来写出数据,主要操作了InputStream的read()抽象方法,从而读取文档中的数据;

    FileOutputStream:是OutputStream的子类,可以指定文件名创建实例,一旦创建文档就开启,接着就可以用来写出数据,主要操作了OutputStream中的write抽象方法,使之可写出数据到文档;

    不使用,时都要用close()关闭文档;

    ByteStream是InputStream的子类,可以指定byte数组创建实例,一旦创建就可以将byte数组当做数据源进行读取。ByteArrayOutputStream是OutputStream的子类,可以指定byte数组创建实例,一旦创建就可以将byte数组当做目的地写出数据;

    • 串流装饰处理器

    若想要为输入输出的数据作加工处理,可以使用打包器类(如:scanner);

    InputStream和OutputStream的一些子类也具有打包器的作用,这些子类创建时,可以接受InputStream和OutputStream实例;

    常用打包器:BufferedInputStream、BufferOutputSream(具备缓冲区作用),DataInputStream、DataOutputStream(具备数据转换处理作用),ObjectInputStream、ObjectOutputStream(具备对象串行化能力)等;代码如下:

    package cc.openhome;
    
    import java.io.*;
    
    public class BufferedIO {
        public static void dump(InputStream src, OutputStream dest)
                                  throws IOException {
            try(InputStream input = new BufferedInputStream(src);
                 OutputStream output = new BufferedOutputStream(dest)) {
                byte[] data = new byte[1024];
                int length;
                while ((length = input.read(data)) != -1) {
                    output.write(data, 0, length);
                }
            }
        }
    }
    
    
    package cc.openhome;
    
    import java.io.*;
    
    public class Member {
        private String number;
        private String name;
        private int age;
    
        public Member(String number, String name, int age) {
            this.number = number;
            this.name = name;
            this.age = age;
        }
    
        public String getNumber() {
            return number;
        }
    
        public void setNumber(String number) {
            this.number = number;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public int getAge() {
            return age;
        }
    
        public void setAge(int age) {
            this.age = age;
        }
        
        @Override
        public String toString() {
            return String.format("(%s, %s, %d)", number, name, age);
        }
        
        public void save() throws IOException {
            try(DataOutputStream output = 
                    new DataOutputStream(new FileOutputStream(number))) {
                output.writeUTF(number);
                output.writeUTF(name);
                output.writeInt(age);
            } 
        }
        
        public static Member load(String number) throws IOException {
            Member member;
            try(DataInputStream input = 
                    new DataInputStream(new FileInputStream(number))) {
                member = new Member(
                        input.readUTF(), input.readUTF(), input.readInt());
            } 
            return member;
        }
    } 
    
    
    package cc.openhome;
    
    import java.io.IOException;
    import static java.lang.System.out;
    
    public class MemberDemo {
        public static void main(String[] args) throws IOException {
            Member[] members = {
                        new Member("B1234", "Justin", 90), 
                        new Member("B5678", "Monica", 95), 
                        new Member("B9876", "Irene", 88)
            };
            for(Member member : members) {
                member.save();
            }
            out.println(Member.load("B1234"));
            out.println(Member.load("B5678"));
            out.println(Member.load("B9876"));
        }
    }
    
    
    import static java.lang.System.out;
    
    public class Member2Demo {
        public static void main(String[] args) throws Exception {
            Member2[] members = {new Member2("B1234", "Justin", 90), 
                                 new Member2("B5678", "Monica", 95), 
                                 new Member2("B9876", "Irene", 88)};
            for(Member2 member : members) {
                member.save();
            }
            out.println(Member2.load("B1234"));
            out.println(Member2.load("B5678"));
            out.println(Member2.load("B9876"));
        }
    }
    

    字符处理类

    • Reader与Writer继承架构

    java.io.Reader类:抽象化了字符数据读入的来源;

    java.io.Writer类:抽象化了数据写出目的地;代码如下:

    FileReader:读取文档并将读到的数据转换成字符;StringWriter:将字符数据写至它最后使用toString()的方法取得字符串;代码如下:

    import java.io.*;
    
    public class CharUtil {
        public static void dump(Reader src, Writer dest) throws IOException {
            try(Reader input = src; Writer output = dest) {
                char[] data = new char[1024];
                int length;
                while((length = input.read(data)) != -1) {
                    output.write(data, 0, length);
                }
            }
        }
    }
    
    package cc.openhome;
    
    import java.io.*;
    
    public class CharUtilDemo {
        public static void main(String[] args) throws IOException {
            FileReader reader = new FileReader(args[0]);
            StringWriter writer = new StringWriter();
            CharUtil.dump(reader, writer);
            System.out.println(writer.toString());
        }
    }
    

    • 字符处理装饰器

    将字节数据转换成对应的编码字符,可以使用InputStreamReader、OutputStreamWriter对串流数据打包;代码如下:

    import java.io.*;
    
    public class CharUtil2 {
        public static void dump(Reader src, Writer dest) throws IOException {
            try(Reader input = src; Writer output = dest) {
                char[] data = new char[1024];
                int length;
                while((length = input.read(data)) != -1) {
                    output.write(data, 0, length);
                }
            }
        }
        
        public static void dump(InputStream src, OutputStream dest, 
                                 String charset) throws IOException {
            dump(
                new InputStreamReader(src, charset), 
                new OutputStreamWriter(dest, charset)
            );
        }
    
        // 采用预设编码
        public static void dump(InputStream src, OutputStream dest) 
                               throws IOException {
            dump(src, dest, System.getProperty("file.encoding"));
        }
    }
    

    提高字符输入输出效率,提供缓冲区作用:BufferedReader、BufferWriter;

    printWriter:对OutStream打包,对writer打包;

    第十一章:线程与并行API

    线程

    • 线程简介

    单线程程序:启动的程序从main()程序进入点开始至结束只有一个流程;多线程程序:拥有多个流程;

    java中从main()开始的流程会由主线程执行可以创建Thread实例来执行Runable实例定义的run()方法;代码如下:(龟兔赛跑)

    package cc.openhome;
    
    public class Tortoise implements Runnable {
        private int totalStep;
        private int step;
    
        public Tortoise(int totalStep) {
            this.totalStep = totalStep;
        }
    
        @Override
        public void run() {
            while (step < totalStep) {
                step++;
                System.out.printf("乌龟跑了 %d 步...%n", step);
            }
        }
    }
    
    import static java.lang.System.out;
    
    public class TortoiseHareRace {
        public static void main(String[] args) {
            boolean[] flags = {true, false};
            int totalStep = 10;
            int tortoiseStep = 0;
            int hareStep = 0;
            out.println("龟兔赛跑开始...");
            while(tortoiseStep < totalStep && hareStep < totalStep) {
                tortoiseStep++;       
                out.printf("乌龟跑了 %d 步...%n", tortoiseStep);
                boolean isHareSleep = flags[((int) (Math.random() * 10)) % 2];
                if(isHareSleep) {
                    out.println("兔子睡着了zzzz");
                } else {
                    hareStep += 2;    
                    out.printf("兔子跑了 %d 步...%n", hareStep);
                }
            }
        }
    }
    
    public class TortoiseHareRace2 {
        public static void main(String[] args) {
            Tortoise tortoise = new Tortoise(10);
            Hare hare = new Hare(10);
            Thread tortoiseThread = new Thread(tortoise);
            Thread hareThread = new Thread(hare);
            tortoiseThread.start();
            hareThread.start();
        }
    }
    

    • Thread与Runnable

    创建Thread实例就是为JVM加装CPU,启动额外CPU就是调用实例的start()方法,额外CPU的进入点可以定义在Runable接口的run()方法中;

    除了将流程这样定义,另一个撰写多线程程序的方式就就是继承Thread类,重新定义run()方法;

    操作Runnable接口的好处就是较有弹性,你的类还有机会继承其他类;若继承了Thread类,通常是为了直接利用Thread中定义的一些方法;

    • 线程生命周期

    Daemon线程:如果一个Thread被标示为Deamon线程,在所有的非Deamon线程都结束时,JVM就会自动终止;代码如下:

    public class DaemonDemo {
    
        public static void main(String[] args) {
            Thread thread = new Thread(() -> {
                while (true) {
                    System.out.println("Orz");
                }
            });
            // thread.setDaemon(true);
            thread.start();
        }
    }
    

    Thread基本状态图:可执行、被阻断、执行中;

    线程看起来但事实是同一个时间点上,一个CPU还是只能执行一个线程,只是因其不断切换且很快,所以看起来像是同时执行;

    线程有其优先权,setPriority()方法设定优先权,利用多线程改进效能;

    当线程使用join()加入另一线程时,另一线程会等待被加入的线程工作完毕再继续它的动作;代码如下:

    线程完成run()方法后,就会进入Dead,此时不可以再调用start()方法否则会抛出IlligleThreadException;

    • 关于ThreadGroup

    每个线程都属于某个线程群组,线程一旦归入某个群组,就无法再更换;可以使用以下程序片段取得当前线程所属线程群组名:Thread.currentThread().getThreadGroup().getname();

    使用uncoughtException()方法处理群组中某个线程出现异常未被捕捉的情况,可以重新定义此方法;
    代码如下:

    package cc.openhome;
    
    public class ThreadGroupDemo {
    
        public static void main(String[] args) {
            ThreadGroup group = new ThreadGroup("group") {
                @Override
                public void uncaughtException(Thread thread, Throwable throwable) {
                    System.out.printf("%s: %s%n", 
                            thread.getName(), throwable.getMessage());
                }
            };
    
            Thread thread = new Thread(group, () -> {
                throw new RuntimeException("测试例外");
            });
    
            thread.start();
        }
    }
    
    public class ThreadGroupDemo2 {
    
        public static void main(String[] args) {
            ThreadGroup group = new ThreadGroup("group");
            
            Thread thread1 = new Thread(group, () -> {
                throw new RuntimeException("thread1 测试例外");
            });
            thread1.setUncaughtExceptionHandler((thread, throwable) -> {
                System.out.printf("%s: %s%n", 
                        thread.getName(), throwable.getMessage());
            });
    
            Thread thread2 = new Thread(group, () -> {
                throw new RuntimeException("thread2 测试例外");
            });
    
            thread1.start();
            thread2.start();
        }
    }
    

    • synchronized与volatile

    如果在方法上标示synchronized,则执行方法必须取得该实例的锁定,才能执行该区块内容;

    可重入同步:线程取得某对象锁定后,若执行过程中又要执行synchronized,尝试取得锁定的对象来源又是同一个,则可以直接执行;

    synchronized:互斥性:该区块同时间只能有一个线程,可见性:线程离开该区块后,另一线程接触到的就是上一线程改变后的对象状态;

    在java中对于可见性的要求,可以使用volatile达到变量范围,在变量上声明volatile,表示变量是不稳定、易变的,也就是可能在多线程下存取,其存取一定是在共享内存中进行,代码如下:

    package cc.openhome;
    
    class Variable1 {
        static int i = 0, j = 0;
    
        static void one() {
            i++;
            j++;
        }
    
        static void two() {
            System.out.printf("i = %d, j = %d%n", i, j);
        }
    }
    
    public class Variable1Test {
        public static void main(String[] args) {
            Thread thread1 = new Thread(() -> {
                while (true) {
                    Variable1.one();
                }
            });
            Thread thread2 = new Thread(() -> {
                while (true) {
                    Variable1.two();
                }
            });
            
            thread1.start();
            thread2.start();
        }
    }
    
    
    class Variable2 {
        static int i = 0, j = 0;
    
        static synchronized void one() {
            i++;
            j++;
        }
    
        static synchronized void two() {
            System.out.printf("i = %d, j = %d%n", i, j);
        }
    }
    
    public class Variable2Test {
        public static void main(String[] args) {
            Thread thread1 = new Thread(() -> {
                while (true) {
                    Variable2.one();
                }
            });
            Thread thread2 = new Thread(() -> {
                while (true) {
                        Variable2.two();
                    }
            }); 
            
            thread1.start();
            thread2.start();
        }
    }
    
    
    class Variable3 {
        volatile static int i = 0, j = 0;
    
        static void one() {
            i++;
            j++;
        }
    
        static void two() {
            System.out.printf("i = %d, j = %d%n", i, j);
        }
    }
    
    public class Variable3Test {
        public static void main(String[] args) {
            Thread thread1 = new Thread(() -> {
                while (true) {
                    Variable3.one();
                }
            });
            Thread thread2 = new Thread(() -> {
                while (true) {
                    Variable3.two();
                }
            });
            thread1.start();
            thread2.start();
        }
    }
    

    • 等待与通知

    调用锁定对象的wait()方法,线程会释放对象锁定,并进入对象等待集合而处于阻断状态,其他线程可以竞争对象锁定,取得锁定的线程可以执行synchronized范围的代码;

    被竞争的对象调用notify()方法时,会从对象等待集合中随机通知一个线程加入排班,再次执行synchronized前,被通知的线程会与其他线程共同竞争对象锁定;代码如下:

    public class Consumer implements Runnable {
        private Clerk clerk; 
        
        public Consumer(Clerk clerk) { 
            this.clerk = clerk; 
        } 
        
        public void run() { 
            System.out.println("消费者开始消耗整数......"); 
            for(int i = 1; i <= 10; i++) { 
                try {
                    clerk.getProduct(); 
                } catch (InterruptedException ex) {
                    throw new RuntimeException(ex);
                }
            } 
        } 
     }
    
    public class Clerk {
        private int product = -1;
    
        public synchronized void setProduct(int product) throws InterruptedException {
            waitIfFull();
            this.product = product;
            System.out.printf("生产者设定 (%d)%n", this.product);
            notify();
        }
    
        private synchronized void waitIfFull() throws InterruptedException {
            while (this.product != -1) {
                wait();
            }
        }
    
        public synchronized int getProduct() throws InterruptedException {
            waitIfEmpty();
            int p = this.product;
            this.product = -1;
            System.out.printf("消费者取走 (%d)%n", p);
            notify();
            return p;
        }
    
        private synchronized void waitIfEmpty() throws InterruptedException {
            while (this.product == -1) {
                wait();
            }
        }
    }
    
    public class ProducerConsumerDemo {
        public static void main(String[] args) {
            Clerk clerk = new Clerk(); 
            new Thread(new Producer(clerk)).start(); 
            new Thread(new Consumer(clerk)).start(); 
        }    
    }
    

    并行API

    • lock、ReadWriteLock与Condition

    java.util.concurrent.locks包中提供Lock、ReadWriteLock、Condition接口以及相关操作类,可以提供类似synchronized、wait()notify()notifyall()的作用,以及更多高级功能;

    Lock接口主要操作类之一为ReentrantLook,可以达到synchronized的作用,也提供额外功能;代码如下:

    ReadWriteLock:如果已经有线程取得Lock对象锁定,尝试再次锁定同一Lock对象是可以的。想要锁定Lock对象,可以调用Lock()方法;

    Condition接口用来搭配Lock,一个Condition对象可代表一个等待集合,可以重复调用Lock的newCondition(),取得多个Condition实例,这代表了有多个等待集合;

    • 使用Executor

    java.util.concurrent.Executor接口,目的是将Runnable的指定与实际如何执行分离,Executor接口只定义了一个execute();

    像线程池这样类服务的行为,实际上是定义在Executor的子接口java.util.concurrent.ExecutorService当中,通用的ExecutorService由抽象类AbstractExecutorService操作,如果需要线程池的功能,则可以使用其子类java.util.concurrent.ThreadPoolExecutor;

    ExecutorService还定义了submit()invokeAll()invokeAny()等方法,这些方法中出现了java.util.concurrent.Future、java.util.concurrent.Callable接口;

    ScheduledExecutorService为ExecutorService的子接口,可以进行工作排程,schedule()方法用来排定Runnable或Callable实例延迟多久执行一次,并返回Future子接口ScheduledFuture的实例;

    • 并行Collection简介

    java.util.concurrent包中,提供一些支持并行操作的Collection子接口与操作类;

    CopyOnWriteArrayList操作了List接口,这个类的实例在写入操作时,内部会建立新数组,并复制原有数组索引的参考,然后在新数组上进行写入操作,写入完成后,再将内部原参考旧数组的变量参考至新数组;

    CopyOnWriteArraySet操作了Set接口,内部使用CopyOnWriteArrayList来完成Set的各种操作,因此一些特性与CopyOnWriteArrayList是相同的;

    BlockingQueue是Queue的子接口,新定义了put()take()方法;代码如下:

    
    import java.util.concurrent.BlockingQueue;
    
    public class Producer3 implements Runnable {
        private BlockingQueue<Integer> productQueue; 
        
        public Producer3(BlockingQueue<Integer> productQueue) { 
            this.productQueue = productQueue; 
        } 
        
        public void run() { 
            System.out.println("生产者开始生产整数......"); 
            for(int product = 1; product <= 10; product++) { 
                try { 
                    productQueue.put(product);
                    System.out.printf("生产者提供整数 (%d)%n", product);
                } catch (InterruptedException ex) {
                    throw new RuntimeException(ex);
                }
            }       
        } 
    }
    
    import java.util.concurrent.BlockingQueue;
    
    public class Consumer3 implements Runnable {
        private BlockingQueue<Integer> productQueue; 
        
        public Consumer3(BlockingQueue<Integer> productQueue) { 
            this.productQueue = productQueue; 
        } 
        
        public void run() { 
            System.out.println("消费者开始消耗整数......"); 
            for(int i = 1; i <= 10; i++) { 
                try {
                    int product = productQueue.take();
                    System.out.printf("消费者消耗整数 (%d)%n", product);
                } catch (InterruptedException ex) {
                    throw new RuntimeException(ex);
                }
            } 
        } 
     }
    
    import java.util.concurrent.*;
    
    public class ProducerConsumerDemo3 {
        public static void main(String[] args) {
            BlockingQueue queue = new ArrayBlockingQueue(1);
            new Thread(new Producer3(queue)).start(); 
            new Thread(new Consumer3(queue)).start(); 
        }    
    }
    

    教材学习中的问题和解决过程

    问题一:p356页代码中的DeadLockDemo为什么会发生死结情况?

    解决过程:因为两个线程在执行cooparate()方法取得目前Resource锁定后,尝试调用另一Resource的doSome(),因无法取得另一Resource的锁定而阻断。即:线程因无法同时取得两个Resource的锁定时,干脆释放已取得的锁定,就可以解决问题;

    问题二:使用Condition提高效率?

    解决过程:一个Condition对象可代表有一个等待集合,可以重复调用Lock的newCondition(),取得多个Condition实例,这代表了可以有多个等待集合。而p362中改写的Clerk类,因为使用了一个Condition,所以实际上也只有一个等待集合,作用将类似11.11.6节中的Clerk类。如果有两个等待集合:一个给生产者线程用,一个给消费者线程用,生产者只通知消费者等待集合,消费者只通知生产者等待集合,会比较有效率。

    代码调试中的问题和解决过程

    由于本周学习过程中需要理解的概念太多,对于代码的理解和掌握相较前面的学习有不足之处,只是直接输入了部分代码进行运行,所以没有太大问题。

    其他(感悟、思考等,可选)

    本周内容较上周更加抽象难懂,似乎开始对面向对象这个概念以及三要素之一的抽象有点感觉了,但是新的类、方法、接口太多,一时有点难以消化,只能通过代码帮助概念的理解。

    代码托管截图

    学习进度条

    代码行数(新增/累积) 博客量(新增/累积) 学习时间(新增/累积) 重要成长
    目标 5000行 30篇 400小时
    第一周 200/200 2/2 20/20
    第二周 300/500 2/4 21/41
    第三周 450/950 3/7 22/63
    第四周 450/1365 2/9 20/83
    第五周 450/1815 2/11 20/113
    第六周 500/2315 2/13 20/133 了解了输入输出及线程基本概念

    参考资料

  • 相关阅读:
    后台服务器经典面试题
    Java英语面试题(核心知识篇)
    Java常用英语汇总(面试必备)
    字符压缩编码
    外排序
    基数排序
    Windows Server 2008 R2 部署服务
    LINUX中常用操作命令
    我的学习笔记_Windows_HOOK编程 2009-12-03 11:19
    CSDN-Code平台使用过程中的5点经验教训
  • 原文地址:https://www.cnblogs.com/xzh20145231/p/5374554.html
Copyright © 2020-2023  润新知