1.JAVA IO
IO指的是输入与输出,Input和Output
java将输入与输出比作流: 流可以理解为是
顺着同一个方向移动的过程,即:流动的概念
输入流:想象为是一个"管道",链接着我们写的java程序与另一端的管道,而流动的方向
是从另一端移动到我们的程序的过程,是一个获取的过程,操作为"读"
输出流:方向从我们写的程序到另一端的方向,操作为:"写"
java为了规范流的行为,定义了两个超类:
java.io.InputStream和java.io.OutputStream
java.io.InputStream是所有字节输入流的超类(同时是抽象类)
定义了几个常见的抽象方法:
int read():读取一个字节
int read(byte[] data):块读取操作
java.io.OutputStream是所有字节输出流的超类(同时是抽象类)
void write(int d):写出一个字节
void write(byte[] data):块写操作
void write(byte[] data,int off,int len):块写部分数据。
文件流:
java.io.FileInputStream和FileOutputStream
文件流继承自java.io.InputStream和OutputStream
这对流是用来读写文件的流
public class FOSDemo {
public static void main(String[] args) throws IOException {
/*
常见构造器:
FileOutputStream(String pathname)
参数为写出的文件的路径
FileOutputStream(File file)
参数为写出的文件所对应的File实例
创建文件输出流时,如果指定的文件不存在文件流会自动将该文件创建出来
但是如果指定的文件所在的目录不存在时,实例化会抛出异常:
java.io.FileNotFoundException
即:文件没找到异常
*/
//向当前目录下的fos.dat文件中写入数据
/*
相对路径中"./"可以忽略不写,默认就是从"./"开始的
比如: "./fos.dat"等价与"fos.dat"
"./demo/fos.dat"等价于"demo/fos.dat"
*/
FileOutputStream fos = new FileOutputStream("fos.dat");
// File file = new File("fos.dat");
// FileOutputStream fos = new FileOutputStream(file);
/*
字节输出流的超类OutputStream上定义了写出一个字节的基础操作
void write(int d):写出一个字节
因此文件流上就具有该方法,实现的目标就是将字节写入到文件中
该方法要求传入一个int值,作用是将该int值对应的2进制的低八位
写入到文件中。
vvvvvvvv
00000000 00000000 00000000 00000000
*/
/*
fos.write(1)
int型整数1的2进制:
00000000 00000000 00000000 00000001
^^^^^^^^
写出的字节
写出后,fos.dat文件中内容为:
00000001
*/
fos.write(-1);
/*
fos.write(2)
int型整数2的2进制:
00000000 00000000 00000000 00000010
^^^^^^^^
写出的字节
写出后,fos.dat文件中内容为:
00000001 00000010
*/
fos.write(2);
System.out.println("写出完毕!");
fos.close();
}
}
文件输入流读取文件数据
public class FISDemo {
public static void main(String[] args) throws IOException {
FileInputStream fis = new FileInputStream("./fos.dat");
/*
InputStream这个超类上定义了基础的读取字节的方法:
int read()
读取1个字节,将读取到的字节(8位2进制)放在返回的int值对应的2进制的
最低8位上。如果返回的int值为整数-1则表示流读取到了末尾。
文件流实现了该方法,作用是从文件中顺序的读取每一个字节。
*/
/*
fos.dat文件中的内容:
00000001 00000010
^^^^^^^^
int d = fis.read();//第一次调用相当于读取文件的第一个字节
读取到的字节是:00000001
而read方法返回的int值2进制的样子:
00000000 00000000 00000000 00000001
|-----前24位2进制全部补0---| |读取的字节|
*/
int d = fis.read();//第一次调用相当于读取文件的第一个字节
System.out.println(d);//1
/*
fos.dat文件中的内容:
00000001 00000010
^^^^^^^^
d = fis.read();//第二次调用相当于读取文件的第二个字节
读取到的字节是:00000010
而read方法返回的int值2进制的样子:
00000000 00000000 00000000 00000010
|-----前24位2进制全部补0---| |读取的字节|
*/
d = fis.read();//2
System.out.println(d);
/*
fos.dat文件中的内容:
00000001 00000010 文件末尾(没有第三个字节)
^^^^^^^^
d = fis.read();//第三次调用相当于读取文件的第三个字节
读取到的字节是:没有读取到数据
而read方法返回的int值2进制的样子:
11111111 11111111 11111111 11111111
-1则表示流读取到了末尾。
*/
d = fis.read();
System.out.println(d);
fis.close();
}
}
文件复制 思路: 1:先创建一个文件输入流用于读取原文件 2:再创建一个文件输出流用于将数据抄到复制的文件中 3:顺序的从原文件中读取每一个字节并写入到复制的文件中
public class CopyDemo {
public static void main(String[] args) throws IOException {
// FileInputStream fis = new FileInputStream("image.jpg");
// FileOutputStream fos = new FileOutputStream("image_cp.jpg");
FileInputStream fis = new FileInputStream("setup.exe");
FileOutputStream fos = new FileOutputStream("setup_cp.exe");
/*
例如:
image.jpg文件内容
01111001 11001100 00001111 11110000 10101010 01010101...
^^^^^^^^
第一次调用:
byte d = fis.read();
d的2进制:00000000 00000000 00000000 01111001
目标是将读取到的第一个字节写入到复制文件的第一个字节的位置上
第一次调用
fos.write(d);
写的是d的2进制的低八位到文件image_cp.jpg中
d的2进制:00000000 00000000 00000000 01111001
^^^^^^^^
写出的字节
image_cp.jpg文件内容:
01111001
*/
int d = 0;
long start = System.currentTimeMillis();
while((d = fis.read()) != -1) {
fos.write(d);
}
long end = System.currentTimeMillis();
System.out.println("复制完毕!耗时:"+(end-start)+"ms");
fis.close();
fos.close();
}
}
通过提高每次读写的数据量减少实际读写的次数可以提高读写效率 一组字节一组字节的读写数据称为块读写
public class CopyDemo2 {
public static void main(String[] args) throws IOException {
FileInputStream fis = new FileInputStream("setup.exe");
FileOutputStream fos = new FileOutputStream("setup_cp.exe");
/*
java.io.InputStream上定义了块读字节数据的方法
int read(byte[] data)
一次性读取给定字节数组总长度的字节量并从字节数组第一个位置开始将读取到的
数据全部存入数组,返回值为实际读取到的字节数。如果返回值为-1则表示读取
到了流的末尾。
setup.exe文件内容
00110011 11001100 10101010 01010101 11110000 00001111
byte[] data = new byte[4];//创建一个4字节长度的数组
data:{00000000,00000000,00000000,00000000}
int len;//用来表示每次实际读取到的字节数
setup.exe文件内容
00110011 11001100 10101010 01010101 11110000 00001111
^^^^^^^^ ^^^^^^^^ ^^^^^^^^ ^^^^^^^^
//第一次调用
len = fis.read(data);//因为数组长度是4,所以可以一次性读4个字节
data:{00110011,11001100,10101010,01010101}//将读取的4字节存入数组
len:4 4表示实际读取到了4个字节
setup.exe文件内容
00110011 11001100 10101010 01010101 11110000 00001111 文件末尾
^^^^^^^^ ^^^^^^^^ ^^^^^^^^ ^^^^^^^^
实际只能读取到2字节
//第二次调用
len = fis.read(data);//因为数组长度是4,所以可以一次性读4个字节,但实际仅读取到了2个字节
data:{11110000,00001111,10101010,01010101}
| 本地读取到的2字节 | | 上次的旧数据 |
len:2 本次实际读取到了2个字节,意味着data数组只有前2个字节有用
setup.exe文件内容
00110011 11001100 10101010 01010101 11110000 00001111 文件末尾
^^^^^^^
没有数据了
//第三次调用
len = fis.read(data);//一个字节都没有读取到
data:{11110000,00001111,10101010,01010101}
| 都是旧数据 |
len:-1 已经是文件末尾了!
java.io.OutputStream超类上提供了块写操作
void write(byte[] data)
将给定的字节数组中所有数据一次性写出
void write(byte[] data,int off,int len)
一次性将给定的字节数组从下标off处开始的连续len个字节一次性写出
*/
byte[] data = new byte[1024*10];//10kb
int len;//记录每次实际读取到的字节数
//16:45回来
long start = System.currentTimeMillis();
while( (len = fis.read(data)) != -1 ){
fos.write(data,0,len);
}
long end = System.currentTimeMillis();
System.out.println("复制完毕,耗时:"+(end-start)+"ms");
fis.close();
fos.close();
}
}
向文件中写入文本数据
public class WriteStringDemo {
public static void main(String[] args) throws IOException {
/*
构造器:
FileOutputStream(String pathname)
FileOutputStream(File file)
以上这两种方式创建的文件流为覆盖模式。
覆盖模式指:文件流在创建时,如果指定的文件已经存在了则会将该文件内容全
部清空。
FileOutputStream(String pathname,boolean append)
FileOutputStream(File file,boolean append)
这两种构造器要求再传入一个boolean参数,如果参数值为true,则文件流
采取追加模式。
追加模式指:文件流再创建时,如果指定的文件已经存在了,则原内容全部保留
新写入的数据会陆续追加到文件中
*/
FileOutputStream fos = new FileOutputStream("demo.txt",true);
/*
UTF-8
英文占1字节,中文占3字节
*/
String line = "一给窝里giao~~";
byte[] data = line.getBytes(StandardCharsets.UTF_8);
fos.write(data);
// String line1 = "我太难了~~~";
// byte[] data1 = line1.getBytes(StandardCharsets.UTF_8);
// fos.write(data1);
//
// String line2 = "嘿嘿嘿~";
// byte[] data2 = line2.getBytes(StandardCharsets.UTF_8);
// fos.write(data2);
System.out.println("写出完毕!");
fos.close();
}
}
读取文件中的文本数据
public class ReadStringDemo {
public static void main(String[] args) throws IOException {
File file = new File("demo.txt");
//将demo.txt文件中所有数据读取回来
FileInputStream fis = new FileInputStream(file);
long len = file.length();//获取文件的大小,单位字节
byte[] data = new byte[(int)len];//常见与文件大小一致的字节数组
fis.read(data);//块读,将文件所有字节读到data数组中
/*
Standard 标准
Charsets 字符集
*/
String line = new String(data, StandardCharsets.UTF_8);
System.out.println(line);
fis.close();
}
}
JAVA将流分为了两类:节点流与处理流 节点流:又称为低级流,是实际链接程序与另一端的"管道",负责实际读写数据的流。 读写一定是建立在低级流的基础上进行的。 处理流:又称为高级流,不能独立存在,必须链接在其它流上,目的是当数据流经当前 流时对数据进行某种加工处理,简化我们相应的操作。 实际开发中,我们总是会串联一组高级流到一个低级流上,以流水线式的加工处理完成 读写的操作,这个过程也成为"流的链接" 缓冲流: java.io.BufferedInputStream和BufferedOutputStream 它们同样继承自InputStream和OutputStream 缓冲流的功能是提高读写效率。
public class CopyDemo3 {
public static void main(String[] args) throws IOException {
FileInputStream fis = new FileInputStream("setup.exe");
BufferedInputStream bis = new BufferedInputStream(fis);
FileOutputStream fos = new FileOutputStream("setup_cp.exe");
//缓冲流内部缓冲区默认为8kb
// BufferedOutputStream bos = new BufferedOutputStream(fos);
//重载的构造器可以自行指定缓冲区大小
BufferedOutputStream bos = new BufferedOutputStream(fos,1024*10);
int d = 0;
long start = System.currentTimeMillis();
while((d = bis.read()) != -1) {
bos.write(d);
}
long end = System.currentTimeMillis();
System.out.println("复制完毕!耗时:"+(end-start)+"ms");
bis.close();
bos.close();
}
}
缓冲输出流写出数据的缓冲区问题
public class FlushDemo {
public static void main(String[] args) throws IOException {
FileOutputStream fos = new FileOutputStream("bos.txt");
BufferedOutputStream bos = new BufferedOutputStream(fos);
String line = "该配合你演出的我演视而不见。";
byte[] data = line.getBytes(StandardCharsets.UTF_8);
bos.write(data);
System.out.println("写出完毕");
/*
java.io.Flushable接口,里面定义了抽象方法flush(),作用是将缓冲流的
缓冲区中已经缓存的数据一次性写出
java.io.OutputStream实现了该接口,这意味着所有的字节输出流都有这个
方法,但是只有缓冲输出流真正实现了该方法的功能,其它的高级输出流flush
方法的作用仅仅是调用其连接的流的flush方法将该动作传递下去,直到传递给
缓冲流的flush做实际操作。
*/
// bos.flush();//冲
bos.close();
}
}
标准的JAVA BEAN规则 应当有无参构造器 属性应当有对应的get,set方法 Person : 人 使用当前类的实例测试对象流的序列化与反序列化操作 序列化:将一个对象按照其结构转化为一组字节的过程 反序列化:将一组字节还原为一个对象的过程
public class Person implements Serializable {
/*
当一个类实现了可序列化接口后,建议明确添加序列化版本号。
这样当我们进行对象的反序列化时,如果当前类的结构改变了(比如新添加了属性)
由于版本号没有改变,那么反序列化就可以成功。
如果不指定序列化版本号,那么对象在序列化时对象输出流会根据当前类结构生成
一个版本号,但是缺点是只要当前类结构改变了,版本号也会一同变化,这会导致
之前序列化的对象都无法反序列化成功,会抛出异常:java.io.InvalidClassException
建议通读文档
Serializable接口的描述
*/
public static final long serialVersionUID = 1L;
private String name;//名字
private int age;//年龄
private String gender;//性别
/*
被关键字:transient修饰的属性,对象序列化时该属性的值会被忽略。
忽略不必要的属性可以达到的对象序列化"瘦身"的目的,减小不必要的开销。
*/
private transient String[] otherInfo;//其它信息
// private int salary;
public Person(){}
public Person(String name, int age, String gender, String[] otherInfo) {
this.name = name;
this.age = age;
this.gender = gender;
this.otherInfo = otherInfo;
}
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 getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public String[] getOtherInfo() {
return otherInfo;
}
public void setOtherInfo(String[] otherInfo) {
this.otherInfo = otherInfo;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", gender='" + gender + '\'' +
", otherInfo=" + Arrays.toString(otherInfo) +
'}';
}
}
对象流java.io.ObjectOutputStream和ObjectInputStream 对象流是一对高级流,功能是进行对象序列化与反序列化。方便我们读写java对象
public class OOSDemo {
public static void main(String[] args) throws IOException {
//将一个Person对象写入文件保存
String name = "苍老师";
int age = 18;
String gender = "女";
String[] otherInfo = {"身高168","是个演员","来自日本","88,90","是我们的启蒙老师"};
Person p = new Person(name,age,gender,otherInfo);
FileOutputStream fos = new FileOutputStream("person.obj");
ObjectOutputStream oos = new ObjectOutputStream(fos);
/*
ObjectOutputStream提供了一个独有的序列化对象的方法:
void writeObject(Object obj)
该方法会将给定的对象按照其结构转换为一组字节并通过其链接的流将字节写出。
如果该方法抛出异常:
java.io.NotSerializableException
则说明给定的对象没有实现序列化接口:java.io.Serializable
还要求该类中所有的引用类型属性也需要实现该接口
常用的数据类型:
String,包装类,数组等都实现可序列化接口
*/
oos.writeObject(p);
System.out.println("写出完毕!");
oos.close();
}
}
使用对象输入流反序列化对象
public class OISDemo {
public static void main(String[] args) throws IOException, ClassNotFoundException {
FileInputStream fis = new FileInputStream("person.obj");
ObjectInputStream ois = new ObjectInputStream(fis);
/*
对象输入流提供了一个独有的方法:
Object readObject()
作用是先通过其链接的流读取一组字节,然后将其还原为对应的java对象并返回。
返回时统一以Object形式返回,所以实际接收后可造型为实际类型去使用。
在进行反序列化时如果抛出下面异常:
java.io.InvalidClassException
原因:
读取到的对象的序列化版本号与当前类的序列化版本号不一致导致的。
*/
Person person = (Person)ois.readObject();
/*
如果执行下面代码会抛出异常:java.io.EOFException
EOF:end of file 文件的末尾
意味着如果使用对象流读取文件时在没有读取完一个对象的所有数据就已经达到
文件末尾时会抛出该异常。
这里的原因是:
上面已经执行了readObject将文件中的对象数据都读取完毕经还原了
然后下面有执行一次readObject,此时文件中已经没有数据了,所以会抛出
文件末尾异常
*/
// System.out.println(ois.readObject());
System.out.println(person);
ois.close();
}
}
JAVA将流按照读写单位划分为字节流与字符流 java.io.InputStream和OutputStream是所有字节输入流与输出流的超类 java.io.Reader和Writer则是所有字符输入流与输出流的超类 InputStream和OutputStream 与 Reader和Writer 是平级关系,相互不存在继承关系 字符流里也同样定义了对应的读取和写出方法,只是单位都以字符为最小单位读写。 转换流 java.io.InputStreamReader和OutputStreamWriter 它们是常用的字符流的一对实现类,同时它们也是一对高级流。 它在流连接中的作用2个: 1:衔接字节流与字符流 2:将字节与字符进行转换方便读写。
public class OSWDemo {
public static void main(String[] args) throws IOException {
//向文件osw.txt中写入字符串
FileOutputStream fos = new FileOutputStream("osw.txt");
/*
转换流通常需要传入第二个参数用来明确字符集。这样通过当前流写出的字符
都会按照该字符集转换为字节后写出
*/
OutputStreamWriter osw = new OutputStreamWriter(
fos,StandardCharsets.UTF_8);
//转换流会将写出的字符串转换为字节后再通过链接的文件流写入文件。
osw.write("摩擦摩擦~在光滑的马路牙子上打出溜滑~");
osw.write("我的滑板鞋~时尚时尚最时尚~");
System.out.println("写出完毕!");
osw.close();
}
}
转换输入流测试字符流读取字符的操作
public class ISRDemo {
public static void main(String[] args) throws IOException {
FileInputStream fis = new FileInputStream("osw.txt");
InputStreamReader isr = new InputStreamReader(fis, StandardCharsets.UTF_8);
/*
字符流的读取字符方法:
int read()
读取一个字符,返回值实际上是一个char.但是是以int型返回。意味着返回的int
值低16位有效。如果返回值为int型-1表示末尾。
*/
// int d = isr.read();
/*
osw.txt UTF-8编码的
11101000 10001100 10000011
V 将utf-8编码转换为unicode2字节返回
10000011 00000011
*/
int d;
while((d = isr.read())!=-1) {
System.out.print((char) d);
}
isr.close();
}
}
缓冲字符流 java.io.BufferedWriter和BufferedReader 缓冲字符流是一对高级流,且是一对字符流。 功能:块写文本数据加速 java.io.PrintWriter 具有自动行刷新的缓冲字符输出流
public class PWDemo {
public static void main(String[] args) throws FileNotFoundException {
//向文件pw.txt中写入文本数据
PrintWriter pw = new PrintWriter("pw.txt");
pw.println("super idol的笑容都没你的甜。");
pw.println("八月正午的阳光都没你耀眼。");
System.out.println("写出完毕!");
pw.close();
}
}
在流连接中使用PrintWriter
public class PWDemo2 {
public static void main(String[] args) throws FileNotFoundException {
//如果希望有追加模式,则需要自行创建文件输出流并指定
FileOutputStream fos = new FileOutputStream("pw.txt",true);
//在转换流上加上字符集,可以按照指定字符集写出。
OutputStreamWriter osw = new OutputStreamWriter(fos, StandardCharsets.UTF_8);
BufferedWriter bw = new BufferedWriter(osw);
/*
当PrintWriter第一个参数为流时,我们就支持再传入一个boolean值参数,如果该值为true则
打开了自动行刷新功能。每当我们调用println方法后就会自动flush一次
注意:调用print方法并不会自动flush!!!!!!!
*/
PrintWriter pw = new PrintWriter(bw,true);
// FileOutputStream fos = new FileOutputStream("pw.txt",true);
// BufferedOutputStream bw = new BufferedOutputStream(osw);
/*
不要将PW直接链接在缓冲字节输出流上,因为PW内部总是链接这BW这个缓冲字符输出流了。
不需要在缓冲字节流上再缓冲了,会降低效率
*/
// PrintWriter pw = new PrintWriter(bw);
Scanner scanner = new Scanner(System.in);
while(true) {
String line = scanner.nextLine();
if("exit".equalsIgnoreCase(line)){
break;
}
pw.println(line);
}
System.out.println("写出完毕!");
pw.close();
}
}
使用缓冲字符输入流java.io.BufferedReader读取文本数据 缓冲字符输入流是一个高级流,有两个主要功能: 1:块读文本数据加速 2:可以按行读取字符串
public class BRDemo {
public static void main(String[] args) throws IOException {
//将当前源代码输出到控制台上
FileInputStream fis = new FileInputStream("./src/io/BRDemo.java");
InputStreamReader isr = new InputStreamReader(fis);
BufferedReader br = new BufferedReader(isr);
String line;
/*
BufferedReader的readLine方法是它的独有方法,作用是读取一行字符串
该方法返回的字符串不包含最后的换行符。如果读取了一个空行(这一行只有换行符),那么
返回值为空字符串,即:""。如果返回值为null则表示流读取到了末尾。
*/
while((line = br.readLine()) != null) {
System.out.println(line);
}
br.close();
}
}