Java中的IO流与Properties
Posted 结城由纪
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java中的IO流与Properties相关的知识,希望对你有一定的参考价值。
IO流
1 File
1.1 File类概述和构造方法
File:它是文件和目录路径名的抽象表示
- 文件和目录是可以通过File封装成对象的
- 对于File而言,其封装的并不是一个真正存在的文件,仅仅是一个路径名而已。它可以是存在的,也可以是不存在的。将来是要通过具体的操作把这个路径的内容转换为具体存在的。
构造方法
- File(String pathname) 通过将给定的路径名字符串转换为抽象路径名来创建新的 File实例
- File(String parent, String child) 从父路径名字符串和子路径名字符串创建新的 File实例
- File(File parent, String child) 从父抽象路径名和子路径名字符串创建新的 File实例
/*
构造方法以及说明:
File(String pathname) 通过将给定的路径名字符串转换为抽象路径名来创建新的 File实例
File(String parent, String child) 从父路径名字符串和子路径名字符串创建新的 File实例
File(File parent, String child) 从父抽象路径名和子路径名字符串创建新的 File实例
*/
public class FileDemo01 {
public static void main(String[] args) {
// File(String pathname)通过将给定的路径名字符串转换为抽象路径名来创建新的 File实例
File f1 = new File("E:/itcast/java.txt");
System.out.println(f1);
// File(String parent, String child)从父路径名字符串和子路径名字符串创建新的 File实例
File f2 = new File("E:/itcast","java.txt");
System.out.println(f2);
// File(File parent, String child)从父抽象路径名和子路径名字符串创建新的 File实例
File f3 = new File("E:/itcast");
File f4 = new File(f3,"java.txt");
System.out.println(f4);
}
}
1.2 File类创建功能
- boolean createNewFile() 当具有该名称的文件不存在时,创建一个由该抽象路径名命名的新的空文件
- boolean mkdir() 创建由此抽象路径名命名的目录
- boolean mkdirs() 创建由此抽象路径名命名的目录,包括任何必需但不存在的父目录
/*
File类创建功能:
boolean createNewFile() 当具有该名称的文件不存在时,创建一个由该抽象路径名命名的新的空文件
boolean mkdir() 创建由此抽象路径名命名的目录
boolean mkdirs() 创建由此抽象路径名命名的目录,包括任何必需但不存在的父目录
*/
public class FileDemo01 {
public static void main(String[] args) {
try {
// 需求1:我要在E:/itcast目录 下创建一个文件java.txt
File file1 = new File("E:\itcast\java.txt");
if (file1.createNewFile()) {
System.out.println("文件创建成功");
} else {
System.out.println("该文件已存在");
}
// 需求2:我要在E:/itcast目录下创建一个目录JavaSE
File file2 = new File("E:\itcast");
File file2_new = new File(file2,"JavaSE");
if (file2_new.mkdir()) {
System.out.println("目录创建成功");
}else{
System.out.println("该目录已存在");
}
// 需求3:我要在E:/itcast目录下创建一个多级目录JavaWEB/html
File file3 = new File(file2,"JavaWEB/HTML");
// mkdir()方法不能创建多级目录,返回false
// System.out.println(file3.mkdir());
if (file3.mkdirs()) {
System.out.println("创建多级目录成功");
} else {
System.out.println("该目录已存在");
}
// 需求4:我要在E:/itcast目录下创建一个文件javase.txt
File file4 = new File(file2, "javase.txt");
// 第一次创建时输出的true且创建的不是txt文件,而是javase.txt文件夹
// 如果创建成功,这个文件夹会占用txt文件名,再次创建javase.txt会返回false
// file1 = new File("E:\itcast\javase.txt");
// System.out.println(file1.mkdir());
if (file4.createNewFile()) {
System.out.println("文件创建成功");
} else {
System.out.println("该文件已存在");
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
1.3 File类判断、获取和删除功能
- boolean isDirectory() 测试此抽象路径名表示的文件是否为目录
- boolean isFile() 测试此抽象路径名表示的文件是否为普通文件
- boolean exists() 测试此抽象路径名表示的文件或目录是否存在
- String getAbsolutePath() 返回此抽象路径名的绝对路径名字符串
- String getPath() 将此抽象路径名转换为路径名字符串
- String getName() 返回由此抽象路径名表示的文件或目录的名称
- String[] list() 返回一个字符串数组,命名由此抽象路径名表示的目录中的文件和目录
- File[] listFiles() 返回一个抽象路径名数组,表示由该抽象路径名表示的目录中的文件
File类删除功能
- public boolean delete():删除由此抽象路径名表示的文件或目录
绝对路径和相对路径的区别:
- 绝对路径:完整的路径名,不需要任何其他信息就可以定位它所表示的文件。
- 相对路径:必须使用取自其他路径名的信息进行解释。
删除目录的注意事项:
如果一个目录中有内容(目录,文件),不能直接删除。应该先删除目录中的内容,最后才能删除目录
/*
boolean isDirectory() 测试此抽象路径名表示的文件是否为目录
boolean isFile() 测试此抽象路径名表示的文件是否为普通文件
boolean exists() 测试此抽象路径名表示的文件或目录是否存在
String getAbsolutePath() 返回此抽象路径名的绝对路径名字符串
String getPath() 将此抽象路径名转换为路径名字符串
String getName() 返回由此抽象路径名表示的文件或目录的名称
String[] list() 返回一个字符串数组,命名由此抽象路径名表示的目录中的文件和目录
File[] listFiles() 返回一个抽象路径名数组,表示由该抽象路径名表示的目录中的文件
public boolean delete():删除由此抽象路径名表示的文件或目录
绝对路径和相对路径的区别:
1、绝对路径:完整的路径名,不需要任何其他信息就可以定位它所表示的文件。
2、相对路径:必须使用取自其他路径名的信息进行解释。
删除目录的注意事项:
如果一个目录中有内容(目录,文件),不能直接删除。应该先删除目录中的内容,最后才能删除目录
*/
public class FileDemo02 {
public static void main(String[] args) throws IOException {
File file = new File("test\src\com\itcast\unit8\test1\java.txt");
// 创建一个文件,如果文件已存在,返回false
file.createNewFile();
// boolean isDirectory() 测试此抽象路径名表示的文件是否为目录
// 这是个文件,返回false
System.out.println(file.isDirectory());
// boolean isFile() 测试此抽象路径名表示的文件是否为普通文件
// 是文件,返回true
System.out.println(file.isFile());
// boolean exists() 测试此抽象路径名表示的文件或目录是否存在
// 文件存在,返回true
System.out.println(file.exists());
// String getAbsolutePath() 返回此抽象路径名的绝对路径名字符串
// 返回绝对路径
System.out.println(file.getAbsolutePath());
// String getPath() 将此抽象路径名转换为路径名字符串
// 返回相对路径
System.out.println(file.getPath());
// String getName() 返回由此抽象路径名表示的文件或目录的名称
// 返回文件名或者目录名
System.out.println(file.getName());
// String[] list() 返回一个字符串数组,命名由此抽象路径名表示的目录中的文件和目录
// 如果指定路径为文件路径则null
file = new File("test\src\com");
String[] ss = file.list();
System.out.println();
for (String s : ss) {
System.out.print(s + " ");
}
System.out.println();
// File[] listFiles() 返回一个抽象路径名数组,表示由该抽象路径名表示的目录中的文件
// 如果指定路径为文件路径则null
file = new File("test\src\com\itcast\unit8\test1");
File[] files = file.listFiles();
for (File f : files) {
/* // 直接输出file,则返回的是file的相对路径
// System.out.print(f + " ");
System.out.print(f.getName() + " ");*/
if (f.isFile()) {
System.out.println(f.getName());
}
}
System.out.println();
file = new File(
"E:\develop\JavaSE_Code\src\com\itcast\unit8\test1\java.txt");
// 第一次执行返回true,删除成功
// 第二次执行因为路径名不存在,返回false
System.out.println(file.delete());;
// 如果要创建一个文件,必定找到这个文件的上级目录,找不到则不能创建,报IOException
File file1 = new File("itcast");
System.out.println(file1.mkdir()); // 如果注释掉,报错IOException:系统找不到指定的路径
File file2 = new File("itcast\java.txt");
System.out.println(file2.createNewFile());
// 如果删除目录,必须先删除目录下所有文件
System.out.println("直接删除目录itcast:"+file1.delete()); // 目录下有java.txt,返回false
System.out.println("删除目录下java.txt:"+file2.delete());
System.out.println("删除目录itcast:"+file1.delete());
}
}
1.4 递归
递归概述:以编程的角度来看,递归指的是方法定义中调用方法本身的现象
递归解决问题的思路:
- 把一个复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解
- 递归策略只需要少量的程序就可描述出解题过程中所需要的多次重复计算
递归解决问题要找到两个内容:
- 递归出口:否则会出现内存溢出
- 递归规则:与原问题相似的规模较小的问题
/*
递归概述:以编程的角度来看,递归指的是方法定义中调用方法本身的现象
递归解决问题的思路:
把一个复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解
递归策略只需要少量的程序就可描述出解题过程中所需要的多次重复计算
递归解决问题要找到两个内容:
1、递归出口:否则会出现内存溢出
2、递归规则:与原问题相似的规模较小的问题
*/
public class DiGuiDemo {
public static void main(String[] args) {
// 不死神兔问题递归版
System.out.println(f1(20));
}
/*
递归解决问题,首先要定义一个方法:
定义一个方法f(n):表示第n个月的兔子对数
那么,第n-1个月的兔子对数该如何表示呢?f(n-1)
那么,第n-2个月的兔子对数该如何表示呢?f(n-2)
斐波那契数列中f(n) = f(n-1) + f(n-2),其中第一个值和第二个值为1
所以f(1) = 1,f(2) = 1
*/
// StackOverflowError:当堆栈溢出发生时抛出一个应用程序递归太深
public static int f1(int n) {
if (n == 1 || n == 2) {
return 1;
}
return f1(n - 1) + f1(n - 2);
}
}
案例:递归求阶乘
需求
- 用递归求5的阶乘,并把结果在控制台输出
public class DiGuiDemo {
public static void main(String[] args) {
// 递归求阶乘
System.out.println(f2(5));
}
/*
递归求阶乘:
n!=n * (n-1) * (n-2) * ...... * 2 * 1;
f(1) = 1;
f(2) = 1*2 = f(1)*2;
f(3) = 1*2*3 = f(2)*3;
f(n) = 1*2*3*...*(n-1)*n = f(n-1)*n;
*/
public static int f2(int n) {
if (n == 1) {
return 1;
}
return f2(n - 1) * n;
}
}
案例:遍历目录
需求:
- 给定一个路径(E:itcast),请通过递归完成遍历该目录下的所有内容,并把所有文件的绝对路径输出在控制台
/*
要求:给定一个路径(E:\itcast),请通过递归完成遍历该目录下的所有内容,并把所有文件的绝对路径输出在控制台
思路:
根据给定的路径创建一个File对象
定义一个方法,用于获取给定目录下的所有内容,参数为第一步创建的File对象
获取给定的File目录下所有的文件或者目录的File数组
遍历该File数组,得到每一个File对象
判断该File对象是否是目录
是:递归调用
否:获取绝对路径输出在控制台上
调用方法
*/
public class TraversalList {
public static void main(String[] args) {
File file = new File("E:\itcast");
traversal(file);
}
// 递归调用不一定非要有返回值,但一定要有出口和如何返回上一级递归
public static void traversal(File f) {
File[] files = f.listFiles();
if (files != null) {
for (File file : files) {
if (file.isFile()) {
System.out.println(file.getAbsolutePath());
} else {
traversal(file);
}
}
}
}
}
2 字节流
2.1 IO流概述和分类
IO流概述:
- IO:输入/输出(Input/Output)
- 流:是一种抽象概念,是对数据传输的总称。也就是说数据在设备间的传输成为流,流的本质是数据传输
- IO流就是用来处理设备间数据传输问题的
- 常见的应用:文件复制;文件上传;文件下载
IO流分类:
- 按照数据的流向
- 输入流:读数据
- 输出流:写数据
- 按照数据类型来分
- 字节流
- 字节输入流;字节输出流
- 字符流
- 字符输入流;字符输出流
- 字节流
一般来说,IO流的分类是按照数据类型来分的
那么这两种流都在什么情况下使用呢
- 如果数据通过Windows自带的记事本软件打开,我们还可以读懂里面的内容,就用字符流,否则用字节流。如果你不知道该用哪种类型的流,就是用字节流。
2.2 字节流写数据
字节流抽象基类:
- InputStream:这个抽象类是表示字节输入流的所有类的超类
- OutputStream:这个抽象类是表示字节输出流的所有类的超类
- 子类名特点:子类名称都是以其父类名作为子类名的后缀
FIleOutputStream:文件输出流用于将数据写入File
- FileOutputStream(String name):创建文件输出流以指定的名称写入文件
public class FileOutputStreamDemo01 {
public static void main(String[] args) throws IOException {
// 创建文件输出流对象
FileOutputStream fos = new FileOutputStream(
"E:\develop\JavaSE_Code\src\com\" +
"itcast \unit8\test1\fos.txt");
/*
做了三件事:
A:调用了系统功能创建了文件
B:创建了字节输出流对象
C:让字节输出流对象指向创建好的文件
*/
// void write(int b):将指定的字节写入此文件输出流
fos.write(97);
fos.write(57);
fos.write(55);
// 最后务必调用close()释放系统资源
// void close():关闭此文件输出流并释放与此流相关联的任何系统资源
fos.close();
}
}
使用字节输出流写数据的步骤:
- 创建字节输出流对象(调用系统功能创建了文件,创建字节输出流对象,让字节输出流对象指向文件)
- 调用字节输出流对象的写数据方法
- 是放资源(关闭此文件输出流并释放与此流相关联的任何系统资源)
2.3字节流写入数据的三种方法
- void write(byte[] b):将 b.length字节从指定的字节数组写入此文件输出流
- 一次写一个字节数组数据
- void write(byte[] b, int off, int len):将 len字节从指定的字节数组开始,从偏移量 off开始写入此文件输出流
- 一次写一个字节数组的部分数据
- void write(int b):将指定的字节写入此文件输出流
- 一次写一个字节数组
/*
void write(byte[] b) 将 b.length字节从指定的字节数组写入此文件输出流
一次写一个字节数组数据
void write(byte[] b, int off, int len) 将 len字节从指定的字节数组开始,从偏移量 off开始写入此文件输出流
一次写一个字节数组的部分数据
void write(int b) 将指定的字节写入此文件输出流
一次写一个字节数组
*/
public class FileOutputStreamDemo02 {
public static void main(String[] args) throws IOException {
// File file = new File("E:\develop\JavaSE_Code\src\com\itcast\unit8\test1\fos.txt");
// 把字符串路径封装成一个file对象
FileOutputStream fos = new FileOutputStream("面试题/src/com/itheima2/unit8/test1/fos.txt");
// 如果没有该路径文件,则new一个
// FileOutputStream fos = new FileOutputStream(
// new File("test\src/com/itcast/unit8/test1/fos.txt"));
byte[] b = {97, 98, 99};
fos.write(b);
fos.write(100);
// IndexOutOfBoundsException:表示某种索引(例如数组,字符串或向量)的索引超出范围
fos.write(b,1,2);
String s = "hello world love you";
byte[] bytes = s.getBytes();
fos.write(bytes,0,bytes.length);
byte[] bytes1 = "我叫沈剑心,是稻香村的一名保安".getBytes();
fos.write(bytes1);
fos.close();
}
}
2.4 字节流写数据的两个小问题
字节流写数据如何换行?
- 用
实现换行,在IDEA中查看可以,用Windows自带记事本则不可以
- 每个系统的换行标识符不一样
- Windows:
- Linux:
- mac:
- 每个系统的换行标识符不一样
字节流写数据如何追加?
- write()方法是覆盖写入
- public FileOutputStream(String name,boolean append)throws FileNotFoundException:创建文件输出流以指定的名称写入文件。
- 如果第二个参数为true ,则字节将写入文件的末尾而不是开头。
public class FileOutputStreamDemo03 {
public static void main(String[] args) throws IOException {
// 创建一个字节流输出对象
File file = new File("test\src/com/itcast/unit8/test1/fos.txt");
FileOutputStream fos = new FileOutputStream(file,true);
for (int i = 0; i < 10; i++) {
fos.write("helloworld".getBytes());
fos.write("
".getBytes());
}
fos.close();
}
/*
public FileOutputStream(String name,boolean append)
创建文件输出流以指定的名称写入文件。如果第二个参数为true,则字节将写入文件的莫问而不是开头
*/
}
2.5 字节流写数据加异常处理
finally:在异常处理时提供finally块来执行所有清除操作。比如说IO流中的释放资源
特点:被finally控制的语句一定会执行,除非JVM退出
/*
字节流写数据加入异常处理
*/
public class FileOutputStreamDemo04 {
public static void main(String[] args) {
File file = new File("test\src/com/itcast/unit8/test1/fos.txt");
/* try {
FileOutputStream fos = new FileOutputStream(file, true);
fos.write("hello world".getBytes());
fos.close(); // 所有跟IO相关操作的内容都要释放资源,为了保证一定能释放到,我们提供了finally关键字
} catch (IOException e) {
e.printStackTrace();
}*/
// 加入finally来实现释放资源
// FileNotFoundException,找不到指定路径
file = new File("Z:src/com/itcast/unit8/test1/fos.txt");
FileOutputStream fos = null;
try {
fos = new FileOutputStream(file, true);
fos.write("hello world".getBytes());
} catch (IOException e) {
e.printStackTrace();
} finally {
// NullPointerException:根本没有创建FileOutputStream,所以空指针
// 加入if判断,如果fos非空,则关闭fos
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
2.6 字节流读数据
字节流读数据(一次读一个字节的文件)
需求:把文件fos.txt中的内容读取出来在控制台输出
FileInputStream:从文件系统中的文件获取输入字节
- FileInputStream(String name):通过打开与市级文件的连接来创建一个FileInputStream:该文件由文件系统中的路径名name命名
使用字节输入流读数据的步骤:
- 创建字节输入流对象
- 调用字节输入流对象的读数据方法
- 释放资源
public class FileInputStreamDemo01 {
public static void main(String[] args) {
// 建字节输入流对象
File file = new File("test\src/com/itcast/unit8/test1/fos.txt");
FileInputStream fis = null;
try {
fis = new FileInputStream(file);
/*
int read = fis.read();
System.out.println((char) read);
read = fis.read();
System.out.println((char) read);
// 如果文件到达末尾,返回-1
read = fis.read();
System.out.println(read);
*/
// 通过循环读取数据
/* int read = fis.read();
while(read != -1){
System.out.print((char) read);
read = fis.read();
}*/
// 代码优化
int read;
/*
fis.read():读数据
by=fis.read():把读取到的数据赋值给by
by!=-1:判断读取到的数据是否是-1
*/
while ((read = fis.read()) != -1) {
System.out.print((char) read);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
案例:复制文本文件
需求:把"E:itcastfos.txt"复制到模块目录下的"fos.txt"
/*
需求:把"E:\itcast\fos.txt"复制到模块目录下的"fos.txt"
分析:
1、符合文本文件,其实就是把文本文件的内容从一个文件中读取出来(数据源),然后写入到另一个文件中(目的地)
2、数据源:
E:\itcast\fos.txt---读数据---InputStream---FileInputStream
3、目的地:
myByteStream\fos.txt---写数据---OutputStream---FileOutputStream
思路:
1、根据数据源创建字节输入流对象
2、根据目的地创建字节输出流对象
3、读写数据,复制文本文件(一次读取一个字节,一次写入一个字节)
4、释放资源
*/
public class IODemo {
public static void main(String[] args) {
File file1 = new File("test\src/com/itcast/unit8/test3/fos.txt");
File file2 = new File("test\src/com/itcast/unit8/test1/fos.txt");
FileOutputStream fos = null;
FileInputStream fis = null;
try {
fos = new FileOutputStream(file1, true);
fis = new FileInputStream(file2);
int read;
while ((read = fis.read()) != -1) {
fos.write(read);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
字节流读数据(一次读一个字节数组的数据)
使用字节输入流读数据的步骤:
- 创建字节输入流对象
- 调用字节输入流对象的读数据方法
- 释放资源
/*
使用字节输入流读取数据的步骤
1:创建字节输入流对象
2:调用字节输入流对象的读数据方法
3:释放资源
*/
public class FileInputStreamDemo02 {
public static void main(String[] args) throws IOException {
// 创建字节输入流对象
File file = new File("fos.txt");
FileInputStream fis = new FileInputStream(file);
/* // 调用字节输入流对象的读数据方法
byte[] b = new byte[5];
// 第一次调用读数据方法
// read返回的是实际读取长度
int read = fis.read(b);
System.out.println(read);
String s = new String(b,0,read);
System.out.println(s);
// 第一次调用读数据方法
read = fis.read(b);
System.out.println(read);
s = new String(b,0,read);
System.out.println(s);
// 第三次调用读数据方法
read = fis.read(b);
System.out.println(read);
s = new String(b,0,read);
System.out.println(s);*/
/*
hello
world
第一次读数据:hello
第二次读数据:
wor
假设读第三次:ld
r
*/
// 用循环改良代码
byte[] bytes = new byte[1024];// 1024及其整数倍
int read;
while ((read = fis.read(bytes))!=-1) {
System.out.println(new String(bytes,0,read));
}
// 关闭字节输入流
fis.close();
}
}
案例:复制图片
需求:把"Lin&Len.png"复制到模块目录下的"悔恨的讯息.png"
public class PicDemo {
public static void main(String[] args) throws IOException {
FileInputStream fis = new FileInputStream("Lin&Len.png");
FileOutputStream fos = new FileOutputStream("test\src\悔恨的讯息.png");
byte[] bytes = new byte[1024];
int read;
while ((read = fis.read(bytes)) != -1) {
fos.write(bytes);
}
fis.close();
fos.close();
}
}
public class PicDemo {
public static void main(String[] args) throws IOException {
FileInputStream fis = new FileInputStream("Lin&Len.png");
FileOutputStream fos = new FileOutputStream("test\src\悔恨的讯息.png");
byte[] bytes = new byte[1024];
int read;
while ((read = fis.read(bytes)) != -1) {
fos.write(bytes);
}
fis.close();
fos.close();
}
}
2.7 字节缓冲流
字节缓冲流
- BufferedOutputStream:该类实现缓冲输出流。
- 通过设置这样的输出流,应用程序可以向底层输出流写入字节,而不必为写入的每个字节导致底层系统的调用
- BufferedInputStream:创建BufferedInputStream将创建一个内部缓冲区数组。
- 当从流中读取或跳过字节时,内部缓冲区将根据需要从所包含的输入流中重新填入,一次很多字节
字节缓冲流用来提高字节的读写速度
构造方法:
- 字节缓冲输出流:BufferedOutputStream(OutputStream out)
- 字节缓冲输入流:BufferedInputStream(InputStream in)
为什么构造方法需要的是字节流,而不是具体的文件或者路径呢?
- 字节缓冲流仅仅提供缓冲区,而真正的读写数据还得依靠基本的字节流对象进行操作
/*
字节缓冲流
BufferedOutputStream:该类实现缓冲输出流。
通过设置这样的输出流,应用程序可以向底层输出流写入字节,而不必为写入的每个字节导致底层系统的调用
BufferedInputStream:创建BufferedInputStream将创建一个内部缓冲区数组。
当从流中读取或跳过字节时,内部缓冲区将根据需要从所包含的输入流中重新填入,一次很多字节
构造方法:
字节缓冲输出流:BufferedOutputStream(OutputStream out)
字节缓冲输入流:BufferedInputStream(InputStream in)
为什么构造方法需要的是字节流,而不是具体的文件或者路径呢?
字节缓冲流仅仅提供缓冲区,而真正的读写数据还得依靠基本的字节流对象进行操作
*/
public class BufferedFileDemo {
public static void main(String[] args) throws IOException {
// 创建字节缓冲输出流
// 两步可以合并为一步
// FileOutputStream fos = new FileOutputStream("test\src\bos.txt");
// BufferedOutputStream bos = new BufferedOutputStream(fos);
BufferedOutputStream bos = new BufferedOutputStream(
new FileOutputStream("test\src\bos.txt"));
// 写数据
bos.write("hello world
".getBytes());
bos.write("键盘敲烂,月薪过万".getBytes());
// 关闭缓冲流和输出流
bos.close();
BufferedInputStream bis = new BufferedInputStream(
new FileInputStream("test\src\bos.txt"));
// 这个方法一次性读取文件里的所有内容
// String s = new String(bis.readAllBytes());
// System.out.println(s);
byte[] bytes = new byte[1024];
int read;
while ((read = bis.read(bytes)) != -1) {
System.out.println(new String(bytes,0,read));
}
bis.close();
}
}
案例:复制视频
需求:把"E:itcast字节流复制图片.avi" 复制到模块目录下的 "字节流复制图片.avi"
思路:
- 根据数据源创建字节输入流对象
- 根据目的地创建字节输出流对象
- 读写数据,复制视频
- 释放资源
/*
需求:把"E:\itcast\字节流复制图片.avi" 复制到模块目录下的 "字节流复制图片.avi"
四种方式实现复制视频,并记录每种方式复制视频的时间
*/
public class BufferedDemo {
public static void main(String[] args) throws IOException {
/* BufferedInputStream bis = new BufferedInputStream(
new FileInputStream("E:\itcast\001_Java语言发展史.avi"));
BufferedOutputStream bos = new BufferedOutputStream(
new FileOutputStream("E:\itcast\JavaSe\001_Java语言发展史.avi")
);
long startTime = System.currentTimeMillis();
// byte[] bytes = bis.readAllBytes();
//// bos.write(bytes);
int read;
while ((read = bis.read()) != -1) {
bos.write(read);
}
long endTime = System.currentTimeMillis();
// System.out.println("字节缓冲流一次读写一个字节数组用时"+(endTime-startTime));// 用时25ms
// System.out.println("字节缓冲流一次读写一个字节用时"+(endTime-startTime));// 用时156ms
bis.close();
bos.close();*/
FileInputStream fis = new FileInputStream("E:\itcast\001_Java语言发展史.avi");
FileOutputStream fos = new FileOutputStream("E:\itcast\JavaSe\001_Java语言发展史.avi");
byte[] bytes = new byte[1024];
int read;
long startTime = System.currentTimeMillis();
// while ((read = fis.read()) != -1) {
// fos.write(read);
// }
while ((read = fis.read(bytes)) != -1) {
fos.write(bytes);
}
long endTime = System.currentTimeMillis();
// System.out.println("字节流一次读写一个字节用时"+(endTime-startTime));// 用时37676ms
System.out.println("字节流一次读写一个字节数组用时"+(endTime-startTime));// 用时69ms
fis.close();
fos.close();
}
}
3 字符流
3.1 为什么会出现字符流
由于字节流操作中文不是特别的方便,所以Java提供了字符流
- 字符流 = 字节流+编码表
用字节流复制文本文件时,文本文件也会有中文,但是没有问题,原因是最终底层操作会自动进行字节拼接成中文,如何识别是中文呢?
- 汉字在存储的时候,无论选择哪种编码,第一个字节一定是负数
/*
一个汉字存储:
如果是GBK编码,占用2个字节
如果是UTF-8编码,占用3个字节
*/
public class FileInputStreamDemo {
public static void main(String[] args) throws IOException {
// String s = "abc"; // [97, 98, 99]
String s = "中国"; // [-28, -72, -83, -27, -101, -67] 默认UTF-8
// byte[] bytes = s.getBytes();
byte[] bytes = s.getBytes("GBK");// [-42, -48, -71, -6] GBK
System.out.println(Arrays.toString(bytes));
}
}
3.2 编码表
基础知识:
- 计算机中存储的信息都是用二进制数表示的;我们在屏幕上看到的英文、汉字等字符是二进制数转换之后的结果
- 按照某种规则,将字符存储到计算机中,称为编码。反之,将存储在计算机中的二进制数按照某种规则解析显示出来,称为解码。 按照A编码存储,必须按照A编码解析,这样才能显示正确的文本符号,否则就会导致乱码现象
- 字符编码:就是一套自然语言的字符与二进制数之间的对应规则
字符集:
- 是一个系统支持的所有字符的集合,包括各国家文字、标点符号、图形符号、数字等
- 计算机要准确的存储和识别各种字符集符号,就需要进行字符编码,一套字符集必然至少有一套字符编码常见的字符集有ASCII字符集、GBXXX字符集、Unicode字符集等
ASCII字符集:
- ASCII(American Standard Code for Information Interchange,美国信息交换标准代码):是基于拉丁字母的一套电脑编码系统,用于显示现代英语,主要包括控制字符(回车键、退格、换行键等)和可显示字符(英文大小写字符、阿拉伯数字和西文符号)
- 基本的ASCII字符集,使用7位表示一个字符,共128字符。ASCII的扩展字符集使用8为表示一个字符,共256个字符,方便支持欧洲常用字符。是一个系统支持的所有字符的集合,包括各国家文字、标点符号、图形符号、数字等
GBXXX字符集:
- GB2312:简体中文码表。一个小于127的字符的意义与原来相同,但两个大于127的字符连在一起时,就表示一个汉字,这样大约可以组合了包含7000多个简体汉字,此外数学符号、罗马希腊的字母、日文的假名等都编进去了,连在ASCII里本来就有的数字、标点、字母都统统重新编了两个字节长的编码,这就是常说的"全角"字符,而原来的127号一下的那些就叫"半角"字符了
- GBK:最常用的中文码表。是在GB2312标准基础上的扩展范围,使用了双字节编码方案,共收录了21003个汉字,完全兼容GB2312标准,同时支持繁体汉字以及日韩汉字等
- GB18030:最新的中文码表。收录汉字70244个,采用多字节编码,每个字可以由1个、2个或4个字节组成。支持中国国内少数民族的文字,同时支持繁体汉字以及日韩汉字等
Unicode字符集:
为表达任意语言的任意字符而设计,是业界的一种标准,也称为统一码、标准万国码。它最多使用4个字节的数字来表达每一个字母、符号,或者文字。有三种编码方案,UTF-8、UTF-16和UTF-32。最为常用的UTF-8编码
UTF-8编码:可以用来表示Unicode标准中任意字符,它是电子邮件、网页及其他存储或传送文字的应用中,优先采用的编码。互联网工程工作小组(IETF)要求所有互联网协议都必须支持UTF-8编码。它使用1-4个字节为每个字符编码
- 编码规则:
- 128个US-ASCII字符,只需1个字节编码
- 拉丁文等字符,需要2个字节编码
- 大部分常用字(含中文),使用3个字节编码
- 其他极少使用的Unicode辅助字符,使用4字节编码
小结:采用何种规则编码,就要采用对应规则解码,否则就会出现乱码
- 编码规则:
3.3 字符串中的编码解码问题
编码:
- byte[] getBytes() 使用平台的默认字符集将该 String编码为一系列字节,将结果存储到新的字节数组中
- byte[] getBytes(String charsetName)使用命名的字符集将该String编码为一系列字节,将结果存储到新的字节数组中
解码:
- String(byte[] bytes) 通过使用平台的默认字符集解码指定的字节数组来构造新的 String
- String(byte[] bytes, String charsetName) 构造一个新的String由指定用指定的字节的数组解码charset
/*
编码:
byte[] getBytes() 使用平台的默认字符集将该 String编码为一系列字节,将结果存储到新的字节数组中
byte[] getBytes(String charsetName)
使用命名的字符集将该String编码为一系列字节,将结果存储到新的字节数组中
解码:
String(byte[] bytes) 通过使用平台的默认字符集解码指定的字节数组来构造新的 String
String(byte[] bytes, String charsetName) 构造一个新的String由指定用指定的字节的数组解码charset
*/
public class StringDemo {
public static void main(String[] args) throws UnsupportedEncodingException {
// 定义一个字符串
String s = "中国";
// byte[] getBytes() 使用平台的默认字符集将该 String编码为一系列字节,将结果存储到新的字节数组中
byte[] bytes = s.getBytes();
System.out.println(Arrays.toString(bytes)); // [-28, -72, -83, -27, -101, -67]UTF-8
// byte[] bytes = s.getBytes("utf-8");
// System.out.println(Arrays.toString(bytes)); // [-28, -72, -83, -27, -101, -67]UTF-8
// byte[] bytes = s.getBytes("gbk");
// System.out.println(Arrays.toString(bytes)); // [-42, -48, -71, -6]GBK
// String ss = new String(bytes);
// System.out.println(ss); // 中国
// String ss = new String(bytes,"utf-8");
// System.out.println(ss); // 中国
String ss = new String(bytes,"gbk");
System.out.println(ss); // 涓浗
}
}
3.4 字符流中的编码解码问题
字符流抽象类型:
- Reader:字符输入流的抽象类
- Writer:字符输出流的抽象类
字符流中和编码问题相关的两个类
- InputStreamReader
- OutputStreamWriter
InputStreamReader:是从字节流到字符流的桥梁
- 它读取字节,并使用指定的编码将其解码为字符
- 它使用的字符集可以由名称指定,也可以被明确指定,或者可以接受平台的默认字符集
- 构造方法
- InputStreamReader(InputStream in) 创建一个使用默认字符集的InputStreamReader
- InputStreamReader(InputStream in, String charsetName) 创建一个使用命名字符集的InputStreamReader
- 构造方法
OutputStreamWriter:是从字符流到字节流的桥梁
- 是从字符流到字节流的桥梁,使用指定的编码将接入的字符编码为字节
- 它使用的字符集可以由名称指定,也可以被明确指定,或者可以接受平台的默认字符集
- 构造方法
- OutputStreamWriter(OutputStream out)创建一个使用默认字符编码的OutputStreamWriter
- OutputStreamWriter(OutputStream out, String charsetName)创建一个使用命名字符集的OutputStreamWriter
- 构造方法
public class ConversionStreamDemo {
public static void main(String[] args) throws IOException {
/* OutputStreamWriter osw = new OutputStreamWriter(
new FileOutputStream("test\src\osw.txt"),"utf-8");
osw.write("中国");
osw.close();*/
OutputStreamWriter osw = new OutputStreamWriter(
new FileOutputStream("test\src\osw.txt"), "GBK");
osw.write("中国");
osw.close();
// 用UTF-8读取GBK会乱码
InputStreamReader isr = new InputStreamReader(
new FileInputStream("test\src\osw.txt"), "GBK");
// 一次读取一个字符数据
int ch;
while ((ch = isr.read()) != -1) {
System.out.println((char) ch);
}
osw.close();
isr.close();
}
}
3.5 字符流写数据的5种方式
构造方法:
- OutputStreamWriter(OutputStream out) 创建一个使用默认字符编码的OutputStreamWriter
写入数据的5种方式:
- void write(int c) 写一个字符
- void write(char[] cbuf) 写入一个字符数组
- void write(char[] cbuf, int off, int len) 写入字符数组的一部分
- void write (String str) 写一个字符串
- void write(String str, int off, int len) 写一个字符串的一部分
public class OutputStreamWriterDemo {
public static void main(String[] args) throws IOException {
OutputStreamWriter osw = new OutputStreamWriter(
new FileOutputStream("src\osw.txt"));
// void write(int c) 写一个字符
osw.write(97);
// void write(char[] cbuf) 写入一个字符数组
char[] chs = {'b','c','d','e','f'};
osw.write(chs);
// void write(char[] cbuf, int off, int len) 写入字符数组的一部分
osw.write(chs,0,3);
// void write (String str) 写一个字符串
String s = "我是沈剑心,是稻香村的一名保安";
osw.write(s);
osw.write(s,0,5);
osw.flush();
osw.close();
/*
flush():刷新流,还可以继续写数据
close():关闭流,释放资源,但是在关闭之前会先刷新流。一旦关闭,就不能再写数据
*/
}
}
3.6 字符流读数据的2种方式
构造方法:
- InputStreamReader(InputStream in) 创建一个使用默认字符集的InputStreamReader
读数据的2种方法
- int read() 读一个字符
- int read(char[] cbuf, int offset, int length) 将字符读入数组的一部分
public class InputStreamReaderDemo {
public static void main(String[] args) throws IOException {
// InputStreamReader(InputStream in) 创建一个使用默认字符集的InputStreamReader
// InputStreamReader isr = new InputStreamReader(
// new FileInputStream("src\osw.txt"));
InputStreamReader isr = new InputStreamReader(
new FileInputStream("test\src\com\itcast\" +
"unit9\test1\编码表"));
// 读取数据
// int read() 读一个字符
// int read;
// while ((read = isr.read()) != -1) {
// System.out.println((char) read);
// }
// int read(char[] cbuf, int offset, int length) 将字符读入数组的一部分
char[] chs = new char[1024];
int read;
while ((read = isr.read(chs)) != -1) {
String s = new String(chs,0,read);
System.out.println(s);
}
// 关闭字符流
isr.close();
}
}
案例:复制java文件
需求: 把目录下的ConversionStreamDemo.java 复制到目录下的Copy.Java
思路:
- 根据数据源创建字符输入流对象
- 根据目的地创建字符输出流对象
- 读写程序,复制文件
- 释放资源
/*
需求:
把模块目录下的ConversionStreamDemo.java 复制到模块目录下的Copy.Java
*/
public class ConversionStreamDemo {
public static void main(String[] args) throws IOException {
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(
"test\src\com\itcast\unit9\test3\Copy.java"));
InputStreamReader isr = new InputStreamReader(new FileInputStream(
"test\src\com\itcast\unit9\test1\ConversionStreamDemo.java"));
int ch;
while ((ch = isr.read()) != -1) {
osw.write(ch);
}
osw.close();
isr.close();
}
}
案例:复制java文件(改进版)
需求:把模块目录下的"ConversionStreamDemo.java"复制到模块目录下的"Copy.java"
分析:
- 转换流的名字比较长,而我们常见的操作都是按照本地默认编码实现的,所以,为了简化书写,转换流提供了对应的子类
子类名:
- FileReader:用于读取字符文件的便捷类
- 构造方法:FileReader(String fileNmae)
- FileWriter:用于写入字符文件的便捷类
- 构造方法:FileWriter(String fileName)
数据源和目的地的分析
- 数据源:ConversionStreamDemo.java---读数据---Reader---InputStreamReader---FileReader
- 目的地:Copy.java---写数据---Writer---OutputStreamWriter---FileWriter
思路:
- ? 根据数据源创建字符输入流对象
- ? 根据目的地创建字符输出流对象
- ? 读写数据,复制文件
- ? 释放资源
public class ConversionStreamDemoPlus {
public static void main(String[] args) throws IOException {
FileReader reader = new FileReader(
"test\src\com\itcast\unit9\test1\ConversionStreamDemo.java");
FileWriter writer = new FileWriter(
"test\src\com\itcast\unit9\test3\Copy.java");
int read;
while ((read = reader.read()) != -1) {
writer.write(read);
}
reader.close();
writer.close();
}
}
3.7 字符缓冲流
BufferedWriter:
- 将文本写入字符输出流,缓冲字符,以提供单个字符,数组和字符串的高效写入,可以指定缓冲区大小,或者可以接受默认大小。 默认值足够大,可用于大多数用途
BufferedReader:
- 从字符输入流读取文本,缓冲字符,以提供字符,数组和行的高效读取,可以指定缓冲区大小,或者可以使用默认大小。 默认值足够大,可用于大多数用途
构造方法:
- ? BufferedWriter(Writer out)
- ? BufferedReader(Reader in)
字符缓冲流特有功能:
- BufferedWriter:
- void newLine():写一行行分隔符,行分隔符字符串由系统属性定义
- BufferedReader:
- public String readLine():读一行文字。结果包含行的内容的字符串,不包括任何行终止字符,如果流的结尾已经到达,则为null
/*
BufferedWriter:
将文本写入字符输出流,缓冲字符,以提供单个字符,数组和字符串的高效写入
可以指定缓冲区大小,或者可以接受默认大小。 默认值足够大,可用于大多数用途
BufferedReader:
从字符输入流读取文本,缓冲字符,以提供字符,数组和行的高效读取
可以指定缓冲区大小,或者可以使用默认大小。 默认值足够大,可用于大多数用途
构造方法:
BufferedWriter(Writer out)
BufferedReader(Reader in)
*/
public class BufferedStreamDemo {
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(
new FileReader("test\src\com\itcast\unit9\test1\编码表"));
BufferedWriter bw = new BufferedWriter(
new FileWriter("test\src\com\itcast\unit9\test1\Copy.txt"));
int ch;
// 一次读写一个字符数据
/*
while ((ch = br.read()) != -1) {
bw.write(ch);
}*/
// 一次读写一行字符串数据
// 读一行文子。结果包含行的内容的字符串,不包括任何行终止字符
String s = br.readLine();
// 如果流的结尾已经达到,则为null
while (s != null) {
bw.write(s);
// 写一行行分隔符,行分隔符字符串由系统属性定义
bw.newLine();
// 一般用字符缓冲输出流写一行字符串,就刷新一次
bw.flush();
s = br.readLine();
}
/*
字符缓冲流中的特有方法:
BufferedWriter:
void newLine():写一行行分隔符,行分隔符字符串由系统属性定义
BufferedReader:
public String readLine():读一行文子。结果包含行的内容的字符串,不包括任何行终止字符
如果流的结尾已经达到,则为null
*/
// 一次读写一个字符数组的数据
/* char[] chs = new char[8192];
while ((ch = br.read(chs)) != -1) {
bw.write(chs, 0, ch);
}*/
br.close();
bw.close();
}
}
★IO流小结
字节流
- 字节输入流InputStream:
- 读数据:
- int read():一次读取一个字节
- int read(byte[] bys):一次读取一个字节数组
- FileInputStream
- BufferedInputStream
- 读数据:
- 字节输出流OutputStream:
- 读数据:
- void write(int by):一次写一个字节
- void write(byte[] bys,int index,int len):一次写一个字节数组的一部分
- FileOutputStream
- BufferedOutputStream
- 读数据:
小结:字节流可以复制任意文件数据,有四种方式一般采用字节缓冲流一次读写一个字节数组的方式
字符流
- 字符输入流Reader:
- 读数据:
- int read():一次读取一个字符
- int read(char[] chs):一次读取一个字符数组
- InputStreamReader(如果牵扯到编码问题只能使用InputStreamReader)
- FileReader(因为InputStreamReader写起来名字太长所以用它的子类FileReader)
- BufferedReader:
- String readLine():一次读取一个字符串
- InputStreamReader(如果牵扯到编码问题只能使用InputStreamReader)
- 读数据:
- 字符输出流Writer:
- 写数据:
- void write(int ch):一次写一个字符
- void write(char[] chs,int index,int len):一次写一个字符数组的一部分
- OutputStreamWriter(如果牵扯到编码问题只能使用OutputStreamWriter)
- FileWriter(因为OutputStreamWriter写起来名字太长所以用它的子类FileWriter)
- BufferedWriter:
- void newLine():写一个换行符
- void write(String line):一次写一个字符串
- OutputStreamWriter(如果牵扯到编码问题只能使用OutputStreamWriter)
- 写数据:
小结:字符流只能复制文本数据,有五种方式,一般采用字符缓冲流的特有功能
案例:集合到文件
需求:把ArrayList集合中的字符串数据写入到文本文件
要求:每一个字符串元素作为文件中的一行数据
分析:
- 创建一个ArrayList集合
- 往集合中存储字符串元素
- 创建字符缓冲输出流,指定目的地
- 遍历ArrayList,得到每一个字符串数据
- 调用字符缓冲输出流对象的方法写数据
- 关闭缓冲流,释放资源
/*
需求:把ArrayList集合中的字符串数据写入到文本文件
要求:每一个字符串元素作为文件中的一行数据
分析:
创建一个ArrayList集合
往集合中存储字符串元素
创建字符缓冲输出流,指定目的地
遍历ArrayList,得到每一个字符串数据
调用字符缓冲输出流对象的方法写数据
关闭缓冲流,释放资源
*/
public class ArrayToFIleDemo {
public static void main(String[] args) throws IOException {
List<String> list = new ArrayList<>();
list.add("hello");
list.add("world");
list.add("love");
list.add("you");
BufferedWriter bw = new BufferedWriter(
new FileWriter("test\src\com\itcast\unit9\test4\Copy.txt"));
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
bw.write(iterator.next());
bw.newLine();
bw.flush();
}
System.out.println("写入成功");
bw.close();
}
}
案例:文件到集合
需求:把文本文件中的数据读取到集合中,并遍历集合。
要求:文件中每一行数据是一个集合元素
思路:
- 创建一个ArrayList集合
- 创建字符缓冲输入流对象,指定数据源
- 调用readLine()方法获取数据源的每一行数据存储为字符串类型
- 将获得的字符串添加到ArrayList集合中
- 关闭字符缓冲输入流,释放资源
- 遍历ArrayList集合
/*
需求:把文本文件中的数据读取到集合中,并遍历集合。
要求:文件中每一行数据是一个集合元素
思路:
创建一个ArrayList集合
创建字符缓冲输入流对象,指定数据源
调用readLine()方法获取数据源的每一行数据存储为字符串类型
将获得的字符串添加到ArrayList集合中
关闭字符缓冲输入流,释放资源
遍历ArrayList集合
*/
public class FileToArrayDemo {
public static void main(String[] args) throws IOException {
List<String> list = new ArrayList<>();
BufferedReader br = new BufferedReader(
new FileReader("test\src\com\itcast\unit9\test5\Copy.txt"));
String s;
while ((s = br.readLine()) != null) {
list.add(s);
}
br.close();
System.out.println(list);
}
}
案例:点名器
需求:有一个文件里存储了班级同学的姓名,每一个姓名占一行,要求通过程序实现随机点名器
思路:
- 创建字符缓冲输入流对象
- 创建ArrayList集合对象
- 调用字符缓输入流对象的方法读数据
- 把读取到的字符串数据存储到集合中
- 释放资源
- 使用Random产生一个随机数,随机数的范围在:[0,集合的长度)
- 把上一步产生的随机数作为索引到ArrayList集合中获取值
- 把上一步得到的数据在控制台输出
/*
需求:我有一个文件里面存储了班级同学的姓名,每一个姓名占一行,要求通过程序实现随机点名器
*/
public class PointNameDemo {
public static void main(String[] args) throws IOException {
List<String> list = new ArrayList<>();
BufferedReader br = new BufferedReader(
new FileReader("test\src\com\itcast\unit9\test6\Name.txt"));
String s;
while ((s = br.readLine()) != null) {
list.add(s);
}
br.close();
System.out.println(list);
Random random = new Random();
for (int j = 0; j < 10; j++) {
int i = random.nextInt(list.size());
System.out.println(list.get(i));
}
}
}
案例:集合到文件改进版
需求:把ArrayList集合中的学生数据写入到文本文件
要求:每一个学生对象的数据作为文件中的一行数据
格式:学号,姓名,年龄,居住地
思路:
- 定义学生类
- 创建ArrayList集合
- 创建学生对象
- 把学生对象添加到集合中
- 创建字符缓冲流输出数据
- 遍历集合,得到每一个学生对象
- 把学生对象读数据拼接成指定格式的字符串
- 调用字符缓冲输出流对象的方法写数据
- 释放资源
/*
需求:把ArrayList集合中的学生数据写入到文本文件
要求:每一个学生对象的数据作为文件中的一行数据
格式:学号,姓名,年龄,居住地
*/
public class ArrayToFileDemoPlus {
public static void main(String[] args) throws IOException {
BufferedWriter bw = new BufferedWriter(
new FileWriter("test\src\com\itcast\unit9\test7\Resouce.txt"));
List<Student> list = new ArrayList<>();
addList(list);
for (Student s : list) {
StringBuilder sb = new StringBuilder();
sb.append(s.getId()).append(",").append(s.getName()).append(",")
.append(s.getAge()).append(",").append(s.getAddress());
String str = sb.toString();
bw.write(str);
bw.newLine();
bw.flush();
}
bw.close();
}
public static void addList(List<Student> list) {
Student student1 = new Student("i001", "沧笙踏歌还", 26, "长歌门");
Student student2 = new Student("i002", "荼芈", 15, "七秀坊");
Student student3 = new Student("i003", "芸葵", 22, "万花谷");
Student student4 = new Student("i004", "芷苑", 23, "五毒");
Student student5 = new Student("i005", "不思汴梁故", 25, "明教");
Student student6 = new Student("i006", "小个核桃", 14, "明教");
Student student7 = new Student("i007", "慑梦", 23, "七秀坊");
Student student8 = new Student("i008", "旌衣", 24, "苍云堡");
Student student9 = new Student("i009", "不加溏", 20, "长歌门");
list.add(student1);
list.add(student2);
list.add(student3);
list.add(student4);
list.add(student5);
list.add(student6);
list.add(student7);
list.add(student8);
list.add(student9);
}
}
// 格式:学号,姓名,年龄,居住地
public class Student {
private String id;
private String name;
private int age;
private String address;
public Student() {
}
public Student(String id, String name, int age, String address) {
this.id = id;
this.name = name;
this.age = age;
this.address = address;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
}
案例:文件到集合改进版
把文本文件中的数据读取到集合中来,并遍历集合。
要求:文件中每一行数据是一个学生对象的成员变量值
思路:
- 定义学生类
- 创建字符缓冲输入流对象
- 创建ArrayList集合对象
- 调用字符缓冲输入流对象的方法读数据
- 把读取到的字符串数据用split()进行分割,得到一个字符串数组
- 创建学生对象
- 把字符串数组中的每一个元素取出来对应的赋值给学生对象的成员变量值
- 把学生对象添加到集合
- 释放资源
- 遍历集合
// 学生类延用上一个案例,此处不再赘述
public class FileToArrayDemoPlus {
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(
new FileReader("test\src\com\itcast\unit9\test8\Resouce.txt"));
List<Student> list = new ArrayList<>();
String line;
while ((line = br.readLine()) != null) {
String[] split = line.split(",");
Student student = new Student(split[0],split[1],Integer.parseInt(split[2]),split[3]);
list.add(student);
}
br.close();
for (Student s : list) {
System.out.println(s.toString());
}
}
}
案例:集合到文件(数据排序改进版)
需求:键盘录入5个学生信息(姓名,语文成绩,数学成绩,英语成绩)。
要求:按照成绩总分从高到低写入文本文件
格式:姓名,语文成绩,数学成绩,英语成绩
思路:
- 定义学生类
- 创建TreeSet集合,通过比较器排序进行排序
- 键盘录入学生数据
- 创建学生对象,把键盘录入的数据对应赋值给学生对象的成员变量
- 把学生对象添加到TreeSet集合
- 创建字符缓冲输出流对象
- 遍历集合,得到每一个学生对象
- 把学生对象的数据拼接成指定格式的字符串
- 调用字符缓冲输出流对象的方法写数据
- 释放资源
/*
需求:键盘录入5个学生信息(姓名,语文成绩,数学成绩,英语成绩)
要求:按照成绩总分从高到低写入文本文件
格式:姓名,语文成绩,数学成绩,英语成绩
举例:林青霞,98,99,100
*/
public class TreeSetToFileDemo {
public static void main(String[] args) throws IOException {
Set<Student> set = new TreeSet<>();
addSet(set);
BufferedWriter bw = new BufferedWriter(
new FileWriter("test\src\com\itcast\unit10\test1\Student.txt"));
for (Student s : set) {
StringBuilder sb = new StringBuilder();
sb.append(s.getName()).append(",").append(s.getChinese()).append(",")
.append(s.getMath()).append(",").append(s.getEnglish());
bw.write(sb.toString());
bw.newLine();
bw.flush();
}
bw.close();
}
public static void addSet(Set<Student> set) {
Scanner scanner = new Scanner(System.in);
Student student;
for (int i = 1; i <= 6; i++) {
student = new Student();
System.out.println("请依次输入第" + i + "个学生的姓名,语文成绩,数学成绩,英语成绩");
// nextLine()会获取包括空格在内的一整行字符串,而next识别空格
String name = scanner.next();
student.setName(name);
int chinese = scanner.nextInt();
student.setChinese(chinese);
int math = scanner.nextInt();
student.setMath(math);
int english = scanner.nextInt();
student.setEnglish(english);
set.add(student);
/*
需要手动输入的数据,每次录入都可以通过单行复制代替
Kirito 100 100 100
Asuna 100 80 90
Lizubete 100 90 80
Cirika 60 95 95
Yuki 60 90 100
Kurain 60 90 100
*/
}
}
}
案例:复制单级文件夹
/*
需求:把"D:\itcast"这个文件夹复制到"test\src\com\itcast\unit10\test2"下
*/
public class CopyDemo {
public static void main(String[] args) throws IOException {
File file1 = new File("D:\itcast01");
String dirName = file1.getName();
File file = new File("test\src\com\itcast\unit10\test2", dirName);
if (!file.exists()) {
file.mkdir();
}
System.out.println(file);
File[] files = file1.listFiles();
copyMethod(files,file);
}
public static void copyMethod(File[] files,File f) throws IOException {
for (File file : files) {
if (file.isFile()) {
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));
File dirFile = new File(f,file.getName());
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(dirFile));
int ch;
while ((ch = bis.read()) != -1) {
bos.write(ch);
}
bis.close();
bos.close();
}
}
}
}
案例:复制多级文件夹
/*
需求:把"D:\itcast01"这个文件夹复制到
"test\src\com\itcast\unit10\test2"下
*/
public class CopyDemoPlus {
public static void main(String[] args) {
// srcouse源
// destination目的地
File srcFile = new File("D:\itcast");
String srcName = srcFile.getName();
File destFile = new File("test\src\com\itheima2\unit10\test2", srcName);
if (!destFile.exists()) {
destFile.mkdir();
}
File[] files = srcFile.listFiles();
copyFile(srcFile, destFile);
}
public static void copyFile(File srcFile, File destFile) {
File[] files = srcFile.listFiles();
for (File f : files) {
String path = destFile.getPath() + "\" + f.getName();
if (f.isDirectory()) {
File pathFile = new File(path);
if (!pathFile.exists()) {
pathFile.mkdir();
}
copyFile(f, pathFile);
} else {
// try{}catch(){}finally{}的方法
BufferedInputStream bis = null;
BufferedOutputStream bos = null;
try {
bis = new BufferedInputStream(new FileInputStream(f));
bos = new BufferedOutputStream(new FileOutputStream(path));
int ch;
while ((ch = bis.read()) != -1) {
bos.write(ch);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if(bis != null){
try {
bis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (bos != null) {
try {
bos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
}
}
3.8 复制文件的异常处理
try...catah...finally的做法:
try{
可能出现异常的代码;
}catch(异常类名 变量名){
异常的处理代码;
}finally{
执行所有的清除操作;
}
JDK7改进方案
try(定义流对象){
可能出现异常的代码;
}catch(异常类名 变量名){
异常处理的代码;
}
**自动释放资源**
JDK9改进方案
定义输入流对象;
定义输出流对象;
try(输入流对象;输出流对象){
可能出现异常的代码;
}catch(异常类名 变量名){
异常的代码处理;
}
**自动释放资源**
4 特殊操作流程
4.1 标准输入输出流
System类中有两个静态的成员变量
- public static final InputStream in:标准输入流。通常该流对应于键盘输入或用户指定的另一个输入源
- public static final PrintStream out:标准输出流。通常该流对应于显示输出或由主机环境或用户指定的另一个输出目标
自己实现键盘录入数据:
- BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
写起来太麻烦,Java就提供了一个类实现键盘录入
- Scanner sc = new Scanner(System.in);
输出语句的本质:是一个标准的输出流
- PrintStream ps = System.out;
- PrintStream类有的方法,System.out都可以使用
public class StandardStreamDemo {
public static void main(String[] args) throws IOException {
// input();
output();
}
public static void input() throws IOException {
// public static final InputStream in:
// 标准输入流。通常该流对应于键盘输入或由主机环境或用户指定的另一个输入源
InputStream in = System.in;
int ch;
// 这是一个字节流,在接受字节数据时显示正常,接收一个字符数据时就会显示异常了
/* while ((ch = in.read()) != -1) {
System.out.println((char) ch);
}*/
// 用转换流把字节流转换成字符流
// InputStreamReader(InputStream in)
InputStreamReader reader = new InputStreamReader(in);
// 使用字符流能不能够实现一次读取一行数据呢?可以
// 但是,一次读取一行数据的方法是字符缓冲输入流的特有方法
BufferedReader br = new BufferedReader(reader);
String line;
// while((line = br.readLine())!=null){
// if(line.equals("break")){
// System.out.println("退出成功");
// break;
// }
// System.out.println(line);
// }
System.out.println("请输入一个字符串");
if ((line = br.readLine()) != null) {
System.out.println(line);
}
System.out.println("请输入一个整形数据");
if ((line = br.readLine()) != null) {
int i = Integer.parseInt(line);
System.out.println(i);
}
// 自己实现键盘录入数据太麻烦,所以Java提供了一个类供我们使用
Scanner sc = new Scanner(System.in);
}
public static void output() throws IOException {
// public static final OutputStream out:
// 标准输出流。通常该流对应于显示输出或由主机环境或用户指定的另一个输出目标
PrintStream out = System.out;
// 能够方便的打印各种类型的数据
// 不换行输出
out.print("hello");
out.print(100);
// 换行输出
out.println();
out.println("hello");
out.println(100);
// System.out的本质是一个字节输出流
System.out.println("hello");
System.out.println(100);
// println()可以不带参数,print()必须带参数
System.out.println();
// System.out.print();
}
}
4.2 打印流
打印流分类:
- 字节打印流:PrintStream
- 字符打印流:PrintWriter
打印流的特点:
- 只负责输出数据,不负责读取数据
- 有自己的特有方法
字节打印流
- PrintStream(String fileName):使用指定的文件名创建新的打印流
- 使用继承父类的方法写数据,查看的时候会转码;使用自己的特有方法写数据,查看的数据原样输出
public class PrintStreamDemo {
public static void main(String[] args) throws IOException {
// PrintStream(String fileName):使用指定的文件名创建新的打印流
PrintStream ps = new PrintStream("test\ps.txt");
// 写数据
// 字节输出流有的方法:
ps.write(97);
String s = "恨不得以浩气之身战死";
byte[] bytes = s.getBytes();
ps.write(bytes,0,bytes.length);
// 使用特有方法写数据
ps.write("
".getBytes());
ps.println("我叫沈剑心,是稻香村的一名保安");
ps.print(98);
ps.print("
");
String line = "行行复行行,何日是归期";
ps.append(line.subSequence(0, line.length()));
ps.flush();
ps.close();
}
}
字符打印流
字符打印流PrintWriter的构造方法
- PrintWriter(String fileName):使用指定的文件名创建一个新的PrintWriter,而不需要自动执行行刷新
- public PrintWriter(OutputStream out,boolean autoFlush):从现有的OutputStream创建一个新的PrintWriter。
- out - 输出流
- autoFlush - 一个布尔值 如果为真,则println , printf ,或format方法将刷新输出缓冲区
public class PrintWriterDemo {
public static void main(String[] args) throws IOException {
// PrintWriter(String fileName)使用指定的文件名创建一个新的PrintWriter,而不需要自动执行行刷新
PrintWriter pw = new PrintWriter("test\pw.txt");
// 写数据
// 字符流的数据不能直接到文件,必须经过刷新,如果添加close方法则执行方法之前会刷新
pw.write("天道不灭,浩气长存");
pw.write("
");
// 刷新后显示输入内容,这种构造方法需要刷新才能显示
pw.flush();
pw.write("一入此谷,永不受苦");
pw.write("
");
pw.flush();
pw.close();
//public PrintWriter(OutputStream out,boolean autoFlush)
// 从现有的OutputStream创建一个新的PrintWriter。
// out - 输出流
// autoFlush - 一个布尔值 如果为真,则println , printf ,或format方法将刷新输出缓冲区
PrintWriter pw2 = new PrintWriter(new FileWriter("test\pw.txt"), true);
// 只有println , printf ,或format方法才能刷新,write不能刷新
pw2.write("道可道,非常道,恶人都是大傻帽
");
// 刷新缓冲区时,之前的write方法写入的数据也一同被刷新出来
pw2.println("天道不灭,浩气长存");
// close方法会自动刷新缓冲区
pw2.close();
}
}
案例:复制Java文件(打印流改进版)
需求:
- 把模块目录下的PrintStreamDemo.java 复制到模块目录下的Copy.java
思路:
- 根据数据源创建字符输入流对象
- 根据目的地创建字符输出流对象
- 读写数据,复制文件
- 释放资源
/*
数据源和目的地自选
*/
public class CopyJavaDemoPlus {
public static void main(String[] args) throws IOException {
/* BufferedReader br = new BufferedReader(
new FileReader("test\src\com\itcast\unit10\test1\Student.java"));
BufferedWriter bw = new BufferedWriter(
new FileWriter("test\src\com\itcast\unit10\test7\Student.java"));
String line;
while ((line = br.readLine()) != null) {
bw.write(line);
bw.newLine();
bw.flush();
}
br.close();
bw.close();*/
/*
以上代码可以用字符打印流改进
*/
BufferedReader br = new BufferedReader(
new FileReader("test\src\com\itcast\unit10\test1\Student.java"));
PrintWriter pw = new PrintWriter(
new FileWriter("test\src\com\itcast\unit10\test7\Student.java")
, true);
String line;
while ((line = br.readLine()) != null) {
pw.println(line);
}
br.close();
pw.close();
}
}
4.3 对象序列化流
对象序列化:
- 就是将对象保存到磁盘中,或者在网络中传输对象
- 这种机制就是使用一个字节序列表示一个对象,该字节序列包含:对象的类型、对象的数据和对象中存储的属性等信息字节序列写到文件之后,相当于文件中持久保存了一个对象信息
- 反之,该字节序列还可以从文件中读取会来,重构对象,对它进行反序列化
要实现序列化和反序列化就要使用对象序列化流和对象反序列化流
- 对象序列化流:ObjectOutputStream
- 对象反序列化流:ObjectInputStream
对象序列化流
对象序列化流:ObjectOutputStream
- 将Java对象的原始数据类型和图形写入OutputStream。可以使用ObjectInputStream读取(重构对象)。可以通过使用流的文件来实现对象的持久存储。如果流是网络套接字流,则可以在另一个主机上或另一个进程中重构对象
构造方法:
- ObjectOutputStream(OutputStream out):创建一个写入指定的OutputStream的ObjectOutputStream
序列化对象的方法:
- void writeObject(Object obj):将指定的对象写入ObjectOutputStream
public class ObjectOutputStreamDemo {
public static void main(String[] args) throws IOException {
// ObjectOutputStream(OutputStream out):创建一个写入指定的OutputStream的ObjectOutputStream
ObjectOutputStream oos = new ObjectOutputStream(
new FileOutputStream("test\oos.txt"));
Student s = new Student("Miss.xia",18);
oos.writeObject(s);
oos.close();
}
}
// 一个对象要想被序列化,该对象所属的类必须实现Serializable接口
// - Serializable是一个标记接口,实现该接口,不需要重写任何方法
public class Student implements Serializable {
private String name;
private int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
}
NotSerializableException:
- 抛出一个实例需要一个Serializable接口。序列化运行时或实例的类可能会抛出此异常。参数应该是类的名称。
Serializable
- 类的序列化由实现java.io.Serializable接口的类启用。
- 不实现此接口的类将不会使任何状态序列化或反序列化。
- 可序列化类的所有子类型都是可序列化的。
- 序列化接口没有方法或字段,仅用于标识可串行化的语义。
注意:
- 一个对象要想被序列化,该对象所属的类必须实现Serializable接口
- Serializable是一个标记接口,实现该接口,不需要重写任何方法
对象反序列化流
对象反序列化流:ObjectInputStream
- ObjectInputStream反序列化先前使用ObjectOutputStream编写的原始数据和对象
构造方法
- ObjectInputStream(InputStream in):创建从指定的InputStreamReader读取的ObjectInputStream
反序列化对象的方法:
- Object readObject():从ObjectInputStream读取一个对象
public class ObjectInputStreamDemo {
public static void main(String[] args) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(
new FileInputStream("test\oos.txt"));
// 如果Student类经过修改,会返回InvalidClassException
/*
当序列化运行时检测到类中的以下问题之一时抛出。
类的串行版本与从流中读取的类描述符的类型不匹配
该类包含未知的数据类型
该类没有可访问的无参数构造函数
*/
Object o = ois.readObject();
Student s = (Student) o;
System.out.println(s.toString());
ois.close();
}
}
疑问:
- 用对象序列化流序列化了一个对象后,假如我们修改了对象所属的类文件,读数据会不会出问题?
- 如果Student类经过修改,会返回java.io.InvalidClassException,当序列化运行时检测到类中的以下问题之一时抛出。
- 类的串行版本与从流中读取的类描述符的类型不匹配
- 该类包含未知的数据类型
- 该类没有可访问的无参数构造函数
- 如果Student类经过修改,会返回java.io.InvalidClassException,当序列化运行时检测到类中的以下问题之一时抛出。
- 如果出问题了,如何解决?
- 给对象所属的类加一个值:private static final long serialVersionUID = 42L;
- 如果一个对象中的某个成员变量的值不想被序列化,又该如何实现
- 被不想被序列化的对象添加关键字transient
- 被关键字transient修饰的成员变量不参与序列化过程
// 修改后的Student类
public class Student implements Serializable {
private static final long serialVersionUID = 42L;
private String name;
private transient int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
}
4.4 Properties
Properties概述
- 是一个Map体系的集合类
- Properties可以保存到流中或从流中加载
练习:Properties作为Map集合的使用
public class PropertiesDemo01 {
public static void main(String[] args) {
// 创建集合对象
Properties properties = new Properties();
// 报错,Properties不能指定泛型
// Properties<String,String> properties1 = new Properties();
// 存储数据
properties.put("Yuki","14");
properties.put("Kirito","15");
properties.put("Asuna","16");
// 遍历集合
Set<Object> set = properties.keySet();
for (Object key : set) {
Object value = properties.get(key);
System.out.println(key+","+value);
}
}
}
Properties作为Map集合的特有方法:
- String getProperty(String key) 使用此属性列表中指定的键搜索属性
- Object setProperty(String key, String value) 调用 Hashtable方法 put
- Set
stringPropertyNames()从该属性列表中返回一个不可修改的键集,其中键及其对应的值是字符串
public class PropertiesDemo02 {
public static void main(String[] args) {
Properties prop = new Properties();
prop.put("Yuki","14");// 设置集合的键和值,都是String类型,底层调用Hashtable方法put
// 底层代码实现
/*
public Object setProperty(String key, String value) {
return this.put(key, value);
}
public Object put(Object key, Object value) {
return this.map.put(key, value);
}
*/
prop.put("Kirito","15");
prop.put("Asuna","16");
Set<String> set = prop.stringPropertyNames();
for (String key : set) {
String value = prop.getProperty(key);
System.out.println(key+","+value);
}
}
}
Properties与IO流结合的方法
- void load(InputStream inStream)
- 从输入字节流读取属性列表(键和元素对)
- void load(Reader reader)
- 以简单的线性格式从输入字符流读取属性列表(关键字和元素对)
- void store(OutputStream out, String comments)
- 将此 Properties表中的此属性列表(键和元素对)以适合于
- 使用 load(InputStream)方法加载到 Properties表格的格式写入输出流。
- void store(Writer writer, String comments)
- 将此属性列表(键和元素对)写入此 Properties表中,以适合
- 使用 load(Reader)方法的格式输出到输出字符流
public class PropertiesDemo03 {
public static void main(String[] args) throws IOException {
// 把集合中的数据加载到文件
myStore();
// 把数据中的文件加载到集合
myLoad();
}
private static void myStore() throws IOException {
Properties prop = new Properties();
prop.put("Yuki", "14");
prop.put("Kirito", "15");
prop.put("Asuna", "16");
FileWriter fw = new FileWriter("E:\itcast01\fw.txt");
// 第二个参数String comments代表要写入数据的备注,不需要可以填null
// prop.store(fw, null);
prop.store(fw, "Sword Art Online");
fw.close();
}
private static void myLoad() throws IOException {
Properties prop = new Properties();
FileReader fr = new FileReader("E:\itcast01\fw.txt");
prop.load(fr);
Set<String> set = prop.stringPropertyNames();
for (String key : set) {
String value = prop.getProperty(key);
System.out.println(key + "," + value);
}
}
}
4.5 ResourceBundle的应用(扩展)
专门用于读取以.properties结尾的文件:
- public abstract class ResourceBundle extends Object
创建对象:
- static ResourceBundle getBundle(String baseName) 使用指定的基本名称,默认语言环境和调用者的类加载器获取资源包。
获得指定key值:
- String getString(String key) 从此资源束或其父项之一获取给定密钥的字符串。
获取key的集合
- abstract Enumeration
getKeys() 返回键的枚举 - Set
keySet() 返回 Set包含在此的所有键的 ResourceBundle及其父包。
private static void main(String[] args) {
ResourceBundle score = ResourceBundle.getBundle("score"); // 不写扩展名。如果路径在src下,则不需要指定路径
// abstract Enumeration<String> getKeys() 返回键的枚举
Enumeration<String> keys = score.getKeys();
while (keys.hasMoreElements()) {
System.out.println(keys.nextElement());
}
// Set<String> keySet() 返回 Set包含在此的所有键的 ResourceBundle及其父包。
Set<String> keySet = score.keySet();
for (String s : keySet) {
System.out.println(s);
}
}
案例:游戏次数
需求:请写程序实现猜数字小游戏只能试玩3次,如果还想玩,提示:游戏试玩已结束
思路:写一个测试类,测试类中有main方法(),main()方法中按照下面步骤完成:
- A:从文件中读取数据到Properties集合,用load()方法实现
- 文件已经存在:game.txt
- 里面有一个数据值:count=0
- B:通过Properties集合获取到玩游戏的次数
- C:判断次数是否到达3次
- 如果到了, 给出提示:游戏试玩已结束
- 如果不到三次:
- 玩游戏
- 次数+1,重新写回文件,用Properties的store()方法实现
public class Test {
public static void main(String[] args) throws IOException {
Properties prop = new Properties();
GuessNumberPuls gnp = new GuessNumberPuls();
gnp.setNum();
FileReader fr = new FileReader(
"test\src\com\itcast\unit10\test10\game");
prop.load(fr);
FileWriter fw = new FileWriter("test\src\com\itcast\unit10\test10\game");
String key = "count";
int count = Integer.parseInt(prop.getProperty(key));
Scanner sc = new Scanner(System.in);
while (count < 3) {
System.out.println("请在1-10之间输入一个数字");
int i = sc.nextInt();
String value = null;
if (i > 10 || i < 0) {
System.out.println("您输入的数据有误");
value = String.valueOf(count);
} else {
gnp.guess(i);
System.out.println("您还有" + (3 - ++count) + "次试玩机会");
value = String.valueOf(count);
}
prop.setProperty(key, value);
prop.store(fw, null);
fw.flush();
prop.load(fr);
}
System.out.println("游戏试玩已结束");
fr.close();
fw.close();
}
}
以上是关于Java中的IO流与Properties的主要内容,如果未能解决你的问题,请参考以下文章
JAVA IO流相关代码(究极整理,字符流与字节流之间的转化)
25_IO_第25天(Properties序列化流打印流CommonsIO)