跳至主要內容

流处理

wangdx大约 46 分钟

流处理

File 类

文件创建和删除

java.io.File 类是在 Java 之中唯一一个与文件本身有关的程序类,利用这个类用户可以实现文件或目录的处理操作,而要想使用这个类进行具体的操作之前则一定要获取一个执行的处理路径,首先来观察一下 File 类的定义:

public class File extends Object implements Serializable,Comparable<File>{}

File 类实现了 Comparable 接口,这个接口的主要功能就是实现数据比较的处理,也就是说 File 类的对象可以通过 Arays.sort() 的方式来实现大小的关系匹配。但是如果要想进行具体的文件或目录的操作,则就必须清楚相应的路径的配置,那么可以使用如下的 File 类的方法来实现一些基本的 File 处理。

除了可以直接进行文件操作路径的定位之外,实际上还可以实现文件的创建以及删除的控制,例如:下面通过一个简单的程序逻辑实现一个功能:如果此时给定的文件路径存在,那么就进行文件的删除,如果文件不存在,则执行文件的创建。范例:实现文件的创建与删除.

问题二:如果以程序的开发为例最为常用的操作系统有两个“Wimndows”(大众群体)、“MacOs”(精英群体),而后在部署的时候 Linux 系统也是最为常用的(CentOs 系统),这个时候就会出现一个路径分隔符的问题:。 Windows 系统中,默认的路径分隔符为“\”,但是后来由于 Windows 系统的不断完善,也可以接受“/”分割: ·Linux 系统中,路径分隔符采用的是“/” 一个设计良好的 IO 程序必然要去更多的考虑操作系统使用的问题,如果每一次都为这些分隔符发愁就非常的麻烦了,为了解决这样的分隔符统一操作问题,在 File 类中提供有一个重要的常量:

详情
public class FileTest {
    public static void main(String[] args) throws IOException {
        String path = "D:\\develop\\project\\wyix\\gitee\\study_service\\java_up\\src\\com\\yix\\stream\\yix.txt";
        String path11 = "D:" + File.separator + "develop" + File.separator + "project" + File.separator + "wyix" + File.separator + "gitee" + File.separator + "study_service" + File.separator + "java_up" + File.separator + "src" + File.separator + "com" + File.separator + "yix" + File.separator + "stream" + File.separator + "yix11.txt";
        String path1 = "D:/develop/project/wyix/gitee/study_service/java_up/src/com/yix/stream/yix1.txt";
        File file = new File(path); //文件获取
        if (file.exists()) {// 文件路径存在
            System.out.println("【文件存在】执行删除操作:" + file.delete());
            ; // 删除给定的文件
        } else {
            System.out.println("【文件不存在】执行创建操作:" + file.createNewFile());
            ;// 创建文件
        }
        File file1 = new File(path1); //文件获取
        if (file1.exists()) {// 文件路径存在
            System.out.println("【文件存在】执行删除操作:" + file1.delete());
            ; // 删除给定的文件
        } else {
            System.out.println("【文件不存在】执行创建操作:" + file1.createNewFile());
            ;// 创建文件
        }
        File file11 = new File(path11); //文件获取
        if (file11.exists()) {// 文件路径存在
            System.out.println("【文件存在】执行删除操作:" + file11.delete());
            ; // 删除给定的文件
        } else {
            System.out.println("【文件不存在】执行创建操作:" + file11.createNewFile());
            ;// 创建文件
        }
    }
}

文件目录操作

详情
public class YootkDemo {    // 李兴华编程训练营:yootk.ke.qq.com
    public static void main(String[] args) throws Exception {
        File file = new File("h:" + File.separator + "muyan" + File.separator + "vip" + File.separator + "yootk.txt"); // 定义要操作的文件路径
        System.out.println("【文件路径】" + file);
        if (!file.getParentFile().exists()) {   // 父路径不存在
            file.getParentFile().mkdirs() ; // 创建父目录
        }
        if (file.exists()) {    // 文件路径存在
            System.out.println("【文件存在】执行删除操作:" + file.delete()); ; // 删除给定的文件
        } else {
            System.out.println("【文件不存在】执行创建操作:" + file.createNewFile()); ;// 创建文件
        }
    }
}

public class YootkDemo {    // 李兴华编程训练营:yootk.ke.qq.com
    private static File parentFile = new File("h:" + File.separator + "muyan" + File.separator + "vip" + File.separator); // 定义要操作的文件路径
    static {    // 静态代码块优先于主方法执行
        if (!parentFile.getParentFile().exists()) {   // 父路径不存在
            parentFile.getParentFile().mkdirs() ; // 创建父目录
        }
    }
    public static void main(String[] args) throws Exception {
        for (int x = 0 ; x < 100 ; x ++) {
            new Thread(()->{
                File file = new File("h:" + File.separator + "muyan" + File.separator + "vip" + File.separator + Thread.currentThread().getName());// 创建文件
                if (file.exists()) {    // 文件路径存在
                    System.out.println("【文件存在】执行删除操作:" + file.delete()); ; // 删除给定的文件
                } else {
                    try {
                        System.out.println("【文件不存在】执行创建操作:" + file.createNewFile());
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }, "MuYan-VIP-" + x).start();
        }
    }
}

获取文件信息

详情
public class YootkDemo {    // 李兴华编程训练营:yootk.ke.qq.com
    public static void main(String[] args) throws Exception {
        File file = new File("H:" + File.separator + "yootk.png") ; // 文件
        if (file.exists()) {    // 获取信息的前提是文件存在
            System.out.printf("【文件权限】可读:%s、可写:%s、可执行:%s\n", file.canRead(), file.canWrite(), file.canExecute());
            System.out.printf("【文件绝对路径】%s\n", file.getAbsoluteFile());
            System.out.printf("【文件目录】%s\n", file.getParent());
            System.out.printf("【文件名称】%s\n", file.getName());
            System.out.printf("【路径类型】文件夹:%s、文件:%s", file.isDirectory(), file.isFile());
        }
    }
}

获取目录信息

详情
public class YootkDemo {    // 李兴华编程训练营:yootk.ke.qq.com
    public static void main(String[] args) throws Exception {
        File file = new File("H:" + File.separator + "muyan" + File.separator) ; // 文件
        if (file.exists() && file.isDirectory()) {    // 如果路径存在且是一个目录
            System.out.println("【list()方法执行结果】" + Arrays.toString(file.list()));
            System.out.println("【listFiles()方法执行结果】" + Arrays.toString(file.listFiles()));
        }
    }
}
public class YootkDemo {    // 李兴华编程训练营:yootk.ke.qq.com
    public static void main(String[] args) throws Exception {
        File file = new File("H:" + File.separator + "muyan" + File.separator) ; // 文件
        info (file) ; // 设置要列表的目录路径
    }
    public static void info(File file) {
        if (file.isDirectory()) {   // 当前的路径是一个目录
            File list[] = file.listFiles((f) -> f.isDirectory() ? true : f.getName().endsWith(".txt")); // 列出目录中的全部组成
            for (File temp : list) {
                info(temp) ; // 递归操作,继续列出
            }
        } else {
            System.out.println(file); // 直接输出文件信息
        }
    }
}

public class YootkDemo {    // 李兴华编程训练营:yootk.ke.qq.com
    public static void main(String[] args) throws Exception {
        File file = new File("H:" + File.separator) ; // 文件
        info (file) ; // 设置要列表的目录路径
    }
    public static void info(File file) {
        if (file.isDirectory()) {   // 当前的路径是一个目录
            File list[] = file.listFiles((f) -> f.isDirectory() ? true : f.getName().endsWith(".txt")); // 列出目录中的全部组成
            if (list != null) { // 存在文件列表
                for (File temp : list) {
                    info(temp); // 递归操作,继续列出
                }
            }
        } else {
            System.out.println(file); // 直接输出文件信息
        }
    }
}

文件更名

在操作系统里面任何的文件都是可以实现名称修改的,所以针对于这种名称修改的操作就可以通过 File 类中所提供的方法来完成,此方法定义如下:。 public boolean renameTo(File dest). 如果要进行更名则一定要配置有一个完善的路径才可以实现这样的更名处理。

详情
public class YootkDemo {    // 李兴华编程训练营:yootk.ke.qq.com
    public static void main(String[] args) throws Exception {
        File oldFile = new File("H:" + File.separator + "yootk.png") ; // 原始文件
        File newFile = new File("D:" + File.separator + "muyan.png") ; // 原始文件
        System.out.println("【文件更名处经理】" + oldFile.renameTo(newFile));
    }
}

面试题:现在有一个数据的采集目录,在目录中所有的日志文件的定义结构为“yootk-日期码-编号.log”命名的,以这种操作方式实现一堆文件的生成:

详情
class Test5 {
    private static File dir = new File("d:" + File.separator + "yix" + File.separator + "logs" + File.separator);

    static {
        if (!dir.exists()) {
            dir.mkdirs();
        }
    }

    public static void main(String[] args) throws IOException {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
        for (int x = 0; x < 100; x++) {
            File file = new File(dir, "yootk-" + sdf.format(new Date()) + "-" + x + ".log");
            if (!file.exists()) {
                file.createNewFile();
            }
        }
    }
}

所以此时老板要求编写一个程序,可以将所有序号的长度设置为统一位数,这个位数由文件的数量动态决定。

详情

class DirRenameUtil {//专属命名工具类 private File dir;//当前操作的目录地址 private int sequenceLength; //保存整体文件编号的最大长度 private int fileCount; //文件个数

public DirRenameUtil(File dir) {
    this.dir = dir;
    this.calcFileCount(dir);//统计文件个数
    this.sequenceLength = String.valueOf(this.fileCount).length();//获取文件序列最大长度
}

public void rename() {
    this.renameHandle(this.dir);
}

private void renameHandle(File file) {
    if (file.isDirectory()) {
        File list[] = file.listFiles();
        if (list != null) {
            for (File sub : list) {
                this.renameHandle(sub);
            }
        }
    } else {
        if (file.isFile()) {
            String fileName = file.getName();
            String seq = fileName.substring(fileName.lastIndexOf("-") + 1, fileName.indexOf(".log"));
            if (seq.length() != this.sequenceLength) {
                String newFileName = fileName.substring(0, fileName.lastIndexOf("-") + 1) + this.addZero(seq) + ".log";
                file.renameTo(new File(file.getParentFile(), newFileName));
            }
        }
    }
}

private String addZero(String seq) {
    StringBuffer buffer = new StringBuffer(seq);
    while (buffer.length() != this.sequenceLength) {
        buffer.insert(0, "0");
    }
    return buffer.toString();
}

private void calcFileCount(File file) {
    if (file.isDirectory()) {
        File list[] = file.listFiles();
        if (list != null) {
            for (File sub : list) {
                this.calcFileCount(sub);
            }
        }
    } else {
        if (file.isFile()) {
            this.fileCount++;
        }
    }
}

}

class Test6 { private static File dir = new File("d:" + File.separator + "yix" + File.separator + "logs" + File.separator);

static {
    if (!dir.exists()) {
        dir.mkdirs();
    }
}

public static void main(String[] args) throws IOException {
    new DirRenameUtil(dir).rename();
}

}


数据流(字节流和字符流)

简介

流(Stream)主要指的是数据的处理方式,以及对于目标内容的处理机制,在现实生活之中输入和输出属于两个不同的处理阶段,所以也有相对性的概念。.

在 Java 编程之中如果要想实现这种输入和输出的操作提供有两种不同的操作流:

  • 字节操作流:OutputStream(字节输出流)、InputStream(字节输入流);
  • 字符操作流:Writer(字符输出流)、Reader(字符输入流)。

不管现在开发者编写程序使用的是何种操作流,实际上所有的数据流都会按照如下的标准做法进行处理(以文件操作为例):

  • 通过 File 类定义一个要操作文件的路径(此操作是在进行文件输入输出的时候所需要采用的方式);
  • 通过字节流或字符流的子类为父类对象实例化;
  • 实现数据的读、写操作;
  • 流属于非常宝贵的资源,操作完毕后一定要进行关闭(closeO)。

OutputStream 字节输出流

java.io.OutputStream 类是在 IO 包中提供的专门实现字节数据的输出流,如果要想理解这个工具类的使用,那么就必须首先通过其完整的定义来进行分析:

public abstract class OutputStream extends Object implements Cloeable,Flushable{}

OutputStream 类是在 JDK 1.0 的时候提供给开发者使用的,但是其所实现的 Closeable 接口是在 JDK15 的时候提供的,实现 的 Flushable 接口也是在 JDK1.5 的时候实现的。

OutputStream 类定义了所有字节输出流中最为重要的三个内容输出方法,但是 OutputStream 类毕竟属于一个抽象类,所以如果要想进行抽象类的对象实例化,则必须依靠子类,既然此时需要实现的是文件的输出操作,所以可以使用 FileOutputStream 子类,这个类主要去观察其内部所提供的构造方法。

详情
public class Test {
    private static String path = "D:" + File.separator + "develop" + File.separator + "project" + File.separator + "wyix" + File.separator + "gitee" + File.separator + "study_service" + File.separator + "java_up" + File.separator + "src" + File.separator + "com" + File.separator + "yix" + File.separator + "stream" + File.separator + "io" + File.separator;
    private static File dir = new File(path);

    static {
        if (!dir.exists()) {
            dir.mkdirs();
        }
    }

    public static void main(String[] args) throws Exception {
        File file = new File(dir, "yix.txt");
        OutputStream outputStream = new FileOutputStream(file);// 实例化OutputStream抽象类对象
        String message = "www.yootk.com"; // 此为要输出的数据内容
        // OutputStream类的输出是以字节数据类型为主的,所以需要将字符串转为字节数据类型
        byte[] data = message.getBytes();// 将字符串转为字节数句
        outputStream.write(data);
        outputStream.close();
    }
}

当本程序执行之后会自动根据要输出的文件进行文件的创建,随后将相应的内容的数据直接写入到文件里面,但是也需要注意的一个问题在于:此时的程序如果重复执行实际上只会出现内容的覆盖,如果要想进行文件内容追加,则就必须在实例化 ileOutputStream 类对象的时候开启 append 追加模式。

详情
class Test1 {
    private static String path = "D:" + File.separator + "develop" + File.separator + "project" + File.separator + "wyix" + File.separator + "gitee" + File.separator + "study_service" + File.separator + "java_up" + File.separator + "src" + File.separator + "com" + File.separator + "yix" + File.separator + "stream" + File.separator + "io" + File.separator;
    private static File dir = new File(path);

    static {
        if (!dir.exists()) {
            dir.mkdirs();
        }
    }

    public static void main(String[] args) throws Exception {
        File file = new File(dir, "yix.txt");
        OutputStream outputStream = new FileOutputStream(file,true);// 实例化OutputStream抽象类对象
        String message = "www.yootk.com\r\n"; // 此为要输出的数据内容
        // OutputStream类的输出是以字节数据类型为主的,所以需要将字符串转为字节数据类型
        byte[] data = message.getBytes();// 将字符串转为字节数句
        outputStream.write(data);
        outputStream.close();
    }
}

InputStream

与 OutputStream 抽象类对应的另外一个操作类就属于 java.1io.InputStream 类,这个类可以基于字节的方式实现指定输入流数据的读取操作,此类的定义如下:

public abstract class InputStream extends Object implements Cloeable
详情
class Test2 {
    private static String path = "D:" + File.separator + "develop" + File.separator + "project" + File.separator + "wyix" + File.separator + "gitee" + File.separator + "study_service" + File.separator + "java_up" + File.separator + "src" + File.separator + "com" + File.separator + "yix" + File.separator + "stream" + File.separator + "io" + File.separator;
    private static File dir = new File(path);

    static {
        if (!dir.exists()) {
            dir.mkdirs();
        }
    }

    public static void main(String[] args) {
        File file = new File(dir, "yix.txt");
        if (file.exists()) {
            try (InputStream input = new FileInputStream(file)) {
                byte[] data = new byte[1024]; // 开辟1K的空间进行读取
                int len = input.read(data);// 读取数据到字节数组并返回读取个数
                System.out.println("读取到的数据内容【" + new String(data, 0, len) + "】");
            } catch (Exception e) {
            }
        }
    }
}

此时的程序已经实现了整个数据内容的读取,但是这里面还会存在有另外一个问题,如果说此时要读取的内容很大,但是所开辟的数组空间很小,这个时候肯定无法一次性进行数据的读取,那么就需要重复多次读取,而如果文件已经读取到底部的时候,调用 read0 方法返回的内容就是“-1”(不再是读取到的字节个数了)。

详情
class Test3 {
    private static String path = "D:" + File.separator + "develop" + File.separator + "project" + File.separator + "wyix" + File.separator + "gitee" + File.separator + "study_service" + File.separator + "java_up" + File.separator + "src" + File.separator + "com" + File.separator + "yix" + File.separator + "stream" + File.separator + "io" + File.separator;
    private static File dir = new File(path);

    static {
        if (!dir.exists()) {
            dir.mkdirs();
        }
    }

    public static void main(String[] args) {
        StringBuffer buffer = new StringBuffer(); // 保存读取到的内容
        File file = new File(dir, "yix.txt");
        if (file.exists()) {
            try (InputStream input = new FileInputStream(file)) {
                byte[] data = new byte[8]; // 开辟1K的空间进行读取
                int len = 0;// 保存数据读取的个数
                do {
                    len = input.read(data);
                    if (len != -1) {
                        buffer.append(new String(data, 0, len)); // 每次读取到的内容保存在缓冲流中
                    }
                } while (len != -1); // 没有读取到底
                System.out.println("读取到的数据内容【" + buffer + "】");
            } catch (Exception e) {
            }
        }
    }
}

以上的程序由于一次性的读取不可能将所有的内容全部读取完成,所以采用的方式就是进行部分读取的处理,而在进行部分读取的时候又需要将每一次读取到的内容保存在 StingBuner 之中,但是在保存的时候必须保证有内容读取的时候(len!=-1)才可以实现数据的添加,只不过这上面的代码有些过于繁琐了,而实际的项目开发过程之中,对于此类的操作一般采用的都是 while 循环的结构方式。

详情
class Test4 {
    private static String path = "D:" + File.separator + "develop" + File.separator + "project" + File.separator + "wyix" + File.separator + "gitee" + File.separator + "study_service" + File.separator + "java_up" + File.separator + "src" + File.separator + "com" + File.separator + "yix" + File.separator + "stream" + File.separator + "io" + File.separator;
    private static File dir = new File(path);

    static {
        if (!dir.exists()) {
            dir.mkdirs();
        }
    }

    public static void main(String[] args) {

        File file = new File(dir, "yix.txt");
        if (file.exists()) {
            StringBuffer buffer = new StringBuffer(); // 保存读取到的内容
            try (InputStream input = new FileInputStream(file)) {
                byte[] data = new byte[8]; // 开辟1K的空间进行读取
                int len = 0;// 保存数据读取的个数
                // 第一个表达式:input.read(data),将输入流的数据读取到字节数组之中
                // 第二个表达式:len = input.read(data),将读取到的数据长度赋值给len变量
                // 第三个表达式:(len = input.read(data)) != -1,判断len的内容是否不为-1
                while ((len = input.read(data)) != -1) {
                    buffer.append(new String(data, 0, len));
                }
                System.out.println("读取到的数据内容【" + buffer + "】");
            } catch (Exception e) {
            }
        }
    }
}

Write 字符输出流

虽然使用 OutputStream 类可以实现数据的输出,但是这个类本身却存在有一个非常严重的问题:需要将所有的数据变为字节数组,而后才能够实现最终的内容输出,为了解决这一设计的问题,在 JDK1.1 版本之后又提供了新的 IO 输出类 -- Writer, Writer 类的定义结构如下:"

public abstract class Writer
extends Object
implements Appendable,closeable,Flushable

在 Writer 类里面在 JDK1.5 之后让其多实现了一个 Appendable 父接口,而这个父接口里面所提供的方法就是追加“append()”

一定要记住,字符和字节数据都是可以自动向 int 类型转换的,所以不管是使用何种数据流,都会有输出或读取单个内容的支持方法,这种方法的参数或者是返回值类型都是 int 型,

相比较 OutputStream 类来讲,使用 Writer 类实现输出最大的优势在于可以直接实现字符串内容的输出,避免了与字节数组之 间的转换处理

详情
class Test5 {
    private static String path = "D:" + File.separator + "develop" + File.separator + "project" + File.separator + "wyix" + File.separator + "gitee" + File.separator + "study_service" + File.separator + "java_up" + File.separator + "src" + File.separator + "com" + File.separator + "yix" + File.separator + "stream" + File.separator + "io" + File.separator;
    private static File dir = new File(path);

    static {
        if (!dir.exists()) {
            dir.mkdirs();
        }
    }

    public static void main(String[] args) {
        File file = new File(dir, "yix.txt");
        if (file.exists()) {
            try (Writer out = new FileWriter(file)) {
                out.write("www.yootk.com\n"); // 输出信息 清空原有数据添加数据
                out.append("edu.yootk.com\n"); // 追加输出信息
            } catch (Exception e) {
            }
        }
    }
}

Reader 字符输入流

java.io.Reader 是在 IO 包中提供的一个实现字符数据输入的操作流,首先来査看一下 Reader 类的定义:

public abstract class Reader
extends Obiect
implements Readable,
Closeable

在 Witer 类中提供有直接实现字符串数据输出的 write() 方法,但是在 Reader 类中却没有提供将所有的字符输入流以字符串形式返回的处理方法,主要的原因在于,如果数据量过大,那么会造成内存溢出,所以正确的读取方式就是声明一个字符数组,随后利用 read() 方法读取数据并根据返回的读取个数实现字符数组与字符串之间的转换。

详情
class Test6 {
    private static String path = "D:" + File.separator + "develop" + File.separator + "project" + File.separator + "wyix" + File.separator + "gitee" + File.separator + "study_service" + File.separator + "java_up" + File.separator + "src" + File.separator + "com" + File.separator + "yix" + File.separator + "stream" + File.separator + "io" + File.separator;
    private static File dir = new File(path);

    static {
        if (!dir.exists()) {
            dir.mkdirs();
        }
    }

    public static void main(String[] args) {
        File file = new File(dir, "yix.txt");
        if (file.exists()) {
            try (Reader in = new FileReader(file)) {
                char data[] = new char[1024]; // 开辟数组
                int len = in.read(data); // 数组读不满
                System.out.println(new String(data, 0, len));
            } catch (Exception e) {
            }
        }
    }
}

字符流与字节流区别

通过之前一系列的分析之后应该已经清楚了字节流和字符流的基本操作形式了,但是现在却存在有另外一个问题:字节流和字符流本身有什么区别?在实际的开发之中应该如何选择呢?.

在本次程序执行输出之后不进行输出流对象的关闭(不调用 close() 方法) 此时的程序已经可以正常的实现了程序的输出,但是由于没有关闭输出流,所以最终发现文件中不存在有任何的内容,而之所以关闭大会出现内容,是因为关闭的时候会强制进行内存缓冲区的刷新,这样就会把内存中的数据强制性进行文件保存,由于缓冲区的输出是需要进行清空处理的,所以即便使用了 append0 方法也不一定会输出,除非你的内容已经很大了,内存存不下了,才会自动进行输出,这个时候如果确定文件不能够关闭,那么可以考虑通过 fush()方法强制性刷新缓冲区。。 通过以上的分析就能够清楚的发现,字节输出流不会进过内存缓冲区进行数据的暂存,而是直接与目标介质进行输出控制,而所有的字符流都需要经过内存处理后才可以实现。实际上大部分的 I0 操作都可以通过字节流的方式来完成,但是如果要是在中文的处理上,首选的一定是字符流。"

详情
public class YootkDemo {    // 李兴华编程训练营:yootk.ke.qq.com
    public static void main(String[] args) throws Exception {
        File file = new File("H:" + File.separator + "muyan" + File.separator + "vip" + File.separator + "yootk.txt") ;
        if (!file.getParentFile().exists()) {   // 此时文件有父目录
            file.getParentFile().mkdirs() ; // 创建父目录
        }
        Writer out = new FileWriter(file) ;
        String message = "www.yootk.com" ; // 此为要输出的数据内容
        out.write(message); // 输出全部字节数组的内容
    }
}

10package com.yootk.demo;

import java.io.*;

public class YootkDemo {    // 李兴华编程训练营:yootk.ke.qq.com
    public static void main(String[] args) throws Exception {
        File file = new File("H:" + File.separator + "muyan" + File.separator + "vip" + File.separator + "yootk.txt") ;
        if (!file.getParentFile().exists()) {   // 此时文件有父目录
            file.getParentFile().mkdirs() ; // 创建父目录
        }
        Writer out = new FileWriter(file) ;
        String message = "www.yootk.com\n" ; // 此为要输出的数据内容
        out.write(message); // 输出全部字节数组的内容
        out.append("edu.yootk.com") ;
        out.flush(); // 强制刷新缓冲区
    }
}

转换流

通过之前的学习可以发现在整个 java.io 包里面可以实现数据的输入与输出操作类的类为字节流和字符流两组,但是在一些环境下有可能会出现这两种不同类型的数据流之间的互相转换问题,所以这种操作就称为转换流,在 java.io 包里面转换流主要分为两种:InputStreamReader、OuputStreamWriter,这两个子类通过具体的名称就可以非常清楚的发现,其都属于字符流的子类。

通过以上的继承关系结构图里面实际上可以得出如下的一个重要的结论信息:转换流全部都是字符流的子类,而转换流的主要功能就是将字节流转为字符流进行操作(字符流在 IO 开发中更加适合于实现中文的处理)。

详情
public class Demo1 {
    private static String path = "D:" + File.separator + "develop" + File.separator + "project" + File.separator + "wyix" + File.separator + "gitee" + File.separator + "study_service" + File.separator + "java_up" + File.separator + "src" + File.separator + "com" + File.separator + "yix" + File.separator + "stream" + File.separator + "io" + File.separator;
    private static File dir = new File(path);

    static {
        if (!dir.exists()) {
            dir.mkdirs();
        }
    }

    public static void main(String[] args) throws Exception {
        File file = new File(dir, "yix.txt");
        if (file.exists()) {
            Writer out = new OutputStreamWriter(new FileOutputStream(file));
            out.write("www.yootk.com"); // 字符流可以直接输出字符串
            out.close();
        }
    }
}
public class YootkDemo {    // 李兴华编程训练营:yootk.ke.qq.com
    public static void main(String[] args) throws Exception {
        File file = new File("H:" + File.separator + "muyan" + File.separator + "vip" + File.separator + "yootk.txt") ;
        if (file.exists()) {
            Reader in = new InputStreamReader(new FileInputStream(file)) ;
            char [] data = new char[1024] ;
            int len = in.read(data) ;
            System.out.println(new String(data, 0, len));
        }
    }
}

以上的这种流的转换处理操作从实际的开发来讲使用率的确不高,但是之所以要强调这个转换流是因为需要进一步解释关于之前学习到的字符需要缓冲区来处理的概念。

文件拷贝

现在要求可以通过 Java 程序来实现这样的文件拷贝的处理命令,如果要自己来实现此时就面临如下几个问题

  • 第一个问题:是选择字节流还是使用字符流呢?

    如果要进行文件拷贝的话有可能拷贝的文件是图片或者音乐视频等,那么使用字符流肯定不合适,这些数据全部都属于二进制的数据内容,那么最佳的做法就是采用字节流完成。

  • 第二个问题:怎样实现拷贝处理呢?

    对于文件的拷贝操作可以将其所有要拷贝的内容全部读取到内存之中,而后一次性的进行输出,或者采用管道的形式一点点的进行读取和拷贝。如果要考虑到实际的性能的话,肯定使用第二种方式会更加方便。

  • 第三个问题:对于拷贝命令中所需要输入的源文件路径和目标文件路径的定义可以采用初始化参数的形式完成处理。

详情
class CopyUtil {
    private File inFile; // 输入文件路径
    private File outFile; // 输出文件路径

    /**
     * 通过数组实现拷贝参数的配置,这个数组的长度一定是2
     * 第一个内容为拷贝文件的源路径,第二个内容为拷贝文件的输出目标路径
     *
     * @param args 拷贝的路径
     */
    public CopyUtil(String args[]) {
        if (args.length != 2) { // 参数的个数不足
            System.out.println("【ERROR】程序拷贝命令输入的参数不足,无法执行。");
            System.out.println("使用参考:java YootkDemo 源文件路径 目标文件路径");
            System.exit(1); // 程序退出
        }
        this.inFile = new File(args[0]);
        this.outFile = new File(args[1]);
    }

    public CopyUtil(String inPath, String outPath) {
        this.inFile = new File(inPath);
        this.outFile = new File(outPath);
    }

    /**
     * 实现文件的拷贝处理操作
     *
     * @return 拷贝文件所花费时间
     */
    public long copy() throws IOException { // IOException是最大的IO异常
        long start = System.currentTimeMillis(); // 获取开始时间
        InputStream input = null;
        OutputStream output = null;
        try {
            input = new FileInputStream(this.inFile); // 字节输入流
            output = new FileOutputStream(this.outFile, true); // 字节输出流
            byte data[] = new byte[2048]; // 每次拷贝2048个字节的内容
            int len = 0; // 保存每次拷贝的长度
            while ((len = input.read(data)) != -1) {
                output.write(data, 0, len); // 内容输出
            }
        } catch (IOException e) {
            throw e;
        } finally {
            if (input != null) {
                input.close();
            }
            if (output != null) {
                output.close();
            }
        }
        long end = System.currentTimeMillis(); // 获取结束时间
        return end - start; // 获取花费的时间
    }
}

class Test1 {
    public static void main(String[] args) throws IOException {
        System.out.println(new CopyUtil(args).copy());
    }
}

如果说你在面试的过程之中遇见有这种编写文件拷贝的问题,那么请一定要按照如上所给出的代码进行定义,但是如果说你现在定位的 JDK 版本为 JDK19 及以后的版本,那么这些版本里面 InputStream 类是有一个新的拷贝方法支持的:public long transferTo(OutputStream out)throws IOException 这个方法的出现就避免了之前每一次通过循环方式进行输入流和输出流的处理了,此时的 copyO 的方法实现简化了。 通过此时源代码的观察可以发现,当前的这种文件的拷贝操作会开辟一个非常大的缓冲区(不是由用户来决定的,是由 JDK 自己来决定的,并且无法修改)。

详情
/**
     * 实现文件的拷贝处理操作
     *
     * @return 拷贝文件所花费时间
     */
    public long copy() throws IOException { // IOException是最大的IO异常
        long start = System.currentTimeMillis(); // 获取开始时间
        InputStream input = null;
        OutputStream output = null;
        try {
            input = new FileInputStream(this.inFile); // 字节输入流
            output = new FileOutputStream(this.outFile, true); // 字节输出流
            input.transferTo(output);
        } catch (IOException e) {
            throw e;
        } finally {
            if (input != null) {
                input.close();
            }
            if (output != null) {
                output.close();
            }
        }
        long end = System.currentTimeMillis(); // 获取结束时间
        return end - start; // 获取花费的时间
    }

以上的程序实现了单个文件的拷贝处理操作,但是如果真的是在笔试的过程之中,有可能会出现这样一种情况:给你一个目录,要求实现整个目录的拷贝,那么这种情况下就必须使用递归的形式来完成了。

详情
import java.io.*;
interface ICopy {
    public long copy() throws IOException ;
}
class CopyUtil implements ICopy {
    private File inFile ; // 输入文件路径
    private File outFile ; // 输出文件路径
    /**
     * 通过数组实现拷贝参数的配置,这个数组的长度一定是2
     * 第一个内容为拷贝文件的源路径,第二个内容为拷贝文件的输出目标路径
     * @param args 拷贝的路径
     */
    public CopyUtil(String args[]) {
        if (args.length != 2) { // 参数的个数不足
            System.out.println("【ERROR】程序拷贝命令输入的参数不足,无法执行。");
            System.out.println("使用参考:java YootkDemo 源文件路径 目标文件路径");
            System.exit(1); // 程序退出
        }
        this.inFile = new File(args[0]) ;   // 有可能是文件路径,也有可能是文件夹路径
        this.outFile = new File(args[1]) ;   // 有可能是文件路径,也有可能是文件夹路径
    }
    public CopyUtil(String inPath, String outPath) {
        this.inFile = new File(inPath) ;   // 有可能是文件路径,也有可能是文件夹路径
        this.outFile = new File(outPath) ;   // 有可能是文件路径,也有可能是文件夹路径
    }
    /**
     * 实现文件的拷贝处理操作
     * @return 拷贝文件所花费时间
     */
    public long copy() throws IOException { // IOException是最大的IO异常
        long start = System.currentTimeMillis() ; // 获取开始时间
        if (!this.outFile.exists()) {   // 输出目录不存在
            if (this.outFile.isDirectory()) {   // 给定的是目录
                this.outFile.mkdirs() ; // 直接创建
            }
            if (this.outFile.isFile()) {    // 给定的是文件
                this.outFile.getParentFile().mkdirs() ; // 创建父目录
            }
        }
        if (this.inFile.isDirectory()) {
            this.copyHandle(this.inFile); // 递归操作
        } else {
            this.copyFile(this.inFile);
        }
        long end = System.currentTimeMillis() ; // 获取结束时间
        return end - start ; // 获取花费的时间
    }
    private void copyHandle(File file) throws IOException { // 实现具体拷贝处理
        if (file.isDirectory()) {   // 给定的输入路径为目录
            File [] list = file.listFiles() ; // 列出全部组成
            for (File temp : list) {    // 迭代全部的目录列表
                this.copyHandle(temp); // 实现拷贝的调用
            }
        } else {
            if (file.isFile()) {    // 当前给出的文件路径是一个文件
                //System.out.println(file);
                this.copyFile(file); // 实现拷贝功能的调用
            }
        }
    }
    private void copyFile(File inputFile) throws IOException {
        InputStream input = null ;
        OutputStream output = null ;
        try {
            input = new FileInputStream(inputFile) ; // 字节输入流
            if (this.inFile.isDirectory()) {   // 是一个输入目录
                String outFileName = inputFile.getPath().replace(this.inFile.getPath(), "") ;
                File outputFile = new File(this.outFile, outFileName) ;
                if (!outputFile.getParentFile().exists()) {    // 不存在输出的目录
                    outputFile.getParentFile().mkdirs() ; // 创建相应的输出目录
                }
                output = new FileOutputStream(outputFile) ; // 字节输出流
                input.transferTo(output) ; // 实现拷贝
            } else if (this.inFile.isFile()) {
                output = new FileOutputStream(this.outFile) ; // 字节输出流
                input.transferTo(output) ; // 实现拷贝
            }
        } catch (IOException e) {
            throw e ;
        } finally {
            if (input != null) {
                input.close(); ;
            }
            if (output != null) {
                output.close();
            }
        }
    }
}
public class YootkDemo {    // 李兴华编程训练营:yootk.ke.qq.com
    public static void main(String[] args) throws Exception {
        System.out.println(new CopyUtil(args).copy());
    }
}

字符编码

在实际项目开发过程之中,比较常见的几种编码类型如下:

  • GBK/GB2312:描述中文国标编码,其中 GBK 可以描述简体中文与繁体中文,而 GB2312 仅仅是简体中文;
  • ISO8859-1:国际的通用编码,可以描述任何文字,但是对于一些图形化的文字需要做出转码;
  • UNICODE:是一种十六进制的编码,可以描述世界上的各种文字信息,包括单字节的或者是多字节的,但是有一个问题,并不是所有的文字都需要如此长的编码,例如:英文字母,如果也使用这种方式进行编码所占用的传输带宽就没有意义了;
  • UTF 编码:可以简单的理解为“ISO8859-1+Unicode”(优势结合),在需要十六进制编码的时候就使用十六进制的长度,如果不需要则使用 ISO8859-1 的形式,这种编码更加适合于网络传输,而常用的规格就是“UTF-8”编码。
详情
public class YootkDemo {    // 李兴华编程训练营:yootk.ke.qq.com
    public static void main(String[] args) throws Exception {
        System.getProperties().list(System.out);
    }
}
public class YootkDemo {    // 李兴华编程训练营:yootk.ke.qq.com
    public static void main(String[] args) throws Exception {
        File file = new File("h:" + File.separator + "message.txt");
        OutputStream output = new FileOutputStream(file) ;
        output.write("李兴华编程训练营:yootk.ke.qq.com".getBytes());
        output.close();
    }
}
public class YootkDemo {    // 李兴华编程训练营:yootk.ke.qq.com
    public static void main(String[] args) throws Exception {
        File file = new File("h:" + File.separator + "message.txt");
        OutputStream output = new FileOutputStream(file) ;
        output.write("李兴华编程训练营:yootk.ke.qq.com".getBytes("ISO8859-1"));
        output.close();
    }
}

内存操作流

使用文件流实现数据的操作最大的特点在于,所有的输入和输出的终端的类型全部为文件,但是如果用文件作为输入和输出存储终端就需要产生有一个文件,那么假设现在我不希望产生具体的文件,同时又需要完成这样的输入和输出操作呢?所以在这样的情况下,就可以基于内存的方式实现输入和输出的处理,在 java.io 包中针对于内存的操作流提供有两组:

  • 字节内存操作流:ByteArayInputStream(字节内存输入流)、ByteArayOutputStream(字节内存输出流);

  • 字符内存操作流:CharArayReader(字符内存输入流)、CharArrayWiter(字符内存输出流);

详情
public class Test {
    public static void main(String[] args) throws Exception {
        String message = "www.YOOTK.com"; // 字母有大小写
//        File file = new File("D:\\develop\\project\\wyix\\gitee\\study_service\\java_up\\src\\com\\yix\\stream\\w01\\yix.txt");
        InputStream inputStream = new ByteArrayInputStream(message.getBytes());
        OutputStream outputStream = new ByteArrayOutputStream();
        int data = 0;
        while ((data = inputStream.read()) != -1) {
            outputStream.write(Character.toLowerCase(data));
        }
        System.out.println(outputStream);
        inputStream.close();
        outputStream.close();
    }
}

通过以上的分析已经清楚了内存流的基本操作(不管是文件流还是内存流或者是其他什么流,只要符合 InputStream、OutputStream 操作标准,操作的形式都是类似的),但是现在有必要强调一下为什么会提供有内存流的意义。

因为在以后学习到更高级的课程,就会出现有一个核心的问题:某些输出的过程仅仅是能够在内存中处理的,例如:你们在项目中所使用到的登录验证码这样的图片都是在内存中处理的。同时在早期的 IO 时代(JDK1.9 以前),可以结合内存输出流以及文件输入流实现整个文件数据的全部读取。.

详情
class Test2 {
    public static void main(String[] args) throws Exception {
        File file = new File("D:\\develop\\project\\wyix\\gitee\\study_service\\java_up\\src\\com\\yix\\stream\\w01\\yix.txt");
        if (!file.exists()) {
            file.createNewFile();
        }
        InputStream inputStream = new FileInputStream(file);
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        byte[] data = new byte[10];
        int len = 0;
        while ((len = inputStream.read(data)) != -1) {
            outputStream.write(data, 0, len);
        }
        String content = new String(outputStream.toByteArray());
        System.out.println(content);
        inputStream.close();
        outputStream.close();
    }
}

此时利用内存输出流保存了每一次文件输入流所读取到的数据内容,最后通过“toByteArayO”方法实现了全部读取字节数据 内容的获取。

一定要记住,对于此时的操作一定是存在有隐患的,因为如果要读取的文件的数据量过大,那么这种情况下就有可能造成内存的溢出,但是毕竟这种做法是在 JDK19 之前的做法,而在 JDK1.9 之后实际上对于 InputStream 类中就有了一个新的功能扩充, public byte[] readAllBytes()throws IOException

详情
class Test3 {
    public static void main(String[] args) throws Exception {
        File file = new File("D:\\develop\\project\\wyix\\gitee\\study_service\\java_up\\src\\com\\yix\\stream\\w01\\desk.jpg");
        if (!file.exists()) {
            file.createNewFile();
        }
        InputStream input = new FileInputStream(file) ;
        String content = new String(input.readAllBytes()) ; // 将字节数组转为字符串
        System.out.println(content);
        input.close();
    }
}

管道流

管道最早指的是进程之间的数据通讯模式,因为在计算机的操作系统之中每一个进程都是完全独立的,也就是说每个进程的数据都是由每个进程自己来进行管理的,但是慢慢会发现在多进程操作系统支持下,不同进程之间就有可能需要实现一些通讯的处理,这样才有了管道的定义

Java 本身是属于多线程的编程语言,而多线程和多进程最大的区别在于:一个进程可以产生多个线程,那么这些线程都属于同一个进程中的组成单元,所以这些线程都可以直接共享给定进程里面的所有资源,包括数据,但是在 Java 里面它强调不同的线程有可能也有属于自己的内容,所以不同线程之间也应该利用管道的方式来进行处理,所以为了解决这种管道流的操作,在 java.io 包中提供有两组类:

  • 字节流(PipedOutputStream、PipedIinputStream)、
  • 字符流(PipedReader、PipedWriter),

既然要进行管道的处理大部分的操作还是都以字节数据为主,所以本次来观察字节管道流的使用,首先来观察类的定义:。

详情
class SendThread implements Runnable {
    private PipedOutputStream output = new PipedOutputStream() ;    // 管道输出流
    @Override
    public void run() {
        try {   // 通过管道实现数据的发送
            this.output.write("李兴华编程训练营:yootk.ke.qq.com".getBytes());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    public PipedOutputStream getOutput() {  // 通过子类操作
        return this.output ;
    }
}
class ReceiveThread implements Runnable {
    private PipedInputStream input = new PipedInputStream() ; // 管道输入流
    @Override
    public void run() {
        try {
            byte data [] = new byte [1024] ;
            int len = this.input.read(data) ; // 接收管道的数据
            System.out.println("【接收到消息】" + new String(data, 0, len));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    public PipedInputStream getInput() {
        return this.input ;
    }
}
class YootkDemo {    // 李兴华编程训练营:yootk.ke.qq.com
    public static void main(String[] args) throws Exception {
        SendThread send = new SendThread() ; // 发送线程
        ReceiveThread receive = new ReceiveThread() ; // 接收线程
        send.getOutput().connect(receive.getInput()); // 管道连接
        new Thread(send).start();
        new Thread(receive).start();
    }
}

RandomAccessFile

在操作系统的编程之中,对于早期的文件为了让其可以保存的数据更加有意义,所以会进行一些严格化的数据管理,例如: 现在假设要在文件里面存放有三个数据,并且希望可以随意的实现某一条数据的读取。

所谓的随机读写实际上就相当于在整个文件中设置有一个文件读取的指针,每次读取的时候都会依据指针所在的位置进行指定长度数据的读取,如果要想读取某一条数据,那么只需要修改指针的文字即可,这样的读取操作被称为随机文件读取,而随机文件读取所能够使用的类就是 java.io.RandomAccessFile,首先来观察此类的具体定义: public class RandomAccessFile extends obiect: implements Data0utput,DataInput,Closeable 读和写两种功能,来观察 Randēm 通过 RandomAccessFile 类的定义来看,其实现了 DataOutput、Datalnput 接口,那么也就意味着 RandomAccessFile 类同时具有

详情

public class YootkDemo {    // 李兴华编程训练营:yootk.ke.qq.com
    public static final int MAX_LENGTH = 8 ; // 数据的最大长度为8位
    public static void main(String[] args) throws Exception {
        File file = new File("H:" + File.separator + "muyan" + File.separator + "vip" + File.separator + "yootk.data");
        if (!file.getParentFile().exists()) {
            file.getParentFile().mkdirs() ; // 创建父目录
        }
        RandomAccessFile raf = new RandomAccessFile(file, "rw"); // 读写模式进行输出
        String names [] = new String[] {"zhangsan", "lisi", "wangwu", "zhaoliu", "sunqi"} ;
        int ages[] = new int[]{17, 18, 16, 19, 20};
        for (int x = 0 ; x < names.length ; x ++) {
            String name = addEscape(names[x]) ;
            raf.write(name.getBytes()); // 输出8位的字节
            raf.writeInt(ages[x]); // 输出4位的整数
        }
        raf.close();
    }
    public static String addEscape(String val) {
        StringBuffer buffer = new StringBuffer(val) ;
        while (buffer.length() < MAX_LENGTH) {
            buffer.append(" ") ; // 在内容后追加空格
        }
        return buffer.toString() ;
    }
}

public class YootkDemo {    // 李兴华编程训练营:yootk.ke.qq.com
    public static final int MAX_LENGTH = 8; // 数据的最大长度为8位

    public static void main(String[] args) throws Exception {
        File file = new File("H:" + File.separator + "muyan" + File.separator + "vip" + File.separator + "yootk.data");
        if (file.exists()) {
            RandomAccessFile raf = new RandomAccessFile(file, "r"); // 采用读模式 进行处理
            {   // 读取“lisi”的数据,属于第2条内容
                raf.skipBytes(12) ; // 跨过12个字节
                byte data [] = new byte[MAX_LENGTH] ; // 姓名的长度统一为8位
                raf.read(data) ; // 读取姓名数据到字节数组之中
                int age = raf.readInt() ; // 读取整型
                System.out.printf("【第2条数据】姓名:%s、年龄:%d\n", new String(data).trim(), age);
            }
            {   // 读取“lisi”的数据,属于第1条内容
                raf.seek(0); // 回到指定的索引位置
                byte data [] = new byte[MAX_LENGTH] ; // 姓名的长度统一为8位
                raf.read(data) ; // 读取姓名数据到字节数组之中
                int age = raf.readInt() ; // 读取整型
                System.out.printf("【第1条数据】姓名:%s、年龄:%d\n", new String(data).trim(), age);
            }
            {   // 读取“sunqi”第5条数据
                raf.skipBytes(36) ; // 跨过12个字节
                byte data [] = new byte[MAX_LENGTH] ; // 姓名的长度统一为8位
                raf.read(data) ; // 读取姓名数据到字节数组之中
                int age = raf.readInt() ; // 读取整型
                System.out.printf("【第5条数据】姓名:%s、年龄:%d\n", new String(data).trim(), age);
            }
        }
    }
}

打印流

在 java.io 开发包中如果要想进行程序内容的输出,主要依靠的只有两个类:OutputStream、Witer,但是这两个类中可以发现-个问题:OutputStream 只支持字节数据(字符串也可以转为字节数组)而 Writer 只支持字符串的输出,因为在程序的开发过程之中可以遇见到的数据输出类型是非常多的,例如:假设现在要求你输出 imnt、double、Obiect、Date 等,那么此时就需要重复的进行做出各种的数据类型转换,将所有不是 String 类型的数据全部转为 String 后输出。

详情
class PrintUtil implements AutoCloseable {
    private OutputStream output; // 所有的输出都必须通过OutputStream完成

    public PrintUtil(OutputStream output) { // 由外部决定输出的位置
        this.output = output;
    }

    public void print(String str) {
        try {
            this.output.write(str.getBytes());  // 实现字符串输出
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void println(String str) {
        this.print(str + "\n");
    }

    public void print(int num) {
        this.print(String.valueOf(num));
    }

    public void println(int num) {
        this.println(String.valueOf(num));
    }

    public void print(double num) {
        this.print(String.valueOf(num));
    }

    public void println(double num) {
        this.println(String.valueOf(num));
    }

    public void print(Object obj) {
        this.print(obj.toString());
    }

    public void println(Object obj) {
        this.println(obj.toString());
    }

    @Override
    public void close() throws Exception {
        this.output.close();
    }
}

class YootkDemo1 {    // 李兴华编程训练营:yootk.ke.qq.com
    public static void main(String[] args) throws Exception {
        File file = new File("D:\\develop\\project\\wyix\\gitee\\study_service\\java_up\\src\\com\\yix\\stream\\w01\\util.txt") ;
        PrintUtil pu = new PrintUtil(new FileOutputStream(file)) ; // 向文件中输出
        pu.print("姓名:");
        pu.println("李兴华");
        pu.println("年龄:美丽的花季少男 - 16岁") ;
        pu.close();
    }
}
class YootkDemo2 {
    public static void main(String[] args) throws Exception {
        File file = new File("D:\\develop\\project\\wyix\\gitee\\study_service\\java_up\\src\\com\\yix\\stream\\w01\\util.txt") ;
        PrintWriter pu = new PrintWriter(new FileOutputStream(file)) ; // 向文件中输出
        pu.print("姓名:");
        pu.println("李兴华");
        pu.println("年龄:美丽的花季少男 - 16岁") ;
        pu.close();
    }
}
class YootkDemo3 {
    public static void main(String[] args) throws Exception {
        File file = new File("D:\\develop\\project\\wyix\\gitee\\study_service\\java_up\\src\\com\\yix\\stream\\w01\\util.txt") ;
        PrintWriter pu = new PrintWriter(new FileOutputStream(file)) ; // 向文件中输出
        String name = "李兴华" ;
        int age = 18 ;
        double score = 98.531982 ;
        pu.printf("姓名:%s、年龄:%d、成绩:%5.2f", name, age, score) ;
        pu.close();
    }
}

System 对 IO 的支持

小小提示:按照 Java 的开发原则来讲所有的常量必须采用字母大写的形式出现,但是以上的字母全部都是小写,因为早期的设计 是没有这种现代常量命名规范约定的,而是与变量采用了同样的命名习惯。。

提示:在实际的项目开发过程之中,如果现在需要对代码进行调试,这个时候可以把一些重要的操作数据通过 System.enr 实现输 出,这样可以便于在一堆的输出信息中方便定位要的数据内容。 System.out 和 System.er 本身都属于 PrintSream 的对象实例(该对象的实例是由 JVM 负责实例化的),所以这两个类的对象都 可以直接向 OutputStream 类进行转型。

详情
public class YootkDemo {    // 李兴华编程训练营:yootk.ke.qq.com
    public static void main(String[] args) throws Exception {
        try {
            Integer.parseInt("yootk"); // 会出错
        } catch (Exception e) {
            System.out.println("【信息输出】" + e);
            System.err.println("【错误输出】" + e);
        }
    }
}


public class YootkDemo {    // 李兴华编程训练营:yootk.ke.qq.com
    public static void main(String[] args) throws Exception {
        OutputStream output = System.out ; // 向屏幕打印
        output.write("www.yootk.com".getBytes());
    }
}

此时的 OutputStream 中的 wite0 方法会根据实例化其子类的不同而带来不同的输出目的地,所以这就属于最为明显的对象多态性所带来的操作特点。 在 System 类中另外一个 in 的常量描述的是通过标准键盘输入设备实现内容的输出处理,那么现在就可以通过这个常量来实现键盘数据的输入控制。.

详情
class YootkDemo4 {
    public static void main(String[] args) throws Exception {
        InputStream keyboardInput = System.in; // 键盘数据输入
        System.out.print("请输入要发送的数据信息:");
        byte data[] = new byte[110];// InputStream是与字节数组结合操作的
        int len = keyboardInput.read(data); // 将要输入的数据保存在字节数组之中
        System.out.println("【输入数据回显】" + new String(data, 0, len));
    }
}

此时己经成功的实现了键盘数据的输入处理,但是对于当前的数据输入来讲会存在有一个问题,因为 InputStream 如果要想实现数据的读取,那么一定需要依靠的是字节数组来完成,但是如果说用户要输入的内容比较长,那么这个时候就比较难处理,所以 这种键盘输入仅仅是 Java 数据输入的基本形式。

BufferedReader 缓冲输入流

在实际项目开发之中,如果直接使用 InputStream 或者是 Reader 类实现内容读取的时候都必须将其所有要读的数据保存在数组之中才可以正常实现功能,但是这样的处理操作实在是过于繁琐,所以此时可以考虑将所有读取到的内容暂时放到一个缓冲区之中,这样在需要的时候一次性读取全部的内容,这样就避免了频繁的数组的操作,所以就有一个专属的类:BuferedReader,下面来观察这个类的定义以及对应的构造方法:

详情
public class YootkDemo {    // 李兴华编程训练营:yootk.ke.qq.com
    public static void main(String[] args) throws Exception {
        BufferedReader keyboard = new BufferedReader(new InputStreamReader(System.in)) ;
        System.out.print("请输入你要发送的信息:"); // 信息发送完毕一定要输入回车
        String str = keyboard.readLine() ; // 以回车为分隔符
        System.out.println("【数据回显】" + str);
    }
}

这个时候所实现的键盘数据输入方式明显要比之前直接使用 System.in 更加的容易了,从综合的设计角度来讲,此时就是 Java 最为传统的键盘数据输入,但是需要提醒的是,对于此时的 BuferedReader 类来讲除了可以实现键盘数据输入之外,也可以实现文本数据的读取。

详情
public class YootkDemo {    // 李兴华编程训练营:yootk.ke.qq.com
    public static void main(String[] args) throws Exception {
        BufferedReader input = new BufferedReader(new FileReader(new File("h:" + File.separator + "message.txt"))) ;
        String data = null ; // 保存每一行读取到的数据
        while ((data = input.readLine()) != null) {
            System.out.println(data);
        }
    }
}

Scanner 输入流

在之前的分析中可以发现如果直接使用 InputStream 类实现数据的输入会非常的麻烦了,因为都需要通过字节的形式来进行处理,而如果通过 BuferedReader 类进行输入流的控制的话,虽然方便,但是其却只能够使用“m”作为读取的分隔符,但是在 JDK1.5 之后为了进一步简化数据输入的处理操作的难度,所以提供有一个 iava.util.Scanner 的工具类,这个类可以实现最为简化读取,首先来观察一下这个类的定义:.

详情
1package com.yootk.demo;

import java.util.Scanner;

public class YootkDemo {    // 李兴华编程训练营:yootk.ke.qq.com
    public static void main(String[] args) throws Exception {
        Scanner scanner = new Scanner(System.in) ;
        System.out.print("请输入要发送的信息:");
        if (scanner.hasNext()) {    // 有数据输入
            String value = scanner.next() ; // 获取数据内容
            System.out.println("回显输入数据:" + value);
        }
    }
}


2package com.yootk.demo;

import java.util.Scanner;

public class YootkDemo {    // 李兴华编程训练营:yootk.ke.qq.com
    public static void main(String[] args) throws Exception {
        Scanner scanner = new Scanner(System.in) ;
        System.out.print("请输入您的年龄:");
        if (scanner.hasNextInt()) {    // 有整数输入
            int value = scanner.nextInt() ; // 获取数据内容
            System.out.println("回显输入数据:" + value);
        } else {
            System.err.println("【ERROR】输入的内容不是数字。");
        }
    }
}


3package com.yootk.demo;

import java.util.Scanner;

public class YootkDemo {    // 李兴华编程训练营:yootk.ke.qq.com
    public static void main(String[] args) throws Exception {
        Scanner scanner = new Scanner(System.in) ;
        System.out.print("请输入注册邮箱:");
        if (scanner.hasNext("\\w+@\\w+\\.\\w+")) {    // 有符合指定正则规范的信息输入
            String value = scanner.next("\\w+@\\w+\\.\\w+") ; // 获取数据内容
            System.out.println("回显输入数据:" + value);
        } else {
            System.err.println("【ERROR】输入数据的格式不正确。");
        }
    }
}


4package com.yootk.demo;

import java.io.File;
import java.util.Scanner;

public class YootkDemo {    // 李兴华编程训练营:yootk.ke.qq.com
    public static void main(String[] args) throws Exception {
        Scanner scanner = new Scanner(new File("H:" + File.separator + "message.txt")) ;
        scanner.useDelimiter("\n") ; // 使用换行作为读取分隔符
        while (scanner.hasNext()) { // 只要存在有数据行,那么就进行内容的获取
            String value = scanner.next() ; // 接收数据内容
            System.out.println(value);
        }
    }
}


此时对于以后的数据的读取操作如果发现使用 InputStream 过于繁琐了,那么最佳的做法就是通过 Scanner 类来实现整个数据的读取的操作功能,但是如果从实际的项目开发来说的,有些时候 Scanner 可能就没有这么好用了,因为在使用 Scanner 处理的时候如果现在发现没有数据内容,或者是数据内容不匹配,则就有可能造成数据输入繁琐,所以在不方便使用 Scanner 类进行数据输入的时候强烈推荐使用 BuferedReader 类实现处理。这个时候就可以给出一个重要的结论: 如果要通过程序实现数据的输出,那么一定要选择使用 PrintWriter/PrintStream; 如果程序要实现数据内容的输入,那么最新的做法使用的是:java.util.Scanner,而如果 Scanner 不好用的时候,建议使用 BufferedReader 类来实现数据读取。

对象序列化

定义

所谓的序列化指的是可以将内存之中的实例化对象内容取出,并且将其直接转换为二进制的数据流,在转换完成之后,其他的程序就可以通过这个二进制的数据内容直接进行该对象的还原处理,但是在 Java 之中并不是所有类的对象都拥有这种二进制转换的功能,所以为了明确的对可以实现二进制转换的类进行标注,在 java.io 包中提供有一个 Serializable 接口,但是需要注意的是 Serializable 接口并没有提供任何的方法供用户使用,因为它只是属于一种标识性的接口,表示一种处理的能力,这种接口的作用和在之前讲解过的 Cloneable 是相同的。

在序列化处理定义之中,为了保证反序列化操作的正确性,一般会在类中去定义有一个序列化版本编号,这个版本编号的常量的名称是固定好的,内容各位“serialVersionUID”,并且需要手工的为其分配一个不会重复的内容,但是随着技术的发展,对于程序中是否要编写“serialVersionUD”版本编号已经没有太多要求了,因为会自动的为其分配一个指定的序列化版本编号。

详情
/*
 * 序列化类定义
 * */
class Book implements Serializable {
    private String title;
    private String author;
    private double price;

    public Book(String name, String author, double price) {
        this.title = name;
        this.author = author;
        this.price = price;
    }

    public String toString() {
        return "【图书】名称:" + this.title + "、作者:" + this.author + "、价格:" + this.price;
    }
}

序列化和反序列化

虽然现在 Book 类实现了序列化的标记接口,但是对于具体的序列化的操作的形式是由 JDK 来决定的,因为序列化之后的数据属于二进制的内容,而对于二进制的数据必须采用正确的方式才可以实现数据的内容的读取(反序列化),为了正确的实现序列化与反序列化的操作,在 iava.io 包中就提供有专属的两个类:ObiectOutputStream、ObiectInputStream。

详情
class YootkDemo {
    private static final File BINARY_FILE = new File("D:\\develop\\project\\wyix\\gitee\\study_service\\java_up\\src\\com\\yix\\stream\\w02\\book.ser");

    public static void main(String[] args) throws Exception {
        serial(new Book("Java从入门到项目实战", "李兴华", 99.8));
        System.out.println(deSerial());
        ;
    }

    public static void serial(Object object) throws Exception {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(BINARY_FILE, true)); // 文件序列化
        oos.writeObject(object); // 序列化输出
        oos.close();
    }

    public static Object deSerial() throws Exception {
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(BINARY_FILE));
        Object data = ois.readObject();
        ois.close();
        return data;
    }
}

transient

通过之前的程序的代码可以清楚的发现,在进行序列化处理的时候实际上是将一个类对象中的所有属性内容实现了二进制的数据存储,但是如果说现在某些属性不希望被序列化下来,那么这个时候就可以考虑在这个属性上使用 transient 关键字进行定义

提示

提示:现在假设有一个描述订单的类,这个订单的类中对于商品的单价、名称以及数量都要求进行序列化保存,而对于商品的总价(商品单价*商品的数量)就没有被序列化的意义所在,所以才有了不希望被序列化属性。

实际上在 Java 里面对于序列化的处理有两种机制:全自动化序列化管理(现在所讲解的就属于全自动化),另外一种是手工实现序列化的管理,如果要是在手工实现的序列化操作中,就可以自己来决定那些属性不要被序列化,但是这样的开发难度比较高,所以才针对于自动序列化的处理支持提供了 tansient 关键字。

//当前属性数据不会序列化
private transient double price;

案例

输入数据比较大小

编写 Java 程序,输入 3 个整数,并求出 3 个整数的最大值和最小值。

  • 首先既然要进行键盘数据的输入,则可以通过 Scanner 或 BuneredReader 两个类来完成,如果以键盘数据输入为例 BufferedReader 要更加的方便,所以首先应该创建有一个键盘数据的输入类。
  • 所有的程序设计都应该按照面向对象的设计要求进行处理,那么既然是面向对象则一定要有相关的程序处理类,所以此时应该首先创建有一个数据处理的功能业务接口,而后再设计它的子类
详情
package com.yix.stream.w03;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

class InputUtil { //键盘输入工具类
    // 实例化一个BufferedReader类对象实例,通过键盘实现数据内容的输入控制
    private static final BufferedReader KEYBOARD_INPUT = new BufferedReader(new InputStreamReader(System.in));

    private InputUtil() {
    } // 此类不提供对象实例化支持

    /**
     * 实现字符串数据的输入
     *
     * @param prompt 提示信息文字
     * @return 返回字符串内容
     */
    public static String getString(String prompt) {
        System.out.println(prompt);
        try {
            String value = KEYBOARD_INPUT.readLine();
            return value;
        } catch (IOException e) {
            return null;
        }
    }

    /**
     * 通过键盘实现整型数据的获取,键盘输入的所有内容都是字符串,所以一定要转型
     *
     * @param prompt 提示的信息文字
     * @return 返回一个可以使用的int数值
     */
    public static int getInt(String prompt) {
        while (true) {   // 死循环
            String value = getString(prompt);
            if (value != null) {
                if (value.matches("\\d+")) {    // 是一个整型
                    return Integer.parseInt(value);
                } else {    // 输入的字符串不是一个整型,那么应该重新进行输入
                    System.err.println("【错误】输入的数据信息必须为整型,请重新进行内容的输入!");
                }
            }
        }
    }
}

interface INumberService {// 数字的业务接口

    public int max();  // 获取最大值

    public int min();  // 获取最小值
}
class ArrayLengthInvalidateException extends RuntimeException {
    public ArrayLengthInvalidateException(String msg) {
        super(msg) ;
    }
}
class NumberServiceImpl implements INumberService {
    private static final int DEFAULT_LENGTH = 3 ; // 设置默认大小
    private int data [] ; // 定义数组保存若干个输入数据
    private int max_value ; // 保存最大值
    private int min_value ; // 保存最小值
    public NumberServiceImpl() {    // 默认大小
        this(DEFAULT_LENGTH) ; // 设置默认的数组长度
    }
    public NumberServiceImpl(int len) { // 传入一个外部的大小
        if (len <= 0) { // 长度存在有问题
            throw new ArrayLengthInvalidateException("要开辟的数据长度错误!") ;
        } else {
            this.data = new int[len]; // 开辟新的数组
        }
        this.inputData();
        this.handleData(); // 处理数据内容
    }
    public void inputData() {  // 实现键盘数据的输入处理
        for (int x = 0 ; x < this.data.length ; x ++) {
            this.data[x] = InputUtil.getInt("请输入第" + (x + 1) + "个数据:") ;
        }
    }
    private void handleData() { // 数据处理操作,可以实现大小的定义
        this.max_value = this.data[0] ; // 假设第一个数据为最大值
        this.min_value = this.data[0] ; // 假设第一个数据为最小值
        for (int temp : this.data) {    // 数组迭代
            if (max_value < temp) { // 不是最大值
                max_value = temp ; // 修改“max_value”的内容
            }
            if (min_value > temp) { // 不是最小值
                min_value = temp ; // 修改“min_value”的内容
            }
        }
    }

    @Override
    public int max() {
        return this.max_value;
    }

    @Override
    public int min() {
        return this.min_value;
    }
}
class ServiceFactory {
    private ServiceFactory() {}
    public static INumberService getNumberServiceInstance(int ... args) {
        if (args.length == 1) {
            return new NumberServiceImpl(args[0]) ;
        }
        return new NumberServiceImpl() ;
    }
}
public class Demo {
    public static void main(String[] args) {
        int len = InputUtil.getInt("请输入比较数据个数:");
        INumberService numberService = ServiceFactory.getNumberServiceInstance(len) ; // 获取接口对象实例
        System.out.println("【输入数据最大值】" + numberService.max());
        System.out.println("【输入数据最小值】" + numberService.min());
    }
}

输入数据排序

从键盘输入以下的数据:“TOM:89|JERRY:90|TONY:95”,数据格式为“姓名:成绩|姓名:成绩|姓名:成绩”,对输入的 内容按照成绩由高到低排序。

详情
package com.yix.stream.w04;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.Arrays;

/**
 * @author wangdx
 */
class InputUtil {
    private static final BufferedReader KEYBOARD_INPUT = new BufferedReader(new InputStreamReader(System.in));

    private InputUtil() {
    }

    /**
     * 实现字符串数据的输入
     *
     * @param prompt 提示信息文字
     * @return 返回字符串内容
     */
    public static String getString(String prompt) {
        System.out.print(prompt + " ");
        try {
            return KEYBOARD_INPUT.readLine();
        } catch (Exception e) {
            return null;
        }
    }

    /**
     * 追加具备正则格式验证的字符串输入处理操作
     *
     * @param prompt 提示信息
     * @param regex  验证的正则表达式
     * @return 符合正则需求的字符串
     */
    public static String getString(String prompt, String regex) {
        while (true) {
            String value = getString(prompt);
            if (value.matches(regex)) { // 符合正则需要
                return value;
            } else {
                System.err.println("【错误】输入的字符串内容不符合格式要求,请重新进行合法的内容输入!");
            }
        }
    }

    /**
     * 通过键盘实现整型数据的获取,键盘输入的所有内容都是字符串,所以一定要转型
     *
     * @param prompt 提示的信息文字
     * @return 返回一个可以使用的int数值
     */
    public static int getInt(String prompt) {
        while (true) {
            String value = getString(prompt);
            if (value != null) {
                if (value.matches("\\d+")) {
                    return Integer.parseInt(value);
                } else {
                    System.err.println("【错误】输入的数据信息必须为整型,请重新进行内容的输入!");
                }
            }
        }
    }
}

class Student implements Comparable<Student> {
    private String name;
    private double score;

    public Student() {
    }

    public Student(String name, double score) {
        this.name = name;
        this.score = score;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public double getScore() {
        return score;
    }

    public void setScore(double score) {
        this.score = score;
    }

    @Override
    public int compareTo(Student o) {
        if (this.score > o.score) {
            return 1;
        } else if (this.score < o.score) {
            return -1;
        } else {
            return 0;
        }
    }

    @Override
    public String toString() {
        return "【Student】姓名:" + this.name + "、成绩:" + this.score;
    }
}

interface IStudentService {
    public Student[] getStudents();
}

class StudentServiceImpl implements IStudentService {
    private static final String INPUT_REGEX = "([a-zA-Z]+:\\d+(\\.\\d+)?\\|?)+";
    private Student students[];

    public StudentServiceImpl() {
        this.input(); // 实现数据的输入
    }

    public void input() {   // 处理数据的输入操作
        String value = InputUtil.getString("请输入字符串(Tom:98.2|Jerry:99.6|Tony:68.9):", INPUT_REGEX);
        String result[] = value.split("\\|");
        this.students = new Student[result.length]; // 根据拆分的结果开辟数组空间
        for (int x = 0; x < result.length; x++) {  // 正则拆分
            String temp[] = result[x].split(":"); // 继续拆分子信息
            this.students[x] = new Student(temp[0], Double.parseDouble(temp[1]));
        }
        Arrays.sort(this.students); // 对象数组排序
    }

    @Override
    public Student[] getStudents() {
        return this.students;
    }
}

class ServiceFactory {
    private ServiceFactory() {
    }

    public static IStudentService getStudentService() {
        return new StudentServiceImpl();
    }
}

public class SortTest {
    public static void main(String[] args) {
        IStudentService studentService = ServiceFactory.getStudentService() ;
        System.out.println(Arrays.toString(studentService.getStudents()));
    }
}

用户登录认证

完成系统登录程序,可以通过初始化参数方式配置用户名和密码,如果没有输入用户名和密码,则提示输入用户名和密码:如果输入了用户名但是没有输入密码,则提示用户输入密码,然后判断用户名是否是 yootk,密码是否是 muyan,如果正确,则提示登录成功;如果错误,显示登录失败的信息,用户再次输入用户名和密码,连续 3 次输入错误后系统退出。

详情
package com.yix.stream.w05;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

/**
 * @author wangdx
 */

class InputUtil {
    private static final BufferedReader KEYBOARD_INPUT = new BufferedReader(new InputStreamReader(System.in));

    private InputUtil() {
    } // 此类不提供对象实例化支持

    /**
     * 实现字符串数据的输入
     *
     * @param prompt 提示信息文字
     * @return 返回字符串内容
     */
    public static String getString(String prompt) {
        System.out.print(prompt);
        try {
            String value = KEYBOARD_INPUT.readLine();
            return value;
        } catch (IOException e) {
            return null;
        }
    }

    /**
     * 追加具备正则格式验证的字符串输入处理操作
     *
     * @param prompt 提示信息
     * @param regex  验证的正则表达式
     * @return 符合正则需求的字符串
     */
    public static String getString(String prompt, String regex) {
        while (true) {
            String value = getString(prompt); // 获取数据信息
            if (value.matches(regex)) { // 符合正则需要
                return value;
            } else {
                System.err.println("【错误】输入的字符串内容不符合格式要求,请重新进行合法的内容输入!");
            }
        }
    }

    /**
     * 通过键盘实现整型数据的获取,键盘输入的所有内容都是字符串,所以一定要转型
     *
     * @param prompt 提示的信息文字
     * @return 返回一个可以使用的int数值
     */
    public static int getInt(String prompt) {
        while (true) {   // 死循环
            String value = getString(prompt);
            if (value != null) {
                if (value.matches("\\d+")) {    // 是一个整型
                    return Integer.parseInt(value);
                } else {    // 输入的字符串不是一个整型,那么应该重新进行输入
                    System.err.println("【错误】输入的数据信息必须为整型,请重新进行内容的输入!");
                }
            }
        }
    }
}

class MaxTryCountException extends RuntimeException {
    public MaxTryCountException(String msg) {
        super(msg);
    }
}

class LoginMain {
    private String args[]; // 外部的接收参数
    private String userName; // 用户名
    private String userPassword; // 密码
    private int count; // 尝试次数

    public LoginMain(String args[]) {
        this.args = args;
        while (true) {
            if (this.count >= 3) {   // 超过了3次测试
                throw new MaxTryCountException("用户登录尝试次数过多,为了安全起见,系统退出!");
            }
            this.handle(); // 此时可以直接获取用户名和密码的信息
            ILoginService loginService = ServiceFactory.getLoginServiceInstance(this.userName, this.userPassword);
            if (loginService.login()) { // 登录验证
                System.out.println("【成功】欢迎“YOOTK”用户登录,请访问:www.yootk.com。");
                break; // 终止外部的循环
            } else {    // 登录失败
                System.out.println("【失败】错误的用户名或密码,请确认你的登录信息。");
                count++; // 次数追加
            }
        }
    }

    public void handle() {  // 通过给定的参数来获取用户名和密码的信息
        if (this.args.length == 0) { // 此时还没有输入任何的用户名和密码
            this.userName = InputUtil.getString("请输入登录用户名:");
            this.userPassword = InputUtil.getString("请输入登录密码:");
        } else if (this.args.length == 1) { // 只有用户名
            this.userName = this.args[0];
            this.userPassword = InputUtil.getString("请输入登录密码:");
        } else if (this.args.length == 2) { // 信息全部包含
            this.userName = this.args[0];  // 设置的用户名
            this.userPassword = this.args[1];  // 设置的密码
        }
    }
}

interface ILoginService {
    public boolean login();
}

class LoginServiceImpl implements ILoginService {
    private String uname;
    private String upass;

    public LoginServiceImpl(String uname, String upass) {
        this.uname = uname;
        this.upass = upass;
    }

    @Override
    public boolean login() {
        return "yootk".equals(this.uname) && "muyan".equals(this.upass);
    }
}

class ServiceFactory {
    private ServiceFactory() {
    }

    public static ILoginService getLoginServiceInstance(String uname, String upass) {
        return new LoginServiceImpl(uname, upass);
    }
}

public class UserLogin {
    public static void main(String[] args) {
        new LoginMain(args) ;
    }
}

投票选举班长

有一个班采用民主投票方法推选班长,班长候选人共 4 位,每个人姓名及代号分别为“张三 1;李四 2;五五 3;赵六 4”。程序操作员将每张选票上所填的代号(1、2、3 或 4)循环输入电脑,输入数字 0 结束输入,然后将所有候选人的得票情况显示出来,并显示最终当选者的信息,具体要求如下:

  • ① 要求用面向对象方法,编写学生类 Suden!,将候选人姓名、代号和票数保存到类 Smudent 中,并实现相应的 getXXX 和 setXXX 方法。.
  • ② 输入数据前,显示出各位候选人的代号及姓名(提示,建立一个候选人类型数组)。
  • ③ 循环执行接收键盘输入的班长候选人代号,直到输入的数字为 0,结束选票的输入工作。。
  • ④ 在接收每次输入的选票后要求验证该选票是否有效,即如果输入的数不是 0、1、2、3、4 这 5 个数字之一,或者输入的是一串字母,应显示出错误提示信息“此选票无效,请输入正确的候选人代号!”,并继续等待输入。。
  • ⑤ 输入结束后显示所有候选人的得票情况,如参考样例所示。
  • ⑥ 输出最终当选者的相关信息,如参考样例所示。

提示

  • 1:张三【0 票】
  • 2:李四【0 票】
  • 3:王五【0 票】
  • 4:赵六【0 票】
  • 请输入班长候选人代号(数字 0 结束):1
  • 请输入班长候选人代号(数字 0 结束):1
  • 请输入班长候选人代号(数字 0 结束):1
  • 请输入班长候选人代号(数字 0 结束):2
  • 请输入班长候选人代号(数字 0 结束):3 - 请输入班长候选人代号(数字 0 结束):4 - 请输入班长候选人代号(数字 0 结束):5 - 此选票无效,请输入正确的候选人代号!
  • 请输入班长候选人代号(数字 0 结束):hello
  • 此选票无效,请输入正确的候选人代号!
  • 请输入班长候选人代号(数字 0 结束):0
    • 1:张三【4 票】
    • 2:李四【1 票】
    • 3:王五【1 票】
    • 4:赵六【1 票】
  • 投票最终结果:张三同学,最后以 4 票当选班长!
详情
package com.yix.stream.w06;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.Arrays;

/**
 * @author wangdx
 */
class InputUtil {
    private static final BufferedReader KEYBOARD_INPUT = new BufferedReader(new InputStreamReader(System.in));

    private InputUtil() {
    }

    public static String getString(String prompt) {
        System.out.print(prompt + " ");
        try {
            return KEYBOARD_INPUT.readLine();
        } catch (Exception e) {
            return null;
        }
    }

    public static String getString(String prompt, String regex) {
        while (true) {
            String value = getString(prompt);
            if (value.matches(regex)) { // 符合正则需要
                return value;
            } else {
                System.err.println("【错误】输入的字符串内容不符合格式要求,请重新进行合法的内容输入!");
            }
        }
    }

    public static int getInt(String prompt) {
        while (true) {
            String value = getString(prompt);
            if (value != null) {
                if (value.matches("\\d+")) {
                    return Integer.parseInt(value);
                } else {
                    System.err.println("【错误】输入的数据信息必须为整型,请重新进行内容的输入!");
                }
            }
        }
    }
    /**
     * 通过键盘实现整型数据的获取,键盘输入的所有内容都是字符串,所以一定要转型
     * @param prompt 提示的信息文字
     * @param error 错误信息
     * @return 返回一个可以使用的int数值
     */
    public static int getInt(String prompt, String error) {
        while(true) {   // 死循环
            String value = getString(prompt);
            if (value != null) {
                if (value.matches("\\d+")) {    // 是一个整型
                    return Integer.parseInt(value);
                } else {    // 输入的字符串不是一个整型,那么应该重新进行输入
                    System.err.println(error);
                }
            }
        }
    }
}
class Student implements Comparable<Student> {
    private long id ;
    private String name ;
    private int count ;
    public Student(long id, String name, int count) {
        this.id = id ;
        this.name = name ;
        this.count = count ;
    }

    public void setCount(int count) {
        this.count = count;
    }

    public int getCount() {
        return count;
    }

    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String toString() {
        return "【候选人】编号:" + this.id + "、姓名:" + this.name + "、票数:" + this.count ;
    }
    @Override
    public int compareTo(Student o) {
        return o.count - this.count ;
    }
}
interface IVoteService {
    public void start() ; // 投票开始
    public Student[] show() ; // 显示投票信息
    public Student result() ; // 获取投票的结果
}
class VoteServiceImpl implements IVoteService {
    private Student[] students = new Student[] {
            new Student(1, "张三", 0) ,
            new Student(2, "李四", 0) ,
            new Student(3, "王五", 0) ,
            new Student(4, "赵六", 0)} ;
    @Override
    public void start() {
        boolean flag = true ;
        while(flag) {
            int choose = InputUtil.getInt("请输入班长候选人代号(数字0结束):", "此选票无效,请输入正确的候选人代号!") ;
            switch(choose) {
                case 0:
                    flag = false ; // 结束选票的控制
                    break;
                case 1:
                    this.students[0].setCount(this.students[0].getCount() + 1);
                    break;
                case 2:
                    this.students[1].setCount(this.students[1].getCount() + 1);
                    break;
                case 3:
                    this.students[2].setCount(this.students[2].getCount() + 1);
                    break;
                case 4:
                    this.students[3].setCount(this.students[3].getCount() + 1);
                    break;
                default:
                    System.out.println("此选票无效,请输入正确的候选人代号!");
            }
        }
    }
    @Override
    public Student[] show() {
        return this.students ;
    }
    @Override
    public Student result() {
        Arrays.sort(this.students); // 进行结果排序
        return this.students[0]; // 倒序排序处理后的结果取第一个
    }
}

class ServiceFactory {
    private ServiceFactory() {}
    public static IVoteService getVoteService() {
        return new VoteServiceImpl() ;
    }
}

class VoteMain {
    private IVoteService voteService ;
    public VoteMain() {
        this.voteService = ServiceFactory.getVoteService() ;
        this.printStudentInfo(this.voteService.show());
        System.out.println("========================== 开始进行班长选举 ==========================");
        this.voteService.start();
        System.out.println("========================== 班长选举投票结束 ==========================");
        this.printStudentInfo(this.voteService.show()); // 输出得票结果
        System.out.println("========================== 得到班长投票结果 ==========================");
        Student result = this.voteService.result() ;
        System.out.println("投票最终结果:" + result.getName() + "同学,最后以" + result.getCount() + "票当选班长!");
    }
    private void printStudentInfo(Student [] students) {
        for (Student student : students) {
            System.out.println(student);
        }
    }
}

public class StudentTest {
    public static void main(String[] args) throws Exception {
        new VoteMain() ;
    }
}
上次编辑于: