java第一阶段(day13)IO

问题: 程序运行期间 数据不能"持久化"保存。 保证不会丢失数据。 如何能够实现数据持久化? 磁盘----> 文件夹(目录) 文件(维护数据) java.io.*

1. File(方法)

方法

作用

String getName()

获得文件名称

String getPath()

获得相对路径

String getAbstroutePath()

获得绝对路径

long length()

获得文件字节大小

String[]  list()

获得目录下面的一级资源(资源名称)

File[]  listFiles()

获得目录下面的一级资源

createNewFile()

创建文件

mkdir()/mkdis()

创建目录

boolean  exits()

是否存在

isDirectory()/isFile()

代表磁盘里面 存在或者不存在的  目录或者文件。

static String pathSeparator   系统的环境变量的配置相关的数据  
static char pathSeparatorChar  在windows:  ;   在linux:  :


"D:\Program Files\JetBrains"
static String separator  路径的盘符分隔符  window: \   在linux: /
static char separatorChar

File(String pathname)  根据指定的文件/目录的路径创建File对象
File(String parent, String child) 
    parent: 父级目录路径 
    child: 子级目录/子级文件路径
    
File(File parent, String child)

1.1 代表文件

//File类代表文件
private static void demo1() {

    //1.创建File对象---->磁盘里面文件
    File file = new File("E:\\test\\a.txt");
    //2.获得File对象的一些基本的属性的信息
    System.out.println("文件名称:" + file.getName());
    //路径: 相对路径  + 绝对路径
    //绝对路径: E:\test\a.txt   http://www.baidu.com   http://127.0.0.1:8080/demo/a.html
    //相对路径:  a/b/c/a.txt  目前项目/相对于项目部署服务器路径
    System.out.println("文件绝对路径:" + file.getAbsolutePath());
    System.out.println("文件相对路径:" + file.getPath());
    System.out.println("文件大小:" + file.length());//文件内容大小  字节
    System.out.println("上一次操作文件的时间:" + file.lastModified());//时间的毫秒数
    System.out.println("上一次操作文件的时间:" + new Date(file.lastModified()));//时间的毫秒数 转换成  年月日的时间数据 Date

    //3.对File进行一些判断
    System.out.println(file.exists());//判断文件是否存在
    System.out.println(file.isFile());//判断file对象是否是文件

    //4.操作文件的权限
    System.out.println(file.setReadOnly());//设置文件只读

    //5.删除文件
    System.out.println(file.delete());//删除磁盘文件

}

创建文件

private static void demo2() {
    //都用绝对路径
    //文件不存在  创建文件
    //src\a.txt
    File file = new File("src/com/javasm/io/b.txt");
    try {
        if (!file.exists()) {
            System.out.println(file.createNewFile());
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
}

1.2 代表目录

//File代表是目录
private static void demo3() {
    //1.创建目录对象
    //File directory = new File("E:\\test\\a\\b");
    File directory = new File("E:\\test\\a", "b");
    System.out.println(directory.getName());
    System.out.println(directory.getAbsolutePath());
    System.out.println(directory.getPath());

    System.out.println(directory.isHidden());
    System.out.println(directory.exists());
    System.out.println(directory.isDirectory());

}

private static void demo5() {
    File file = new File("src"+File.separator+"demo.txt");
    //对指定的文件改名
    //路径不一致: 剪切+重命名
    System.out.println(file.renameTo(new File("src/demo/a/hello.txt")));
}

private static void demo4() {
    File directory = new File("src", "demo/a/b");
    //创建目录
    //directory.mkdir() 创建1级目录
    /*if (!directory.exists()) {
            System.out.println(directory.mkdirs());//创建>1级目录
        }*/

    //删除目录
    System.out.println(directory.delete());//删除文件  删除目录(里面没有资源)

}

1.3 查找子级资源

展示指定父级目录下的所有的子级的资源。

//File类里面的方法 : 获得指定父级目录下的一级资源
String[] list = directory.list();//获得指定目录下的所有的子级资源名称(文件名称/目录名称)
System.out.println(Arrays.toString(list));
File[] files = directory.listFiles();//获得指定目录下的所有的子级资源对象(文件对象/目录对象)
System.out.println(Arrays.toString(files));

2. IO

File类只能操作文件或者目录一些属性。无法操作文件里面的数据。

场景:  上传文件/下载文件   数据导入导出....

操作文件里面数据:
  1. 读取文件里面的数据  read
  2. 将数据写入文件中  write
  
  
  
  IO:  实现对文件数据“读写”功能。
  
  
  I: input  Stream  输入流
  O: output  Stream 输出流
  
 分析:
    程序/代码   数据    磁盘文件
    使用程序将数据写入磁盘 ----> OutputStream  write
    使用程序读取磁盘数据----->InputStream  read

流
从流向划分: 输入和输出

计算机里面 存储的文件都属于2进制文件  里面存储内容都是以byte存储的。
一个汉字:  不同的编码格式下  占据的字节不太一样的。

从操作内容划分:
   1. 字节流: 字节输入流   字节输出流----> 字节---->可以操作计算机里面的任意类型的文件
   2. 字符流: 字符输入流   字符输出流 -----> 字符---->只能操作纯文本文件。
   
   文本文件:
       纯文本文件:txt  java  ini
       带样式的文本文件: doc ppt   poi

3. 字节流

public abstract class InputStream
extends Object
implements Closeable  
//类实现Closeable  证明这个类是一个资源(物理资源)  
//释放资源------> finally  close()

3.1 字节输入流 InputStream

读取文件数据。   read

常用的功能方法:

 int read()  一次读取一个字节     返回值: 读取的一个字节的内容   -1(读到文件末尾)
     
 int read(byte[] b) 一次读取指定b.length个字节  内容存储到了字节数组中了 
   返回值: 读取到的有效的字节数量  -1 
 
 int read(byte[] b, int off, int len)  
       一次读取b.length个字节内容  
       off: 从数组的指定索引开始存储
       len: 存储len个字节数据  (数组里面存储多少个与len有关系)
       返回值: len的数据  或者-1    
    
 int available()  获得流里面的有效的字节数量  等价与 File.length()
    
 void close()   释放资源

FileInputStream(String name) 
FileInputStream(File file) 
    使用特定的文件对象/文件路径创建字节输入流对象

1. read()

 private static void demo1() {
        //读取指定文件里面的数据----> 不要操作中文
        String filePath = "src/info.txt";
        //读取
        InputStream inputStream = null;
        try {
            //1.创建字节输入流对象 并获得文件字节数据
            inputStream = new FileInputStream(filePath);
            //2.读取流里面字节数据
            /*int data = inputStream.read();
            //System.out.println("读取到的第一个字节:" + (char)data);
            //读完所有的字节数据  读到文件末尾  -1
            while (data != -1) {
                System.out.print((char) data);
                data = inputStream.read();
            }*/
            int result;
            while ((result = inputStream.read()) != -1) {
                System.out.print((char) result);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //释放资源
            try {
                if (inputStream != null) inputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }

        }


    }

2. try...with...resources

 private static void demo3() {
        //1.7+ 提供了一种更加优雅的方式"释放流对象"  自动调用close
        //try....with...resources
        //资源释放就不需要编写了 不需要finally
        try (
                //创建流对象-----> 必须实现Closeable
                InputStream inputStream = new FileInputStream("src/info.txt");
                MyClass myClass = new MyClass();
        ) {
            System.out.println(inputStream.read());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

private static void demo4() throws FileNotFoundException {
    InputStream inputStream = new FileInputStream("src/info.txt");
    MyClass myClass = new MyClass();
    try (inputStream; myClass;) {
        inputStream.read();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

3. read(byte[] by)

private static void demo5() throws FileNotFoundException {

        //对于输入流而言: read 文件必须要存在的
        FileInputStream inputStream = new FileInputStream("src\\com\\javasm\\io\\InputStreamDemo.java");
        try (inputStream) {
            //读取数据
            byte[] bytes = new byte[1024 * 5];//一般都是1024的整数倍  理论上来说  空间越大 速度越快
            //int result = inputStream.read(bytes);
            //一次读取1024个字节内容 读取到的字节的数据都存储到了bytes数组中
            //result: 维护读取到的有效的字节个数
            //有效的字节的数据是多少个? 不一定是length
            //System.out.println(result);
            //System.out.println(Arrays.toString(bytes));
            //1.循环遍历字节数组  将每一个字节转换成char  result次
            //2.将字节数组转换成字符串的数据(看不懂的数据转换成看的懂字符串)
            //String s = new String(bytes, 0, result);
            //System.out.println(s);

            //循环读取指定的文件的数据
            int len;
            while ((len = inputStream.read(bytes)) != -1) {
                System.out.print(new String(bytes, 0, len));
            }
            //100个字节
            //read()  读取一个字节 并输出打印一次
            //length=50
            //read(bytes[])  读取一个字节 输出打印2次

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

3.2 字节输出流 OutputStream

将流里面的数据写入文件中。  write

public abstract class OutputStream
extends Object
implements Closeable, Flushable

void write(int b)  一次写一个字节
void write(byte[] b)  一次写入b.length个字节
void write(byte[] b, int off, int len)  
    
void close()  ----->提前flush
void flush()  将缓冲的数据刷出到文件中

FileOutputStream(String name) 
FileOutputStream(File file) 
    
FileOutputStream(String name, boolean append) 
FileOutputStream(File file, boolean append)     
    
append: 追加数据到文件末尾  还是直接覆盖源文件数据  默认: false

1. write

public static void main(String[] args) throws FileNotFoundException {

    //src/a.txt
    //对于输出流  文件不存在?  是否会报错?  不会报错  文件会自动创建(File.createNewFile())
    //一定要保证父级目录要存在
    //读写内容: 出现乱码  编码格式不符
    //字节流读取字符(汉字)数据
    OutputStream outputStream = new FileOutputStream("src/a.txt", true);
    try (outputStream) {
        outputStream.write("张三".getBytes());
        outputStream.write(97);
        outputStream.write('\n');
        outputStream.write("我们".getBytes(Charset.forName("UTF-8")));
        //outputStream.write("世界那么大".getBytes(), 0, 5);
    } catch (IOException e) {
        e.printStackTrace();
    }
}

3.3 模拟上传头像

复制粘贴

分析:
  1. 告诉我你的文件在哪里?=====> 上传的文件的路径
  2. 读取用户传的数据  写入服务器中一个文件里面
     2.1 保证文件名称唯一性
  3. 服务器里面的一个文件: 文件的内容 与用户要传的内容一致

/**
 * @author: Lisa
 * @className: UploadImageDemo
 * @description: 模拟用户上传头像
 * @date: 2022/3/7 15:29
 * @version: 0.1
 * @since: jdk11
 */
public class UploadImageDemo {

    private static final String PARENT_DIRECTORY = "upload/user/";//根目录

    /**
     * 上传文件
     *
     * @param sourceFilePath 源文件路径
     * @return 上传成功之后 文件所在的服务器路径  upload/user/a.jpg
     */
    public static String upload(String sourceFilePath) throws FileNotFoundException {
        Objects.requireNonNull(sourceFilePath);

        //读取源文件数据: InputStream  read
        //写入目标文件中 OutputStream  write
        InputStream inputStream = new FileInputStream(sourceFilePath);
        //获得目标文件文件名称
        //1. 获得原文件名称  最后\之后的数据  弊端: 不同的用户可能会上传重名的文件
        //String fileName = sourceFilePath.substring(sourceFilePath.lastIndexOf(File.separator) + 1);
        //2. 保证名称唯一性 UUID
        //3.用户量增加 所有文件都在一个目录中  读取文件性能比较低----> 分目录存储图片
        //目录打散
        // 动态创建目录 以当前时间为目录名称  2022-03-07
        String curDate = LocalDate.now().toString();
        //判断目录是否存在 File.exsits()
        File directory = new File(PARENT_DIRECTORY, curDate);
        if (!directory.exists()) {
            System.out.println("1111111111111111111");
            directory.mkdirs();
        }
        //获得原文件后缀
        String extension = sourceFilePath.substring(sourceFilePath.lastIndexOf("."));
        String targetFileName = UUID.randomUUID().toString().replaceAll("-", "") + extension;

        OutputStream outputStream = new FileOutputStream(new File(directory, targetFileName));
        try (inputStream; outputStream) {
            //循环读写文件数据
            //1.读一个字节 写一个字节   小文件ok  大文件有问题
            //2.缓冲---->  byte[]
            byte[] bytes = new byte[1024 * 10];
            int len;
            while ((len = inputStream.read(bytes)) != -1) {
                //outputStream.write(len);
                outputStream.write(bytes, 0, len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        System.out.println("文件上传成功");
        return PARENT_DIRECTORY + curDate + "/" + targetFileName;
    }


    //4588
    public static void main(String[] args) throws FileNotFoundException {

        long begin = System.currentTimeMillis();
        String path = "D:\\chaoyue.jpg";
        path = "E:\\pic\\chaoyue.jpg";
        path = "E:\\tools\\IntelliJ IDEA\\ideaIU-2020.1.1.exe";
        System.out.println(upload(path));//回显图片
        long end = System.currentTimeMillis();
        System.out.println(end - begin);
        //        System.out.println(UUID.randomUUID().toString().replaceAll("-",""));
    }

}

3.4 高效字节流

高效:  相比普通字节流  性能较高。 底层自带缓冲。默认为8192个字节。

BufferedInputStream:  高效字节输入流

BufferedOutputStream: 高效字节输出流

  • 装饰者设计模式
BufferedInputStream(InputStream in) 将基本的字节输入流转换成高效输入流对象
BufferedOutputStream(OutputStream out)  将基本的字节输出流转换成高效字节输出流

public static void main(String[] args) throws FileNotFoundException {

    //模拟文件上传:
    long begin = System.currentTimeMillis();
    String path = "E:\\tools\\IntelliJ IDEA\\ideaIU-2020.1.1.exe";
    BufferedInputStream inputStream = new BufferedInputStream(new FileInputStream(path));
    BufferedOutputStream outputStream = new BufferedOutputStream(new FileOutputStream("D:\\a.exe"));

    try (inputStream; outputStream) {
        //循环读写
        int len;
        byte[] bytes = new byte[1024 * 5];
        //自定义的缓冲 >底层的缓冲  就不使用底层的缓冲
        //自定义的缓冲 < 底层的缓冲 8192
        while ((len = inputStream.read(bytes)) != -1) {
            //一次读取一个字节 一次写一次字节
            outputStream.write(bytes,0,len);//将1个字节写入缓冲  满的时候 调用底层的flush
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
    long end = System.currentTimeMillis();
    System.out.println(end - begin);//8
}

4. 字符流

主要是操作文本文件。 字节+编码格式。  操作内容是字符数据。

4.1 字符输入流 Reader

文件必须要存在。

int read()  
    
int read(char[] cbuf)  
    
abstract int read(char[] cbuf, int off, int len)

FileReader(String fileName) 
FileReader(File file)

4.2 字符输出流  Writer

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

abstract void close() 
关闭流,先刷新。  
abstract void flush() 
刷新流。  
void write(char[] cbuf) 
写入一个字符数组。  
abstract void write(char[] cbuf, int off, int len) 
写入字符数组的一部分。  
void write(int c) 
写一个字符  
void write(String str) 
写一个字符串  
void write(String str, int off, int len)

FileWriter(String fileName) 
    
FileWriter(String fileName, boolean append)

1. 注册用户

private static int idIndex = 1000;

private static void userRegister() throws IOException {
    //模拟用户注册: 持久化报存用户信息
    //将每个用户信息保存在userInfo.txt     id-name-pass-age
    @Cleanup
    Scanner input = new Scanner(System.in);
    //将用户写入文件 输出流。 字节输出流   字符输出流 Writer---->FileWriter
    @Cleanup
    FileWriter writer = new FileWriter("src/userInfo.txt", true);
    List<UserInfo> userInfoList = findAllUser();
    if (!userInfoList.isEmpty()) {
        idIndex = userInfoList.get(userInfoList.size() - 1).getId();
    }
    String answer;
    do {
        System.out.println("请录入用户name:");
        String name = input.nextLine();
        System.out.println("请录入用户pass:");
        String pass = input.nextLine();
        System.out.println("请录入用户age:");
        String age = input.nextLine();
        //新注册的用户信息 id到底是多少? 看之前有没有注册过
        //没有注册过
        writer.write(String.join("-", String.valueOf(++idIndex), name, pass, age));//1024
        writer.write("\n");
        writer.flush();
        System.out.println("是否继续?y/n");
        answer = input.nextLine();
    } while (Objects.equals("y", answer));
    System.out.println("注册结束");
}

4.3 高效字符输入流

1. 查询用户

public static List<UserInfo> findAllUser() throws IOException {
    //读取---> 查询所有的用户--->获得最后一个用户id
    //将每一行的记录装配成一个个的用户对象
    //读取文件里面的每一行记录  read 输入流 字节输入流  字符输入流
    //装饰基本字符流的功能 ----> 一次读取一行----> 高效字符流  BufferedReader
    //1. 底层有缓冲 8192个字符
    //2. 方法更加好用  一次读取一行
    List<UserInfo> userInfoList = new ArrayList<>(10);
    @Cleanup
    BufferedReader reader = new BufferedReader(new FileReader("src/userInfo.txt"));
    //String line = reader.readLine();//null 读取文件末尾
    String line;
    while ((line = reader.readLine()) != null) {
        String[] infoArray = line.split("-");
        UserInfo userInfo = new UserInfo();
        userInfo.setId(Integer.parseInt(infoArray[0]));
        userInfo.setName(infoArray[1]);
        userInfo.setPass(infoArray[2]);
        userInfo.setAge(Integer.parseInt(infoArray[3]));
        userInfoList.add(userInfo);
    }
    return userInfoList;
}

5. 其它流

1. 数据流(字节流)

读写字面量类型(基本数据类型+String)==的数据。

必须先写。 再去读。

public class DataDemo {
    public static void main(String[] args) {

        //注册用户:  id-name-pass-age   FileWriter.write(String str)
        //读取一个用户信息:  BufferedReader.readLine()  split()  Integer.parseInt()

        try {
            testRead();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }

    }

    //对于数据流  场景:  writeUTF/readUTF
    //网络数据传输: 传输的都是字节内容  使用writeUTF/readUTF避免出现乱码的问题、

    private static void testWrite() throws FileNotFoundException {
        //1.创建数据输出流对象
        DataOutputStream dataOutput = new DataOutputStream(new FileOutputStream("src/b.txt"));
        try (dataOutput) {
            dataOutput.writeInt(1001);
            dataOutput.writeUTF("张三");
            dataOutput.writeDouble(89999.567);
            dataOutput.writeByte(20);
            dataOutput.writeBoolean(true);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    private static void testRead() throws FileNotFoundException {
        //1.创建数据输入流对象
        DataInputStream dataInputStream = new DataInputStream(new FileInputStream("src/b.txt"));
        User user = new User();
        try (dataInputStream) {
            user.setId(dataInputStream.readInt());//4
            user.setName(dataInputStream.readUTF());
            user.setBalance(dataInputStream.readDouble());
            user.setAge(dataInputStream.readByte());
            System.out.println(dataInputStream.readBoolean());

            System.out.println(user);
            //java.io.EOFException  end of file
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static void testRead1() throws FileNotFoundException {

        //弊端: 每读一个数据  都要进行类型的强制转换
        BufferedReader reader = new BufferedReader(new FileReader("src/user.txt"));
        User user = new User();
        try (reader) {
            String id = reader.readLine();
            String name = reader.readLine();
            String balance = reader.readLine();
            String age = reader.readLine();
            user.setId(Integer.parseInt(id));
            user.setName(name);
            user.setAge(Byte.parseByte(age));
            user.setBalance(Double.parseDouble(balance));

            System.out.println(user);
        } catch (IOException e) {
            e.printStackTrace();
        }


    }
}

2. 对象流(字节流)

读写对象。  必须先写。 再去读。

ObjectInputStream(反序列化流-----> 读取文件里面对象)

ObjectOutputStream(序列化流---> 将对象写入文件)

void writeObject(Object obj)  
    
Object readObject()

2.1 写对象

private static void demo1() {
    //对于数据流  弊端:  读取多次 获得每个属性的信息  赋值多次
    //直接读写一个完整的对象信息?
    //对象流也是在数据流基础之上 丰富了一些功能。除了可以操作字面量类型 还可以操作对象类型。
    try {
        ObjectOutput objectOutput = new ObjectOutputStream(new FileOutputStream("src/a.ini"));
        Student student = new Student();
        student.setId(1);
        student.setName("张三");
        student.setAge((byte) 20);
        student.setBalance(100000.123);
        objectOutput.writeObject(student);
        objectOutput.close();
    } catch (IOException e) {
        e.printStackTrace();
    }


}

没有支持序列化异常。

java.io.NotSerializableException: com.lisa.io.Student
	at java.base/java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1185)
	at java.base/java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:349)
	at com.lisa.io.ObjectDemo.demo1(ObjectDemo.java:32)
	at com.lisa.io.ObjectDemo.main(ObjectDemo.java:18)

解决

//Serializable是一个标记接口  jvm可以将这个类型的对象  写入文件中
@Data
public class Student implements Serializable {

    private Integer id;
    private String name;
    private double balance;
    private byte age;
}

2.2 读对象

private static void demo2() {
        //读取文件里面的对象
        try {
            ObjectInput objectInput = new ObjectInputStream(new FileInputStream("src/a.ini"));
            Student student = (Student) objectInput.readObject();
            System.out.println("读取到的文件对象:" + student);

            objectInput.close();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            //jvm里面找不到这个类型的class文件
            //1.1 java文件没有被编译
            //1.2 可能没有引入第三方的类库
            e.printStackTrace();
        }
    }

2.3 问题

在读取对象中:
  1.1 读取的对象是否与原对象一致?  不一致
      不一致? 对象的创建有没有执行构造?  没有执行构造  jvm自身的规则
      
      
  1.2 读取的对象的属性数据是否与源对象一致?  一样的
  
  1.3 任意一个对象的数据改变 是否会影响其他对象? 不影响的  类似深克隆

只要修改的java程序代码  在没有重新写的前提下  直接读取对象

java.io.InvalidClassException: com.lisa.io.Student; local class incompatible: stream classdesc serialVersionUID = -5811316772388944105, local class serialVersionUID = -7066445612577640178
	at java.base/java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:689)
	at java.base/java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:2025)
	at java.base/java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1875)
	at java.base/java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2206)
	at java.base/java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1692)
	at java.base/java.io.ObjectInputStream.readObject(ObjectInputStream.java:499)
	at java.base/java.io.ObjectInputStream.readObject(ObjectInputStream.java:457)
	at com.lisa.io.ObjectDemo.demo2(ObjectDemo.java:25)
	at com.lisa.io.ObjectDemo.main(ObjectDemo.java:18)

原因:  Stream里面class文件的版本与本地编译出来的版本不一致

解决方案:

1.修改一次  重新write一次  重新read  (很麻烦  生产阶段 不能这么做)

2.固定一个class文件的版本号id

//Serializable是一个标记接口  jvm可以将这个类型的对象  写入文件中
//修改java文件 都需要重新编译新的class 产生新的class文件的版本号 serialVersionUID  唯一的
//在正常开发中: 记住一点  封装数据类上面 一定要实现 Serializable  并提供版本id
//类实现 Serializable接口在开发中 肯定是没有问题的(Redis/RMI)
//不实现Serializable  程序就有可能出现问题。

3. 转换流(字符流)

字节流 和字符流之间的相互转换。

3.1 字节转字符

将网络上一些小说章节内容  下载到本地。----->将小说数据写入磁盘文件中,

写: 输出流
字节输出流 OutputStream
字符输出流 Writer  一次读取一行  一次写一行
BufferedReader
BufferedWriter/FileWriter

private static void downloadNovel() throws IOException {
    String novelPath = "https://read.qidian.com/chapter/WS49qjrNDI7v7_WH3wfEdQ2/uNA4HnHXD0P6ItTi_ILQ7A2/";
    novelPath = "https://read.qidian.com/chapter/OD1kAAaaLgMKgXB091LLaA2/eGiW7wfkoWTM5j8_3RRvhw2/";
    //novelPath = "http://book.zongheng.com/chapter/1099697/64049078.html";

    //网络上的所有的数据都是字节流。
    //获得万维网的资源指针。有网。 URL--->万维网资源指针。
    //URL url = new URL(novelPath);//读取网络资源 InputStream
    //InputStream inputStream = url.openStream();//网络的数据都在inputStream
    //在读取的功能里面: 程序本应该使用字节流进行读取
    //但是我们想使用字符流进行高效读取。
    //转换: 字节流读取转换成字符流读取


    //高效字符流实现读写功能
    BufferedReader reader = new BufferedReader(new InputStreamReader(new URL(novelPath).openStream(), StandardCharsets.UTF_8));
    BufferedWriter writer = new BufferedWriter(new FileWriter("src/novel1.txt"));
    try (reader; writer) {
        //循环读写
        String line;
        while ((line = reader.readLine()) != null) {
            //System.out.println(line);
            if (line.contains("class=\"read-content j_readContent\"")) {
                reader.readLine();
                reader.readLine();
                line = reader.readLine();
                writer.write(line.replaceAll("<p>","\n"));
                break;
            }
        }
    }
    System.out.println("下载成功");
}

3.2 字符转字节

OutputStreamWriter

private static void demo1() throws FileNotFoundException {
    //读取指定文件的数据 在控制台打印输出---->将数据写在控制台
    //read  输入流  字节输入流   字符输入流Reader BufferedReader
    //write  输出流

    BufferedReader reader = new BufferedReader(new FileReader("src/novel.txt"));
    BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(System.out));

    //底层实现: 将数据打印在控制台---->System.out  字节输出流
    try (reader;writer) {
        String line;
        while ((line = reader.readLine()) != null) {
            writer.write(line);//字符输出流写 转成底层的字节输出流
            writer.newLine();
        }
    } catch (IOException e) {
        e.printStackTrace();
    }

}

4. Properties

属性集。 属性名称----属性值。 key---value

核心作用:  读取核心资源配置文件的数据。

//配置文件:  properties  xml  yml/yaml
private static void demo4() {
    Properties properties = new Properties();
    try {
        //加载文件的数据
        properties.load(new FileInputStream("src/user.txt"));
    } catch (IOException e) {
        e.printStackTrace();
    }
    System.out.println(properties);
    String name = properties.getProperty("name");
    System.out.println(name);
    //乱码了---->解码  手动解码  String
    String decodeName = new String(name.getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8);
    System.out.println(decodeName);

}

private static void demo5() {
    Properties properties = new Properties();
    try {
        //加载文件的数据
        properties.load(new FileInputStream("src/user.properties"));
    } catch (IOException e) {
        e.printStackTrace();
    }
    System.out.println(properties);
}

private static void demo6() {
    Properties properties = new Properties();
    properties.setProperty("id", "1001");
    properties.setProperty("score", "90");
    properties.setProperty("studentName", "李雷");

    //将properties里面的数据写入文件中
    try {
        properties.store(new FileOutputStream("src/student.properties"),"哈哈哈哈哈");
    } catch (IOException e) {
        e.printStackTrace();
    }
}

6. 单例模式问题

一个进程里面只有1个类对象。

public class Admin implements Cloneable, Serializable {
    private static final long serialVersionUID = 1486187929375171608L;

    private Admin() {
    }

    private static final Admin admin = new Admin();

    public static Admin getInstance() {
        return admin;
    }

    public Object readResolve() {
        return admin;
    }

    @Override
    public Admin clone() {
        /*Admin admin = null;
        try {
            admin = (Admin) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return admin;*/
        return this;
    }
}

public static void main(String[] args) {

    //保证admin的单例
    Admin admin = Admin.getInstance();
    System.out.println("原对象:" + admin);

    /*Admin cloneAdmin = admin.clone();
    System.out.println("克隆的对象:" + cloneAdmin);*/

    //序列化----> ObjectOutputStream
    String path = "src/aa.txt";
    try {
        ObjectOutput objectOutput = new ObjectOutputStream(new FileOutputStream(path));
        objectOutput.writeObject(admin);
        objectOutput.close();
    } catch (IOException e) {
        e.printStackTrace();
    }

    //反序列化: ObjectInputStream
    try {
        ObjectInputStream objectInput = new ObjectInputStream(new FileInputStream(path));
        Admin readAdmin = (Admin) objectInput.readObject();
        System.out.println("读取的对象:" + readAdmin);
    } catch (IOException | ClassNotFoundException e) {
        e.printStackTrace();
    }


    //单例的最佳实现方案:  枚举类

    //获得对象方式:
    //1. new
    //2. 克隆: 打破单例  避免打破单例: return this;
    //3. 序列化: 打破单例 readResolve()
    //4. 反射
}

7.课堂练习

1. File类

public class FileDemo1 {

    public static void main(String[] args) {
        demo6();
    }

    private static void demo6() {
        System.out.println(File.separator);   //路径盘符分隔符 (不同系统不同)
        System.out.println(File.separatorChar);   //路径盘符分隔符(不同系统不同)
        System.out.println(File.pathSeparator);   //路径分隔符(不同系统不同)
        System.out.println(File.pathSeparatorChar);  //路径分隔符(不同系统不同)
    }

    private static void demo5() {
        //重命名  file.renameTo
        
        File file = new File("src"+File.separator+"demo.txt");  //File.separator 路径盘符分隔符\
        file.renameTo(new File("src/demo.txt"))
        //若路径名都不同,还要对指定文件重命名,相当于剪切,粘贴,重命名三合一        
        System.out.println(file.renameTo(new File("src/demo/a/hello.txt")));
        
    }

    private static void demo4() {
        //目录不存在  创建目录
        File directory = new File("src", "demo/a/b");
        
        //directory.mkdir() 创建1级目录
        /*if (!directory.exists()) {
            System.out.println(directory.mkdirs());    //创建多级目录
        }*/

        //删除目录
        System.out.println(directory.delete());//删除文件  删除目录(里面没有资源) 若b里没资源,只能删了b,删不到demo,连a都删不了
    }
    
    private static void demo3() {
        //File代表是目录时的操作
        
        //1.创建目录对象
        //File directory = new File("E:\\test\\a\\b");   //File无参的构造创建对象
        File directory = new File("E:\\test\\a", "b");   //File有参的构造创建对象(父级路径,子级路径)
        System.out.println(directory.getName());
        System.out.println(directory.getAbsolutePath());
        System.out.println(directory.getPath());

        System.out.println(directory.isHidden());
        System.out.println(directory.exists());
        System.out.println(directory.isDirectory());
        //目录的名称,相对绝对路径,是否隐藏,是否存在,是否为目录。
    }

    private static void demo2() {
        //文件不存在  创建文件
        
        File file = new File("src/com/javasm/io/b.txt");
        try {
            if (!file.exists()) {
                System.out.println(file.createNewFile());   //创建文件
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        
        System.out.println("文件名称:" + file.getName());
        System.out.println("文件绝对路径:" + file.getAbsolutePath());
        System.out.println("文件相对路径:" + file.getPath());
        System.out.println("文件大小:" + file.length());    //文件内容大小(以字节大小表示)
        System.out.println("上一次操作文件的时间:" + file.lastModified());   //时间的毫秒数
        System.out.println("上一次操作文件的时间:" + new Date(file.lastModified()));
    }
    
    private static void demo1() {
        //File类代表文件时的操作
        
        //1.创建File对象---->磁盘里面文件
        File file = new File("E:\\test\\a.txt");
        //2.获得File对象的一些基本的属性的信息
        System.out.println("文件名称:" + file.getName());
        //路径: 相对路径  + 绝对路径
        //绝对路径: E:\test\a.txt   http://www.baidu.com   http://127.0.0.1:8080/demo/a.html
        //相对路径:  a/b/c/a.txt  目前项目/相对于项目部署服务器路径
        System.out.println("文件绝对路径:" + file.getAbsolutePath());
        System.out.println("文件相对路径:" + file.getPath());
        System.out.println("文件大小:" + file.length());//文件内容大小  字节
        System.out.println("上一次操作文件的时间:" + file.lastModified());//时间的毫秒数
        System.out.println("上一次操作文件的时间:" + new Date(file.lastModified()));//时间的毫秒数 转换成  年月日的时间数据 Date

        //3.对File进行一些判断
        System.out.println(file.exists()); //判断文件是否存在
        System.out.println(file.isFile()); //判断file对象是否是文件

        //4.操作文件的权限
        System.out.println(file.setReadOnly()); //设置文件只读

        //5.删除文件
        System.out.println(file.delete()); //删除磁盘文件
    }
}

2. File类案例

展示指定父级目录下的所有的子级的资源。

拿到父级目录下的所有子级资源

//流程:1.创建目录对象 2.获得父级路径下面的1级资源(可能为目录,也可能为文件) 3.若2的一级资源为目录,拿二级资源  ...(递归)
public class FindChildDemo {
    public static void main(String[] args) {
        String parentDirectoryPath = "E:\\workspace\\one\\day19";   //父级路径
        //要展示day19下的所有资源,展示效果
        // |-.idea
        // ||-.libraries
        //...
        // |-.out
        // |-.src
        //|-.day19.iml

        File directory = new File(parentDirectoryPath);
        listChild(directory,"|-");
    }

    private static void listChild(File directory,String s) {  //directory为父级路径,该路径是活的,在3中就是2的一级目录
        //获得指定父级目录下的一级资源  两种方法
        //String[] list = directory.list();  //获得指定目录下的所有子级资源名称(文件名称和目录名称)
        File[] childArray = directory.listFiles();  //获得指定目录下的所有子级资源对象(文件对象和目录对象),该方法还可拿其下面的资源
        for (File child : childArray) {
            System.out.println(s+child.getName());
            //判断子级是否为目录,若为目录,继续往下拿
            if (child.isDirectory()){
                //再去获得子级资源,用递归(自己调自己所在的方法逻辑)
                listChild(child,"|"+s);  //每递归一次就加一次||-
            }
        }
    }
}

拿到父级目录下的所有java文件

//过滤掉不符合条件的一些资源
public class FindChildDemo {
    public static void main(String[] args) {
        String parentDirectoryPath = "E:\\workspace\\one\\day19";
        File directory = new File(parentDirectoryPath);
        listChild(directory,"|-");
    }

    private static void listChild(File directory,String s) {

        File[] childArray = directory.listFiles();
        for (File child : childArray) {
            String childName = child.getName();
            if (child.isDirectory()){
                //System.out.println(s+childName);  //这行可不要,但输出的java文件可能并不知道出自哪个目录,层级会乱
                listChild(child,"|"+s);
            }else{
              //这里是文件
                if (childName.endsWith("java")){
                    System.out.println(s+childName);
                }
            }
        }

    }
}

用list去做:

展示指定父级目录下的所有的子级的资源

public class FindChildDemo {
    public static void main(String[] args) {
        String parentDirectoryPath = "E:\\workspace\\one\\day19";
        File directory = new File(parentDirectoryPath);
        listChild(directory,"|-");
    }

    private static void listChild(File directory,String s) {
        //这里用list去做
        String[] nameArray = directory.list();
        for (String childName : nameArray) {
            File child = new File(directory,childName);  //这里需要带两个参的构造,将父级路径传入  File里只能传路径
            System.out.println(s+childName);
            if(child.isDirectory()){
                listChild(child,"|-"+s);
            }
        }
    }
}

3.InputStream流(输入流)-----对应读read

class MyClass implements Closeable {

    @Override
    public void close() throws IOException {
        System.out.println("我被关闭了......");
    }
}

public class InputStreamDemo {

    public static void main(String[] args) {
        
    }

    private static void demo6() throws FileNotFoundException {

        FileInputStream inputStream = new FileInputStream("src/info.txt");

        try (inputStream) {
            byte[] bytes = new byte[5];
            //public int read(byte b[], int off, int len)   //read带三个参数的
            //off:指定的索引位置存储字节内容
            //len: 存储字节个数
            int result = inputStream.read(bytes, 0, bytes.length);
            System.out.println(Arrays.toString(bytes));
            System.out.println(result);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static void demo5() throws FileNotFoundException {

        //对于输入流而言: read      文件必须要存在的
        FileInputStream inputStream = new FileInputStream("src\\com\\javasm\\io\\InputStreamDemo.java");
        try (inputStream) {
            //读取数据
            byte[] bytes = new byte[1024];      //一般都是1024的整数倍,理论上来说,空间越大,速度越快
            //int result = inputStream.read(bytes);    //read参数为字节数组
            
            //一次读取1024个字节内容 读取到的字节的数据都存储到了bytes数组中
            //result: 维护读取到的有效的字节个数
            //有效的字节的数据是多少个? 不一定是length  
            //System.out.println(result);
            //System.out.println(Arrays.toString(bytes));
            //1.循环遍历字节数组  将每一个字节转换成char  result次
            //2.将字节数组转换成字符串的数据(看不懂的数据转换成看的懂的字符串)
            //String s = new String(bytes, 0, result);
            //System.out.println(s);

            //循环读取指定的文件的数据
            int len;
            while ((len = inputStream.read(bytes)) != -1) {       //还未读到文件末尾
                System.out.print(new String(bytes, 0, len));
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static void demo4() throws FileNotFoundException {       //抛异常
        
        InputStream inputStream = new FileInputStream("src/info.txt");
        MyClass myClass = new MyClass();
        try (inputStream; myClass;) {         //1.7+之后,可在小括号里加多个需要释放资源的对象
            inputStream.read();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static void demo3() {
        //1.7+ 提供了一种更加优雅的方式"释放流对象",会自动调用close
        //try....with...resources   资源释放就不需要编写了 不需要finally
        try (    
                //try()里放的是需要释放资源的对象,要求这些对象必须实现Closeable
                InputStream inputStream = new FileInputStream("src/info.txt");
                MyClass myClass = new MyClass();   //也实现了Closeable接口
        ) {
            System.out.println(inputStream.read());
        } catch (IOException e) {
            e.printStackTrace();
        }                                //不再需要finally释放资源,已自动调用close  
        //如何证明close被自动关闭   让MyClass实现Closeable接口,并重写close方法。第一次运行try里不加MyClass myClass = new MyClass(); 
        //的代码,然后在try里加该代码,若真的关闭了,自然会走MyClass重写的方法
    }                

    private static void demo2() {
        InputStream inputStream = null;
        try {
            //1.创建字节输入流对象
            inputStream = new FileInputStream("src/info.txt");
            //文件不存在,无法读取
            System.out.println(inputStream.read());
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (inputStream != null)
                    inputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    private static void demo1() {
        //读取指定文件里面的数据----> 不要操作中文    read(读)----inputStream输入流
        String filePath = "src/info.txt";
        //读取
        InputStream inputStream = null;
        try {
            //1.创建字节输入流对象 并获得文件字节数据
            inputStream = new FileInputStream(filePath);   //多态创建
            //2.读取流里面字节数据
            /*int data = inputStream.read();    //无参的,一次读一个字节,返一个字节内容     读完所有的字节数据,读到文件末尾,返-1
            //System.out.println("读取到的第一个字节:" + (char)data);
            //循环去读
            while (data != -1) {
                System.out.print((char) data);
                data = inputStream.read();
            }*/
            
            int result;
            while ((result = inputStream.read()) != -1) {
                System.out.print((char) result);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //释放资源
            try {
                if (inputStream != null) inputStream.close();     //不为空关闭
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

4.OutputStream流(输出流)-----对应写write

只要调close,就会提前flush()。

public class OutputStreamDemo {

    public static void main(String[] args) throws FileNotFoundException {

        //src/a.txt
        //对于输出流  文件不存在?  是否会报错?  不会报错  文件会自动创建(File.createNewFile())
        //一定要保证父级目录要存在   src/demo/a.txt  连父级目录demo都没有,则出错。不可能连父级目录也创建出来
        //读写内容: 出现乱码  编码格式不符
        //字节流读取字符数据,仅对于汉字,可能会出现乱码。
        OutputStream outputStream = new FileOutputStream("src/a.txt", true);   //不加true,默认覆盖。加true,在文件末尾添加
        try (outputStream) {
            outputStream.write(97);   //a
            outputStream.write("张三".getBytes());
            outputStream.write("97".getBytes());
            outputStream.write('\n');
            outputStream.write("我们".getBytes(Charset.forName("UTF-8")));   //在指定格式下编码
            //outputStream.write("世界那么大".getBytes(), 0, 5);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

5. 字节流案例--文件上传

模拟用户上传头像

//流程:1.告知文件在哪?--上传文件路径   2.读取用户传的数据,写入服务器中的一个文件里(要保证文件名称唯一)
public class UploadImageDemo {

    private static final String PARENT_DIRECTORY = "upload/user/";  //假设要将文件传到day19里的upload文件夹里的user文件夹里

    public static String upload(String sourceFilePath) throws FileNotFoundException {   //返回值是上传成功后的文件路径    形参是告知的要上传的文件路径
        Objects.requireNonNull(sourceFilePath);
        //文件很大,循环读写文件数据
        //1.读取源文件数据  InputStream
        //2.写入目标文件中  OutputStream
        InputStream inputStream = new FileInputStream(sourceFilePath);
        //获得目标文件名称
        String fileName = sourceFilePath.substring(sourceFilePath.lastIndexOf(File.separator) + 1);   //获得源文件名称,最后\之后的数据
        OutputStream outputStream = new FileOutputStream(PARENT_DIRECTORY+fileName);  //目标文件路径
        try (inputStream;outputStream){
            //循环读写数据
            int len;  //有效字节数
            while ((len=inputStream.read())!=-1){
                outputStream.write(len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        System.out.println("文件上传成功");
        return PARENT_DIRECTORY+fileName;   //PARENT_DIRECTORY+文件名+后缀
    }

    public static void main(String[] args) throws FileNotFoundException {
        String path = "E:\\io\\tom.jpg";   //要上传的文件路径
        System.out.println(upload(path));   //传过去要上传路径,返回上传后的路径。(回显图片)
    }

}

不同用户上传重名文件时:

public class UploadImageDemo {

    private static final String PARENT_DIRECTORY = "upload/user/";

    public static String upload(String sourceFilePath) throws FileNotFoundException {
        Objects.requireNonNull(sourceFilePath);
        InputStream inputStream = new FileInputStream(sourceFilePath);
        //String fileName = sourceFilePath.substring(sourceFilePath.lastIndexOf(File.separator) + 1);
        //保证名称唯一性,用UUID(128位的无符号的16进制的字符串,几乎不可能重复),一般不要其中的-
        String extension = sourceFilePath.substring(sourceFilePath.lastIndexOf("."));
        String targetFileName = UUID.randomUUID().toString().replaceAll("-","")+extension;
        OutputStream outputStream = new FileOutputStream(PARENT_DIRECTORY+targetFileName);
        try (inputStream;outputStream){
            int len;
            while ((len=inputStream.read())!=-1){
                outputStream.write(len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        System.out.println("文件上传成功");
        return PARENT_DIRECTORY+targetFileName;
    }

    public static void main(String[] args) throws FileNotFoundException {
        //String path = "E:\\io\\tom.jpg";
        String path1 = "D:\\io\\tom.jpg";   //另一个用户又上传了一个同名的图片
        System.out.println(upload(path1));
    }

}

若上传大量文件,将文件传到多个目录中:

public class UploadImageDemo {

    private static final String PARENT_DIRECTORY = "upload/user/";

    public static String upload(String sourceFilePath) throws FileNotFoundException {
        Objects.requireNonNull(sourceFilePath);
        InputStream inputStream = new FileInputStream(sourceFilePath);
        //用户量增加,解决所有文件都在同一个目录的问题,用分目录存储图片
        //一般以时间命名目录
        //动态创建目录,目录名以当前时间为目录名称     2022-03-07这样的格式
        String curDate = LocalDate.now().toString();   //当前时间
        //判断目录是否存在
        File directory = new File(PARENT_DIRECTORY,curDate);
        if(!directory.exists()){
            directory.mkdirs();    //若不存在,创建目录
        }

        String extension = sourceFilePath.substring(sourceFilePath.lastIndexOf("."));
        String targetFileName = UUID.randomUUID().toString().replaceAll("-","")+extension;
        OutputStream outputStream = new FileOutputStream(new File(directory,targetFileName));  //这里也变动
        try (inputStream;outputStream){
            int len;
            while ((len=inputStream.read())!=-1){
                outputStream.write(len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        System.out.println("文件上传成功");
        return PARENT_DIRECTORY+curDate+"/"+targetFileName;   //多了一级目录
    }

    public static void main(String[] args) throws FileNotFoundException {
        //String path = "E:\\io\\tom.jpg";
        String path1 = "D:\\io\\tom.jpg";
        System.out.println(upload(path1));
    }

}

若用户量更多,以后还有目录打散(利用哈希表)的方法。

若上传文件很大:

public class UploadImageDemo {

    private static final String PARENT_DIRECTORY = "upload/user/";

    public static String upload(String sourceFilePath) throws FileNotFoundException {
        Objects.requireNonNull(sourceFilePath);
        InputStream inputStream = new FileInputStream(sourceFilePath);
        String curDate = LocalDate.now().toString();
        File directory = new File(PARENT_DIRECTORY,curDate);
        if(!directory.exists()){
            directory.mkdirs();
        }

        String extension = sourceFilePath.substring(sourceFilePath.lastIndexOf("."));
        String targetFileName = UUID.randomUUID().toString().replaceAll("-","")+extension;
        OutputStream outputStream = new FileOutputStream(new File(directory,targetFileName));
        try (inputStream;outputStream){
            //目前是读一个字节就写一个字节,很慢
            //优化,来个缓冲,就是byte[]   (再大的是多任务,多线程完成的)
            byte[] bytes = new byte[1024];  //一次读1024个字节放到字节数组中
            int len;
            while ((len=inputStream.read(bytes))!=-1){
                outputStream.write(bytes,0,len);   //这里用带三个参的,给有效字节,若用write(byte[]),写的东西肯定大于源文件
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        System.out.println("文件上传成功");
        return PARENT_DIRECTORY+curDate+"/"+targetFileName;
    }

    public static void main(String[] args) throws FileNotFoundException {

        long begin = System.nanoTime();     //开始时间,纳秒
        //String path = "E:\\io\\tom.jpg";
        String path1 = "D:\\io\\tom.jpg";
        System.out.println(upload(path1));
        long end = System.nanoTime();
        System.out.println(end-begin);    //计算上传时间
    }

}

大文件,不用缓冲,用高效流(底层自带缓冲,8192)替换也可以:

public class BufferDemo {
    
    public static void main(String[] args) throws FileNotFoundException {
        //模拟文件上传:
        long begin = System.currentTimeMillis();
        String path = "E:\\tools\\IntelliJ IDEA\\ideaIU-2020.1.1.exe";
        BufferedInputStream inputStream = new BufferedInputStream(new FileInputStream(path));
        BufferedOutputStream outputStream = new BufferedOutputStream(new FileOutputStream("D:\\a.exe"));

        try (inputStream; outputStream) {
            //循环读写 
            int len;
            byte[] bytes = new byte[1024 * 5];   //底层自带数组,若自定义数组
            //自定义的缓冲 >底层的缓冲  就不使用底层的缓冲
            //自定义的缓冲 < 底层的缓冲 8192
            while ((len = inputStream.read(bytes)) != -1) {
                //一次读取一个字节 一次写一次字节
                outputStream.write(bytes,0,len);   //将1个字节写入缓冲,当缓冲满的时候,调用底层的flush,写到文件
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        long end = System.currentTimeMillis();
        System.out.println(end - begin);    //8
    }

}

6. 字符流

public class ReaderDemo {

    public static void main(String[] args) {
        try {
            demo2();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static void demo2() throws IOException {
        //创建字符输出流对象
        @Cleanup         //close注解
        FileWriter writer = new FileWriter("src/a.txt");   
        writer.write('a');
        writer.write('\n');
        writer.write('好');
        writer.write('\n');
        writer.write(97);
        writer.write("hello world  你好 世界");    

    }

    private static void demo1() throws FileNotFoundException {

        //1.创建字符输入流对象 Reader
        Reader reader = new FileReader("src/info.txt");
        try (reader) {
            //2.循环读取文件数据
            //reader.read();         //一次读取一个字符  -1
            /*int data = reader.read();
            System.out.println((char) data);*/
            /*int len;
            while ((len=reader.read())!=-1){
                System.out.print((char)len);
            }*/

            //reader.read(char[] chs);       一次读取length个字符数据  返回值: 有效的字符个数
           /* char[] chars = new char[20];
            int len = reader.read(chars);
            //将chars数组转字符串
            System.out.println(new String(chars, 0, len));*/
            /*char[] chars = new char[20];
            int len;
            while ((len=reader.read(chars))!=-1){
                System.out.print(new String(chars, 0, len));
            }*/

        } catch (IOException e) {
            e.printStackTrace();
        }

    }
}

7. 字符流案例

模拟用户注册,持久化保存

public class UserDemo {
    public static void main(String[] args) throws IOException {
      userRegister();
    }
    private static int idIndex = 1001;
    private static void userRegister() throws IOException {
        //模拟用户注册:持久化保存用户信息
        //将每个用户信息保存到userInfo.txt     id-name-pass-age   一行存一个信息
        @Cleanup
        FileWriter writer = new FileWriter("src/userInfo.txt",true);   //将用户信息写入文件中
        @Cleanup
        Scanner input = new Scanner(System.in);
        String answer;
        do {
            System.out.println("请录入用户name");
            String name = input.nextLine();
            System.out.println("请录入用户pass");
            String pass = input.nextLine();
            System.out.println("请录入用户age");
            String age = input.nextLine();

            writer.write(String.join("-",String.valueOf(idIndex++),name,pass,age));
            writer.write("\n");   //换行
            writer.flush();   //写一次看一次
            System.out.println("是否继续?y/n");
            answer = input.nextLine();
        } while (Objects.equals("y",answer));
        System.out.println("注册结束");
    }
}

多个用户注册结束,再次注册,id会从1001开始,如何解决?

@Data
class UserInfo{
    private Integer id;
    private String name;
    private String pass;
    private Integer age;
}

public class UserDemo {
    public static void main(String[] args) throws IOException {
      userRegister();
    }

    public static List<UserInfo> findAllUser() throws IOException {
        //查询所有用户,获得最后一个用户的id
        //将每一行记录装配成一个个用户对象(读取文件里的每一行记录,分割,再装配)
        List<UserInfo> userInfoList = new ArrayList<>(10);
        @Cleanup
        BufferedReader reader = new BufferedReader(new FileReader("src/userInfo.txt"));
        //String line = reader.readLine();   //读到末尾为null
        String line;
        while ((line=reader.readLine())!=null){
            String[] infoArray = line.split("-");
            UserInfo userInfo = new UserInfo();
            userInfo.setId(Integer.parseInt(infoArray[0]));
            userInfo.setName(infoArray[1]);
            userInfo.setPass(infoArray[2]);
            userInfo.setAge(Integer.parseInt(infoArray[3]));
            userInfoList.add(userInfo);
        }
        return userInfoList;
    }


    private static int idIndex = 1001;
    private static void userRegister() throws IOException {
        //模拟用户注册:持久化保存用户信息
        //将每个用户信息保存到userInfo.txt     id-name-pass-age   一行存一个信息
        @Cleanup
        FileWriter writer = new FileWriter("src/userInfo.txt",true);   //将用户信息写入文件中
        List<UserInfo> userInfoList = findAllUser();
        if(!userInfoList.isEmpty()){
           idIndex=userInfoList.get(userInfoList.size()-1).getId();
        }
        @Cleanup
        Scanner input = new Scanner(System.in);
        String answer;
        do {
            System.out.println("请录入用户name");
            String name = input.nextLine();
            System.out.println("请录入用户pass");
            String pass = input.nextLine();
            System.out.println("请录入用户age");
            String age = input.nextLine();
            //新注册用户信息id,要看之前注册过没有
            writer.write(String.join("-",String.valueOf(++idIndex),name,pass,age));
            writer.write("\n");   //换行
            writer.flush();   //写一次看一次
            System.out.println("是否继续?y/n");
            answer = input.nextLine();
        } while (Objects.equals("y",answer));
        System.out.println("注册结束");
    }
}

8. 数据流

//注册用户:  id-name-pass-age   每次拿,需要读一行,然后分割,再转类型
//如何直接拿,不需要转型  用数据流(也是字节流,但一般的字节流操作汉字会出现乱码)
@Data
class User {
    private Integer id;
    private String name;
    private double balance;
    private byte age;
}

public class DataDemo {
    public static void main(String[] args) {

        //注册用户:  id-name-pass-age    FileWriter.write(String str)
        //读取一个用户信息:  BufferedReader.readLine()  split()  Integer.parseInt()

        try {
            testRead();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
    }

    //对于数据流  最常用的场景:  writeUTF/readUTF   (写一个汉字,读一个汉字)
    //何时用它?  网络数据传输: 传输的都是字节内容  使用writeUTF/readUTF避免出现乱码的问题

    private static void testWrite() throws FileNotFoundException {
        //1.创建数据输出流对象    将数据从任意类型转为一系列字节,并写入二进制流   这是写给dataInputStream看的
        DataOutputStream dataOutput = new DataOutputStream(new FileOutputStream("src/b.txt"));
        try (dataOutput) {
            dataOutput.writeInt(1001);
            dataOutput.writeUTF("张三");
            dataOutput.writeDouble(89999.567);
            dataOutput.writeByte(20);
            dataOutput.writeBoolean(true);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static void testRead() throws FileNotFoundException {
        //1.创建数据输入流对象
        DataInputStream dataInputStream = new DataInputStream(new FileInputStream("src/b.txt"));

        User user = new User();
        //可以读,但前提要先写出来,才能读   人工写的它不认识    从二进制流里读数据
        try (dataInputStream) {
            user.setId(dataInputStream.readInt());     //每次读4个字节
            user.setName(dataInputStream.readUTF());
            user.setBalance(dataInputStream.readDouble());
            user.setAge(dataInputStream.readByte());
            System.out.println(dataInputStream.readBoolean());
            System.out.println(user);
            //未先写就读,报此异常 java.io.EOFException  end of file   读到文件末尾也没发现这些东西
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static void testRead1() throws FileNotFoundException {
        //用以前的方法做
        //弊端: 每读一个数据  都要进行类型的强制转换
        BufferedReader reader = new BufferedReader(new FileReader("src/user.txt"));
        User user = new User();
        try (reader) {
            String id = reader.readLine();
            String name = reader.readLine();
            String balance = reader.readLine();
            String age = reader.readLine();
            user.setId(Integer.parseInt(id));
            user.setName(name);
            user.setAge(Byte.parseByte(age));
            user.setBalance(Double.parseDouble(balance));

            System.out.println(user);
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
}

9. 对象流

//Serializable是一个标记接口  jvm可以将这个类型的对象写入文件中
//修改java文件都需要重新编译新的class,产生新的class文件的版本号serialVersionUID,版本号唯一的,通过它可找到class文件
//在正常开发中: 记住一点  封装数据类(用户类,商品类等)上面 一定要实现 Serializable  并提供版本id
//类实现 Serializable接口在开发中 肯定是没有问题的(后期的Redis缓存/远程接口访问RMI)
//不实现Serializable  程序就有可能出现问题。
@Setter
@Getter
@ToString
public class Student implements Serializable {
    private static final long serialVersionUID = -1775089732470255694L;
    //版本号的id是自动生成的    编译工作是IDEA调javac做的,想生成id,告诉IDEA(让idea提醒我们自动生成id值)
    //setting--Editor--inspections--java--serialization issues--serializable class without ‘serialVersionUID’--选中

    private Integer id;
    private String name;
    private double balance;
    private int age;
    private int num;

    public Student() {
        System.out.println("无参构造.....");
    }


}

//对象流也要求先写才能读
public class ObjectDemo {
    public static void main(String[] args) {
        demo2();
    }

    private static void demo2() {
        //读取文件里面的对象
        try {
            ObjectInput objectInput = new ObjectInputStream(new FileInputStream("src/a.ini"));
            Student student = (Student) objectInput.readObject();
            System.out.println("读取到的文件对象:" + student);
            //读取到的文件对象:com.lisa.io.Student@7d9d1a19
            objectInput.close();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            //ClassNotFoundException 找不到字节码文件,也就是jvm里面找不到这个类型的class文件
            //出现该异常有两种原因:
            //1.1 java文件没有被编译(类没有被编译)
            //1.2 可能没有引入第三方的类库(架包)
            e.printStackTrace();
        }
    }

    private static void demo1() {
        //对于数据流    弊端:  读取多次 获得每个属性的信息  赋值多次
        //对象流:直接读写一个完整的对象信息
        //对象流也是在数据流基础之上丰富了一些功能。除了可以操作字面量类型 还可以操作对象类型。
        try {
            ObjectOutput objectOutput = new ObjectOutputStream(new FileOutputStream("src/a.ini"));
            //无参构造.....
            //写入的对象:com.lisa.io.Student@38082d64
            Student student = new Student();
            System.out.println("写入的对象:"+student);
            student.setId(1);
            student.setName("张三");
            //student.setAge((byte) 20);
            student.setBalance(100000.123);
            objectOutput.writeObject(student);
            objectOutput.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        //只写完运行会报 NotSerializableException 没有支持序列化异常
        //这里做的事情:将对象写入磁盘文件中。     对于jvm,若操作对象 这个对象类型必须支持序列化(序列化跟writeObject有关)
        //之前学的克隆: 克隆一个对象   也是jvm底层做的  要克隆的前提: 实现Cloneable接口
        //类似的,想序列化student,必须让它支持序列化,告诉jvm,student可以被序列化,故student要实现Serializable接口
        //序列化流就是ObjectOutputStream,将对象写入文件
        //反序列化流就是ObjectinputStream,读取文件里的对象
    }
}

10. 转换流

字节转字符----将网络上的小说章节内容,下载到本地

字符转字节----打印输出

public class NovelDemo {
    public static void main(String[] args) {
        try {
            demo1();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
    }

    private static void demo1() throws FileNotFoundException {
        //需求:读取指定文件的数据,在控制台打印输出(将数据写在控制台)
        //读取是read  输入流  字节输入流   字符输入流  比较合适的是字符输入流,从中选BufferedReader
        //打印是write  输出流  对应的用Bufferedwriter

        BufferedReader reader = new BufferedReader(new FileReader("src/novel.txt"));
        BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(System.out));   //写到控制台上去,之前是new FileWriter
        //OutputStreamWriter字符转字节

        //System.out.println("fasf") 为何调此命令可将内容打印到控制台,是字符串的println做的
        //out是printStream,继承FilterOutputStream字节输出流  关键还是在out
        //底层实现: 将数据打印在控制台---->System.out  字节输出流
        try (reader;writer) {
            String line;
            while ((line = reader.readLine()) != null) {    //循环去读
                writer.write(line);      //字符输出流写,但其实底层是转成字节输出流    一次写一行   因为想输出到控制台得结合打印流完成
                writer.newLine();        //换行
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    private static void downloadNovel() throws IOException {
        //字节转字符场景:拿取网络资源,且和转换流相关的一般用BufferedReader和BufferedWriter   已成模板

        String novelPath = "https://read.qidian.com/chapter/WS49qjrNDI7v7_WH3wfEdQ2/uNA4HnHXD0P6ItTi_ILQ7A2/";  //小说章节路径(肯定加密了)
        novelPath = "https://read.qidian.com/chapter/OD1kAAaaLgMKgXB091LLaA2/eGiW7wfkoWTM5j8_3RRvhw2/";
        //novelPath = "http://book.zongheng.com/chapter/1099697/64049078.html";   //全加密,这个就没法下载

        //网络上的所有的数据都是字节流。
        //想获得万维网的资源指针。条件:1.有网。2.获得万维网资源指针URL(统一资源定位符),拿到该指针就能拿到网络上的数据
        //URL url = new URL(novelPath);      //url也是个类,在java.net  读取网络资源,用InputStream
        //InputStream inputStream = url.openStream();   //此时网络的数据都在inputStream
        //在读取的功能里面: 程序本应该使用字节流进行读取   但是我们想使用字符流进行高效读取
        //转换: 字节流读取转换成字符流读取

        //高效字符流实现读写功能
        //BufferedReader reader = new BufferedReader(new FileReader(novelPath) FileReader是找磁盘文件,故不能这样写
        BufferedReader reader = new BufferedReader(new InputStreamReader(new URL(novelPath).openStream(), StandardCharsets.UTF_8));
        //new URL(novelPath).openStream()是字节流,需转字符流,同时指定编码格式(不能随便给,看人家编码格式给的什么)
        BufferedWriter writer = new BufferedWriter(new FileWriter("src/novel1.txt"));
        try (reader; writer) {
            //循环读写
            String line;
            while ((line = reader.readLine()) != null) {    //每次读一行
                //System.out.println(line);
                if (line.contains("class=\"read-content j_readContent\"")) {   //这是看着下载的模板进项修改,去除没用部分
                    reader.readLine();
                    reader.readLine();
                    line = reader.readLine();
                    writer.write(line.replaceAll("<p>", "\n"));
                    break;
                }
            }
        }
        System.out.println("下载成功");
    }
}

11. Properties

一个能和IO结合使用的类,是hashTable的一个子类,称为属性集

一般存 属性名=属性值

和map方法一样,但一般不用

public class PropertiesDemo {

    public static void main(String[] args) {

        demo6();

    }

    private static void demo6() {
        Properties properties = new Properties();
        properties.setProperty("id", "1001");
        properties.setProperty("score", "90");
        properties.setProperty("studentName", "李雷");

        //将properties里面的数据写入文件中
        try {
            properties.store(new FileOutputStream("src/student.properties"),"哈哈哈哈哈");  //第二个参数是注释
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static void demo5() {
        Properties properties = new Properties();
        try {
            //加载文件的数据
            properties.load(new FileInputStream("src/user.properties"));
        } catch (IOException e) {
            e.printStackTrace();
        }
        //加载properties文件,出现汉字也会乱码,此时改idea配置,改后不再乱
        //setting--搜encoding--File Encodings--全改成utf-8
        System.out.println(properties);
    }


    //配置文件后缀:  properties  xml  yml/yaml
    private static void demo4() {

        //用properties读取demo3()的文件数据
        Properties properties = new Properties();
        try {
            //加载文件的数据
            properties.load(new FileInputStream("src/user.txt"));
        } catch (IOException e) {
            e.printStackTrace();
        }
        System.out.println(properties);
        String name = properties.getProperty("name");   //想要value,直接给key
        System.out.println(name);
        //将jim改成中文的李四,结果乱码了,此时要解码      手动解码,用String     properties底层默认ISO_8859_1编码格式
        String decodeName = new String(name.getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8);  //转成UTF-8
        System.out.println(decodeName);

    }

    private static void demo3() throws FileNotFoundException {
        //Properties:体现在读取核心配置文件的数据
        //要求文件里面数据格式是固定的:key=value,且一行中只有一组数据

        //假设一个配置文件里的内容如下:
        //id=1001
        //name=jim
        //age=20

        //如何获得id  name  age的数据
        //读: 字节/字符输入流  Reader  FileReader(一次读一个字符或字符数组,不合适)  BufferedReader(读一行)

        BufferedReader reader = new BufferedReader(new FileReader("src/user.txt"));

        try (reader) {
            String line;
            while ((line = reader.readLine()) != null) {
                String[] array = line.split("=");
                System.out.println(array[0] + "---" + array[1]);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    private static void demo2() {

        Properties properties = new Properties();
        //一般推荐使用以下的方法
        properties.setProperty("id", "1001");      //一般参数是String类型
        properties.setProperty("name", "张三");
        properties.setProperty("age", "20");

        System.out.println(properties.getProperty("id"));
        System.out.println(properties.getProperty("name"));
        System.out.println(properties.getProperty("age"));

        //相比map集合方法: 类型转换的次数少一些
        //但仍不是最重要的方法,要结合IO用
    }

    private static void demo1() {

        //针对于Properties: 以下方法 都不用
        //回顾以下map集合的方法
        Properties properties = new Properties();
        //存储数据
        properties.put("id", 1001);
        properties.put("name", "张三");
        properties.put("age", 20);

        //获得数据
        /*int id = (int) properties.get("id");
        String name = (String) properties.get("name");
        int age = (int) properties.get("age");

        System.out.println(id);
        System.out.println(name);
        System.out.println(age);*/

        //遍历Properties里面所有的数据
        //properties.forEach((k,v)-> System.out.println(k+"----"+v));
        /*Set<Map.Entry<Object, Object>> entrySet = properties.entrySet();
        for (Map.Entry<Object, Object> entry : entrySet) {
            System.out.println(entry.getKey() + "---" + entry.getValue());
        }*/
    }
}

12. 单例模式问题

//单例模式是保证单实例的
public class Admin implements Cloneable, Serializable {
    private static final long serialVersionUID = 1486187929375171608L;

    private Admin() {    //1.构造方法私有化
    }

    private static final Admin admin = new Admin();   //饿汉,一上来就new

    public static Admin getInstance() {   //提供对外访问的入口
        return admin;
    }

    public Object readResolve() {
        return admin;
    }

    @Override
    public Admin clone() {
        /*Admin admin = null;
        try {
            admin = (Admin) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return admin;*/
        return this;
    }
}

public class Demo {

    public static void main(String[] args) {

        //目的:保证admin的单例
        Admin admin = Admin.getInstance();   //获得一个实例
        System.out.println("原对象:" + admin);

        /*Admin cloneAdmin = admin.clone();    //克隆一个对象,此时Admin要实现Cloneable接口,且重写clone方法
        System.out.println("克隆的对象:" + cloneAdmin);*/   //产生了新的对象

        //序列化----> ObjectOutputStream   写
        String path = "src/aa.txt";
        try {
            ObjectOutput objectOutput = new ObjectOutputStream(new FileOutputStream(path));
            objectOutput.writeObject(admin);
            objectOutput.close();
        } catch (IOException e) {
            e.printStackTrace();
        }

        //反序列化: ObjectInputStream    读
        try {
            ObjectInputStream objectInput = new ObjectInputStream(new FileInputStream(path));
            Admin readAdmin = (Admin) objectInput.readObject();
            System.out.println("读取的对象:" + readAdmin);      //又产生了新的对象
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }


        //维护单例的最佳实现方案:  枚举类

        //获得对象方式:
        //1. new
        //2. 克隆: 可以打破单例     为避免打破单例,在重写的克隆方法里写: return this/Admin; 就这一句话
        //3. 序列化: 可以打破单例      为避免打破单例,需在单例模式里提供readResolve()方法,这个只能返Admin,不能用this
        //4. 反射,也能打破单例
    }
}

8.课后作业

1.使用IO技术,创建一个目录,然后复制一个文件到该目录

public class Exercise {
    public static void main(String[] args) {
        //将src/a.txt 复制到 exercise/xxx.txt
        File file1 = new File("src/a.txt");   //要复制的文件(源文件)
        File file2 = new File("src/b.txt");   //复制后的文件(目标文件)
        try {
            exercise1(file1, file2);
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    private static void exercise1(File sourceFile, File targetFile) throws IOException {

        //高效字符输入流
        BufferedReader reader = new BufferedReader(new FileReader(sourceFile));
        //高效字符输出流
        BufferedWriter writer = new BufferedWriter(new FileWriter(targetFile));

        try (reader; writer) {
            String line;
            while ((line = reader.readLine()) != null) {
                writer.write(line);
                writer.newLine();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

2.使用IO技术,开发出一个控制台的资源管理器!要求:从命令行输入一个路径!如果存在,将该目录下所有的文件和文件夹列举出来,如果不存在则输出不存在该路径。

public class Exercise {
    public static void main(String[] args) {
       String path = "E:\\workspace\\one\\day24";
        File directory = new File(path);
        exercise2(directory, "|-");

    } 

    private static void exercise2(File directory, String s) {
        if (!directory.exists()) {
            System.out.println(directory + "路径不存在  无法查询子级");
            return;
        }
        File[] files = directory.listFiles();
        for (File child : files) {
            System.out.println(s + child.getName());
            if (child.isDirectory()) {
                exercise2(child, "| " + s);
            }
        }
    }

    }

3.基于转换流,从控制台输入一些字符串,并将该类信息保存到日志文件”log.txt”中去

public class Exercise {
    public static void main(String[] args) {
       try{
           exercise3();
       }catch(IOException e){
           e.printStackTrace();
       }

    }

    private static void exercise3() throws IOException {
        //读取用户在控制台录入的数据  输入流 read
        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));   //in是个InputStream流
        BufferedWriter writer = new BufferedWriter(new FileWriter("src/log.txt"));

        System.out.println("请录入一些数据:");
        try (reader; writer) {                     //释放资源

            while (true) {
                String line = reader.readLine();         //拿到录入的每行内容
                if ("bye".equals(line)) {                //假设最后是bye
                    break;
                }
                writer.write(line);
                writer.newLine();
            }

        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    }

4.从控制台进行输入用户名以及用户密码,判断是否登录成功!要求准确的用户名和密码存在配置文件中!

public class Exercise {
    public static void main(String[] args) {
        login();

    }

    private static void login() {

        Scanner input = new Scanner(System.in);
        System.out.println("录入name:");
        String name = input.nextLine();
        System.out.println("录入pass:");
        String pass = input.nextLine();

        //获得配置文件里面的数据
        Properties properties = new Properties();
        try {
            properties.load(new FileInputStream("src/user.properties"));
        } catch (IOException e) {
            e.printStackTrace();
        }

        String username = properties.getProperty("username");
        String password = properties.getProperty("password");
        if (!name.equals(username) || !pass.equals(password)) {
            System.out.println("登录失败");
            input.close();
            return;
        }
        System.out.println("登录成功,欢迎你:" + username);

        input.close();

    }

    }

public class Exercise4 {
    public static void main(String[] args) {
        demo();
    }

    private static void demo() {
        Properties properties = new Properties();
        try {
            //properties.load(new FileInputStream("src/userinfo.properties"));  前期这样写
            //后期这样写
            properties.load(Exercise4.class.getClassLoader().getResourceAsStream("userinfo.properties"));
        } catch (IOException e) {
            e.printStackTrace();
        }
        System.out.println(properties);
    }

}

5.创建一个学生类,包含属性:学号、姓名、性别,包含show()方法用于显示学生的详细信息。

创建测试类,在控制台上显示添加学生信息,要求程序循环运行,并依次提示接收学生类的所有属性值,保存到学生对象中,再将学生对象保存到集合对象中,并提示“是否继续添加(y/n):”,如果选择“y”则继续添加,否则退出循环,并将保存学生数据的集合对象通过序列化保存到“student.dat”文件中。

实现从“student.dat”文件中反序列化保存学生数据的集合对象,并遍历打印输出学生信息。

class Student implements Serializable {
    private Integer id;
    private String name;

    @Override
    public String toString() {
        return "Student{" +
                "id=" + id +
                ", name='" + name + '\'' +
                '}';
    }

    public Integer getId() {
        return id;
    }

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

    public String getName() {
        return name;
    }

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

public class Exercise {
    public static void main(String[] args) {
        try {
            exercise4();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    private static void exercise5() throws IOException, ClassNotFoundException {
        ObjectInput objectInput = new ObjectInputStream(new FileInputStream("src/student.dat"));

        List<Student> list = (List<Student>) objectInput.readObject();
        list.forEach(System.out::println);
        objectInput.close();
    }

    private static void exercise4() throws IOException {
        Scanner input = new Scanner(System.in);
        int idIndex = 1001;      //id自增,只需录入name
        List<Student> studentList = new ArrayList<>(10);

        String s;
        do {
            System.out.println("录入name:");
            String name = input.nextLine();
            Student student = new Student();
            student.setId(idIndex++);
            student.setName(name);
            studentList.add(student);

            System.out.println("是否继续添加学生信息?y/n");
            s = input.nextLine();
        } while (Objects.equals("y", s));

        //将集合对象写入文件中---->序列化  ObjectOutputStream
        ObjectOutput objectOutput = new ObjectOutputStream(new FileOutputStream("src/student.dat"));

        try(objectOutput){
            objectOutput.writeObject(studentList);
            System.out.println("序列化成功");
        }catch (IOException e){
            e.printStackTrace();
        }
    }

    }

class Student implements Serializable {

    //要加版本号,上个同理。   这里省略
    private Integer id;
    private String name;

    @Override
    public String toString() {
        return "Student{" +
                "id=" + id +
                ", name='" + name + '\'' +
                '}';
    }

    public Integer getId() {
        return id;
    }

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

    public String getName() {
        return name;
    }

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

public class Exercise {
    public static void main(String[] args) {
        try {
            exercise4();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    private static void exercise5() throws IOException, ClassNotFoundException {
        ObjectInput objectInput = new ObjectInputStream(new FileInputStream("src/student.dat"));

        Object object;
        while ((object = objectInput.readObject()) != null) {        //readObject() 没有条件进行判断是否读取到末尾  ,故这里没意义
            System.out.println(object);
        }

        //对于序列化而言: 只写一个  以及  只读一次

        //若写入多次  写多个对象  用集合
        //java.io.EOFException 当前线程就会终止
        objectInput.close();
    }

    private static void exercise4() throws IOException {
        Scanner input = new Scanner(System.in);
        int idIndex = 1001;

        ObjectOutput objectOutput = new ObjectOutputStream(new FileOutputStream("src/student.dat"));
        String s;
        do {
            System.out.println("录入name:");
            String name = input.nextLine();
            Student student = new Student();
            student.setId(idIndex++);
            student.setName(name);

            objectOutput.writeObject(student);
            System.out.println("是否继续添加学生信息?y/n");
            s = input.nextLine();
        } while (Objects.equals("y", s));
    }

    }

6.已知文件a.txt文件中的内容为“AAbcdea22dferwplkCC321ou1”,请编写程序读取该文件内容,要求去掉重复字母(区分大小写字母)并按照自然排序顺序后输出到b.txt文件中。即b.txt文件内容应为”123ACabcdefklopruw”这样的顺序输出。

public class Exercise {
    public static void main(String[] args) {
        try {
            exercise6();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    private static void exercise6() throws IOException {
        //1.读取log.txt文件内容  read  123ACabcdefklopruw  字符输入流 Reader
        List<String> list = new ArrayList<>(10);
        FileReader reader = new FileReader("src/log.txt");

        //2.将读取到的每个字符 存储集合中  List  Set---> TreeSet    Map
        int len;
        while ((len = reader.read()) != -1) {
            list.add(String.valueOf((char) len));
        }
        System.out.println(list);
        list = list.stream().sorted().distinct().collect(Collectors.toList());
        System.out.println(list);
        
        //遍历list集合 将每个字符写入文件中   略
        reader.close();

    }

    }

7. 读取任意txt文件内容,并统计出这个文本中每个字符以及每个字符出现的次数, 并以以下格式: 字符=次数 持久化保存文件中。

public class Exercise {
    public static void main(String[] args) {
        try {
            exercise7();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static void exercise7() throws IOException {

        //Map ---> Properties.store()  将内容直接存到文件里
        Properties properties = new Properties();
        FileReader reader = new FileReader("src/log.txt");
        int len;
        while ((len = reader.read()) != -1) {
            String key = String.valueOf((char) len);
            String countStr = properties.getProperty(key);
            if (countStr == null) {
                countStr = "1";
            } else {
                countStr = String.valueOf(Integer.parseInt(countStr) + 1);
            }
            properties.setProperty(key, countStr);
        }
        properties.store(new FileOutputStream("src/b.txt"), "");
        reader.close();
    }

    }

8.使用集合相关的功能,存储10个1-50(含50)的随机偶数元素,要求数字不能重复,添加完成后从大到小倒序遍历输出到控制台并使用IO流将集合中的元素按指定格式输出到当指定文件中,例如: 48,44,40,38,34,30,26……

public class Exercise {
    public static void main(String[] args) {
        try {
            exercise8();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static void exercise8() throws IOException {

        ThreadLocalRandom random = ThreadLocalRandom.current();       //获得一个随机数对象
        List<Integer> list = new ArrayList<>(10);
        for (int i = 0; i < 10; i++) {
            int num = random.nextInt(1, 51);
            if (num % 2 == 0) {
                list.add(num);
            } else {
                i--;
            }
        }
        list.sort(Comparator.comparing(Integer::intValue).reversed());
        FileWriter writer = new FileWriter("src/b.txt");
        for (Integer num : list) {
            writer.write(num.toString());
            writer.write(",");           //最后会多个逗号
        }
        writer.close();


        //List<Integer> integers = random.ints(10, 1, 51).boxed().collect(Collectors.toList());   若啥都不管,只随机10个
    }

    }

9.已知student_info.txt文件中有如下数据:(姓名-年龄-总分)

* 张三-21-98

* 李四-23-97

* 王五-25-100

* 赵六-15-100

* 孙七-19-93

运用IO技术获取将该文件中的数据分别封装成5个Student(姓名为String类型,年龄为int类型,总分为int类型 )对象存入集合中(需要自己定义Student类)

要求:根据学生的总分进行排序(降序),如果分数相同则比较年龄,年龄较大的排在前面。并显示排序之后的结果。

public class StudentInfo {

    private String name;
    private Integer age;
    private Integer score;

    @Override
    public String toString() {
        return "StudentInfo{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", score=" + score +
                '}';
    }

    public String getName() {
        return name;
    }

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

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public Integer getScore() {
        return score;
    }

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

public class Exercise {
    public static void main(String[] args) {
        try {
            exercise9();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static void exercise9() throws IOException {
        //5个Student(姓名为String类型,年龄为int类型,总分为int类型 )对象存入集合中
        BufferedReader reader = new BufferedReader(new FileReader("src/b.txt"));
        String info;
        List<StudentInfo> list = new ArrayList<>(10);
        while ((info = reader.readLine()) != null) {
            String[] array = info.split("-");
            StudentInfo studentInfo = new StudentInfo();
            studentInfo.setName(array[0]);
            studentInfo.setAge(Integer.parseInt(array[1]));
            studentInfo.setScore(Integer.parseInt(array[2]));
            list.add(studentInfo);
        }
        //根据学生的总分进行排序(降序)
        list.sort(Comparator.comparing(StudentInfo::getScore).reversed().thenComparing(StudentInfo::getAge));
        list.forEach(System.out::println);

    }

    }
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值