大数据学习笔记——Java篇之IO

IO学习笔记整理

1. File类

1.1 File对象的三种创建方式:

File对象是一个抽象的概念,只有被创建出来之后,文件或文件夹才会真正存在

注意:File对象想要创建成功,它的目录必须存在!

import java.io.File;

/*
    演示三种创建File对象的方式
 */
public class FileDemo {
    public static void main(String[] args) throws Exception {
        //指定完整路径名的字符串
        File f1 = new File("e:/test/a.txt");
        //指定父路径名以及子路径名的字符串
        File f2 = new File("e:/test", "b.txt");
        //指定父路径的File对象以及子路径名的字符串
        File f3 = new File(new File("e:/test"), "c.txt");

        f1.createNewFile();
        f2.createNewFile();
        f3.createNewFile();

    }
}

如果使用的是相对路径(即不以盘符开头),那么默认的是当前项目的路径

1.2 mkdir和createNewFile方法

mkdir和createNewFile两个方法的区别,前者看成文件夹,哪怕写上了后缀,后者看成文件

import java.io.File;

/*
    演示mkdir和createNewFile的区别
    注:如果出现同名的文件以及文件夹,File会首先创建文件!
 */
public class FileDemo2 {
    public static void main(String[] args) throws Exception {
        File file1 = new File("e:/test/a.txt");
        File file2 = new File("e:/test/aaa.txt");
        //创建一个文本文件
        file1.createNewFile();
        //创建一个名为aaa.txt的文件夹
        file2.mkdir();
    }
}

在调用delete方法的时候要注意要删除的文件夹不能包含内容,否则就会报错

1.3 renameTo方法

renameTo方法如果在不同路径,就相当于剪切加重命名

import java.io.File;

/*
    演示rename方法以及剪切的效果
 */
public class FileDemo3 {
    public static void main(String[] args) {
        File srcFile = new File("e:/test/a.txt");
        File destFile = new File("e:/test/b.txt");
        //如果源文件和目标文件拥有相同的父目录,那么就是改名操作
        //srcFile.renameTo(destFile);
        //如果两者的父目录不相同,那么就相当于剪切并改名
        File destFile2 = new File("e:/test1/b.txt");
        srcFile.renameTo(destFile2);
    }
}

1.4 length方法的说明

关于length()方法,如果是文件的话返回文件的字节数,而如果是文件夹的话,是不确定的

import java.io.File;

/*
    关于length方法的说明
 */
public class FileDemo4 {
    public static void main(String[] args) {
        //如果是一个文件的话,length方法即返回这个文件的字节数
        File f1 = new File("e:/test/a.txt");
        System.out.println(f1.length());
        //如果是文件夹的话,返回值是不确定的,不一定是0,有时会是别的数字
        File f2 = new File("e:/test");
        System.out.println(f2.length());
    }
}

1.5 listFiles方法

listFiles方法的优点是由于返回的是一个File类型的数组,而File本身有各种各样的方法,因此非常灵活,可以实现各种操作

import java.io.File;
import java.io.FilenameFilter;

/*
    演示listFiles方法,实现找到一个文件夹下一级子目录的所有txt文件,并把文件名打印出来
 */
public class FileDemo5 {
    public static void main(String[] args) {
        //常规方法
        File f = new File("e:/test");
        File[] files = f.listFiles();
        for (File file : files) {
            if(file.getName().endsWith(".txt")){
                System.out.println(file.getName());
            }
        }
        System.out.println("<---------------------->");
        //使用FileNameFilter过滤出a.txt这个文件
        //采用匿名内部类方式
        File[] files1 = f.listFiles(new FilenameFilter() {
            public boolean accept(File dir, String name) {
                return new File(dir,name).getName().equals("a.txt");
            }
        });
        for (File file : files1) {
            System.out.println(file.getName());
        }
    }
}

1.6 递归操作(查找以及删除)

递归地查找某一类文件

import java.io.File;

/*
    演示递归地查找jpg文件并将文件名打印出来
 */
public class FileRecursiveDemo1 {
    public static void main(String[] args) {
        findJPG(new File("e:"));
    }
    public static void findJPG(File file){
        File[] files = file.listFiles();
        for (File f : files) {
            if(f.isDirectory()){
                findJPG(f);
            }
            else{
                if(f != null && f.getName().toUpperCase().endsWith("JPG")){
                    System.out.println(f.getName());
                }
            }
        }
    }
}

递归删除文件夹

import java.io.File;

/*
    演示递归删除文件夹以及文件夹下所有内容
 */
public class FileRecursiceDemo2 {
    public static void main(String[] args) {
        deleteDir(new File("e:/test"));
    }
    public static void deleteDir(File f){
        File[] files = f.listFiles();
        for (File file : files) {
            if(file.isDirectory()){
                deleteDir(file);
            }else{
                file.delete();
            }
        }
        //文件都删除干净后,最后再把文件夹删除即可
        f.delete();
    }
}

2. 输入流和输出流

2.1 java IO框架中各种流的继承及实现图:

大数据学习笔记——Java篇之IO_第1张图片

2.2 带异常处理的拷贝程序

基本思路:在try块中执行基本业务逻辑,catch中捕捉异常,记住使用Exception兜底,最后将关流操作放在finally中执行

import java.io.*;

/*
    演示带完整异常处理的文件拷贝程序
 */
public class CopyFileDemo {
    public static void main(String[] args) {
        BufferedReader br = null;
        BufferedWriter bw = null;
        try {
            br = new BufferedReader(new FileReader("e:/test/a.txt"));
            bw = new BufferedWriter(new FileWriter("e:/test/b.txt"));
            String line = null;
            while((line = br.readLine()) != null){
                bw.write(line);
                bw.newLine();
            }
        } catch (FileNotFoundException e) {
            System.out.println("File Not Found !!!");
        } catch (IOException e){
            System.out.println("IO Exception !!!");
        } catch (Exception e){
            System.out.println("Unknown Exception !!!");
        }finally {
            if(br != null){
                try {
                    br.close();
                } catch (IOException e) {
                }
            }
            if(bw != null){
                try {
                    bw.close();
                } catch (IOException e) {
                }
            }
        }
    }
}

新版try-catch语句,可以将需要关闭的流使用try(){}形式放在小括号内,这样就不用再写finally块就能自动关流了

import java.io.*;

/*
    演示带完整异常处理的文件拷贝程序升级版
 */
public class CopyFileDemo2 {
    public static void main(String[] args) {
        try (
                BufferedReader br = new BufferedReader(new FileReader("e:/test/a.txt"));
                BufferedWriter bw = new BufferedWriter(new FileWriter("e:/test/b.txt"));
                ){
                String line = null;
                while((line = br.readLine()) != null){
                    bw.write(line);
                    bw.newLine();

                }
        } catch (FileNotFoundException e) {
            System.out.println("File Not Found !!!");
        } catch (IOException e){
            System.out.println("IO Exception !!!");
        } catch (Exception e){
            System.out.println("Unknown Exception");
        }
    }
}

2.3 三种方式触发流的关闭

import java.io.BufferedOutputStream;
import java.io.FileOutputStream;

/*
    演示三种方式触发流的关闭
 */
public class StreamCloseDemo {
    public static void main(String[] args) throws Exception {
        //test1();
        //test2();
        test3();
    }

    public static void test1() throws Exception {
        //使用close方法关闭流
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("e:/test/a.txt"));
        bos.write("hello world".getBytes());
        bos.close();
    }
    public static void test2() throws Exception{
        //手动刷新
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("e:/test/a.txt"));
        bos.write("hello world".getBytes());
        bos.flush();
    }
    public static void test3() throws Exception{
        //当写出的字节数大于等于8192的时候,就会自动触发关流
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("e:/test/a.txt"));
        bos.write(new byte[8192]);
    }
}

对于第三种方式,对比图如下:

当写出的字节数为8191的时候,文件字节大小为0字节,说明并未关流

 

 

而当写出字节数为8192的时候,文件字节正好为8KB,说明写出成功!

2.4 数据流

由于后期在用到hadoop中的时候会涉及到ComparableWritable接口的重写方面的知识,因此这里需要关注,特别关注writeInt方法的底层实现!可以查看这些方法的底层源码!

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;

/*
    演示数据流的基本方法:
    1.数据流具有丰富的API可以读写各种各样的数据类型而不用每次都调用getBytes()方法
    2.注意,若用数据流将数据写出到文本文件,则会出现乱码,解决方案是再用read方法读回来
 */
public class DataInputOutputStreamDemo {
    public static void main(String[] args) throws Exception {
        DataOutputStream dos = new DataOutputStream(new FileOutputStream("e:/test/a.txt"));
        dos.writeBoolean(true);
        dos.writeByte(97);
        dos.writeChar('b');
        dos.writeInt(10);
        dos.writeDouble(3.14);
        dos.writeUTF("hello world");
        //然后用相对应的方法读回来
        DataInputStream dis = new DataInputStream(new FileInputStream("e:/test/a.txt"));
        System.out.println(dis.readBoolean());
        System.out.println(dis.readByte());
        System.out.println(dis.readChar());
        System.out.println(dis.readInt());
        System.out.println(dis.readDouble());
        System.out.println(dis.readUTF());
    }
}

 

WriteInt和ReadInt的源码分析:

  // writeInt
    public final void writeInt(int v) throws IOException {
        out.write((v >>> 24) & 0xFF);
        out.write((v >>> 16) & 0xFF);
        out.write((v >>>  8) & 0xFF);
        out.write((v >>>  0) & 0xFF);
        incCount(4);
    }

 

查看了writeInt方法的实现原理,可知,它底层是通过将一个int类型的数据通过无符号右移操作从高位到低位一个一个拿出8位来实现的,而与0xFF进行与运算则保证了,除了这8位,其他位全部都是0,而readInt()方法则正好相反,将所有的获得的4个数字再进行左移操作,再加到一起去即可,具体代码如下:

public final int readInt() throws IOException {
        int ch1 = in.read();
        int ch2 = in.read();
        int ch3 = in.read();
        int ch4 = in.read();
        if ((ch1 | ch2 | ch3 | ch4) < 0)
            throw new EOFException();
        return ((ch1 << 24) + (ch2 << 16) + (ch3 << 8) + (ch4 << 0));
    }

 

知道了底层原理之后,自己也可以写字节数组和int值相互转换的方法:

/*
    写两个将字节数组与int值相互转换的方法
 */
public class MyUtils {

    public static void main(String[] args) {
        //进行测试
        int b = -256;
        byte[] bytes = int2Bytes(b);
        System.out.println(bytes2Int(bytes));
    }

    public static byte[] int2Bytes(int i){
        byte[] bytes = new byte[4];
        bytes[0] = (byte) (i >> 24);
        bytes[1] = (byte) (i >> 16);
        bytes[2] = (byte) (i >> 8);
        bytes[3] = (byte) (i >> 0);
        return bytes;
    }

    public static int bytes2Int(byte[] bytes){
        int i1 = bytes[0] & 0xFF << 24;
        int i2 = bytes[1] & 0xFF << 16;
        int i3 = bytes[2] & 0xFF << 8;
        int i4 = bytes[3] & 0xFF << 0;
        return i1 + i2 + i3 + i4;
    }
}

 

2.5 打印流

打印流是单向的,只有输出方法,没有读取方法,但是有其特殊的打印方法,并且经过设置可以实现自动关流

import java.io.FileOutputStream;
import java.io.PrintWriter;

/*
    演示打印流的基本操作
 */
public class PrintWriterDemo {
    public static void main(String[] args) throws Exception {
        //设置成可以自动关流的那种,根据文档可知,自动关流只对println等方法有效
        PrintWriter pw = new PrintWriter(new FileOutputStream("e:/test/a.txt"),true);
        //使用自带的特殊方法
        pw.println(10);
        pw.println(3.14);
        pw.println("hello world");
    }
}

2.6 内存流

本质上不涉及到文件的IO,是用数组来实现的,写的时候会写出到一个无名的数组,当调用toByteArray方法时可以返回字节数组

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;

/*
    演示内存流的基本方法
 */
public class ByteArrayInputOutputStreamDemo {
    public static void main(String[] args) throws Exception {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        baos.write("hello world".getBytes());
        byte[] bytes = baos.toByteArray();
        System.out.println(new String(bytes));
        ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
        int len = 0;
        byte[] buf = new byte[1024];
        len = bais.read(buf);
        System.out.println(new String(buf,0,len));

    }
}

2.7 RandomAccessFile(随机访问文件)

import java.io.RandomAccessFile;

/*
    演示随机访问文件类:
    1.该类相当于是一个巨大的byte数组,即可以读文件又可以写文件
    2.具有seek方法可以随便移动指针
    3.有只读模式以及正常模式
 */
public class RandomAccessFileDemo {
    public static void main(String[] args) throws Exception {
        //指定的模式使得它既可以读,又可以写
        RandomAccessFile raf = new RandomAccessFile("e:/test/a.txt", "rw");
        raf.writeByte(98);
        raf.writeLong(100L);
        raf.writeUTF("hello world");
        //试验:将指针移动至第9个字节处,查看是否可以读到hello world字符串
        raf.seek(9L);
        System.out.println(raf.readUTF());
    }
}

2.8 Properties类(配置文件)

此类并不是IO,但是和IO关系很紧密,查看源码可知它是继承自HashTable的,因此可以把它当成map使用

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.util.Properties;

/*
    演示一个简单的配置文件的读取以及更新等操作
 */
public class PropertiesDemo {
    public static void main(String[] args) throws Exception {
        //先用一个输入流读取已经存在了的配置文件
        FileInputStream fis = new FileInputStream("e:/test/prop.txt");
        Properties prop = new Properties();
        prop.load(fis);
        System.out.println(prop.getProperty("name"));
        //进行更新操作,如果不存在,那就是添加
        prop.setProperty("id","0001");
        //更新的内容还是输出到原来文件中去
        //如果不想写上comments的话,可以写null,但是绝对不可以出现中文,否则就会出现乱码
        prop.store(new FileOutputStream("e:/test/prop.txt"),null);
    }
}

2.9 对象输出 / 输入流 (ObjectOutputStream / ObjectInputStream)

对象输出流使得对象,这样一个具有立体结构类型的数据能够永久化地保存在磁盘上去,(这一过程就叫做持久化,在之后的大数据学习过程中会反复接触到),这样就使得对象可以脱离程序而存在,该过程就叫做“序列化”,而"反序列化“则正好相反,将一个已经保存在磁盘上的对象再反过来读到内存中

想要序列化一个对象,必须具备这样一个前提,那就是该对象要实现Serializable接口,该接口十分特殊,方法体内没有任何代码,是一个标记性接口,相当于在告诉用户,如果对象要想序列化,就得实现这个接口,不实现它,那就不能用!

public interface Serializable {
}

 

判断末尾的方法:之前的都要一个结束符,-1或是null,而Object却没有,因此,判断结束条件的方法不一样

需要注意的是:对象流和之前接触到的流判断是否达到文件末尾的方法不一样,由于对象流在写出到文件时,并不会在末尾添加-1或null这样的结束符,因此通过原来的方式判断是否达到文件末尾将不再适用,会抛出EOFException,解决方案是将所有的对象全部放在一个集合中,在反序列化时一次性读取整个集合即可,演示代码如下所示:

/*
    演示对象流
    演示解决EOFException的方案
 */

import java.io.*;
import java.util.ArrayList;

class Employee implements Serializable {
    private int id;
    private String name;

    public Employee() {
    }

    public Employee(int id, String name) {
        this.id = id;
        this.name = name;
    }

    public int getId() {
        return id;
    }

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

    public String getName() {
        return name;
    }

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

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

public class ObjectStreamDemo {
    public static void main(String[] args) throws Exception {
        //serialize();
        deSerialize();
    }
    //序列化方法
    public static void serialize() throws Exception {
        //创建100个员工对象将它们放到集合中并持久化保存起来
        ArrayList list = new ArrayList<>();
        for(int i = 1; i <= 100; i++){
            Employee emp = new Employee();
            emp.setId(i);
            emp.setName("tom" + i);
            list.add(emp);
        }
        //对象输出流
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("e:/test/oos.txt"));
        //使用该输出流特有的方法writeObject()
        oos.writeObject(list);
        oos.close();
    }
    //反序列化方法
    public static void deSerialize() throws Exception{
        //一次性读取一整个集合,这样就避免了EOFException异常的问题
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("e:/test/oos.txt"));
        Object obj = ois.readObject();
        ArrayList list = (ArrayList) obj;
        for (Object o : list) {
            System.out.println((Employee) o);
        }
    }
} 
  
 

 

3. IO的应用

3.1 切割小文件 & 合并小文件

  /**
     * 给定需要进行切割的大文件的文件路径,需要存放小文件的目标文件夹,以及每个小文件的大小
     *
     * @param srcFilePath 源文件的路径
     * @param destDir 目标文件夹
     * @param size 每个小文件的大小
     */
    public static void splitFile(String srcFilePath, String destDir, long size){
        //先进行判断,只有源文件的大小大于等于小文件大小时,才进行切割
        File srcFile = new File(srcFilePath);
        long srcFileLen = srcFile.length();
        if(srcFileLen < size){
            System.out.println("Small file's size is larger than source file's size!");
        }else{
            //判断能够把文件切割成几份
            int count = (int) (srcFileLen % size == 0 ? srcFileLen / size : srcFileLen / size + 1);
            //遍历count变量,如果是最后一个小文件的话,重新计算文件长度
            long len = size;
            for(int i = 0; i < count; i++){
                if(srcFileLen % size != 0 && i == count - 1){
                    len = srcFileLen % size;
                }
                copyFile(srcFile,destDir,i,i * size,len);
            }
        }
    }

    /**
     * 将切割好的文件进行合并,合并到另一个目录下
     *
     * @param srcDir 源文件夹
     * @param destDir 目标文件夹
     */
    public static void mergeFile(String srcFilePath, String srcDir, String destDir){
        String srcFileName = new File(srcFilePath).getName();
        FileOutputStream fos = null;
        try {
            fos = new FileOutputStream(new File(destDir,srcFileName));
            File[] files = new File(srcDir).listFiles();
            for (File file : files) {
                FileInputStream fis = new FileInputStream(file);
                byte[] buf = new byte[1024 * 8];
                int len = 0;
                while((len = fis.read(buf)) != -1){
                    fos.write(buf,0,len);
                }
                fis.close();
            }

        } catch (FileNotFoundException e) {
            System.out.println("File Not Found!");
        } catch (Exception e){
            System.out.println("Unknown Exception!");
        } finally {
            if(fos != null){
                try {
                    fos.close();
                } catch (IOException e) {
                }
            }
        }
    }

    /**
     *
     * 单独将拷贝文件方法抽取出来
     *
     * @param srcFile 源文件
     * @param destDir 目标文件夹
     * @param i 小文件索引
     * @param offset 被切割文件的偏移量,即从哪个字节开始切割
     * @param len 需要拷贝的小文件的长度
     */
    public static void copyFile(File srcFile, String destDir, int i, long offset, long len){
        RandomAccessFile raf = null;
        FileOutputStream fos = null;
        try {
            raf = new RandomAccessFile(srcFile,"rw");
            raf.seek(offset);
            byte[] buf = new byte[(int) len];
            //一次性把数组读满
            raf.read(buf);
            fos = new FileOutputStream(new File(destDir, srcFile.getName() + "_" + i));
            fos.write(buf);

        } catch (FileNotFoundException e) {
            System.out.println("File Not Found!");
        } catch (Exception e){
            System.out.println("Unknown Exception!");
        } finally {
            if(raf != null){
                try {
                    raf.close();
                } catch (IOException e) {
                }
            }
            if(fos != null){
                try {
                    fos.close();
                } catch (IOException e) {
                }
            }
        }
    }
}

3.2 归档文件 & 解档文件

简单说明:文件归档过后若想要解档,则必须要知道文件名是什么,以及文件长度是多少,因此,在进行归档操作时,必须写入这些控制字符,然后在解档时先对这些控制字符进行解析,解析完之后才能真正地进行文件的写出

/**
     * 指定源文件夹,将文件夹下的所有文件夹归档至目标文件夹去
     *
     * @param srcDir 源文件夹
     * @param destDir 目标文件夹
     */
    public static void archiveFile(String srcDir, String destDir){
        FileOutputStream fos = null;
        try {
            fos = new FileOutputStream(new File(destDir, "archive.dat"));
            //列出这个文件夹下的文件
            File[] files = new File(srcDir).listFiles();
            for (File file : files) {
                if(file.isFile()){
                    //先写文件名的长度,一个字节存放足够
                    int fileNameLen = file.getName().length();
                    fos.write(fileNameLen);
                    //然后写真实的文件名
                    fos.write(file.getName().getBytes());
                    //写四个字节的文件内容长度,自己手写一个工具类
                    fos.write(int2Bytes((int) file.length()));
                    //开始正式拷贝真实的文件
                    FileInputStream fis = new FileInputStream(file);
                    byte[] buf = new byte[1024 * 8];
                    int len = 0;
                    while((len = fis.read(buf)) != -1){
                        fos.write(buf,0,len);
                    }
                    fis.close();
                }
            }
        } catch (FileNotFoundException e) {
            System.out.println("File Not Found!");
        } catch (Exception e){
            System.out.println("Unknown Exception!");
        } finally {
            //关闭资源
            if(fos != null){
                try {
                    fos.close();
                } catch (IOException e) {
                }
            }
        }
    }

    /**
     * 将归好档的文件进行解档操作至目标文件夹中去
     *
     * @param srcDir 已经归好档的文件所在的文件夹
     * @param destDir 需要解档到的目标文件夹
     *
     */
    public static void unarchiveFile(String srcDir, String destDir) {
        FileInputStream fis = null;
        try {
            fis = new FileInputStream(new File(srcDir,"archive.dat"));
            //先用read方法读一个字节读出文件名长度的字节
            int b = 0;
            while((b = fis.read()) != -1){
                byte[] fileNameBytes = new byte[b];
                //读取b长度字节数的文件名数组
                fis.read(fileNameBytes);
                //解析出文件名
                String fileName = new String(fileNameBytes);
                //读取4个字节的byte数组并将其还原回int值表示文件的真实长度
                byte[] fileLenBytes = new byte[4];
                fis.read(fileLenBytes);
                int fileLen = bytes2Int(fileLenBytes);
                //读取fileLen长度的文件真实数据
                byte[] fileBytes = new byte[fileLen];
                fis.read(fileBytes);
                FileOutputStream fos = new FileOutputStream(new File(destDir, fileName));
                fos.write(fileBytes);
                fos.close();
            }
        } catch (FileNotFoundException e) {
            //System.out.println("File Not Found!");
        } catch (Exception e){
            System.out.println("Unknown Exception!");
        } finally {
            if(fis != null){
                try {
                    fis.close();
                } catch (IOException e) {
                }
            }
        }
    }

    /**
     * 将一个整型数转换成字节数组
     *
     * @param i 传入的int值
     */
    public static byte[] int2Bytes(int i){
        byte[] bytes = new byte[4];
        bytes[0] = (byte) (i >> 24);
        bytes[1] = (byte) (i >> 16);
        bytes[2] = (byte) (i >> 8);
        bytes[3] = (byte) (i >> 0);
        return bytes;
    }

    /**
     * 将一个字节数组还原成int值
     *
     * @param bytes 长度为4的字节数组
     *
     */
    public static int bytes2Int(byte[] bytes){
        int i1 = (bytes[0] & 255) << 24;
        int i2 = (bytes[1] & 255) << 16;
        int i3 = (bytes[2] & 255) << 8;
        int i4 = (bytes[3] & 255) << 0;
        return i1 + i2 + i3 + i4;
    }

你可能感兴趣的:(大数据学习笔记——Java篇之IO)