java IO 流

一、文件

1.1 概念

        文件是保存数据的地方,比如大家经常使用的 word 文档,txt 文档,excel 文件等都是文件。它既可以保存一张图片,也可以保存视频、声音等。

1.2 文件流

        文件在程序中是以流的形式来操作的

        1、流:数据在数据源(文件)和程序(内存)之间经历的路径。

        2、输入流:数据从数据源(文件)到程序(内存)的路径。

        3、输出流: 数据从程序(内存)到数据源(文件)的路径。

1.3 文件常用操作

        创建文件对象的相关构造器和方法

1.3.1 创建文件

# 根据路径构建一个 File 对象
new File(String pathName)

# 根据父目录文件+子路径构建
new File(File parent,String child)

# 根据父目录+子路径构建
new File(String parent,String child)

# 创建新文件
createNewFile

1.3.2 案例测试

        请在 e 盘下创建 news1.txtnews2.txtnews3.txt,用三种不同的方式,代码如下

import java.io.File;
import java.io.IOException;

public class FileCreate {
    public static void main(String[] args) throws IOException {
        // 方式一 new File(String pathName)
        String path1 = "e:\\news1.txt";
        File file1 = new File(path1);
        file1.createNewFile();
        System.out.println("news1.txt 文件创建成功!");

        // 方式二 new File(File parent,String child)
        // 这里的 fileParent 对象,在 java 程序中只是一个对象
        // 只有执行了 createNewFile() 方法才会真正的在磁盘创建该文件
        File fileParent = new File("e:\\");
        String path2 = "news2.txt";
        File file2 = new File(fileParent, path2);
        file2.createNewFile();
        System.out.println("news2.txt 文件创建成功!");

        // 方式三 new File(String parent,String child)
        String parentPath = "e:\\";
        // String parentPath = "e:/"; 这种写法也是可以的
        String filePath3 = "new3.txt";
        File file3 = new File(parentPath, filePath3);
        file3.createNewFile();
        System.out.println("news3.txt 文件创建成功!");
    }
}

1.3.3 获取文件

        获取文件的相关方法,如下所示:

# 获取文件名字
getName()

# 获取绝对路径
getAbsolutePath()

# 获取父级目录
 getParent()

# 获取文件大小,返回的是字节
length()

# 文件是否存在
exists()

# 是否为一个文件
isFile()

# 是否为一个目录
isDirectory()

        测试代码如下所示:

public class FileRead {
    public static void main(String[] args) {
        // 先创建文件对象
        File file = new File("e:\\news1.txt");
        // 调用相应的方法,得到对应信息
        System.out.println("文件的名字=" + file.getName());
        System.out.println("文件绝对路径 = " + file.getAbsolutePath());
        System.out.println("父级目录 = " + file.getParent());
        System.out.println("文件大小 = " + file.length());
        System.out.println("文件是否存 = " + file.exists());
        System.out.println("否为一个文件 = " + file.isFile());
        System.out.println("是否为一个目录 = " + file.isDirectory());
    }
}

1.3.4 目录操作

        操作目录的方法如下所示:

# 创建一级目录
boolean mkdir()

# 创建多级目录
boolean  mkdirs()

# 删除空目录或文件,如果目录下有文件则需要先删除文件再删除目录
boolean delete()

        测试代码如下所示:

public class Directory {
    public static void main(String[] args) {
        ifNotExistCreate();
    }
    // 判断 e:\\news1.txt 是否存在,如果存在就删除
    public static void ifExistDel(){
        File file = new File("e:\\news1.txt");
        if(file.exists()){
            if(file.delete()){
                System.out.println("删除成功!");
            }else{
                System.out.println("删除失败!");
            }
        }else{
            System.out.println("该文件不存在");
        }
    }
    // 判断 e:\\demo02 是否存在,存在就删除,否则提示不存在
    public static void ifExistDelDir(){
        File file = new File("e:\\demo02");
        if(file.exists()){
            if(file.delete()){
                System.out.println("删除成功!");
            }else{
                System.out.println("删除失败!");
            }
        }else{
            System.out.println("该目录不存在");
        }
    }
    // 判断 e:\\demo\\a\\b\\c 目录是否存在,如果存在就提示已经存在,否则就创建
    public static void ifNotExistCreate() {
        File file = new File("e:\\demo\\a\\b\\c");
        if(file.exists()){
            System.out.println("该目录已经存在");
        }else{
            boolean mkdirs = file.mkdirs();
            if(mkdirs){
                System.out.println("目录创建成功");
            }else{
                System.out.println("目录创建失败");
            }
        }
    }
}

二、IO 流

2.1 IO 流原理

        1、I/O Input/Output 的缩写,I/O 技术是非常实用的技术,用于处理数据传输。如读写文件,网络通讯等。

        2、Java 程序中,对于数据的输入/输出操作以"流(Stream)" 的方式进行。

        3、java.io 包下提供了各种 ”流“ 类和接口,用以获取不同种类的数据,并通过方法输入或输出数据。

        4、输入 input:读取外部数据到程序中。

        5、输出 output:将程序数据输出到磁盘、光盘等存储设备中。

2.2 流的分类

        1、按操作数据单位不同分为:字节流(读取二进制文件)和字符流(读取文本文件)。

        2、按数据流的流向不同分为:输入流和输出流

        3、按流的角色不同分为:节点流和处理流(包装流)。

         4、Java IO 流共涉及 40 多个类,实际上非常规则,都是从如上 4 个抽象基类派生的。由这 4 个类派生出来的子类名称都是以其父类名作为子类名后缀。

2.3 IO 流体系图

三、InputStream 和 FileOutputStream

3.1 FileInputStream

        字节输入流。

3.1.1 构造方法摘要

# 通过打开一个到实际文件的连接来创建一个 FileInputStream,该文件通过文件系统中的路径名 name 指定
FileInputStream(String name);

# 通过打开一个实际的文件连接来创建一个 FileInputStream。该文件通过文件系统中的 File 对象指定
FileInputStream(File file);

# 通过使用文件描述符 fdobj,创建一个 FileInputStream,该文件描述符表示到文件系统中某个实际文件的现有连接
FileInputStream(FileDescriptor fdObj);

3.1.2 方法摘要

# 返回下一次对此输入流调用的方法可以不受阻塞地从此输入流读取(或跳过)估计剩余字节数
int available()

# 确保在不再引用文件输入流时调用其 close 方法
void finalize()

# 返回与此文件输入流有关的唯一 FileChannel 对象
FileChannel getChannel()

# 关闭此文件输入流并释放与此流有关的所有系统资源
void close()

# 从此输入流中读取一个输入字节
int read()

# 从此输入流中将最多读取 b.length 个字节的数据读入到一个 byte 数组中
# 效率比上面一个一个读取的效率高
int read(byte [] b)

# 从此输入流中将最多 len 个字节的数据读入到一个byte 数组中
int read(byte[] b,int off,int len)

# 从输入流中跳过并丢弃 n 个字节的数据
long skip(long n)

3.1.3 应用实例

        请使用 FileInputStream 读取 hello.txt 文件,并将文件内容显示到控制台。首先在 e 盘下创建一个 hello.txt 文件,并在里面写上 hello,world 内容。演示代码如下:

// 第一种方式:使用 read() 单字节读取,效率较低
public static void readFile01(){
	String filePath = "e:\\hello.txt";
	FileInputStream fileInputStream = null;
	int readData = 0;
	try {
		// 创建 FileInputStream 对象,用于读取文件
		fileInputStream = new FileInputStream(filePath);
		// 正常 readCount 返回的是读取到的字节内容,如果它返回了 -1,则证明读取文件完毕
		while((readData = fileInputStream.read()) != -1){
			// 转成 char 显示
			System.out.print((char)readData);
		}
	} catch (IOException e) {
		e.printStackTrace();
	}finally {
		// 关闭文件流,释放资源
		try {
			fileInputStream.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}

        程序运行没有问题,如下图所示:

        如果我在 hello.txt 中添加一些中文,是否会出现乱码呢?如下,确实出现了乱码。

        一个汉字在 utf-8 的格式下占用 3 个字节,即 3 个字节组合起来构成一个字。而我们在调用 read 方法读取的时候,是读取一个字节打印一个字节的。这样就会导致打印出来的汉字乱码了。所以文本文件最好使用字符流来处理。

        接下来使用第二种方式读取文件,如下所示:

// 第二种方式:使用 read(byte [] b) 读取文件,提高效率
public static void readFile02(){
	String filePath = "e:\\hello.txt";
	FileInputStream fileInputStream = null;
	// 一次读取 8 个字节
	byte [] buf = new byte[8];
	int readLen = 0;
	try {
		// 创建 FileInputStream 对象,用于读取文件
		fileInputStream = new FileInputStream(filePath);
		// readLen 表示实际读取的字节数量,如果它返回了 -1,则证明读取文件完毕
		// buf 表示此次读取的字节内容
		while((readLen = fileInputStream.read(buf)) != -1){
			// 显示
			System.out.print(new String(buf,0,readLen));
		}
	} catch (IOException e) {
		e.printStackTrace();
	}finally {
		// 关闭文件流,释放资源
		try {
			fileInputStream.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}

3.2 FileOutputStream

        字节输出流。

3.2.1 构造方法摘要

# 创建文件输出流以写入由指定的 File 对象表示的文件。
# 写入的新数据会覆盖原有文件数据
FileOutputStream(File file) 

# 创建文件输出流以写入由指定的 File对象表示的文件。
# 当 append 为 true 时,写入的新数据会追加到原数据尾部
FileOutputStream(File file, boolean append) 

# 创建文件输出流以写入指定的文件描述符,表示与文件系统中实际文件的现有连接。 
FileOutputStream(FileDescriptor fdObj) 

# 创建文件输出流以指定的名称写入文件。
# 写入的新数据会覆盖原有文件数据
FileOutputStream(String name) 

# 创建文件输出流以指定的名称写入文件
# 当 append 为 true 时,写入的新数据会追加到原数据尾部
FileOutputStream(String name, boolean append) 

3.2.2 方法摘要

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

# 清理与文件的连接,并确保当没有更多的引用此流时,将调用此文件输出流的 close方法。  
protected void finalize() 

# 返回与此文件输出流相关联的唯一的FileChannel对象。  
FileChannel getChannel() 

# 返回与此流相关联的文件描述符。  
FileDescriptor getFD() 

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

# 将 len字节从位于偏移量 off的指定字节数组写入此文件输出流。  
void write(byte[] b, int off, int len)

#将指定的字节写入此文件输出流。 
void write(int b) 

3.2.3 应用实例

        请使用 FileOutputStream  a.txt 文件中写入 ”hello world“,如果文件不存在,会创建文件演示代码如下:

public static void writeData(){
	String filePath = "e:\\a.txt";
	// FileOutputStream :当 e:\a.txt 不存在时,会自动创建文件,前提是可以找到 e 盘
	FileOutputStream fileOutputStream = null;
	try{
		// 1、使用 new FileOutputStream(filePath) 创建方式,当向文件里面写入内容时,会覆盖掉原文件所存储的数据
		// 2、使用 new FileOutputStream(filePath,true) 创建方式,当向文件里面写入内容时,会把新数据追加到原数据后面
		fileOutputStream = new FileOutputStream(filePath);

		// 第一种方式:使用 write() 方法写入单个字节
		fileOutputStream.write('H');

		String str = "hello,world";
		// 第二种方式:使用 write(byte [] b) 方法写入多个字符串
		// str.getBytes() 可以把字符串转换成字节数组
		fileOutputStream.write(str.getBytes());

		// 第三种方式:使用 write(byte [] b,int off,int len)方法,从这个数组 b 的 off 位置开始写,向后写 len 长度。
		// 其实下面这种写法 当 len = str.length() 时等价于上面的写法
		// 只写入 str 的前三个字节
		fileOutputStream.write(str.getBytes(),0,3);
	}catch (IOException e){
		e.printStackTrace();
	}finally {
		try {
			fileOutputStream.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}

3.3 拷贝案例

        写代码完成图片/音乐的拷贝,在 e 盘下有个 pic.jpg 文件,将其拷贝到 f 盘下,代码如下

public static void picCopy(){
	// 1、创建文件的输入流,将文件读入到程序
	// 2、创建文件的输出流,将读取到的文件数据,写入到指定的文件
	String srcFilePath = "e:\\pic.jpg";
	String destFilePath = "f:\\pic.jpg";
	FileInputStream fileInputStream = null;
	FileOutputStream fileOutputStream =  null;

	try{
	   fileInputStream = new FileInputStream(srcFilePath);
	   fileOutputStream = new FileOutputStream(destFilePath);
	   // 定义一个字节数组,提高读取效率
	   byte [] buff = new byte[1024];
	   int readLen = 0;
	   while((readLen = fileInputStream.read(buff)) != -1){
		   // 读取到字节后就写入到文件,一边读一边写的
		   // 一定要用这个方法
		   fileOutputStream.write(buff,0,readLen);
	   }
		System.out.println("拷贝成功!");
	}catch (IOException e){
		e.printStackTrace();
	}finally {
		try{
			if(fileInputStream != null){
				fileInputStream.close();
			}
			if(fileOutputStream != null){
				fileOutputStream.close();
			}
		}catch (IOException e){
			e.printStackTrace();
		}
	}
}

 四、FileReader 和 FileWriter

        FileReader FileWriter 是字符流,即按照字符来操作 I/O

4.1 FileReader

        字符输入流。

4.1.1 构造方法摘要

# 通过打开一个到实际文件的连接来创建一个 FileReader,该文件通过文件系统中的路径名 name 指定
FileReader(String fileName)

# 通过打开一个实际的文件连接来创建一个 FileReader。该文件通过文件系统中的 File 对象指定
FileReader(File file)

4.1.2 方法摘要

# 每次读取单个字符,返回该字符,如果到文件末尾返回 -1
read()

# 批量读取多个字符到数组,返回读取到的字符数,如果到文件末尾返回 -1
read(char [])

# 将 char [] 转换成 String
new String(char [])

# 将 char [] 的指定部分转换成 String
new String(char [],off,len)

4.1.3 应用实例

        使用 FileReader story.txt 读取内容,并显示,代码如下:

// 使用单个字符读取文件
public static void fileReader01(){
	String path = "e:\\story.txt";
	FileReader fileReader = null;
	int data = 0;
	try{
		fileReader = new FileReader(path);
		// 循环读取,单个字符读取
        // 如果返回 -1,说明到文件结束了
		while ((data = fileReader.read()) != -1){
			System.out.print((char)data);
		}
	}catch (IOException e){
		e.printStackTrace();
	}finally {
		try {
			if(fileReader != null){
				fileReader.close();
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}
// 使用字符数组读取文件
public static void fileReader02(){
	String path = "e:\\story.txt";
	FileReader fileReader = null;
	int readLen = 0;
	char [] buf = new char[8];
	try{
		fileReader = new FileReader(path);
		// 循环读取,使用 read(buf),返回的是实际读取的字符数
		// 如果返回 -1,说明到文件结束了
		while ((readLen = fileReader.read(buf)) != -1){
			System.out.print(new String(buf,0,readLen));
		}
	}catch (IOException e){
		e.printStackTrace();
	}finally {
		try {
			if(fileReader != null){
				fileReader.close();
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}

4.2 FileWriter

        字符输出流

4.2.1 构造方法摘要

# 创建文件输出流以指定的名称写入文件。
# 写入的新数据会覆盖原有文件数据
FileWriter(String fileName)

# 创建文件输出流以指定的名称写入文件
# 当 append 为 true 时,写入的新数据会追加到原数据尾部
FileWriter(String fileName, boolean append)

# 创建文件输出流以写入由指定的 File 对象表示的文件。
# 写入的新数据会覆盖原有文件数据
FileWriter(File file)

# 创建文件输出流以写入由指定的 File对象表示的文件。
# 当 append 为 true 时,写入的新数据会追加到原数据尾部
FileWriter(File file, boolean append)

4.2.2 方法摘要

        FileWriter 使用后,必须要关闭(close)或刷新(flush),否则写入不到指定的文件。

# 写入单个字符
void write(int)

# 写入指定数组
void write(char [])

# 写入指定数组的指定部分
void write(char [] b, int off, int len )

# 写入整个字符串
void write(String)

# 写入字符串的指定部分
void write(string,off,len)

4.2.3 应用实例

        使用 FileWriter 将 "风雨之后,定见彩虹" 写入到 note.txt 文件中,注意细节。

public static void fileWriter(){
	String path = "e:\\note.txt";
	FileWriter fileWriter = null;
	try{
		// 默认是覆盖写入
		fileWriter = new FileWriter(path);
		// 第一种方式:write(int) 写入单个字符
		fileWriter.write('H');

		// 第二种方式:write(char [] ) 写入指定数组
		char [] chars = {'a','b','c'};
		fileWriter.write(chars);

		// 第三种方式:write(char [],off,len) 写入指定数组的指定部分
		fileWriter.write("我是快乐的小三菊".toCharArray(),0,3);

		// 第四种方式:write(string) 写入指定字符串
		fileWriter.write("风雨之后,定见彩虹");

		// 第五种方式:write(string,off,len) 写入字符串的指定部分
		fileWriter.write("老铁没毛病干就完了",0,2);

		// 在数据量大的情况下,可以使用循环操作
	}catch (IOException e){
		e.printStackTrace();
	}finally {
		try {
			// FileWriter 一定要关闭流,或者调用 flush() 方法才可以把真正的数据写入到文件中
			// close() = flush() + 关闭流
			fileWriter.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}

五、节点流和处理流

5.1 基本介绍

5.1.1 节点流

        节点流可以从一个特定的数据源读写数据,如 FileReaderFileWriter 等。

5.1.2 处理流

        处理流也叫包装流,是 ”连接“ 在已存在的流之上,为程序提供更为强大的读写功能,如 BufferedReaderBufferedWriter 等。

5.2 字节流和处理流一览图

5.3 节点流和处理流区别和联系

        1、节点流是底层流/低级流,直接跟数据源相接。

        2、处理流(包装流)包装节点流,既可以消除不同节点流的实现差异,也可以提供更方便的方法来完成输入输出。

        3、处理流(包装流)对节点流进行包装,使用了修饰器涉及模式,不会直接与数据源相连。

5.4 BufferedReader 和 BufferedWriter

        BufferedReader BufferedWriter 属于字符处理流,是按照字符来读取数据的,关闭处理流的时候,只需要关闭外层流即可。

5.4.1 BufferedReader

        使用 BufferedReader 读取文本信息,并显示在控制台,代码如下:

public static void bufferedReader() throws  Exception{
	String path = "e:\\story.txt";
	// 创建 bufferedReader
	BufferedReader bufferedReader = new BufferedReader(new FileReader(path));

	// 按行读取效率高
	String len;
	// bufferedReader.readLine() 是按行读取文件
	// 当 返回 null 时,表示文件已读取完毕
	while((len = bufferedReader.readLine()) != null){
		System.out.println(len);
	}
	// 关闭流,这里注意,只需要关闭 BufferedReader 即可,因为底层会自动的去关闭节点流
	bufferedReader.close();
}

5.4.2 BufferedWriter

        使用 BufferedWriter ”hello,老铁“,写入到文件中,代码如下:

public static void bufferedReader() throws Exception{
	String path = "e:\\ok.txt";
	// 1、new FileWriter(path) 表示以覆盖的方式写入
	// 2、new FileWriter(path,true) 表示以追加的方式写入
	BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(path));

	bufferedWriter.write("hello,老铁");
	// 如果想要换行可以调用这个方法
	bufferedWriter.newLine();
	bufferedWriter.write("hello,老铁2");

	// 关闭外层流即可,传入的 new FileWriter(path) 会在底层关闭。
	bufferedWriter.close();
}

5.4.3 综合练习

        使用 BufferedReader  BufferedWriter 完成文本文件的拷贝,注意编码,代码如下:

public static void bufferedReadWriteTest(){
	String srcPath = "e:\\story.txt";
	String destPath = "e:\\story2.txt";
	BufferedReader br = null;
	BufferedWriter bw = null;
	String len;
	try{
		// BufferedReader 和 BufferedWriter 是针对字符操作的
		// 不要去操作二进制文件【声音、视频、doc、pdf等】,可能会造成文件损坏
		br = new BufferedReader(new FileReader(srcPath));
		bw = new BufferedWriter(new FileWriter(destPath));
		// readLine 读取一行内容,但是没有换行
		while((len = br.readLine()) != null){
			// 每读取一行就写入
			bw.write(len);
			bw.newLine();
		}
	}catch (IOException e){
		e.printStackTrace();
	}finally {
		try {
			// 关闭流
			if(bw != null){
				bw.close();
			}
			if(br != null){
				br.close();
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}

5.5 BufferedInputStream 和 BufferedOutputStream

5.5.1 BufferedInputStream

        BufferedInputStream 是字节流,在创建 BufferedInputStream 时,会创建一个内部缓冲区数组。

5.5.2 BufferedOutputStream

        BufferedOutputStream 是字节流,实现缓冲的输出流,可以将多个字节写入底层输出流中,而不必对每个字节写入调用底层系统。

5.5.3 综合练习

        使用 BufferedInputStream BufferedOutputStream 完成图片/音乐的拷贝,代码如下:

// 使用 BufferedInputStream 和 BufferedOutputStream 可以完成二进制文件的拷贝
// 也可以用来操作文本文件
public static void bufferedInOutStream() {
	String srcPath = "e:\\pic.jpg";
	String destPath = "e:\\pic2.jpg";
	BufferedInputStream bi = null;
	BufferedOutputStream bo = null;
	try {
		bi = new BufferedInputStream(new FileInputStream(srcPath));
		bo = new BufferedOutputStream(new FileOutputStream(destPath));
		byte[] buff = new byte[1024];
		int len;
		// 循环读取文件,并写入到输出流中
		// 当返回 -1 时,就表示文件读取完毕
		while ((len = bi.read(buff)) != -1) {
			bo.write(buff, 0, len);
		}
	} catch (IOException e) {
		e.printStackTrace();
	} finally {
		// 关闭输入和输出流
		try {
			if (bi != null) {
				bi.close();
			}
			if (bo != null) {
				bo.close();
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}

5.6 ObjectInputStream 和 ObjectOutputStream

5.6.1 需求描述

        1、 int num = 100 这个 int 类型保存到文件中,注意不是 100 数字,而是 int 100,并且,能够从文件中直接恢复 int 100

        2、Dog dog = new Dog("小黄",3) 这个 dog 对象保存到文件中,并且能够从文件中恢复。

        上面的要求就是能够将基本数据类型或者对象进行序列化和反序列化操作。

5.6.2 序列化和反序列化

        序列化就是在保存数据时,保存数据的值和数据类型。反序列化就是在恢复数据时,恢复数据的值和数据类型。

        如果想要让某个对象支持序列化机制,则必须让其类是可序列化的,为了让某个类是可序列化的,该类必须实现如下的两个接口之一。

Serializable     # 一个标记接口,没有方法

Externalizable   # 该接口有方法想要实现,因此我们一般使用上面的方式

5.6.3 基本介绍

        ObjectInputStream ObjectOutputStream 提供了对基本数据类型或对象类型的序列化和反序列化的方法。

        1、ObjectInputStream 提供序列化功能。

        2、ObjectOutputStream 提供反序列化功能。

5.6.4 ObjectOutputStream

        使用 ObjectOutputStream  序列化一个基本数据类型和一个 Dog 对象,并保存到 data.dat 文件中,代码如下:

public class ObjectOutputStreamTest {
    public static void main(String[] args) throws Exception{
        // 序列化后,保存的文件格式不是存文本,而是按照它自己的格式保存的
        String filePath = "e:\\data.dat";

        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(filePath));

        // 序列化数据到 e:\data.dat
        oos.writeInt(100);        // int -> Integer(实现了 Serializable)
        oos.writeBoolean(true);   // boolean -> Boolean(实现了 Serializable)
        oos.writeDouble(9.4);     // double -> Double(实现了 Serializable)
        oos.writeChar('a');       // char -> Char(实现了 Serializable)
        oos.writeUTF("老铁你好");   // String (实现了 Serializable)
        // 保存一个 dog 对象
        oos.writeObject(new Dog("旺财",2));
        oos.close();
        System.out.println("数据保存成功(序列化形式)");
    }
}
// 如果想要序列化某个类的对象,必须实现 Serializable 接口
public class Dog implements Serializable {
    private String name;
    private int age;
    // SerialVersionUID 序列化版本号,可以提高版本兼容性
    private static final long SerialVersionUID = 1L;
    public Dog(String name, int age) {
        this.name = name;
        this.age = age;
    }
    @Override
    public String toString() {
        return "Dog{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
    public String getName() {
        return name;
    }
}

5.6.5 ObjectInputStream

        使用 ObjectInputStream 读取 data.dat 并反序列化恢复数据,代码如下:

public class ObjectInputStreamTest {
    public static void main(String[] args) throws Exception {
        // 指定反序列化的文件
        String filePath = "e:\\data.dat";
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filePath));

        // 1、读取(反序列化)的顺序要和保存数据(序列化)的顺序一致,斗则会出现异常
        System.out.println(ois.readInt());
        System.out.println(ois.readBoolean());
        System.out.println(ois.readDouble());
        System.out.println(ois.readChar());
        System.out.println(ois.readUTF());
        Object dog = ois.readObject();
        System.out.println("运行类型为:"+dog.getClass());

        // 1、如果我们希望调用 Dog 的方法,需要向下转型
        // 2、需要我们将 Dog 类的定义,拷贝到可以引用的位置
        Dog dog2 = (Dog)dog;
        System.out.println(dog2.getName());
        // 关闭外层流
        ois.close();

    }
}

5.6.6 注意事项和细节

        1、读写顺序要一致

        2、要求实现序列化或反序列化,需要实现 Serializable 接口

        3、序列化的类中建议添加 SerialVersionUID,为了提高版本的兼容性

        4、序列化对象时,默认将里面所有属性都进行序列化,但除了 static transient 修饰的成员

        5、序列化对象时,要求里面属性的类型也需要实现序列化接口

        6、序列化具备可继承性,也就是如果某类已经实现了序列化,则它的所有子类也已经默认实现了序列化。

5.7 标准输入输出流

5.7.1 标准输入

        System.in 标准输入,类型为 InputStream,默认设备为键盘

5.7.2 标准输出

        System.out 标准输出,类型为 PrintStream,默认为显示器

5.7.3 案例解析

public class InputAndOutput {
    public static void main(String[] args) {
        // 1、System.in 的编译类型为 InputStream
        // 2、System.in 的运行类型为 BufferedInputStream
        // 3、表示的是标准输入 键盘
        System.out.println(System.in.getClass());

        // 1、编译类型为 PrintStream
        // 2、运行类型为 PrintStream
        // 3、表示标准输出 显示器
        System.out.println(System.out.getClass());

        // scanner 会去键盘的输入处获取数据
        Scanner scanner = new Scanner(System.in);
        System.out.println("请输入内容:");
        String next  = scanner.next();
        System.out.println("next:"+next);
    }
}

5.8 转换流 InputStreamReader 和 OutputStreamWriter

        转换流的意思就是将字节流转换成字符流。

5.8.1 乱码问题

        先写一段代码,如下所示:

public class CodeQuestion {
    public static void main(String[] args) throws Exception{
        // 读取 e:\note.txt 文件到程序中
        // 默认情况下,读取文件是按照 utf-8 编码读取的
        String path = "e:\\note.txt";
        BufferedReader br = new BufferedReader(new FileReader(path));
        String s = br.readLine();
        System.out.println("读取到的内容为:"+s);

        br.close();
    }
}

        运行结果也没有问题,如下所示: 

        我们在读取这个文件的时候,这个文件默认的编码为 utf-8,如下

        如果我们要读取的文件,默认不是 utf-8 编码的,可能就会出现问题了,我们将这个文件以 ANSI 编码另存为一份,然后重新使用代码读取下,显示如下: 

        出现乱码的根本原因就是读取文件的时候没有指定这个文件的编码方式,如果我们指定了读取文件的编码方式,这个问题就得到解决了。

        而这个转换流是可以将字节流转换为字符流,而字节流是可以指定一个编码方式。

5.8.2 介绍

        1、InputStreamReader Reader 的子类,可以将 InputStream(字节流)包装成 Reader(字符流)。

        2、OutputStreamWriterWriter 的子类,可以将 OutputStream(字节流)包装成 Writer(字符流)。

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

        4、可以在使用时指定编码格式(比如:utf-8gbkgb2312ISO8859-1 等)。

5.8.3 InputStreamReader

        接下来我们使用 InputStreamReader 转换流解决中文乱码问题,代码如下:

public class InputStreamReaderTest {
    public static void main(String[] args) throws IOException {
        // 读取 e:\note.txt 文件到程序中
        String path = "e:\\note.txt";
        // 1、把 FileInputStream 转换成 InputStreamReader
        // 2、指定编码 gbk
        InputStreamReader isr = new InputStreamReader(new FileInputStream(path), "gbk");
        // 3、把 InputStreamReader 传入给 BufferedReader
        BufferedReader br = new BufferedReader(isr);

        // 可以将 2 和 3 写在一起,如下
        // BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(path), "gbk"));
        // 4、读取
        String s = br.readLine();
        System.out.println("读取到的内容为:"+s);
        // 5、关闭外层流
        br.close();
    }
}

5.8.4 OutputStreamWriter

        编写代码将字节流 FileOutputStream 包装成字符流 OutputStreamWriter,对文件进行写入(按照 gbk 格式,也可以指定其他,如 utf-8),代码如下:

public class OutputStreamWriterTest {
    public static void main(String[] args) throws IOException {
        String path = "e:\\xhf.txt";
        OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(path), "gbk");
        // 可以直接调用 OutputStreamWriter 的 write 方法进行写入操作
        // osw.write("老铁没毛病abcd12213");

        // 也可以再转换成 BufferedWriter,然后再写入
        BufferedWriter bw = new BufferedWriter(osw);
        bw.write("老铁没毛病abcd12213");
        bw.close();
    }
}

5.9 打印流

        打印流只有输出流,没有输入流。

5.9.1 PrintStream 字节流

public static void main(String[] args) throws IOException {
	PrintStream out = System.out;
	// 在默认情况下,PrintStream 输出数据的位置是标准输出,即显示器
	out.print("hello world");

	// 因为 print() 方法底层调用的是 write() 方法,所以我们可以直接调用 write 方法进行打印/输出
	out.write("老铁,你好".getBytes());

	// 我们可以去修改打印流输出的位置/设备
	// 1、修改到 e:\f2.txt
	System.setOut(new PrintStream("e:\\f2.txt"));
	// 2、下面这句话就会输出到文件里面
	System.out.println("曾梦想仗剑走天下");
	out.close();
}

5.9.2 PrintWriter 字符流

public static void main(String[] args) throws IOException {
	// 默认输出为显示器
	// PrintWriter pw = new PrintWriter(System.out);

	// 指定输出的位置
	PrintWriter pw = new PrintWriter(new FileWriter("e:\\f3.txt"));
	pw.print("北京欢迎你~~~");
	// 只有关闭流,才会将数据写入进去
	pw.close();
}

六、Properties 类

6.1 需求描述

        有一个配置文件 mysql.peoperties,如下所示,要求使用代码读取 ipuser pwd 的值是多少?

ip=192.168.0.13
user=root
pwd=12345

6.2 传统方式

public static void main(String[] args) throws IOException {
	String path = "src\\mysql.properties";
	BufferedReader br = new BufferedReader(new FileReader(path));
	String line = "";
	// 循环读取
	while((line = br.readLine()) != null){
		String[] split = line.split("=");
		System.out.println(split[0]+"值是:"+split[1]);
	}
}

6.3 Properties

6.3.1 基本介绍

        Properties 是专门用于读写配置文件的结合类,要求配置文件的格式为 键=值。需要注意的是键值对不需要有空格,值不需要用引号引起来,默认类型为 String

6.3.2 常用方法

# 加载配置文件的键值对到 Properties 对象
load()

# 将数据显示到指定设备
list()

# 根据键获取值
getProperties(key)

# 设置键值对到 Properties 对象
# 相当于更新
setProperties(key,value)

# 将 Properties 中的键值对存储到配置文件,在 idea 中,保存信息到配置文件,如果含有中文,会存储为 unicode 码
# 相当于新增
store

6.3.3 应用案例

        1、使用 Properties 类完成对 mysql.properties 的读取。

public static void main(String[] args) throws IOException {
	// 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");
	String pwd = properties.getProperty("pwd");
	System.out.println("用户名:"+user);
	System.out.println("密码:"+pwd);
}

        2、使用 Properties 类添加 key-val 到新文件 mysql2.properties 中;使用 Properties 类完成对 mysql2.properties 的读取,并修改某个 key-val

public static void main(String[] args) throws IOException {
	// 使用 Properties 类来创建配置文件,并修改配置文件的内容
	Properties properties = new Properties();

	// 1、如果该文件没有 key 就是创建
	// 2、如果该文件有 key 就是更新
	properties.setProperty("charset","utf-8");
	// 中文保存时,会保存中文的 unicode 码值
	properties.setProperty("user","张三");
	properties.setProperty("pwd","999999");

	// 将 k-v 存储文件中即可
	properties.store(new FileWriter("src\\mysql2.properties"),"");
	System.out.println("保存配置文件成功");
}

七、习题练习

7.1 练习一

        1、判断 e 盘目录下是否有文件夹 mytemp,如果没有就创建 mytemp

        2、 e:\\temp 目录下,创建文件 hello.txt

        3、如果 hello.txt 已经存在,提示该文件已经存在,就不要在重复创建了

        4、 hello.txt 文件中,写入 hello,world~

public static void main(String[] args) throws IOException {
	String path = "e:\\mytemp";
	File file = new File(path);
	if(file.exists()){
		System.out.println("该文件已经存在");
	}else{
		if(file.mkdir()){
			System.out.println("文件创建成功");
		}else{
			System.out.println("文件创建失败");
		}
	}
	String path2 = "e:\\mytemp\\hello.txt";
	File file2 = new File(path2);
	if(file2.exists()){
		System.out.println("hello.txt 文件已经存在");
	}else{
		if(file2.createNewFile()){
			System.out.println("hello.txt 文件创建成功");
		}else{
			System.out.println("hello.txt 文件创建失败");
		}
	}
	BufferedWriter bw = new BufferedWriter(new FileWriter(path2));
	bw.write("hello,world~");
	bw.close();
}

7.2 练习二

        1、使用 BufferedReader 读取一个文本文件,为每行加上行号,连同内容一并输出到屏幕上

        2、如果把文件编码改成 utf-8,此时会出现中文乱码,该如何解决。

public static void main(String[] args) throws IOException {
	String path = "e:\\story.txt";
	BufferedReader br = new BufferedReader(new FileReader(path));
	String len;
	int i = 0;
	while((len = br.readLine()) != null){
		System.out.println("第"+(++i)+"行输出为:"+len);
	}
	br.close();
}
public static void main(String[] args) throws IOException {
	String path = "e:\\story.txt";
	InputStreamReader isr = new InputStreamReader(new FileInputStream(path), "gbk");
	BufferedReader br = new BufferedReader(isr);
	String len;
	int i = 0;
	while((len = br.readLine()) != null){
		System.out.println("第"+(++i)+"行输出为:"+len);
	}
	br.close();
}

7.3 练习三

        1、编写一个 dog.properties 文件,属性如下:

name=tom
age=5
color=red

        2、编写 Dog 类(name、age、color)创建一个 Dog 对象,读取 dog.properties 用相应的内容完成属性初始化并输出。

        3、将创建的 Dog 对象,序列化到文件 dog.dat 文件。

        4、再将 dog.dat 文件反序列回来

public static void main(String[] args) throws Exception {
	Properties p = new Properties();

	p.load(new BufferedReader(new FileReader("src\\dog.properties")));
	String name = p.get("name").toString();
	String ageTemp = p.get("age").toString();
	int age = Integer.valueOf(ageTemp);
	String color = p.get("color").toString();

	Dog dog = new Dog(name,age,color);
	System.out.println(dog.toString());
	
	String destPath = "e:\\dog.dat";
	ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(destPath));
	oos.writeObject(dog);
	oos.close();

	ObjectInputStream ois = new ObjectInputStream(new FileInputStream(destPath));
	Object o = ois.readObject();
	Dog dog2 = (Dog)o;
	System.out.println(dog2);
	ois.close();
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

快乐的小三菊

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值