栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

Java中的IO流

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Java中的IO流

1.常见文件操作 1.1 创建文件对象
// 方式1:
String filePath = "D:\test.txt";
File file = new File(filePath);  // 此时还未创建,只是在内存创建了对象
try {
    file.createNewFile();  // 此时才写入硬盘
} catch (IOException e) {
    e.printStackTrace();
}
// 方式2:父目录文件+子路径
File parentFile = new File("D:\");
String filename = "test2.txt";
File file = new File(parentFile, filename);
try {
    file.createNewFile();
} catch (IOException e) {
    e.printStackTrace();
}
// 方式3:父目录名称+子路径
String parentname = "D:\";
String filename = "test3.txt";
File file = new File(parentname, filename);
try {
    file.createNewFile();
} catch (IOException e) {
    e.printStackTrace();
}
1.2 获取文件相关信息
File file = new File("D:\test.txt");
System.out.println("文件名:" + file.getName());
System.out.println("文件绝对路径:" + file.getAbsolutePath());
System.out.println("文件父级目录:" + file.getParent());
System.out.println("文件大小(字节):" + file.length());  // 一个汉字3个字节
System.out.println("文件是否存在:" + file.exists());
System.out.println("是否为目录:" + file.isDirectory());
1.3 目录的操作和文件删除
File file = new File("D:\testdir");
// 删除操作
if (file.exists()) {
    if (file.delete()) {
        System.out.println("删除成功");
    }else {
        System.out.println("删除失败");
    }
} else {
    System.out.println("不存在");
}
// 创建操作
File file = new File("D:\testdir\dir1");
if (file.exists()) {
	System.out.println(file+"存在");
} else {
	if (file.mkdirs()){  // 创建多级目录,mkdir()只能创建一级目录
		System.out.println("创建成功");
	}else {
		System.out.println("创建失败");
	}
}

2.IO流原理和流的分类 2.1 Java IO 流原理

Java程序中,对于数据的输入/输出操作是以流的方式进行输入:读取外部数据(磁盘、网络、另一个程序)到程序(即内存)中输出:将程序(即内存)数据输出到外部

2.2 流的分类

以操作数据单位分类:

字节流(8bit),操作二进制文件(音视频等)时可以保证无损(也可以操作文本,但是效率不高)

字符流(具体字符大小由编码方式决定):用于操作文本文件

抽象基类字节流字符流
输入流InputStreamReader
输出流OutputStreamWriter

以数据流的流向分类:

输入流输出流

以流的角色分类:

节点流处理流/包装流

tips:

Java中的IO流类都是从表格中4个基类派生的,由它们派生的子类名称都以其父类为名字后缀,如FileReader等


3.字节流和字符流 3.1 IO流体系图

3.2 文件和流

要实现内存和文件之间的数据转换,就要通过流

3.3 Input/OutputStream 3.3.1 FileInputStream
String filePath = "D:\test.txt";
int read = 0;
byte[] buf = new byte[8];  // 一次读取8个字节
// 创建对象,用于读取文件
FileInputStream fileInputStream = null;  // 拿出来是因为finally中需要使用
try {
    fileInputStream = new FileInputStream(filePath);
    // 从输入流中读取一个字节的数据,返回-1表示读取完毕
    //while ((read = fileInputStream.read()) != -1) {
    //    System.out.print((char)read);
    //}
    // 从输入流中读取最多b.length个字节的数据到字节数组中
    // 读取正常返回实际读取的字节数,读取完毕则返回-1
    while ((readLen = fileInputStream.read(buf)) != -1) {
                System.out.println(readLen);
                System.out.println(new String(buf, 0, readLen));
    }
} catch (IOException e) {
    e.printStackTrace();
} finally {
    // 关闭文件流,释放资源
    try {
        fileInputStream.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

tips:

如果文件中出现字符,以上述方式读取文件会出现乱码,因为1个字符由3个字节组成,读取单独的一个字节必然会乱码

3.3.2 FileOutputStream
String filePath = "D:\test1.txt";
// 创建对象,用于写入文件
FileOutputStream outputStream = null;
try {
        // 该方法以覆盖的方式写入,可以调用另一个构造器以追加形式写入
        // outputStream = new FileOutputStream(filePath); // 文件不存在会创建
        outputStream = new FileOutputStream(filePath, true);
        // 写入单个字节
        outputStream.write('J');
        // 再写入多个字节
        outputStream.write("IO".getBytes());
} catch (IOException e) {
    	 e.printStackTrace();
}finally {
        try {
            outputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
}

tips:

将一个文件内容读取到另一个文件:

String srcFile = "D:\test.txt";
String destFile = "D:\test1.txt";
FileInputStream inputStream = null;
FileOutputStream outputStream = null;
try {
    inputStream = new FileInputStream(srcFile);
    outputStream = new FileOutputStream(destFile);
    byte[] buf = new byte[1024];
    int readLen = 0;
    while ((readLen = inputStream.read(buf)) != -1) {
        // 边读边写
        // 如果一个文件有1025个字节,需要执行两次while循环,剩余的那一个字节就覆盖buf[0]
        // 如果使用write(buf),会把buf[1-1023]再次写入
        outputStream.write(buf, 0, readLen);  // 使用该方法
    }
} catch (IOException e) {
    e.printStackTrace();
}finally {
    try {
        if (inputStream!=null){
            inputStream.close();
        }
        if (outputStream!=null){
            outputStream.close();
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
}
3.4 Writer/Reader 3.4.1 FileReader
String filePath = "D:\test.txt";
// 创建对象,用于读取文件
FileReader fileReader = null;
int read = 0;
int readLen = 0;
char[] buf = new char[8];  // 一次读取8个字符
try {
   // 该方法以覆盖的方式写入,可以调用另一个构造器以追加形式写入
   // outputStream = new FileOutputStream(filePath); // 文件不存在会创建
   fileReader = new FileReader(filePath);
   // 每次读取一个字符
   //while ((read = fileReader.read()) != -1) {
   //    System.out.println((char) read);
   //}
   // 从输入流中读取最多buf.length个字符的数据到字符数组中
   // 读取正常返回实际读取的字符数,读取完毕则返回-1
   while ((readLen = fileReader.read(buf)) != -1) {
       System.out.println(new String(buf, 0, readLen));
   }

} catch (IOException e) {
   e.printStackTrace();
} finally {
   try {
       fileReader.close();
   } catch (IOException e) {
       e.printStackTrace();
   }
}
3.4.2 FileWriter
String filePath = "D:\test1.txt";
// 创建对象,用于写入文件
FileWriter fileWriter = null;
char[] chars = {'P','杰'};
try {
   // 该方法以覆盖的方式写入,可以调用另一个构造器以追加形式写入
   // fileWriter = new FileWriter(filePath); // 文件不存在会创建
   fileWriter = new FileWriter(filePath, true);
   // 写入单个字节
   fileWriter.write('杰');
   // 再写入多个字节
   fileWriter.write(chars);
} catch (IOException e) {
   e.printStackTrace();
}finally {
   try {
       fileWriter.close();  // 一定要关闭,不然无法往文本写入内容
   } catch (IOException e) {
       e.printStackTrace();
   }
}

tips:

FileWriter使用后,必须要关闭或者刷新,否则写入不到指定文件,还是在内存而已(因为在执行close方法或者flush方法时,会调用writeBytes方法)


4.节点流和处理流 4.1 基本介绍

概念:

节点流:可以从一个特定的数据源(存放数据的地方,可以是文件、数组、字符串、管道等)读写数据,如FileReader、FileWriter等

处理流(包装流):连接已经存在的流(节点流或处理流)之上,为程序提供更为强大的读写功能,也更加灵活(处理的数据源可以为多个),如BufferReader等

区别:

节点流是底层流,直接和数据源相接;而处理流对节点流进行包装时,使用了修饰器设计模式,不会直接与数据源相连

// 定义抽象类
public abstract class Reader_ {
    public abstract void read();
}
// 定义两个节点流
public class FileReader_ extends Reader_ {
    @Override
    public void read() {
        System.out.println("对文件进行操作...");
    }
}
public class StringReader_ extends Reader_{
    @Override
    public void read() {
        System.out.println("对字符串进行操作...");
    }
}
// 定义处理流
public class BufferReader_ {
    private Reader_ reader_;  // 属性是Reader_类型
    public BufferReader_(Reader_ reader_) {
        this.reader_ = reader_;
    }
    // 扩展read方法,实现大量读取不同数据源的内容
    public void readMore(int num){
        for (int i = 0; i < num; i++) {
            reader_.read();
        }
    }
}
// 使用处理流对不同数据源进行操作
// 通过BufferReader对文件操作
BufferReader_ bufferReader_ = new BufferReader_(new FileReader_());
bufferReader_.readMore(5);  // 动态绑定机制
// 通过BufferReader对字符串操作
BufferReader_ bufferReader_2 = new BufferReader_(new StringReader_());
bufferReader_2.readMore(5);  // 动态绑定机制

处理流包装节点流,既可以消除不同节点流的实现差异(即处理的数据源可以为多个),也可以提供更方便的方法完成输入输出:

public class BufferedReader extends Reader {
    private Reader in;
    ...
// 在BufferReader中有属性Reader,说明封装了节点流,该节点流可以是Reader的子类中的任意一个,如FileReader、PipedReader等

处理流的优势:

提高性能:主要以增加缓冲的方式提高输入输出的效率操作便捷:提供了一系列便捷方法来一次输入输出大量的数据

4.2 处理流 4.2.1 字符流BufferedReader和BufferedWriter
String path = "D:\test.txt";
BufferedReader bufferedReader = new BufferedReader(new FileReader(path));
String line;
try {
    // 按行读取,性能效率高
    // 返回null时表示读取完毕
    while ((line = bufferedReader.readLine()) != null) {
        System.out.println(line);
    }
} catch (IOException e) {
    e.printStackTrace();
}finally {
    try {
        bufferedReader.close();  // 只需要关闭BufferReader即可
    } catch (IOException e) {
        e.printStackTrace();
    }
}
String path = "D:\test1.txt";
// 实现追加方式写入文件的话是在节点流上不同的构造器
BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(path, true));
bufferedWriter.write("hello IO");
bufferedWriter.newline();  // 插入一个和系统相关的换行符
bufferedWriter.write("hello2 IO");
bufferedWriter.close();

tips:

关闭处理流时,只需要关闭外层流即可,底层会自动关闭被包装的节点流:

public void close() throws IOException {
    synchronized (lock) {
        if (in == null)  // in就是节点流对象
            return;
        try {
            in.close();
        } finally {
            in = null;
            cb = null;
        }
    }
}
4.2.2 字节流BufferedInputStream和BufferedOutputStream
String srcFilePath = "D:\1.jpg";
String destFilePath = "D:\2.jpg";
//创建 BufferedOutputStream 对象 BufferedInputStream 对象
BufferedInputStream bis = null;
BufferedOutputStream bos = null;
try {
    //因为 FileInputStream 是 InputStream 子类
    bis = new BufferedInputStream(new FileInputStream(srcFilePath));
    //因为 FileOutputStream 是 OutputStream 子类
    bos = new BufferedOutputStream(new FileOutputStream(destFilePath));
    //循环的读取文件,并写入到 destFilePath
    byte[] buff = new byte[1024];
    int readLen = 0;
    //当返回 -1 时,就表示文件读取完毕
    while ((readLen = bis.read(buff)) != -1) {
        bos.write(buff, 0, readLen);
    }
    System.out.println("文件拷贝完毕");
} catch (IOException e) {
    e.printStackTrace();
} finally {
    //关闭流 , 关闭外层的处理流即可,底层会去关闭节点流
    try {
        if (bis != null) {
            bis.close();
        }
        if (bos != null) {
            bos.close();
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
}

tips:

BufferedInputStream在创建时会创建一个内部缓冲区数组BufferedOutputStream可以将多个字节写入底层输出流,而不需要每次将字节写入时调用底层系统字节流可以操作二进制文件,也可以操作文本文件

4.2.3 对象流ObjectInputStream和ObjectOutputStream

作用:假设需要保存int num=100这个int数据保存到文件中,之所以强调为int类型是因为100可能是String类型,要能将基本数据类型或对象进行序列化和反序列化操作。而对象流提供了对基本类型或对象类型的序列化和反序列化的方法

class Person implements Serializable{
    private String name;

    public Person(String name) {
        this.name = name;
    }
}
// ObjectInputStream
String filePath = "D:\data.dat";
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(filePath));
try {
    oos.writeInt(100);// int -> Integer (实现了Serializable)
    oos.writeBoolean(true);// boolean -> Boolean (实现了Serializable)
    oos.writeObject(new Person("psj"));
} catch (IOException e) {
    e.printStackTrace();
}finally {
    oos.close();
}
// ObjectOutputStream
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:\data.dat"));
try {
    System.out.println(ois.readInt());
    System.out.println(ois.readBoolean());
    System.out.println(ois.readObject());
} catch (ClassNotFoundException e) {
    e.printStackTrace();
} finally {
    ois.close();
}

注意事项:

ObjectOutputStream读取数据的顺序要和储存的顺序一致序列化的类中建议添加SerialVersionID,提高版本的兼容性:

class Person implements Serializable {
    private String name;
    private static final long serialVersionUID = 1L;

    public Person(String name) {
        this.name = name;
    }
}
// 当该类添加了某个属性或方法时,serialVersionUID可以使得在进行序列化/反序列化的时候不会认为是一个全新的类

序列化对象时,默认将类的所有属性进行序列化,但是除了static或transient修饰的成员:

class Person implements Serializable {
    private String name;
    private static String nation;

    public Person(String name, String nation) {
        this.name = name;
        this.nation = nation;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + ''' +
                "nation='" + nation + ''' +
                '}';
    }
}
// 测试反序列化
Person p = (Person) ois.readObject();
System.out.println(p);
// 输出:Person{name='psj'nation='null'}

序列化对象时,要求属性的类型也实现序列化接口,如果存在属性没有序列化会报错序列化具有可继承性

tips:

什么是序列化和反序列化?

序列化:在保存数据时,保存数据的值和数据类型。要让某个类是可序列化的,需要实现以下两个接口之一

Serializable:标记接口,没有方法Externalizable:有方法需要实现 反序列化:在恢复数据时,恢复数据的值和数据类型

序列化后保存的文件格式不是文本,而是按照其他格式保存

假设在序列化后修改了对象的属性/方法或者改变了该类的路径,此时直接进行反序列化会报错,需要重新进行序列化

4.2.4 标准输入/输出流System.in和System.out
运行类型默认设备
System.inInputStream键盘
System.outPrintStream显示器
4.2.5 转换流InputStreamReader和OutputStreamReader

使用场景:当test.txt的编码方式不是UTF-8时,使用下面代码读取文件会出现乱码(因为默认读取方式为UTF-8),所以需要一个能指定编码方式的方式

String filePath = "D:\test.txt";
BufferedReader bufferedReader = new BufferedReader(new FileReader(filePath));
System.out.println(bufferedReader.readLine());  # 输出乱码
bufferedReader.close();

InputStreamReader:Reader的子类,可以将字节流包装成字符流

String filePath = "D:\test.txt";
// 1.使用FileInputStream以字节形式读入文件内容
// 2.将FileInputStream转为InputStreamReader,并指定编码
InputStreamReader inputStreamReader = new InputStreamReader(new FileInputStream(filePath), "gbk");
// 2.把InputStreamReader传入BufferedReader(可以按行读取,处理效率高)
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
System.out.println(bufferedReader.readLine());
bufferedReader.close();  # 还是关闭最外层的流即可

OutputStreamReader:writer的子类,可以将字节流包装成字符流

OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("D:\test1.txt"), "gbk");
osw.write("hello杰");
osw.close();

tips:

处理纯文本数据时,如果使用字符流效率更高,并且可以有效解决中文问题,所以建议将字节流转换为字符流

4.2.6 打印流PrintStream和PrintWriter

PrintStream:

// out的类型就是PrintStream
PrintStream out = System.out;
// 默认情况下输出数据的位置是标准输出,即显示器
// 可以修改输出位置
System.setOut(new PrintStream("D:\test.txt"));
System.out.print("helloIO");

PrintWriter:

PrintWriter printWriter = new PrintWriter(new FileWriter("D:\test1.txt"));
printWriter.print("hello杰");
printWriter.close();

tips:

打印流只有输出流,没有输入流不管是处理流还是节点流都需要关闭,不关闭不会报错,但是只有关闭了才会写入文件


5.Properties类

使用传统的BufferReader类读出文件的代码如下:

//读取mysql.properties文件
BufferedReader br = new BufferedReader(new FileReader("src\mysql.properties"));
String line = "";
while ((line = br.readLine()) != null) { //循环读取
 String[] split = line.split("=");
 if("ip".equals(split[0])) {
     System.out.println(split[0] + "值是: " + split[1]);
 }
}
br.close();

基本介绍:

专门用于读写配置文件的集合类配置文件的格式:键=值 使用:

// 读取文件
//1. 创建 Properties 对象
Properties properties = new Properties();
//2. 加载指定配置文件
properties.load(new FileReader("src\mysql.properties"));
//3. 把 k-v 显示控制台
properties.list(System.out);
//4. 根据 key 获取对应的值
String user = properties.getProperty("user");

// 创建文件并修改
Properties prop
// key存在就是修改,没有就是创建
erties = new Properties();
properties.setProperty("charset", "utf8");
properties.setProperty("user", "汤姆");  // 保存的是中文的unicode码值
// 将设置的k-v保存到文件中
// 第二个参数是注释,会写在文件开头
properties.store(new FileOutputStream("src\mysql2.properties"), null);

tips:

键值对不需要空格,值也不需要引号,默认类型为StringProperties类保存中文字符时保存的是其unicode码值


转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/760096.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号