基础系列【十二】--IO流
IO流
按照传输的方向:
流具有方向性,将数据从外部位置传输给程序的流 称之为 输入流。
将数据从程序输出给外部位置的流 称之为 输出流。
按照操作的数据:
流中流动的数据,可以是 字节数据 或 字符数据。
字节流动起来组成的流称之为 字节流。
字符流动起来形成的流称之为 字符流。
**注意:字符流的底层仍然是字节流,只不过字符传输的需求非常的常见,所以专门提供了 直接操作字符的流,看起来操作的是字符,其实底层仍然是将字符转换为字节来处理的。
java.io 输入流 输出流
----------------------------------------
字节流 | InputStream | OutputStream
字符流 | Reader | Writer
-----------------------------------------
**以上四大流,都是抽象类,无法直接使用,java提供了以上四种基本流的类的具有具体功能的子类,我们平常用的都是具体子类。
**目的地包括但不限于: 文件、网络、内存、控制台 等等。。
字符输出流 - Writer
案例:从程序中利用字符输出流向文件中写出数据
输出 -- 输出流 字符数据 -- 字符流
字符输出流 - Writer
操作的是文件 - Writer - OutputStreamWriter - FileWriter
1.FileWriter的构造方法
FileWriter(File file) //接受一个File对象 创建到指定file对象代表的文件的字符输出流 输出数据时 默认覆盖文件中的数据
FileWriter(File file, boolean append) //额外接收一个布尔类型的参数 指定是否要以追加数据的方式执行写出
FileWriter(String fileName) //接受一个文件的路径字符串 创建到指定路径文件的字符输出流 输出数据时 默认覆盖文件中的数据
FileWriter(String fileName, boolean append) //额外接收一个布尔类型的参数 指定是否要以追加数据的方式执行写出
**如果创建流时文件已经存在则连接到该文件进行操作
**如果创建流时文件不存在,则自动创建该文件并连接到该文件进行操作
**如果指定的位置不可到达 或 没有对应权限进行操作,则抛出IOException
2.输出相关的方法
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)
**write方法写出数据时 并不会直接将数据写出到外部设备中 而是会在流内做缓冲 来提升操作效率,但是这样的方式 可能造成 数据并不能立即写出当程序意外 退出时可能造成 部分数据 残留在缓冲区中 丢失数据。
3.刷新流
void flush() // 刷新缓冲区 将缓冲区中残留的数据 写出到目的地中
4.关闭流
void close() //关闭流
**流用完后一定要关闭,否则流会一直存在,一方面浪费程序内存资源,另一方面占用着文件,使文件无法被正常操作。
**close操作会自动做一次flush 在关闭流之前 将缓冲区中的数据 刷出到目的地中。
/*
* IO
* 案例:利用字符输出流向文件写出数据
*/
public class IOExr {
public static void main(String[] args) throws IOException {
//FileWriter:向文件中写入字符。
// FileWriter(File file):根据给定的 File 对象构造一个 FileWriter 对象。
// FileWriter(File file, boolean append):根据给定的 File 对象构造一个 FileWriter 对象,并以追加的形式写入文件。。
// FileWriter(String fileName):根据给定的文件名构造一个 FileWriter 对象。
// FileWriter(String fileName, boolean append):根据给定的文件名以及指示是否附加写入数据的 boolean 值来构造 FileWriter 对象。
FileWriter fw = new FileWriter("D:\\1.txt");//文件不存在,则创建。若文件路径不可达,或者没有访问权限,则抛IOException
/*
* writer相关的方法:
* writer(int c);
* write(char[] buf);
* write(char[] cbuf, int off, int len);
* writer(String str);
* write(String str,int off,int len);
* 注意:在使用writer方法向文件写数据的时候,会先将数据写入到缓冲区中,来提高效率。
* 但是可能会出现程序结束之后,缓冲区的数据并没有将数据全部写入到文件中。
* 因此我们提供void flush() --将缓冲区的的数据写入到文件中。
* 注意2:流用完后一定要关闭,否则,在程序运行期间,一直会占用该文件(不能再针对此文件进行其他的操作),浪费资源。
*/
fw.write("abcd");//没有写入权限的时候会抛异常
fw.flush();// 再写出的时候,发生了意外操作(U盘被拔出),找不到刷新的目的地。
fw.close();// 若在close()前面的代码发生了异常,此close()可能不执行,所以,一般释放资源的操作,需放在finally里面
}
}
流的异常处理
FileWriter writer = new FileWriter("D:\\1.txt",true); //当指定的位置不存在 或 没有权限访问时 则抛出IOException
writer.write("abcd");//文件没有访问权限 则抛出IOException
writer.flush();//当要flush时因为一些意外情况造成flush失败 抛出IOException
writer.close();//当要close时因为一些意外情况造成close失败 抛出IOException
异常的处理是IO操作时必然遇到的问题
//1.close要在finally里执行 所以在本地trycatchfinally处理
//2.为了让writer可以在finally被访问到 所以讲 writer 外置定义
FileWriter writer = null;
try {
writer = new FileWriter("D:\\1.txt",true);
writer.write("abcd");
int i = 1/0;
writer.flush();
} catch (IOException e) {
e.printStackTrace();
} finally {
//3.因为wirter可能为null 为了防止空指针异常 在此处判断非空
if(writer != null){
//4.因为close方法也有可能抛出异常 捕获处理
try {
writer.close();
} catch (IOException e) {
e.printStackTrace();
} finally {
//5.因为close异常时 可能没有正常关闭
//所以此处讲只用置为null 则对象失去所有引用 变为垃圾
//有机会被垃圾回收器回收 在回收时被关闭
writer = null;
}
}
}
字符输入流 - Reader
案例:从程序中利用字符输入流从文件中读取数据
读取 -- 输入流 字符数据 -- 字符流
字符输入流 - Reader
操作的是文件 - Reader - InputStreamReader - FileReader
1.FileReader的构造方法
FileReader(File file)//接受一个File对象 创建到指定file对象代表的文件的字符输入流
FileReader(String fileName)//接受一个文件的路径字符串 创建到指定路径文件的字符输入流
**如果指定的位置不可到达 或 没有对应权限进行操作,则抛出IOException
2.输入相关的方法
read()
read(char[] cbuf, int offset, int length)
read(char[] cbuf)
read(CharBuffer target)
**read()方法 返回的是一个int值 为什么不直接返回一个char?因为无法用一个特定的 char来表示文件读到结尾的情况 -- 无论返回什么都会有歧义,到底是文件结尾了还是真的文件里就是这个字符 -- 所以 用了 4个字节的int作为返回值,如果正常的读取到一个字符数据,则 在int的后两位保存该字节数据 并保证该int为一个正数,这样直接将int强转为char就可以读取到正确的字符,而当文件到达结尾时 返回一个特定值 -1 表示文件到达了结尾。
3.关闭流
void close() //关闭流
**流用完后一定要关闭,否则流会一直存在,一方面浪费程序内存资源,另一方面占用着文件,使文件无法被正常操作。
如何决定选择什么流呢?
1.当前数据是输入程序还是从程序中输出
2.要输出的数据是否是字符数据,如果不是字符数据必须用字节流,如果是字符数据可以用字节流也可以用字符流,推荐使用字符流,可以省去自己进行字节字符转换的操作。
3.考虑数据的目的地是什么,从四大基本流的继承结构中找寻对应的能使用的流。
4.考虑是否需要提高效率,如果需要可以通过Buffered.....来包装一把。
/*
* IO
* 案例:利用字符流从文件读取数据
*/
public class IOExr3 {
public static void main(String[] args) {
FileReader fr = null;
try {
fr = new FileReader("D:\\1.txt");// 文件不存在,则创建。若文件路径不可达,或者没有访问权限,则抛IOException
int i = -1;
while ((i = fr.read()) != -1) {
// fr.read();//读取一个字符
System.out.println((char) i);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fr != null) {
try {
fr.close();// 若在close()前面的代码发生了异常,此close()可能不执行,所以,一般释放资源的操作,需放在finally里面
} catch (IOException e) {
e.printStackTrace();
} finally {
fr = null;
}
}
}
}
}
//案例;通过BufferedReader和BufferedWriter实现缓冲高效读写数据
public class IOExr4 {
public static void main(String[] args) {
FileReader reader = null;
FileWriter writer = null;
BufferedReader br = null;
BufferedWriter bw = null;
try {
reader = new FileReader("E:\\1.txt");
br = new BufferedReader(reader);
writer = new FileWriter("E:\\2.txt");
bw = new BufferedWriter(writer);
// 底层通过缓冲数组 char[]
int i = -1;
while((i=br.read())!=-1){
bw.write(i);
}
bw.flush();
/*// 自定义缓冲数组
char[] cs = new char[1024];
int i = -1;
while ((i = reader.read(cs)) != -1) {
writer.write(cs,0,i);
}
writer.flush();*/
} catch (Exception e) {
e.printStackTrace();
} finally {
if (br!=null) {
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
} finally {
br = null;
}
}
if (bw!=null) {
try {
bw.close();
} catch (IOException e) {
e.printStackTrace();
} finally {
bw = null;
}
}
/*if (reader!=null) {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
} finally {
reader = null;
}
}
if (writer!=null) {
try {
writer.close();
} catch (IOException e) {
e.printStackTrace();
} finally {
writer = null;
}
}*/
}
}
}
IO流中基本的流
1. 字节输入流:以字节为单位,操作数据,可以操作任何形式的数据
1. InputStream(所有直接输入流的父类(抽象))
1. FileInputStram
构造方法:
FileInputStream(File file)
FileInputStream(String name)
方法:
int read()
int read(byte[] b)
int read(byte[] b, int off, int len)
2. 自带缓冲区的流
1. FileInputStream--BufferedInputStream
2. OutputStream - FilterOutputStream - BufferedOutputStream
1. 构造方法:
BufferedInputStream(OutputStream out)
BufferedInputStream(OutputStream out, int size)
BufferedOutputStream(OutputStream out)
BufferedOutputStream(OutputStream out, int size)
2. 利用了装饰设计模式 传入流 自动增加缓冲功能
2. ObjectInputStream
2. 字节输出流
6. 系统流:当前系统对接的标准输出流中(默认控制台)读写数据
System.out -- 标准输出流
System.err -- 标准错误输出流
System.in -- 标准输入流
**可以通过setIn(InputStream in) setOut(PrintStream out) 来修改标准流 的对应的位置
/*
* 系统流:当前系统对控制的输出输入的操作
*/
public class Exr3 {
public static void main(String[] args) throws IOException {
// OutputStream out = System.out;
// byte[] bs = "lalal".getBytes();
// out.write(bs);
// 系统输出流实际上是一个PrintStream
PrintStream out = System.out;
out.print("hello io~");
out.println("hello io~");
out.printf("my name is %s\r\n", "小王");//my name is 小王
// 标准错误输出流
PrintStream err = System.err;
err.println("c");//控制台字符变红
// 标准输入流
InputStream in = System.in;
byte[] data = new byte[1024];
int len = in.read(data);
System.out.println(new String(data,0,len));//21sakjfdadsfjk 21sakjfdadsfjk
// 利用转化流便捷读取行
BufferedReader br = new BufferedReader(new InputStreamReader(in));
String line = null;
while ((line = br.readLine())!=null) {
System.err.println(line);
}
}
}
PrintStream - 打印流
OutputStream - FilterOutputStream - PrintStream
为其他流量增加了按照特定格式打印输出数据的功能。
另外能够保证:
永远不抛异常
自动flush
构造方法:
PrintStream(File file)
PrintStream(String fileName)
PrintStream(OutputStream out)
其他方法:
各种print方法 输出各种类型的数据
各种println方法 输出各种类型的数据 并在其后换行
printf方法 输出格式化字符串
转换流
转换流,可以将字节流转换为字符流,本质上就是在字节流的基础上 通过 装饰设计模式 增加了 编解码的功能 从而可以以字符为单位来输入输出数据。
Reader - InputStreamReader - 将字节输入流转换为字符输入流
构造方法:
InputStreamReader(InputStream in)//不指定码表默认使用当前平台码
InputStreamReader(InputStream in, Charset cs)//也可是手动指定码表
InputStreamReader(InputStream in, String charsetName)//也可是手动指定码表
案例:利用FileReader读取一个UTF-8的文本文件
发现FileReader默认按照平台码读取文件 当读取其他码表文件时 会乱码
如果想要解决这个乱码 FileReader做不到 需要自己来通过InputStreamReader来构建特定编码机的字符输入流
案例:利用InputStreamReader来创建一个可以读取任意编码集文件的字符输入流
Writer - OutputStreamWriter - 将字节输出流转换为字符输出流
案例:用FileWriter输出数据到文件中 观察文件的编码集
发现输出的文件默认是gbk(平台码) 且无法修改
如果想要按照指定编码来输出数据 需要 创建字节输出流 再通过转换流 转换为字符流 再在这个过程中指定编码
案例:用转换流 转换 文件字节输出流 为字符流 且指定编码输出数据
Scanner的用法
Scanner
java.util.Scanner 扫描器类 可以对输入的内容做扫描操作
构造方法:
Scanner(File source)//以文件作为源来进行扫描
Scanner(File source, String charsetName) //以文件作为源来进行扫描 可以指定编码集
Scanner(InputStream source) //以流作为源来进行扫描
Scanner(InputStream source, String charsetName) //以流作为源来进行扫描 可以指定编码集
Scanner(String source) //以字符串为源来进行扫描
其他方法:
useDelimiter(regex)//默认使用空格作为分隔符 处理输入 切为多个子串来进行扫描 可以通过此方法 配置 接受正则表达式作为分隔符
各种hasNext方法,判断后续是否有指定类型的数据
各种next方法,扫描后续的数据
close方法 关闭扫描器
findInLine(regex) 接受正则表达式 匹配语句 获取匹配的子串
**通过scan从控制台读取数据
Scanner scan = new Scanner(System.in);
String line = scan.nextLine();
public static void main(String[] args) throws IOException {
/*
* 构造方法:
* Scanner(String source);
* Scanner(File source);
* Scanner(InputStream source);
*/
Scanner s = new Scanner("my name is w age 15 18");
// 从指定字符串扫描,默认按空格分割
while(s.hasNext()){
System.out.println(s.next());
/*
* my
name
is
w
age
15
18
*/
}
// 可以指定分割符:按照字符分割
/*s.useDelimiter("\\d");
while (s.hasNext()) {
System.out.println(s.next());//my name is w age
}*/
// 通过正则匹配子串
String str = s.findInLine("\\w+\\s\\d+");
System.out.println(str);
//从控制台按行读
/*BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String str = br.readLine();
System.out.println(str);*/
}
properties
//java.util.HashTable---java.util.Properties:线程安全的类,多个线程可以共享。
/*
* 表示可以存储磁盘中的properties文件中的键值对集合。properties可以方便的写入流和从流中读取。
* 注意: 文件的键和值都是字符串。
* Properties继承HashTable,可以使用put和putAll方法,但不建议,因为put等方法允许插入非String类型的键和值
* 建议使用该类对象的setProperties(String key,String value)方法
* 构造函数:
* public Properties() 创建一个无默认值的空属性列表。
* 相关方法:
* //加载properties文件
* void load(Reader reader);
* void load(Reader reader);
* //获取文件的键值对
* String getProperty()
* 设置 文件的键值对
* Object setProperty(String key, String value)
* 遍历文件的键
* Set<String> stringPropertyNames()
*
* public void store(OutputStream out,String comments)
*
*/
public class Prop {
public static void main(String[] args) throws FileNotFoundException, IOException {
Properties prop = new Properties();
prop.load(new FileInputStream("prop.properties"));
System.out.println(prop.getProperty("name"));
//新增属性到prop中
prop.setProperty("gender", "男");
prop.put("那", "la");
//遍历文件的键
Set<String> keys = prop.stringPropertyNames();
for (String string : keys) {
System.out.println(prop.getProperty(string));
}
//将prop中的数据写入到properties中
prop.store(new FileOutputStream("prop.properties"), null);
}
}
序列化和反序列化
/*
* 序列化--将内存中的对象信息转化为字节,目标是让对象可以持久储存。
* 持久化:将内存中易失的对象信息,序列化后,保存到磁盘中,持久保存。
* 序列化:将对象转字节的过程。
* 序列化是持久化的前提,持久化是序列化的目标之一。
* 注意点:想要实现序列化和反序列化的类,必须实现 Serializable接口。
* 改接口没有任何方法和属性,仅仅作为一个标志,要求能狗屎序列化的类
* 作为特定的标记,告知虚拟机,告诉该类的对象可以序列化。
* 关键字:
* transient :在序列化的过程中的,忽略有该关键字的属性,不会被序列化。
* serialVersionUID:根据此版本号判定两个类是否是同一个类。
* 构造方法:
* ObjectOutputStream(OutputStream out) :装饰者模式
* 常用方法:
* void writeObject(Object obj) 将指定的对象写入 ObjectOutputStream。
* void flush();
* void close();
* ===================================================================
* 反序列化:
* 构造放法:
* ObjectInputStream(InputStream in)
* 其他方法:
* Object readObject() 从 ObjectInputStream 读取对象。
*/
public class Person implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
private int age;
transient private String password;
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
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;
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + ", password=" + password + "]";
}
public static void main(String[] args) throws Exception {
// 序列化
Person p = new Person();
p.setName("小白");
p.setAge(18);
p.setPassword("12345");
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("1.txt"));
oos.writeObject(p);
oos.flush();
oos.close();
// 反序列化
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("1.txt"));
Person p1 = (Person) ois.readObject();
System.out.println(p1);
ois.close();
}
ByteArrayInputStream ByteArrrayOutputStream
java.io.ByteArrayInputStream - 以字节数组为目标的字节输入流
构造方法:
ByteArrayInputStream(byte[] buf)
ByteArrayInputStream(byte[] buf, int offset, int length)
其他方法:
int read()
int read(byte[] b, int off, int len)
close()
java.io.ByteArrrayOutputStream
构造方法:
ByteArrayOutputStream()
ByteArrayOutputStream(int size)
其他方法:
byte[] toByteArray()
void write(int b)
void write(byte[] b, int off, int len)
flush()
close()//关闭效果无效
public static void main(String[] args) throws Exception {
FileInputStream fis = new FileInputStream("E:\\1.txt");
//内部维护一个缓冲数组,将读取的数据先写到内存数组里
ByteArrayOutputStream bos = new ByteArrayOutputStream();
int i= -1;
byte[] data = new byte[1024];
while((i=fis.read(data))!=-1){
bos.write(data,0,i);
}
bos.flush();
fis.close();
//该操作写不写都可以,因为bos没有连接任何文件
bos.close();
//将内存缓冲数据转化为数组
byte[] bs = bos.toByteArray();
String str = new String(bs,"gbk");
bs = str.getBytes("GBK");
FileOutputStream fos = new FileOutputStream("E:\\2.txt");
fos.write(bs);
fos.flush();
fos.close();
}
字符串流和合并流
/*
* Reader--StringReader:以字符串为数据来源的字符的输入流
* 构造方法;
* StringReader(String s) 创建一个新字符串 reader。
* 其他方法;
* int read() 读取单个字符
* int read(char[] cbuf, int off, int len) 将字符读入数组的某一部分。
*
* Writer--StringWriter:以字符串为数据的目的地的输出流
* 构造方法:
* 其他方法:
* StringWriter append(char c) 将指定字符添加到此 writer
* void flush() 刷新该流的缓冲。
* StringBuffer getBuffer() 返回该字符串缓冲区本身。
* String toString() 以字符串的形式返回该缓冲区的当前值。
* void write(char[] cbuf, int off, int len) 写入字符数组的某一部分。
* void write(int c) 写入单个字符。 void write(String str) 写入一个字符串。
* void write(String str, int off, int len)
* SquenceInputStream--合并流
* 构造方法:
* SequenceInputStream(Enumeration<? extends InputStream> e)
* 通过记住参数来初始化新创建的 SequenceInputStream,该参数必须是生成运行时类型为 InputStream 对象的 Enumeration 型参数。
* SequenceInputStream(InputStream s1, InputStream s2)
* 通过记住这两个参数来初始化新创建的 SequenceInputStream(将按顺序读取这两个参数,先读取 s1,然后读取 s2),以提供从此 SequenceInputStream 读取的字节。
* 其他方法:
* 合并流案例:将多个个文件合并为一个文件
*/
public class StringReaders {
public static void main(String[] args) throws Exception {
String str = "小鸡吃米图";
StringReader r = new StringReader(str);
//System.out.println((char)r.read());//小
int i = -1;
while ((i = r.read())!=-1) {
System.out.println((char)i);
}
r.close();
StringWriter sw = new StringWriter();
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String line = br.readLine();//我气你四
sw.write(line);
line = br.readLine();//嘎嘎
sw.write(line);
sw.flush();
sw.close();
System.out.println(sw.toString());//我气你四嘎嘎
// 创建文件读取流
FileInputStream fis1 = new FileInputStream("1.txt");
FileInputStream fis2 = new FileInputStream("2.txt");
FileInputStream fis3 = new FileInputStream("3.txt");
// 创建文件合并流
Vector<FileInputStream> v = new Vector<>();
v.add(fis1);
v.add(fis2);
v.add(fis3);
SequenceInputStream sis = new SequenceInputStream(v.elements());
// 创建输出流
FileOutputStream fos = new FileOutputStream("4.txt");
// 对接流
int d= -1;
byte[] bs = new byte[1024];
while ((d=sis.read(bs))!=-1) {
fos.write(bs,0,d);
}
// 关闭流
sis.close();
fos.close();
}
}
/*
* 需求分析:
* 统计工作空间中java文件的数量,和java代码的行数
* 已有信息:
* 工作空间的位置:D:\\WorkSpace
* 步骤:
* 遍历工作空间的的所有的文件和子文件夹
* 如果是文件则文件数++,并读取文件的行数,行数++
* 如果不是,则忽略。
* 如果是子文件夹
* 则进入子文件夹,重复以上步骤
*/
public class Exr {
static int javaFileCount;
static int javaLine;
public static void main(String[] args) {
// 创建file对象
File file = new File("D:" + File.separator + "WorkSpace");
//
mx(file);
System.out.println(javaFileCount);
System.out.println(javaLine);
}
static void mx(File file) {
if (file == null) {
throw new RuntimeException("文件不存在");
}
if (file.isDirectory()) {
File[] files = file.listFiles();
for (File f : files) {
if (f.isDirectory()) {
mx(f);
}else if (f.isFile()) {
//
if (f.getName().endsWith(".java")) {
javaFileCount++;
//
BufferedReader br = null;
try {
br = new BufferedReader(new FileReader(f));
while (br.readLine() != null) {
javaLine++;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (br!=null) {
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
} finally {
br = null;
}
}
}
}
} else{
continue;
}
}
}
}
}
下一篇:
基础系列【十三】–集合