前言
由于File类只能操作文件对象本身。所以对于文件的读写操作,我们最长使用的是IO流。那么IO流按照传从方向分为:输出流,以及输入流:
**输出流:**将内存中的数据输出到磁盘或是网络中去
**输入流:**将磁盘中的数据输入至内存中
最常用的IO流分类如下,下面是对应的实现类:
字节输入流 字节输出流 字符输入流 字符输出流
InputStream OutputStream Reader Writer —(抽象类)
FileInputStream FileOutStream FileReader FileWriter —(实现类)
字节输入流:InputStream
顾名思义,是完成字节输入的IO流。以下代码演示:
首先new一个file类,参数是目标文件的地址
File file = new File("IO流文件/IO流示例.txt");
然后创建一个字节输入流管道,捕获FileNotFound异常,字节输入流仿佛是打通磁盘和内存的一个管道,每次流入到内存一个字节大小的数据
InputStream inputStream = null;
try {
inputStream = new FileInputStream(file);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
假如将IO流示例.txt文本中只放入一个字母’a’,执行以下:
int fristCode = inputStream.read();
System.out.println(fristCode);
System.out.println((char) fristCode);
System.out.println(inputStream.read());
控制台输出:
从而我们看到,执行字节输入流的read方法,我们能到到一个字节码,97(字母a的ASCII码),转换成char后输出字母‘a’,并且输入流中再没有其他字节,所以返回-1。
但是我们知道,在UTF-8编码中:字母等于1字节,而中文等于3字节;Unicode编码中:中英文都是两个字节。
从而我们若使用字节输入流,在utf-8编码中,必然出现中文乱码问题,所以这种按照字节读取数据的方式,并不实用。那么由于字节输入流的read()方法每次只读取一个字节,性能较差,我们应该使用更加高效的方法。
使用byte[ ]
假如IO流把文件数据变成了水流,那么我们使用每次只能够得到一个水滴的方式,必然是低效的。从而应该使用字节数组,这好比一个瓢,这样我们一次性就能够得到多个字节的数据。
首先,创建一个字节数组,我们定义整个水瓢的大小是一次性可以舀取9个字节大小的数据流
byte[] buffer = new byte[9];
在创建文件输入流,参数可以直接写入目标文件的相对或绝对路径
FileInputStream fileInputStream = new FileInputStream("IO流文件/IO流示例.txt");
//读取到的字节数len
int len = fileInputStream.read(buffer);
System.out.println("读取到的字节数:"+ len);
String sr = new String(buffer);
System.out.println(sr);
那么现在我们把IO流示例.txt文件中写入"菜虚坤"三个中字,控制台输出:
首先我们看到了,确实读取到9个字节,那么我们就能知道带有参数的read(byte[ ] bytes)方法,其实返回的是我们所用的水瓢的大小(此时是9个字节大小)。另外,由于编译器默认为utf-8编码,从而每三个字节组成了一个中文汉字,所以一个9字节的byte[ ]数组,刚好能放入三个中字,那么改变字节数组大小呢?
那么必然出现最后一个汉字乱码的情况。
byte[ ]的基础上,使用循环
代码如下:
FileInputStream fileInputStream = new FileInputStream("IO流文件/IO流示例.txt");
//使用大小为2的字节数组
byte[] buffer = new byte[2];
//定义长度
int len;
//定义循环
while ((len = fileInputStream.read(buffer)) != -1) {
String rs = new String(buffer,0,len);
System.out.print(rs);
}
那么这种方式呢,
在循环条件上,首先带参数的read方法会返回字节数组的大小(本例中为2)给变量len,然后之与-1比较,若len为-1,则说明字节输入流已经将数据读取完毕,退出循环。
在循环内部,String方法有三个参数,首先是字节数组的大小,0代表第一个字节索引,即从头开始,,最后一个参数表示的是该string的长度。
当然,此时还是没有完全避免中文乱码问题。
字节输出流:OutputStream
字节输出流呢,将内存中的数据,以字节的形式输出到磁盘中的文件中。
首先,创建一个字节输出流,第二个参数为布尔类型,代表着下次将数据写入文件是,不覆盖上次的数据
OutputStream outputStream = new FileOutputStream("IO流文件/IO流示例.txt",true);
对应于输入流的read方法,OutputStream中有write方法,可以写入字节数据
outputStream.write(97);
write方法将int自动转化为对应编码中的数据,97对应着小写字母"a", 从而文件中:
我们也可以使用字节数组,写入文件中
byte[] buffer = new byte[]{97,98,99,100};
outputStream.write(buffer);
对应的文件中会被写入:
或是把字符串转成字节码写入文件
buffer = "唱跳rap篮球".getBytes();
outputStream.write(buffer);
用完记得关闭这个流
outputStream.close();
字节输入、输出流完成文件的复制
**分析:**创建输入输出流,确定文件的地址,以及要复制的目标地址,文件名,将源文件读入输入流中,然后从输入流中得到文件流,在输出至目标文件。
首先,创建字节输入、输出流,确定相关路径
inputStream = new FileInputStream("C:\\Users\\Pictures\\毒液.png");
outputStream = new FileOutputStream("D:\\IO流目标文件夹\\毒液copy.png");
然后我们不可能一个字节一个字节去复制,太过于愚蠢,所以定义一个1024字节(1kb) 的水瓢
byte[] buffer = new byte[1024];
然后,利用我们之前讲到的循环读取、写入方式
//长度
int len;
while ((len = inputStream.read(buffer)) != -1){
outputStream.write(buffer,0,len);
}
System.out.println("复制完成!");
}
带有参数的read方法每次回返回1024字节大小长度给len(文件的最后一部分未必是1024字节,可能更小),然后判断是否为-1,即是否读入完毕。然后再循环体内部,将输入流中读取到的字节流,源源不断的写至目标地址下的文件中去。write的参数参考以上String方法。
最后别忘了异常抛出以及关闭输入输出通道,释放资源。
若我们使用try-catch结构,可以将资源放在try参数中,程序运行结束会自动释放资源。
try (
InputStream inputStream = new FileInputStream("/...");
){
//...
}catch (Exception e){
e.printStackTrace();
}
字符输入流——Reader
字符输入流,就是字符为最小单位将文件流读入内存。
首先,我们创建一个字符输入流
File file = new File("IO流文件/IO流示例.txt");
Reader reader = new FileReader(file);
之后我们在文件中写入一个"菜",然后同样使用无参read方法进行读取
int code = reader.read();
System.out.println(code);
System.out.println((char)code);
控制台输出结果
所以无参read方法同样返回了这个字的编码,然后将编码转换成字符类型输出,得到这个汉字
同样的,我们也可以使用循环,逐个字符的对文件中的数据进行输出
int ch;
while ((ch = reader.read()) != -1){
System.out.print((char)ch);
}
控制台输出
不过在实际使用中,不会逐字符进行操作,所以我们仍然需要一个舀取IO流的水瓢,,由于字符输入流以字符为最小单位,我们的容器就必须是字符数组,其余操作参考字节流
//每次放入3个字符
char[] buffer = new char[3];
由于字符输出流Writer与字节输出流十分相似,只是输出的最小单位为字符,所以使用方法不再赘述。
缓冲字节输入流——BufferedInputStream
缓冲流中再内存中加入了缓冲池,内存可以在离自己更近的缓冲池中加载数据,大大提高性能
写法如下,首先我们创建一个基本的字节输入流,它可以被包装成高级缓冲输入流
InputStream fis = new FileInputStream("IO流文件/IO流示例.txt");
BufferedInputStream bis = new BufferedInputStream(fis);
我们同样可以用循环来读取
byte[] bytes = new byte[1024];
int len;
while ((len =bis.read(bytes))!=-1){
System.out.println(new String(bytes,0,len));
}
缓冲字符流——BufferedReader
话不多说,直接开始演示
首先我们将一个字符输入流放入try小括号中,定义一个字符数组,以及长度len,循环输出字符
try (
Reader reader = new FileReader("IO流文件/IO流示例.txt");
)
{
char[] chars = new char[2];
int len;
while((len = reader.read(chars))!=-1){
System.out.println(new String(chars,0,len));
}
但我们遇到一个问题,当字符数组很小时(此时为2),我们输出的数据是这样的
所以,我们需要按行输出,而不是按照字符
首先将刚才的字符输入流包装在缓冲流中,定义String类型line,然后调用的是缓冲输入流的readline方法
BufferedReader bufferedReader = new BufferedReader(reader);
String line;
while((line = bufferedReader.readLine()) != null){
System.out.println(line);
}
这样我们可以得到完整的数据
缓冲字符输出流——Writer
原理与上相似,是性能更好的输出流
首先我们定义一个字符输出流,将其是否不覆盖上次写入数据设置为true,将其包装在缓冲输出流中
Writer wr = new FileWriter("IO流文件/IO流示例.txt",true);
BufferedWriter bufferedWriter = new BufferedWriter(wr);
然后我们将数据写入,newline方法起到换行作用。
写完数据,我们务必要关闭流,因为关闭的同时,执行一个刷新操作,帮助我们把数据写入文件,如果使用后不关闭,不仅浪费资源,且可能导致部分数据写入时丢失
bufferedWriter.newLine();
bufferedWriter.write("这句话是使用缓冲字符输出流写的");
bufferedWriter.newLine();
bufferedWriter.write("这一句也是");
bufferedWriter.close();
写入后
字节输入转换流——InputStreamReader
将字节流转换为字符流,首先创建一个字节输入流,然后利用字节输入转换流,把这个字节流(默认时utf-8编码)转换成字符输入流,且时GBK编码格式,然后逐行输出
InputStream is = new FileInputStream("IO流文件/IO流示例.txt");
Reader isr = new InputStreamReader(is,"GBK");
BufferedReader bufferedReader = new BufferedReader(isr);
String line;
while ((line =bufferedReader.readLine())!= null)
{
System.out.println(line);
}
输出结果,由于GBK与utf-8编码中,中文所占字节数不同,所以转换完成后必定乱码
字节输出转换流——OutputStreamWriter
道理同上,直接上例子,这次直接用utf-8格式,不会乱码
首先创建字节输出流,并将不覆盖上一次数据内容设置为true,然后将此字节流转换成字符输出流,编码格式utf-8
用write方法写入一句话
OutputStream outputStream = new FileOutputStream("IO流文件/IO流示例.txt",true);
Writer writer =new OutputStreamWriter(outputStream,"UTF-8");
writer.write("\r\n这一句话来自字符转换输出流!");
执行后,文件中:
序列化与反序列化
简单的说:
序列化即为把java对象存入文件中取,反序列化就是拿出java对象放入内存中
序列化与反序列化分别对应IO流:
序列化: —对象字节输入流 ObjectInputStream
反序列化: —对象字节输出流 ObjectOutputStream
示例:首先创建一个字节流将java类写入文件,然后将其包装成对象字节输出流
然后创建一个user类,调用writeObject方法,将该java对象存入文件中,关闭流
OutputStream os = new FileOutputStream("IO流文件/IO流示例.txt");
ObjectOutputStream oos = new ObjectOutputStream(os);
user user = new user("cxk","123","菜虚坤");
oos.writeObject(user);
oos.close();
System.out.println("对象序列化成功!");
将java对象写入后,如何进行读取呢?
首先创建输入流,将字节输入流包装成对象输入流,使用readObject方法,读取java对象
InputStream inputStream = new FileInputStream("IO流文件/IO流示例.txt");
ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
System.out.println(objectInputStream.readObject());
System.out.println("反序列化成功!");
控制台输出结果
Properties属性集对象
与map相似,Properties可以保存键值对,通常被当作属性文件使用
示例,首先创建一个Properties对象,用setProperty方法放入它的key和value
Properties properties = new Properties();
properties.setProperty("admin1","123");
properties.setProperty("admin2","321");
创建一个字节输出流对象,使用properties的store方法,将键值对放入目标文件,第二个参数是用来做一个备注
OutputStream os = new FileOutputStream("D:\\IO流目标文件夹\\user.properties");
properties.store(os,"yes!");
os.close();
System.out.println("成功!");
得到的文件
根据键的名字取值
properties.load(new FileInputStream("D:\\IO流目标文件夹\\user.properties"));
//根据键取值
System.out.println(properties.getProperty("admin1"));
打印流
高效的IO流,因为底层也实现了缓冲机制
用法
//创建打印流
PrintStream ps =new PrintStream(outputStream);
//字符打印流
PrintWriter pw = new PrintWriter(outputStream);
pw.println("这是一条由打印流打出的数据");
pw.close();
这样就将数据打印到了文件夹里
**重定向:**让本该在控制台显示的东西打印到这个文件中
OutputStream outputStream = new FileOutputStream("IO流文件/IO流示例.txt",true);
PrintStream printStream = new PrintStream(outputStream);
//setOut改变了打印流的走向
System.setOut(printStream);
System.out.println("这一条是来自控制台打印的重定向日志"+System.nanoTime());
printStream.close();
从而,在控制台中什么也没有,而在目标文件中
以上是关于IO流基础的一些总结。