IO

所谓IO就是输出输出(input/output)。一般的理解都是相对于计算机而言的输入输出。

比如:

输出设备:显示器,耳机,音响,打印机.....

输入设备:键盘,鼠标,麦克风......

上面的所有的输入输出都是相对于计算机而言的。

我们这里说的输入输出是相对于内存而言的

  • 数据读取到内存就是输入。

  • 数据从内存写出就是输出。

输入输出的原理:

输出的数据流称之为输出流,输入的数据流称之为输入流。

 面试题:java的流是如何分类的?
 按照方向分为:输入流和输出流。
 按照处理方式:分为字节流和字符流。

 

File类

在java.io包下有一个File。 这个类的所有对象都表示一个文件。(一个File类的对象就是一个文件或者文件夹的抽象表示)

内部的常量

  • static String pathSeparator 与系统相关的路径分隔符字符,为方便起见,表示为字符串。

  • static char pathSeparatorChar 与系统相关的路径分隔符。

  • static String separator 与系统相关的默认名称 - 分隔符字符,以方便的方式表示为字符串。

  • static char separatorChar 与系统相关的默认名称分隔符。

前面两个常量是路径相关的分隔符。后面两个是名称分隔符。

 以前在linux中分隔符是 "/" 在windows中是 "\"

构造方法

  • File(File parent, String child) 从父抽象路径名和子路径名字符串创建新的 File实例。

  • File(String pathname) 通过将给定的路径名字符串转换为抽象路径名来创建新的 File实例。

  • File(String parent, String child) 从父路径名字符串和子路径名字符串创建新的 File实例。

  • File(URI uri) 通过将给定的 file: URI转换为抽象路径名来创建新的 File实例。

 相对路径和绝对路径。
 所谓相对路径,就是相对于当前的文件而言,目标的位置。
 所谓绝对路径,就是从盘符开始的路径。
 一般建议使用相对路径。
 public class HelloFile{
  public static void main(String [] args){
  // 我们这里先使用绝对路径
  File file = new File("E:\\202201\\第二阶段\\01.java高级\\0317\\temp\\hello.txt");
  // 当然我们也可以使用相对路径表示
  File file1 = new File("s.txt");
  file1.createNewFile();
 
  // 使用父子文件创建(父路径字符串,子路径字符串)
  File file2 = new File("E:\\202201\\第二阶段\\01.java高级\\0317\\temp","hello.txt");
  File file3_1 = new File("E:\\202201\\第二阶段\\01.java高级\\0317\\temp");
  File file3 = new File(file3_1,"hello.txt");
  }
 }

File类的一些API:

获取信息的---

  • exists() 测试此抽象路径名表示的文件或目录是否存在。

  • getName() 返回由此抽象路径名表示的文件或目录的名称。

  • getAbsolutePath() 返回此抽象路径名的绝对路径名字符串。

  • getParent() 返回此抽象路径名的父 null的路径名字符串,如果此路径名未命名为父目录,则返回null。

  • getParentFile() 返回此抽象路径名的父,或抽象路径名 null如果此路径名没有指定父目录。

  • isDirectory() 测试此抽象路径名表示的文件是否为目录

  • isFile() 测试此抽象路径名表示的文件是否为普通文件

  • lastModified() 返回此抽象路径名表示的文件上次修改的时间。

  • length() 返回由此抽象路径名表示的文件的长度。

  • list() 返回一个字符串数组,命名由此抽象路径名表示的目录中的文件和目录。

  • listFiles() 返回一个抽象路径名数组,表示由该抽象路径名表示的目录中的文件。

案例:

 public class FileTest1 {
     public static void main(String[] args) {
         // 文件
         String path = "E:\\202201\\第二阶段\\01.java高级\\0317\\temp\\hello.txt";
         File file = new File(path);
         // 文件夹
         String path1 = "E:\\202201\\作业\\20220315";
         File file1 = new File(path1);
         System.out.println("------------------");
         System.out.println("文件名:"+file.getName());
         System.out.println("文件夹名:"+ file1.getName());
         System.out.println("文件的绝对路径"+file.getAbsolutePath());
         System.out.println("文件夹的绝对路径:"+file1.getAbsolutePath());
         System.out.println("文件是否存在:"+file.exists());
         System.out.println("文件夹是否存在:"+file1.exists());
         System.out.println("文件的父文件夹路径:"+file.getParent());
         System.out.println("文件是否是文件:"+file.isFile());
         System.out.println("文件是否是文件夹:"+file.isDirectory());
         System.out.println("文件的最后修改时间:"+new Date(file.lastModified()));
         System.out.println("文件夹的最后修改时间:"+new Date(file1.lastModified()));
         System.out.println("文件长度:"+file.length()+" 字节");
         System.out.println("-- 文件夹的length是无效 --");
         System.out.println("文件夹长度:"+file1.length()+" 字节");
         // list是获取当前文件夹下的所有文件和文件夹的名字的字符串数组
         String[] names = file1.list();
         System.out.println(Arrays.toString(names));
         // listFile获取File类型的数组
         File[] files = file1.listFiles();
         for (File f: files){
             System.out.println(f.getAbsolutePath()+":"+f.length());
        }
    }
 }

我们查看文件的大小,从现在开始都是看字节:

 1B byte = 8bit
 1K = 1024B
 1M = 1024K
 1G = 1024M
 1T = 1024G
 1P = 1024T

创建删除判断的一些API----

  • canExecute() 测试应用程序是否可以执行此抽象路径名表示的文件。

  • canRead() 测试应用程序是否可以读取由此抽象路径名表示的文件。

  • canWrite() 测试应用程序是否可以修改由此抽象路径名表示的文件。

  • delete() 删除由此抽象路径名表示的文件或目录。

  • boolean mkdir() 创建由此抽象路径名命名的目录

  • boolean mkdirs() 创建由此抽象路径名命名的目录,包括任何必需但不存在的父目录。

  • createNewFile() 当且仅当具有该名称的文件尚不存在时,原子地创建一个由该抽象路径名命名的新的空文件。

异常问题:

案例:

 public class FileTest2 {
     public static void main(String[] args) throws IOException {
         File file = new File("file-list");
         // 创建这个文件夹
         // 文件不存在或者文件不是文件夹
         if(!file.exists() || !file.isDirectory()){
             // 创建它
             file.mkdir();
        }
         // 创建一组文件夹
         File file1 = new File("E:\\202201\\第二阶段\\01.java高级\\0317\\temp\\stu\\info\\names");
         if(!file1.exists() || !file1.isDirectory()){
             // 创建它 前往不要忘记s
             boolean mkdirs = file1.mkdirs();
             System.out.println(mkdirs);
        }
         // 创建文件
         File file2 = new File("file-list\\names.txt");
         if(!file2.exists() || !file2.isFile()){
             file2.createNewFile();// 这里会有异常,直接抛出
        }
         // 删除文件
         File file3 = new File("s.txt");
         boolean delete = file3.delete();
         System.out.println(delete);
    }
 }

字节输入流InputStream

所有的字节输入流的老祖宗就是 java.io.InputStream。

字节流的特点:是以字节的方式处理数据

这个类是一个抽象类,其目的就是为了让其他的类实现。所以我们都是使用这个类的子类。

浏览API:

  • int available() 返回从该输入流中可以读取(或跳过)的字节数的估计值,而不会被下一次调用此输入流的方法阻塞。 (这个方法对于网络流是无效的)

  • void close() 关闭此输入流并释放与流相关联的任何系统资源。

  • void mark(int readlimit) 标记此输入流中的当前位置。

  • boolean markSupported() 测试这个输入流是否支持 mark和 reset方法。

  • abstract int read() 从输入流读取数据的下一个字节。

  • int read(byte[] b) 从输入流读取一些字节数,并将它们存储到缓冲区 b 。

  • int read(byte[] b, int off, int len) 从输入流读取最多 len字节的数据到一个字节数组。

  • void reset() 将此流重新定位到上次在此输入流上调用 mark方法时的位置。

  • long skip(long n) 跳过并丢弃来自此输入流的 n字节数据。

FileInputStream--InputStream的实现类

构造方法:

  • FileInputStream(File file) 通过打开与实际文件的连接创建一个 FileInputStream ,该文件由文件系统中的 File对象 file命名。

  • FileInputStream(String name) 通过打开与实际文件的连接来创建一个 FileInputStream ,该文件由文件系统中的路径名 name命名。

API:就是参考父类的API。

使用:按照单个的字节读取数据。

 public class FileInputStreamTest {
     public static void main(String[] args) {
         // 通过文件名构建一个字节输入流
         InputStream in = null;
         try {
             // 创建输入流
             in = new FileInputStream("names.txt");
             int len = in.available();// 可读去字节的长度
             System.out.println("数据长度:"+len);
             // 准备一个len长度的字节数组
             byte [] buff = new byte[len];
             int index = 0;
             // 准备读取数据
             // 读取一个字节的数据
             //int read = in.read();
             // 循环读取
             int data = -1;
             while((data = in.read())!=-1){
  //System.out.println(data);
                 // 将数据放入字节数组
              buff[index++] = (byte)data;
                 // index++;
            };
             // 字节数组中的数据
             System.out.println(Arrays.toString(buff));
             // 根据字节数组创建一个字符串
             String str = new String(buff);
             System.out.println(str);
        }catch (IOException e){
             e.printStackTrace();
        }finally {
             try{
                 if(in!=null){
                     in.close();
                }
            }catch (IOException ex){
                 ex.printStackTrace();
            }
        }
    }
 }

输入流还提供了一些API可以按组读取数据

按组读取数据的API-1:

public class FileInputStreamTest1 {
    public static void main(String[] args) {
        // 通过文件名构建一个字节输入流
        InputStream in = null;
        try {
            // 根据文件创建输入流
            File file = new File("names.txt");
            in = new FileInputStream(file);
            // 按组读取
            // int read(byte[] b)读取最多b.length字节的数据到字节数组    返回值实际读取的长度。如果到达文件末尾返回-1
            // 准备一个字节数组
            byte [] buff = new byte[500];
            // 读取数据到缓冲数组
            int len = in.read(buff);
            System.out.println("实际读取的长度是:"+len);
            String str = new String(buff);// 根据整个数组创建字符串
            System.out.println(str);
            System.out.println("--------------");
            // 根据实际读取的字节长度创建字符串
            //(字节数组,偏移量,使用长度)
            String info = new String(buff,0,len);
            System.out.println(info);
        }catch (IOException e){
            e.printStackTrace();
        }finally {
            try{
                if(in!=null){
                    in.close();
                }
            }catch (IOException ex){
                ex.printStackTrace();
            }
        }
    }
}

按组读取数据API-2:

 public class FileInputStreamTest2 {
     public static void main(String[] args) {
         // 通过文件名构建一个字节输入流
         InputStream in = null;
         try {
             // 根据文件创建输入流
             File file = new File("names.txt");
             in = new FileInputStream(file);
             // 按组读取
             // read(byte[] b, int off, int len) 读取数据,从还数组的off位置开始放数据,放len长度
             // 准备一个字节数组
             byte [] buff = new byte[500];
             // 读取数据
             int len = in.read(buff, 100, 355);
             // 创建字符串
             String str = new String(buff,100,len);
             System.out.println(str);
        }catch (IOException e){
             e.printStackTrace();
        }finally {
             try{
                 if(in!=null){
                     in.close();
                }
            }catch (IOException ex){
                 ex.printStackTrace();
            }
        }
    }
 }

实际应用中读取文件:

上面的按组读取的例子中,都是一次性将所有的数据读取到内存中。

如果一个文件有一个T,我们肯定无法一次性读取到内存。

解决方案:按组,循环读取,重复使用缓冲数组。

看程序:

 public class FileInputStreamTest3 {
     public static void main(String[] args) {
         // 通过文件名构建一个字节输入流
         InputStream in = null;
         try {
             // 根据文件创建输入流
             File file = new File("data.txt");
             in = new FileInputStream(file);
             // 准备一个缓冲数组
             byte [] buff = new byte[1024];
             int len = -1;
             long total = 0;
             // 将每次读取到数组中的实际的长度赋值给len,如果len是-1表示已经到达文件末尾,结束读取
             while((len = in.read(buff))!=-1){
                 total += len;
                 // 输出缓冲数组中的数据
                 String str = new String(buff,0,len);
                 System.out.print(str);
            }
             System.out.println("\n读取的数据的总长度是:"+total);
        }catch (IOException e){
             e.printStackTrace();
        }finally {
             try{
                 if(in!=null){
                     in.close();
                }
            }catch (IOException ex){
                 ex.printStackTrace();
            }
        }
    }
 }

字节输出流OuputStream

OuputStream是所有的字节输出流的老祖宗。

API:

  • void close() 关闭此输出流并释放与此流相关联的任何系统资源。

  • void flush() 刷新此输出流并强制任何缓冲的输出字节被写出。

  • void write(byte[] b) 将 b.length字节从指定的字节数组写入此输出流。

  • void write(byte[] b, int off, int len) 从指定的字节数组写入 len个字节,从偏移 off开始输出到此输出流。

  • abstract void write(int b) 将指定的字节写入此输出流。

FileOutputStream--OutputStream的实现类

  • FileOutputStream(File file) 创建文件输出流以写入由指定的 File对象表示的文件。

  • FileOutputStream(File file, boolean append) 创建文件输出流以写入由指定的 File对象表示的文件。

  • FileOutputStream(String name) 创建文件输出流以指定的名称写入文件。

  • FileOutputStream(String name, boolean append) 创建文件输出流以指定的名称写入文件。

api就是父类中的方法。

构造方法案例:

public class FileOutputStreamTest1 {
    public static void main(String[] args) {
        // 申明字节输出流
        OutputStream out = null;
        try{
            // 创建流
            // 根据文件创建输出流
            File file = new File("st.txt");
            // 从文件开头开始写数据。 覆盖了原有的数据
            out = new FileOutputStream(file);
            // 根据文件创建输出流
            // append:
            //  true 从文件的末尾开始写,续在文件原有数据的后面。
            //  false  从文件的开头开始写,覆盖原有数据
            out = new FileOutputStream(file,true);
            // 根据文件名创建输出流
            // 从文件开头开始写数据。 覆盖了原有的数据
            out = new FileOutputStream("hell.txt");
            // append:
            //  true 从文件的末尾开始写,续在文件原有数据的后面。
            //  false  从文件的开头开始写,覆盖原有数据
            out = new FileOutputStream("hell.txt",true);
        }catch (IOException e){
            e.printStackTrace();
        }finally {
            try{
                if(out!=null){
                    out.close();
                }
            }catch (IOException ex){
                ex.printStackTrace();
            }
        }
    }
}

tips:如果我们创建的输出流指向的文件不存在,当我们写出数据的时候会自动创建这个文件。

三个写数据的API:

输出一个字节:

public class FileOutputStreamTest1 {
    public static void main(String[] args) {
        // 申明字节输出流
        OutputStream out = null;
        try{
            //--------------------------------------------------
            // 创建流
            out = new FileOutputStream("hell.txt",true);// ***
            // 准备一个字符串
            String str = "吃鸡全靠苟";
            // 将字符串转换为字节数组
            byte[] bytes = str.getBytes();//???
            // 循环的将字节数组的内容写入文件
            for(byte b : bytes) {
                out.write(b);// ****
            }
            //-------------------------------------------------
        }catch (IOException e){
            e.printStackTrace();
        }finally {
            try{
                if(out!=null){
                    out.close();
                }
            }catch (IOException ex){
                ex.printStackTrace();
            }
        }
    }
}

写出一个字节数组:

public class FileOutputStreamTest2 {
    public static void main(String[] args) {
        // 申明字节输出流
        OutputStream out = null;
        try{
            //------------------------
            // 创建流
            // 根据文件创建输出流
            File file = new File("st1.txt");
            // 从文件开头开始写数据。 覆盖了原有的数据
            out = new FileOutputStream(file);
            // 准备一个字符串
            String str = "吃鸡全靠苟";
            // 将字符串转换为字节数组
            byte[] bytes = str.getBytes();
            // 直接写出一个字节数组
            out.write(bytes);
            //------------------
        }catch (IOException e){
            e.printStackTrace();
        }finally {
            try{
                if(out!=null){
                    out.close();
                }
            }catch (IOException ex){
                ex.printStackTrace();
            }
        }
    }
}

写出字节数组的一部分:

 public class FileOutputStreamTest3 {
     public static void main(String[] args) {
         // 申明字节输出流
         OutputStream out = null;
         try{
             // 创建流
             // 根据文件创建输出流
             File file = new File("st2.txt");
             // 从文件开头开始写数据。 覆盖了原有的数据
             out = new FileOutputStream(file);
             // 准备一个字符串
             String str = "吃鸡全靠苟";
             // 将字符串转换为字节数组
             byte[] bytes = str.getBytes();
             // 直接写出一个字节数组的一部分
             // (字节数组,偏移量,长度) 从字节数组的偏移量位置开始写数据,写长度这么常
             out.write(bytes,0,6);
        }catch (IOException e){
             e.printStackTrace();
        }finally {
             try{
                 if(out!=null){
                     out.close();
                }
            }catch (IOException ex){
                 ex.printStackTrace();
            }
        }
    }
 }

一个需求:

从控制台输入一个商品的信息,将商品信息写入商品文件。文件中一行存储一个商品信息。

 /**
  * 从控制台输入一个商品的信息,将商品信息写入商品文件。文件中一行存储一个商品信息。
  */
 public class Ex1 {
     public static void main(String[] args) {
         Scanner sc = new Scanner(System.in);
         OutputStream out = null;
         try{
             // 创建输出流
             out = new FileOutputStream("products.txt",true);
             // 提示输出商品的内容
             String op = "";
             do{
                 System.out.println("请输入商品编号:");
                 String itemId = sc.next();
                 System.out.println("请输入商品名称:");
                 String itemName = sc.next();
                 System.out.println("请输入商品价格:");
                 String itemPrice = sc.next();
                 // 用户输入的数据要写入文件
                 String info = itemId+"&"+itemName+"&"+itemPrice+"\n";
                 // 将字符串写入文件
                 out.write(info.getBytes());
                 System.out.println("退出请输入(exit):");
                 op = sc.next();
            }while(!op.equalsIgnoreCase("exit"));
        }catch (IOException e){
             e.printStackTrace();
        }finally {
             try{
                 if(out!=null){
                     out.close();
                }
            }catch (IOException ex){
                 ex.printStackTrace();
            }
        }
    }
 }

再来一个需求:

将上一个需求写文件的商品,读取出来,使用[9527,尤克里里,1525] 形式输出到控制台。

 /**
  * 将上一个需求写文件的商品,读取出来,使用[9527,尤克里里,1525] 形式输出到控制台。
  */
 public class Ex2 {
     public static void main(String[] args) {
         // 读取所有的数据,再进行分割处理
         InputStream in = null;
         try{
             // 创建输入流
             in = new FileInputStream("products.txt");
             // 循环的读取所有的内容放在一个StringBuilde中
             StringBuilder sb = new StringBuilder();
             // 准备读取
             int len = -1;// 每次读取的实际长度
             byte [] buff = new byte[1024]; // 缓冲数组
             while((len = in.read(buff))!=-1){
                 // 将数据转换为字符串
                 String str = new String(buff,0,len);
                 // 将字符串键入StringBuilder
                 sb.append(str);
            }
             System.out.println(sb.toString());
             // 完整的商品信息字符串
             String ps = sb.toString();
             String[] rows = ps.split("\n");// 使用换行符拆分
             for (String row : rows){
                 // 9530&路基亚打电脑&150
                 String[] attrs = row.split("&");
                 String info = "["+attrs[0]+","+attrs[1]+","+attrs[2]+"]";
                 System.out.println(info);
            }
        }catch (IOException e){
             e.printStackTrace();
        }finally {
             try{
                 if(in!=null){
                     in.close();
                }
            }catch (IOException ex){
                 ex.printStackTrace();
            }
        }
    }
 }

文件拷贝:

文件拷贝流程:

我们来拷贝一个压缩文件:

 public class CopyFileTest {
     public static void main(String[] args) {
         InputStream in = null;
         OutputStream out = null;
         try {
             // -------------------------------------------
             // 创建输入流
             in = new FileInputStream("java-SE.rar");
             // 创建输出流
             out = new FileOutputStream("java-SE-V1.rar");
             // 使用字节流读取数据
             int len = -1;
             byte [] buff = new byte[1024];
             while((len = in.read(buff))!=-1){
                 // 写出数据
                 out.write(buff,0,len);
            }
             // ---------------------------------------------
        }catch (IOException e){
             e.printStackTrace();;
        }finally {
             try{
                 if(in != null){
                     in.close();
                }
                 if(out != null){
                     out.close();
                }
            }catch (IOException ex){
                 ex.printStackTrace();
            }
        }
    }
 }

字符流

字符流,就是按照字符处理文件的。

字节流,按照字节处理文件。可以处理所有的文件。一般使用中,字节流偏多。

文本文件:纯文本文件。不保存任何的格式,文件头之类的信息。

字符流往往是用来处理纯文本文件或者网络上的文本信息

字符输入流Reader

Reader本身是一个抽象类

API:

  • abstract void close() 关闭流并释放与之相关联的任何系统资源。

  • void mark(int readAheadLimit) 标记流中的当前位置。

  • boolean markSupported() 告诉这个流是否支持mark()操作。

  • int read() 读一个字符

  • int read(char[] cbuf) 将字符读入数组。

  • abstract int read(char[] cbuf, int off, int len) 将字符读入数组的一部分。

  • int read(CharBuffer target) 尝试将字符读入指定的字符缓冲区。

  • boolean ready() 告诉这个流是否准备好被读取。

  • void reset() 重置流。

  • long skip(long n) 跳过字符

FileReader --Reader类的子类

也是我们常用的字符输入流. (FileInputStream)

构造方法

  • FileReader(File file) 创建一个新的 FileReader ,给出 File读取。

  • FileReader(String fileName) 创建一个新的 FileReader ,给定要读取的文件的名称。

API:所有的API都是来自于父类。

使用:

 public class FileReaderTest1 {
     public static void main(String[] args) {
         FileReader reader = null;
         try{
             // 创建reader
             reader = new FileReader("names.txt");
             // 读取一个字符(这里返回的是整形)
             int read = reader.read();
             // 将整形强制转换为字符型
             char ch = (char)read;
             System.out.println(ch);
             // 读取一组字符
             // 准备一个字符缓冲区
             char [] buff = new char[20];
             reader.read(buff);
             System.out.println(Arrays.toString(buff));
             // 读取一部分字符,放在一个字符数组的某一部分
             int len = reader.read(buff,3,10);
             System.out.println(Arrays.toString(buff));
        }catch (IOException e){
             e.printStackTrace();
        }finally {
             try{
                 if(reader!=null)
                     reader.close();
            }catch (IOException ex){
                 ex.printStackTrace();
            }
        }
    }
 }

字符输出流Writer

API:

  • Writer append(char c) 将指定的字符附加到此作者。

  • Writer append(CharSequence csq) 将指定的字符序列附加到此作者。

  • Writer append(CharSequence csq, int start, int end) 将指定字符序列的子序列附加到此作者。

  • 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--在字节流中对应:FileOutputStream

构造方法:

  • FileWriter(File file) 给一个File对象构造一个FileWriter对象。

  • FileWriter(File file, boolean append) 给一个File对象构造一个FileWriter对象。

  • FileWriter(String fileName) 构造一个给定文件名的FileWriter对象。

  • FileWriter(String fileName, boolean append) 构造一个FileWriter对象,给出一个带有布尔值的文件名,表示是否附加写入的数据。

没有自己的特殊API。全部是父类的API。

案例:

 public class FileWriterTest1 {
     public static void main(String[] args) {
         FileWriter writer = null;
         try{
             // 创建writer (当写入数据的时候,会自动创建这个文件)
             writer = new FileWriter("rzs.txt",true);
             // 可以写数据了
             writer.write("木叶的旗木五五开老师!!!\n");
             // 也可以写一个字符
             writer.write(96);
             writer.write(97);
             // 可以写一个字符数组
             char [] chs = {'宇','智','波','五','五','开','\n'};
             writer.write(chs);
             // 写字符数组的一部分
             writer.write(chs,3,4);
        }catch (IOException e){
             e.printStackTrace();
        }finally {
             try{
                 if(writer!=null)
                     writer.close();
            }catch (IOException ex){
                 ex.printStackTrace();
            }
        }
    }
 }

下载一个HTML文件

 package com.qidian.spider;
 
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.net.HttpURLConnection;
 import java.net.MalformedURLException;
 import java.net.URL;
 import java.net.URLConnection;
 
 public class DownloadHtml {
     private static String path = "http://search.dangdang.com/?key=java&act=input";
     public static void main(String[] args) {
         // 申明
         InputStream in = null;
         OutputStream out = null;
         try {
             // 连接网络-------------
             URL url = new URL(path);
             HttpURLConnection urlConnection =
                (HttpURLConnection) url.openConnection();
             urlConnection.connect();
             // 得到指向这个网页的输入流
             in = urlConnection.getInputStream();
             out = new FileOutputStream("hehe.html");
             // 拷贝的过程
             int len = -1;
             byte [] buff = new byte[1024];
             while((len = in.read(buff))!=-1){
                 out.write(buff,0,len);
            }
        }catch (IOException e){
             e.printStackTrace();
        }finally {
             try{
                 if(in!=null)
                     in.close();
                 if(out!=null)
                     out.close();
            }catch (IOException ex){
                 ex.printStackTrace();
            }
        }
    }
 }

字符流的文件拷贝

 package com.qidian.demo1;
 
 import java.io.*;
 public class CopyFileTest {
     public static void main(String[] args) {
         Reader reader = null;
         Writer writer = null;
         try {
             reader = new FileReader("hehe.html");
             writer = new FileWriter("haha.html");
             int len = -1;
             char [] buff = new char[256];
             while((len = reader.read(buff))!=-1){
                 writer.write(buff,0,len);
            }
        }catch (IOException e){
             e.printStackTrace();
        }finally {
             try{
                 if(reader!=null)
                     reader.close();
                 if(writer!=null)
                     writer.close();
            }catch (IOException ex){
                 ex.printStackTrace();
            }
        }
    }
 }

缓冲字符输入流:BufferedReader

构造方法:

  • BufferedReader(Reader in) 创建使用默认大小的输入缓冲区的缓冲字符输入流。

  • BufferedReader(Reader in, int sz) 创建使用指定大小的输入缓冲区的缓冲字符输入流。

缓冲流中用到了装饰设计模式。 使用BufferedReader包裹一个Reader,实现缓冲。

其中大部分API和Reader没啥区别。有一个特殊的API:

  • String readLine() 读一行文字。

案例:

 public class BufferedReaderTest {
     public static void main(String[] args) {
         BufferedReader bufReader = null;
         Reader reader = null;// 这个reader对象就是让BufferedReader包裹的对象
         try{
             // 先创建reader
             reader = new FileReader("names.txt");
             // 创建缓冲输入流 (任何需要父类型的地方,都可以使用子类型代替)
             bufReader = new BufferedReader(reader);
             // 按行读取(读取不到数据的时候,返回null)
 //           String s = bufReader.readLine();
 //           System.out.println(s);
             String line = null;
             while((line = bufReader.readLine())!=null){
                 System.out.println(line);
            };
        }catch (IOException e){
             e.printStackTrace();
        }finally {
             try{
                 if(bufReader!=null)
                     bufReader.close();
            }catch (IOException ex){
                 ex.printStackTrace();
            }
        }
    }
 }

缓冲字符输出流--BufferedWriter

构造方法:

  • BufferedWriter(Writer out) 创建使用默认大小的输出缓冲区的缓冲字符输出流。

  • BufferedWriter(Writer out, int sz) 创建一个新的缓冲字符输出流,使用给定大小的输出缓冲区。

创建BufferedWriter的时候需要包裹一个Writer对象

特殊的API:

  • void newLine() 写一行行分隔符。

缓冲输出流要注意的问题:

当调用了缓冲输出流的write方法之后,数据可能只是写在缓冲区,并没有实际写到输出流。理论上当关闭输出流的时候,所有缓冲区的数据会刷出。 一般建议我们手动调用 flush方法刷出缓冲区数据。

案例:

 public class BufferedWriterTest {
     public static void main(String[] args) {
         BufferedWriter bufWriter = null;
         Writer writer = null;
         try{
             // 创建writer
             writer = new FileWriter("rzs.txt");
             // 创建BufferedWriter
             bufWriter = new BufferedWriter(writer);
             // 写数据
             // 循环写
             for(int i = 0;i < 100;i++){
                 // 缓冲流在调用了write方法之后,不一定把数据写出去了。
                 // 数据可能仅仅在缓冲区。
                 bufWriter.write("[adminName:'鸣人"+i+"',adminPass:'123456']");
                 // 写入一个换行符
                 bufWriter.newLine();
            }
             // 刷出缓冲区的数据
             //bufWriter.flush();
        }catch (IOException e){
             e.printStackTrace();
        }finally {
             // 缓冲流在关闭的时候,理论上会将缓冲区所有的数据全部写出去
             try{
                 if(bufWriter!=null)
                     bufWriter.close();
                 if(writer!=null)
                     writer.close();
            }catch (IOException ex){
                 ex.printStackTrace();
            }
        }
    }
 }

缓冲字节输入流--BufferedInputStream

构造方法

  • BufferedInputStream(InputStream in) 创建一个 BufferedInputStream并保存其参数,输入流 in ,供以后使用。

  • BufferedInputStream(InputStream in, int size) 创建 BufferedInputStream具有指定缓冲区大小,并保存其参数,输入流 in ,供以后使用。

其他的API,不需要说明,和FileOutputStream用法完全一致。只不过提高了底层的效率。

缓冲字节输出流:BufferedOutputStream

构造方法

  • BufferedOutputStream(OutputStream out) 创建一个新的缓冲输出流,以将数据写入指定的底层输出流。

  • BufferedOutputStream(OutputStream out, int size) 创建一个新的缓冲输出流,以便以指定的缓冲区大小将数据写入指定的底层输出流。

API........你就把它当FileOutputStream使用就行了。

我们使用 缓冲(高效)字节流完成一个文件拷贝的过程。

案例:

 public class CopyFileTest {
     public static void main(String[] args) {
         InputStream in = null;
         OutputStream out = null;
         BufferedInputStream bufIn = null;
         BufferedOutputStream bufOut = null;
         try{
             // 创建4个流。
             // 输入流
             in = new FileInputStream("java-SE.rar");
             bufIn = new BufferedInputStream(in);
             // 输出流
             out = new FileOutputStream("java-SE-V1.rar");
             bufOut = new BufferedOutputStream(out);
             // 拷贝过程。。。。。。
             int len = -1;
             byte [] buff = new byte[1024];
             while((len = bufIn.read(buff))!=-1){
                 bufOut.write(buff,0,len);
            }
        }catch (IOException e){
             e.printStackTrace();
        }finally {
             try{
                 // 输入流先关闭里面的流,在关闭外面的流
                 if(in!=null)
                     in.close();
                 if(bufIn!=null)
                     bufIn.close();
                 // 输出流先关闭外面的流,再关闭里面的流
                 if(bufOut!=null)
                     bufOut.close();
                 if(out!=null)
                     out.close();
            }catch (IOException ex){
                 ex.printStackTrace();
            }
        }
    }
 }

转换流目的:将字节流转换为字符流。 能不能将字符流转换为字节流?NO

转换输入流:InputStreamReader

构造方法:

  • InputStreamReader(InputStream in) 创建一个使用默认字符集的InputStreamReader。

  • InputStreamReader(InputStream in, Charset cs) 创建一个使用给定字符集的InputStreamReader。

  • InputStreamReader(InputStream in, CharsetDecoder dec) 创建一个使用给定字符集解码器的InputStreamReader。

  • InputStreamReader(InputStream in, String charsetName) 创建一个使用命名字符集的InputStreamReader。

API和Reader一致。

转换输出流:OutputStreamWriter

  • OutputStreamWriter(OutputStream out) 创建一个使用默认字符编码的OutputStreamWriter。

  • OutputStreamWriter(OutputStream out, Charset cs) 创建一个使用给定字符集的OutputStreamWriter。

  • OutputStreamWriter(OutputStream out, CharsetEncoder enc) 创建一个使用给定字符集编码器的OutputStreamWriter。

  • OutputStreamWriter(OutputStream out, String charsetName) 创建一个使用命名字符集的OutputStreamWriter。

API和writer完全一致。

案例:转换输入流。

 public class InputStreamReaderTest {
     public static void main(String[] args) {
         InputStream in = null;
         InputStreamReader reader = null;
         BufferedReader bufReader = null;
         try{
             // 创建这些流
             // 创建字节流
             in = new FileInputStream("names.txt");
             // 将字节流转换字符流
             reader = new InputStreamReader(in);
             // 将字符流包裹为缓冲字符流
             bufReader = new BufferedReader(reader);
             // 循环读取数据
             String line = null;
             while((line = bufReader.readLine())!=null){
                 System.out.println(line);
            };
        }catch (IOException e){
             e.printStackTrace();
        }finally {
             try{
                 if(bufReader!=null)
                     bufReader.close();
            }catch (IOException ex){ex.printStackTrace();}
        }
    }
 }

字节输出流转换为字符输出流:

案例:

public class OutputStreamWriterTest {
    public static void main(String[] args) {
        OutputStream out = null;
        OutputStreamWriter writer = null;
        try {
            // 创建字节流
            out = new FileOutputStream("hehe.txt");
            // 将字节流包裹成字符流
            writer = new OutputStreamWriter(out);
            // 写字符串
            writer.write("一个字符串\n");
            writer.write("下午两点上课\n");
            writer.write("上完可,做好防护,去做核算");
            writer.flush();
        }catch (IOException e){
            e.printStackTrace();
        }finally {
            try{
                if(out!=null){
                    out.close();
                }
            }catch (IOException ex){
                ex.printStackTrace();
            }

        }
    }
}

对象流

序列化

为了方便数据的存储或者传输,将数据或者对象转换为可存储或者传输的形式的过程称之为序列化相反就是反序列化。

例子:在线购物,卖家给你打包的过程就是序列化。你拆包裹的过程就是反序列化

项目中的:

将一个对象转换为json输出到客户端。将对象转换为json就是序列化。

将JSON转换对象的过程就是反序列化。

对象流就是专门用来对对象进行序列化和反序列化。

对象输出流:ObjectOutputStream(写出)

专门用来序列化对象的。(卖家打包)

构造方法:

  • ObjectOutputStream(OutputStream out) 创建一个写入指定的OutputStream的ObjectOutputStream。

对象输出流内部其实是包裹了一个字节输出流。

对象输出流的作用:

API:

  • void close() 关闭流。

  • void write(byte[] buf) 写入一个字节数组。

  • void write(byte[] buf, int off, int len) 写入一个子字节数组。

  • void write(int val) 写一个字节。

  • void writeBoolean(boolean val) 写一个布尔值。

  • void writeByte(int val) 写入一个8位字节。

  • void writeBytes(String str) 写一个字符串作为字节序列。

  • void writeChar(int val) 写一个16位的字符。

  • void writeChars(String str) 写一个字符串作为一系列的字符。

  • void writeDouble(double val) 写一个64位的双倍。

  • void writeFloat(float val) 写一个32位浮点数。

  • void writeInt(int val) 写一个32位int。

  • void writeLong(long val) 写一个64位长

  • void writeShort(int val) 写一个16位短。

  • void writeObject(Object obj) 将指定的对象写入ObjectOutputStream。

对象输出流是写一个对象出去的操作。

java中都有哪些对象?

java中的对象真的很多,但是所有的对象统称为Object。其实对象流特意准备了几个方法,写这些对象。

  • writeXxxx(Xxx xxx) 这里的Xxxx表示任何一个基本类型。

  • writeObject(Object obj) 其余的类型全部使用Object。

案例1: 序列化基本类型

public class ObjectWriteTest1 {
    public static void main(String[] args) {
        OutputStream out = null;
        ObjectOutputStream objOut = null;
        try{
            // 创建字节输出流
            out = new FileOutputStream("ints.txt");
            // 创建对象流
            objOut = new ObjectOutputStream(out);
            // 写出一个整形
            objOut.writeInt(12);
            objOut.writeDouble(12.5);
            // 刷出数据
            objOut.flush();
        }catch (IOException e){
            e.printStackTrace();
        }finally {
            try{
                if(out!=null)
                    out.close();
            }catch (IOException ex){
                ex.printStackTrace();;
            }
        }
    }
}

案例2: 序列化自定义类型

准备一个实体类:

public class User {
    private int id;
    private String name;
    
}

测试:

出现异常。

这个异常产生的原因是因为ObjectOutputStream写的对象必须实现接口java.io.Serializable否则无法序列化。 这个接口本身是没有任何的方法和成员的,它就是用来标记一个类的对象是否可以被序列化。

public interface Serializable {
}

我们让我们的User类实现这个接口:

完整的测试代码:

public class ObjectWriteTest2 {
    public static void main(String[] args) {
        OutputStream out = null;
        ObjectOutputStream objOut = null;
        try{
            // 创建字节输出流
            out = new FileOutputStream("u.txt");
            // 创建对象流
            objOut = new ObjectOutputStream(out);
            // 创建一个User
            User u = new User(1,"就乌尔奇");
            // 写出对象u
            objOut.writeObject(u);
            // 刷出数据
            objOut.flush();
        }catch (IOException e){
            e.printStackTrace();
        }finally {
            try{
                if(out!=null)
                    out.close();
            }catch (IOException ex){
                ex.printStackTrace();;
            }
        }
    }
}

对象输入流:ObjectInputStream(读取)

专门用来反序列化对象的。(买家拆包裹)

构造方法:

  • ObjectInputStream(InputStream in) 创建从指定的InputStream读取的ObjectInputStream。

ObjectInputStream会包裹一个InputStream

其他的API:

  • int read() 读取一个字节的数据。

  • int read(byte[] buf, int off, int len) 读入一个字节数组。

  • boolean readBoolean() 读取布尔值。

  • byte readByte() 读取一个8位字节。

  • char readChar() 读一个16位字符。

  • double readDouble() 读64位双倍。

  • float readFloat() 读32位浮点数。

  • int readInt() 读取一个32位int。

  • long readLong() 读64位长。

  • short readShort() 读取16位短。

  • Object readObject() 从ObjectInputStream读取一个对象。

有个小问题:无法判定是否到达了文件的末尾。

当使用ObjectInputStream读取数据的时候,如果出现了EOFException。说明到达了文件的末尾。

案例1: 读取上一小节写入文件的int和double。

public class ObjectInputTest1 {
    public static void main(String[] args) {
        InputStream in = null;
        ObjectInputStream objIn = null;
        try{
            // 创建字节流
            in = new FileInputStream("ints.txt");
            // 创建对象流
            objIn = new ObjectInputStream(in);
            // 反序列化数据
            int i = objIn.readInt();
            System.out.println(i);
            double d = objIn.readDouble();
            System.out.println(d);
        }catch (IOException e){
            e.printStackTrace();
        }finally {
            try{
                if(objIn!=null)
                    objIn.close();
            }catch (IOException ex){
                ex.printStackTrace();
            }
        }
    }
}

案例2:读取写入的自定义对象

public class ObjectInputTest2 {
    public static void main(String[] args) {
        InputStream in = null;
        ObjectInputStream objIn = null;
        try{
            // 创建字节流
            in = new FileInputStream("u.txt");
            // 创建对象流
            objIn = new ObjectInputStream(in);
            // 反序列化数据
            Object o = objIn.readObject();
            // 类型转换
            User u = (User) o;
            System.out.println(u);
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            try{
                if(objIn!=null)
                    objIn.close();
            }catch (IOException ex){
                ex.printStackTrace();;
            }
        }
    }
}

一组对象的读写

情况1: 循环读写

循环的写入数据

public class ObjectWriteTest3 {
    public static void main(String[] args) {
        OutputStream out = null;
        ObjectOutputStream objOut = null;
        try{
            // 创建字节输出流
            out = new FileOutputStream("us.txt");
            // 创建对象流
            objOut = new ObjectOutputStream(out);
            // 创建一组User
            List<User> us = new ArrayList<>();
            for (int i = 0;i< 100; i++){
                us.add(new User(i+1,"鸣人"+i));
            }
            // 循环的写出对象
            for (User u : us) {
                objOut.writeObject(u);
            }
            // 刷出数据
            objOut.flush();
        }catch (IOException e){
            e.printStackTrace();
        }finally {
            try{
                if(out!=null)
                    out.close();
            }catch (IOException ex){
                ex.printStackTrace();;
            }
        }
    }
}

循环的读数据

public class ObjectInputTest3 {
    public static void main(String[] args) {
        InputStream in = null;
        ObjectInputStream objIn = null;
        try{
            // 创建字节流
            in = new FileInputStream("us.txt");
            // 创建对象流
            objIn = new ObjectInputStream(in);
            // 反序列化数据
            // 循环读取数据
            // 知道循环次数的处理
//            for (int i = 0;i < 100;i++){
//                Object o = objIn.readObject();
//                User u = (User) o;
//                System.out.println(u);
//            }
            // 不知道循环次数
            while(true){
                // 当到达文件的末尾的时候,会抛出异常EOFException
                Object o = objIn.readObject();
                User u = (User) o;
                System.out.println(u);
            }
        }catch(EOFException e){
            System.out.println("读取完成");
        } catch (Exception e){
            e.printStackTrace();
        }finally {
            try{
                if(objIn!=null)
                    objIn.close();
            }catch (IOException ex){
                ex.printStackTrace();;
            }
        }
    }
}