本文内容是根据毕晓东老师的视频教程总结而得。包括IO流概述、IO流的分类(字节流/字符流或输入流/输出流)、File流、Properties集合与IO流结合类、IO流的各种工具类(打印流PrintWriter、合并流(序列流)SequenceInputStream、对象序列化ObjectOutputStream/ObjectInputStream、管道流PipedInputStream/PipedOutputStream、随机访问文件的读取和写入类RandomAccessFile、操作基本数据类型的流对象DataStream、操作字节数组中数据的流对象ByteArrayInputStream与ByteArrayOutputStream)、转换流的字符编码等java IO流方面的基础知识。
1.IO流概述
IO流(Input/Output):
- IO流用来处理设备之间的数据传输
- java对数据的操作是通过流的方式
- java用于操作流的对象都在IO包中
- 流按操作数据分为两种:字节流与字符流
- 流按流向分为:输入流、输出流
设备(内存、硬盘等)上有的数据:硬盘上的文件、内存中固有的数据。数据的体现方式都是字节(二进制等)。最初的流都是字节流。
为什么有字符流?有一部分数据,如文本等单独分离出来形成字符流。英文和二进制 ASCII码;gb2312中文和二进制对应表—>GBK。unicode码表把各国文字和二进制都进行对应,后来发展成utf-8。Unicode里无论什么字符都用2个字节表示,但是英文只需要一个字节,所以就有了utf-8(最多用三个字节来表示一个字符),有一个字节和2个字节的码表。
问题:在不同计算机中编码不同,则文件存储后,在另外不同编码的机器上会形成乱码。为解决此问题,java在流技术上基于字节流产生了字符流。字符流好处:可以在内部融合编码表(读到的数据可以由自己指定是使用Unicode编码还是utf-8编码),这样处理文字时就会变得方便,即字符流的对象流融合了编码表。但是处理图片、音频、视频等文件仍然使用字节流。字符流基于字节流。
IO流常用的4个基类:
- 字节流的抽象基类(读、写):InputStream、OutputStream
- 字符流的抽象基类:Reader、Writer
注:由这四个类派生出来的子类名称都是以其父类名作为子类名的后缀。如:InputStream的子类FileInputStream,如:Reader的子类FileReader。
体系:字节流和字符流
- 字节流基类:InputStream、OutputStream
- 字符流基类:Reader、Writer
2.FileWriter
Writer类字符流:
IO流是用于操作数据的,数据的最常见的体现形式是文件。因此先以操作文件来进行演示。
需求:在硬盘上,创建一个文件并写入一些文字数据。
专门用于操作文件的Writer子类对象FileWriter,后缀名是父类名,前缀名是功能。
步骤:
- 创建一个FileWriter对象,该对象一被初始化就必须明确被操作的文件, 该文件会被创建到指定的目录下。如果该目录下已有同名文件将被覆盖。其实该句就是在明确数据要存放的目的地。FileWriter fw = new FileWriter(“demo.txt”);
- writer()方法写入字符串,该方法没有直接将字符串写入到文件中,而是写入到了内存(流)中。fw.writer(“abcde”);
- 使用flush()方法刷新该流的缓冲区(即将流里的内容写到目标文件里去)。
问题:是否可以再flush()后继续写入,发现每次写入后都需要刷新一次(flush())。
解决:使用close()方法
flush()和close()区别:
- close()关闭流资源,但是关闭之前会刷新一次内部缓冲区中的数据,并将数据刷新到目的文件中;
- flush()每次写入后都需要刷新一次,刷新后,流可以继续使用,但是close刷新后,流会关闭。
package com.vnb.javabase.base.io;
import java.io.FileWriter;
import java.io.IOException;
public class FileWriterDemo {
public static void main(String[] args) {
try {
//该文件会被创建到指定的目录下
FileWriter fw = new FileWriter("demo.txt");
fw.write("abcasf--de");
//刷新流对象中的缓冲数据到目的文件中
//fw.flush();
fw.close();
} catch (IOException e) {//有可能找不到该文件,所以需要捕捉异常
e.printStackTrace();
}
}
}
3.IO异常处理方式
package com.vnb.javabase.base.io;
import java.io.FileWriter;
import java.io.IOException;
public class ExceptionDemo {
public static void main(String[] args) {
FileWriter fw = null;
try {
fw = new FileWriter("demo.txt");
fw.write("abcasf--de");
} catch (IOException e) {
e.printStackTrace();
}finally{
try {
if(fw!=null){//当文件不存在时,文件流对象没有创建,所以流关闭不了
fw.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
4.文件的续写—通过FileWriter(String filename,Boolean append)构造函数参数实现
需求:如果文件中已经有文件了,需要在原有文件中添加数据。而由上例可知对于fw.write();在文件一创建就会将其覆盖掉原有的文件。
FileWriter(String filename,Boolean append):根据给定的文件名以及指定是否附加写入数据的boolean值来构造FileWriter对象。append表示一个boolean值,如果为true,则将数据写入文件末尾处,而不是写入文件开始处。
package com.vnb.javabase.base.io;
import java.io.FileWriter;
import java.io.IOException;
public class FileWriterDemo1 {
public static void main(String[] args) {
FileWriter fw = null;
try {
//传递true表示不覆盖原有文件,并在已有文件的末尾处进行续写。
fw = new FileWriter("demo.txt",true);
fw.write("nihaoxiexei");
} catch (IOException e) {
e.printStackTrace();
}finally{
try {
if(fw!=null){//当文件不存在时,文件流对象没有创建,所以流关闭不了
fw.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
结果:发现文件续写成功
需求:在nihao后面换行让xiexie处于第二行。
注意:在windows中回车键是有\r和\n,在linux中\n可以换行,但是在windows的记事本中直接写\n不行。但实际上是已经换过行了,但是要使用\r\n才可以完美换行。
fw.write("nihao\r\nxiexei");
write(char[] str)写入字符数组。
5.文本文件读取
5.1文本文件读取方式一:read()方法读取
读取文件字符流Reader,读取文件不会刷新流。读取文件字符流Reader的子类对象FileReader,此字符流有默认编码(系统级编码System类里的Property里的编码值)。
FileReader(String fileName) :在给定从中读取数据的文件名的情况下创建一个新的FileReader。
步骤:
- 创建一个文件读取流对象,和指定名称的文件相关联。要保证该文件已经存在,如果不存在会发生FileNotFoundException异常。FileReader fr = new FileReader("demo.txt");
- 调用读取流对象的read方法(字符读取,如果已经达到流的结尾,则为-1)
发现fr.read();一次读取一个字符,调用一次读取一个字符。
package com.vnb.javabase.base.io;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
public class FileReaderDemo {
public static void main(String[] args) {
FileReader fr = null;
try {
//创建一个文件读取流对象,和指定名称的文件相关联。要保证该文件已经存在,如果不存在会发生FileNotFoundException异常
fr = new FileReader("demo.txt");
int ch;
try {
ch = fr.read();
System.out.println("ch="+ch);
ch = fr.read();
System.out.println("ch="+ch);
ch = fr.read();
System.out.println("ch="+ch);
ch = fr.read();
System.out.println("ch="+ch);
ch = fr.read();
System.out.println("ch="+ch);
} catch (IOException e) {
e.printStackTrace();
}
} catch (FileNotFoundException e) {
e.printStackTrace();
}finally{
try {
if(fr!=null){
fr.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
发现在读到-1时文件读取完毕,所以就有了循环条件,在-1时结束读取
FileReader fr = null;
try {
//创建一个文件读取流对象,和指定名称的文件相关联。要保证该文件已经存在,如果不存在会发生FileNotFoundException异常
fr = new FileReader("demo.txt");
int ch = 0;
try {
while((ch=fr.read())!=-1){
System.out.println("ch="+(char)ch);
}
} catch (IOException e) {
e.printStackTrace();
}
发现只要使用while((ch=fr.read())!=-1)即可将文件读取出来。
读取文件图解:fr.read()读取到系统上的读取硬盘的方法,当读完字符g后,就读取到了硬盘上存数据的结束标识,java接收到这个标识后,就会返回给我们-1。
5.2文本文件读取方式二:int read(char[] cbuf)
通过字符数组进行读取。int read(char[] cbuf):将字符读入数组,返回读取的字符数,达到末尾则返回-1。
定义一个字符数组,用于存储读到的字符,该int read(char[] cbuf) 返回的是读取到的字符个数,并通过String(char[] char]构造方法将字符数组转为字符串显示。
package com.vnb.javabase.base.io;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
public class FileReaderDemo2 {
public static void main(String[] args) {
FileReader fr = null;
try {
fr = new FileReader("demo.txt");
char[] buf = new char[3];
//该read方法返回的是读到的字符数
try {
int num = fr.read(buf);
System.out.println("num="+num+"..."+new String(buf));
num = fr.read(buf);
System.out.println("num="+num+"..."+new String(buf));
num = fr.read(buf);
System.out.println("num="+num+"..."+new String(buf));
num = fr.read(buf);
System.out.println("num="+num+"..."+new String(buf));
} catch (IOException e) {
e.printStackTrace();
}
} catch (FileNotFoundException e) {
e.printStackTrace();
}finally{
try {
if(fr!=null){
fr.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
结果:
num=3...aba
num=3...sef
num=2...sdf
num=-1...sdf
int read(char[] cbuf)读取字符分析:对于字符串abasefsd,第一次当aba读完后,不再往下读,因为字符数组空间为3;第二次从s还是读,仍然往数组里装,因为超出数组长度了所以会之前字符数组中的aba被sef替代;第三次从s开始读,当sd装进字符数组时,没有数据了就不会往下读取了,就不会覆盖上一次的值,于是返回sdf。但是此时f是重复的,可以使用String(char[] value,int offset,int count)分配一个新的String,它包含取子字符数组参数:从第几个字符开始取;标识取几个(读到几个取几个)字符。
发现读完后num为-1,读到的字符仍返回上一次返回的字符
package com.vnb.javabase.base.io;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
public class FileReaderDemo2 {
public static void main(String[] args) {
FileReader fr = null;
try {
fr = new FileReader("demo.txt");
//一个字符2个字节,1024即2K空间,对于字符少的定义一次空间即可,字符大的,再重新定义空间
char[] buf = new char[1024];
//该read方法返回的是读到的字符数
try {
int num = 0;
while((num=fr.read(buf))!=-1){
//从零开始读,num表示读取多少个字符
System.out.println("num="+num+"..."+new String(buf,0,num));
}
} catch (IOException e) {
e.printStackTrace();
}
} catch (FileNotFoundException e) {
e.printStackTrace();
}finally{
try {
if(fr!=null){
fr.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
结果:num=8...abasefsd
原理:读一个字符存储一个字符,当读取完毕后再一起打印。(一般定义成字符数组的大小定义为char[1024])
5.3文本文件读取练习
需求:读取一个.java文件,并打印到控制台。
package com.vnb.javabase.base.io;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
public class FileReaderTest {
//将一个java文件读取并打印到控制台(先将所有的读到内存中,再进行打印)
public static void main(String[] args) {
FileReader fr = null;
try {
fr = new FileReader("D:/EclipseWork/SpringmvcQuartz/src/com/vnb/javabase/base/io/FileReaderDemo.java");
char[] buf = new char[1024];
try {
int num = 0;
while((num=fr.read(buf))!=-1){
//不使用println是因为如果数据超过1024时,不会换行打印
System.out.print(new String(buf,0,num));
}
} catch (IOException e) {
e.printStackTrace();
}
} catch (FileNotFoundException e) {
e.printStackTrace();
}finally{
try {
if(fr!=null){
fr.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
控制台打印结果:
package com.vnb.javabase.base.io;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
public class FileReaderDemo {
public static void main(String[] args) {
FileReader fr = null;
try {
//创建一个文件读取流对象,和指定名称的文件相关联。要保证该文件已经存在,如果不存在会发生FileNotFoundException异常
fr = new FileReader("demo.txt");
int ch = 0;
try {
while((ch=fr.read())!=-1){
System.out.println("ch="+(char)ch);
}
} catch (IOException e) {
e.printStackTrace();
}
} catch (FileNotFoundException e) {
e.printStackTrace();
}finally{
try {
if(fr!=null){
fr.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
6.拷贝文本文件
需求:将C盘文本文件复制到D盘。复制文件的原理:其实就是将C盘下的文件数据存储到D盘的一个文件中。
步骤:
- 在D盘创建一个文件,用于存储C盘文件中的数据
- 定义读取流和C盘文件关联
- 通过不断的读写完成数据存储
- 关闭资源
方法一:一个一个的读取字符边读边存边打印
package com.vnb.javabase.base.io;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
public class CopyFileTest {
public static void main(String[] args) {
copy_1();
}
//从C盘读一个字符就往D盘写一个字符
public static void copy_1(){
FileReader fr = null;
FileWriter fw = null;
try {
fr = new FileReader("D:/EclipseWork/SpringmvcQuartz/src/com/vnb/javabase/base/io/FileReaderDemo.java");
fw = new FileWriter("C:/Users/Administrator/Desktop/backup/FileReaderDemo_copy.java");
int ch = 0;
while((ch = fr.read())!=-1){
fw.write(ch);
}
} catch (IOException e) {
e.printStackTrace();
}finally{
try {
if(fr!=null){
fr.close();
}
if(fw!=null){
fw.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
//从C盘读完所有字符并存储到缓冲中后,再一起往D盘写入
public static void copy_2(){
}
}
结果:
发现:有一个字符循环一次,效率低,速度慢,不建议使用。
解决:使用缓冲区,将所有字符读到一个字符数组再一起读出
package com.vnb.javabase.base.io;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
public class CopyFileTest {
public static void main(String[] args) {
copy_2();
}
//从C盘读完所有字符并存储到缓冲中后,再一起往D盘写入
public static void copy_2(){
FileReader fr = null;
FileWriter fw = null;
try {
fr = new FileReader("D:/EclipseWork/SpringmvcQuartz/src/com/vnb/javabase/base/io/FileReaderDemo.java");
fw = new FileWriter("C:/Users/Administrator/Desktop/backup/FileReaderDemo_copy.java");
int len = 0;
char[] buf = new char[1024];
while((len = fr.read(buf))!=-1){
//有多少字符写多少字符,如果写成fw.write(buf)若只有4个字符,会将1024空间的所有写出去
fw.write(buf,0,len);
}
} catch (IOException e) {
throw new RuntimeException("读写数据失败");
}finally{
//注意不同的流分开catch
try {
if(fr!=null){
fr.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
if(fw!=null){
fw.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
结果:复制成功
7.拷贝文本文件图例
8.BufferedWriter
字符流的缓冲区:
- 缓冲区的出现提高了对数据的读写效率
- 对应类:BufferedWriter、BufferedReader
- 缓冲区要结合流才可以使用
- 在流的基础上对流的功能进行了增强
BufferedWriter:将文本写入字符输出流,缓冲各个字符,从而提供单个字符、数组和字符串的高效写入。(先存储完,再一次性写出)
缓冲区的出现是为提高流的读写操作效率,所以在创建缓冲区之前应该先有流对象,所以BufferedWriter没有空构造。
使用步骤:
- 创建一个字符写入流对象
- 为了提高字符写入流的效率,加入缓冲技术(其实就是对象里封装数组,一次性存入,一次性写出),只要将需要被提高效率的流对象作为参数传递给缓冲区的构造函数即可
- 使用缓冲区的方法进行写入(BufferedWriter也是Writer的子类,所以也有write()方法)
- 使用flush()或close()方法才能真正将数据写出(只要用到缓冲区,就要记得刷新)
缓冲区是为提高效率的,而真正操作文件写入的是FileWriter,所以缓冲区关闭的,其实是底层操作流写入的FileWriter流对象。所以,bufw.close()后,FileWriter不需要再关闭了。
BufferedWriter缓冲区中方法newLine():写入一个行分隔符。
使用\r\n也可以换行,为什么还要使用newLine()?
因为不同的操作系统换行的符号不同,\r\n是windows系统的,而在Linux中只要使用\n即可,所以需要有统一的方法实现跨平台性。
package com.vnb.javabase.base.io;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
public class BufferedWriterDemo {
public static void main(String[] args) {
try {
FileWriter fw = new FileWriter("D:/EclipseWork/SpringmvcQuartz/BufferdWriterDemo.txt");
BufferedWriter bw = new BufferedWriter(fw);
bw.write("abczdfwa");
//该缓冲区中提供为了跨平台的换行符方法newLine()
bw.newLine();
bw.write("asdfadsssabczdfwa");
//写一次刷新一次,防止意外宕机时目标文件里没有数据。
bw.flush();
//缓冲区是为提高效率的,而真正操作文件写入的是FileWriter,所以缓冲区关闭的,其实是底层操作流写入的FileWriter流对象。所以,bufw.close()后,FileWriter不需要再关闭了。
bw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
结果:
9.BufferedReader
从字符输入流中读取文本,缓冲各个字符,从而实现字符、数组和行的高效读取。
方法:
readLine()方法读取一行文本到缓冲区。返回:包含该行内容的字符串,不包含任何终止符,如果已达到末尾,则返回null。
步骤:
- 创建一个读取流对象和文件相关联
- 为了提高效率加入缓冲技术,将字符读取流对象作为参数给缓冲对象的构造函数
- 该缓冲区提供了一个一次读一行的方法readLine,方便对文本数据的获取,当返回null时标识读到文件末尾。
package com.vnb.javabase.base.io;
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
public class BufferedReaderDemo {
public static void main(String[] args) {
try {
//创建一个读取流对象和文件相关联
FileReader fr = new FileReader("D:/EclipseWork/SpringmvcQuartz/BufferdWriterDemo.txt");
//为了提高效率加入缓冲技术,将字符读取流对象作为参数给缓冲对象的构造函数
BufferedReader br = new BufferedReader(fr);
try {
String line = null;
//该缓冲区提供了一个一次读一行的方法readLine,方便对文本数据的获取,当返回null时标识读到文件末尾。
while((line = br.readLine())!=null){
System.out.println(line);
}
} catch (IOException e) {
e.printStackTrace();
}
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
}
结果:
abczdfwa
asdfadsssabczdfwa
10.通过缓冲区复制文本文件
需求:通过缓冲区复制一个.java文件
package com.vnb.javabase.base.io;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
public class CopyTextByBuf {
@SuppressWarnings("resource")
public static void main(String[] args) {
BufferedReader br = null;
BufferedWriter bw = null;
try {
br = new BufferedReader(new FileReader("D:/EclipseWork/SpringmvcQuartz/src/com/vnb/javabase/base/io/CopyFileTest.java"));
try {
bw = new BufferedWriter(new FileWriter("D:/EclipseWork/SpringmvcQuartz/CopyFileTest_copy.java"));
String line = null;
//readLine方法只返回回车符前的有效数据,不返回换行等符号
while((line = br.readLine())!=null){
System.out.print(line);
bw.write(line);
//加上换行符
bw.newLine();
bw.flush();
}
} catch (IOException e) {
throw new RuntimeException("读取操作失败");
}
} catch (FileNotFoundException e) {
throw new RuntimeException("该文件不存在");
}
}
}
结果:操作成功
11.readLine的原理图例
无论读取一行还是读取单个字符,最终在硬盘上都是一个字符一个字符的取,所以readLine最终使用的仍然是read方法一次读一个的方法。其实readLine方法内部有一个数组。如下图,取到a时没有立即返回,而是临时存到缓冲区的数组中,一次读取所有字符,当读取当\r时不会再将其读到数组中了,当再读到\n时,认为这一行结束,于是将数组中已经存好的字符串返回。然后再调用readLine方法继续读取下一行,原理相同。
12.自定义缓冲区读取类—MyBufferedReader
需求:明白了BufferedReader类中特有方法readLine的原理后,可以自定义一个类中包含一个功能和readLine一致的方法,来模拟一下BufferedRead(自己写一个缓冲区,且基于read方法,定义自己的一行数据的存储单位)。
自定义缓冲区类:
package com.vnb.javabase.base.io;
import java.io.FileReader;
import java.io.IOException;
public class MyBufferedReader {
private FileReader fr;
MyBufferedReader(FileReader fr){
this.fr = fr;
}
//可以一次读一行数据的方法
public String myReadLine() throws IOException{
//定义一个临时容器,原BufferedReader封装的是字符数组,为了演示方便定义一个StringBuilder容器,因为最终还是要将数据变成字符串
StringBuilder sb = new StringBuilder();
int ch = 0;
while((ch=fr.read())!=-1){
if(ch=='\r'){
continue;
}
//读到行末,返回字符串
if(ch=='\n'){
return sb.toString();
}else{
sb.append((char)ch);
}
}
return null;
}
//定义缓冲区关闭方法
public void myClose() throws IOException{
//缓冲区使用的是FileReader,因此使用的使用不再使用FileReader的close方法
fr.close();
}
}
自定义读取类测试类:
package com.vnb.javabase.base.io;
import java.io.BufferedWriter;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
public class MyBufferedReaderTest {
public static void main(String[] args) {
FileWriter fw = null;
try {
FileReader fr = new FileReader("D:/EclipseWork/SpringmvcQuartz/CopyFileTest_copy.java");
try {
MyBufferedReader myBr = new MyBufferedReader(fr);
String line = "";
while((line=myBr.myReadLine())!=null){
System.out.println(line);
}
myBr.myClose();
} catch (IOException e) {
e.printStackTrace();
}
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
}
结果:成功读取
package com.vnb.javabase.base.io;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
public class CopyFileTest {
public static void main(String[] args) {
copy_2();
}
//从C盘读完所有字符并存储到缓冲中后,再一起往D盘写入
public static void copy_2(){
FileReader fr = null;
FileWriter fw = null;
try {
fr = new FileReader("D:/EclipseWork/SpringmvcQuartz/src/com/vnb/javabase/base/io/FileReaderDemo.java");
fw = new FileWriter("C:/Users/Administrator/Desktop/backup/FileReaderDemo_copy.java");
int len = 0;
char[] buf = new char[1024];
while((len = fr.read(buf))!=-1){
//有多少字符写多少字符,如果写成fw.write(buf)若只有4个字符,会将1024空间的所有写出去
fw.write(buf,0,len);
}
} catch (IOException e) {
throw new RuntimeException("读写数据失败");
}finally{
//注意不同的流分开catch
try {
if(fr!=null){
fr.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
if(fw!=null){
fw.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
存在的问题:
当去掉某行文本后的回车键时,文本读取会出现问题,文本存进去了但是没有正常返回。
解决:判断如果没有读到返回的换行,直接返回剩下的数据。
if(sb.length()!=0){
return sb.toString();
}
return null;
13.装饰设计模式
装饰设计模式:当想要对已有的对象进行增强时,可以定义类,将已有对象传入,基于已有对象的功能,并提供加强功能,自定义的该类就称为装饰类。
对于人吃饭的方法如上例,但是,一段时间后,发现需要对Person类进行更改。于是重新定义类对原有功能进行增强,并把原有类传进去。如下:
使用时不需要更改,调用时调用更改后的方法即可:
一般Person和SuperPerson同属于一个接口或者同属于一个体系。
特点:装饰类通常会通过构造方法接收被装饰的对象,并基于被装饰的对象的功能提供更强的功能。
14.装饰和继承的区别
如有一个MyReader专门读取数据的类。数据有很多种,为了分别描述对不同数据的描述,于是建立了对不同数据进行描述读取的类,如MyTextReader、MyMediaReader等,而这些类都有读取的行为,于是向上抽取,形成MyReader。由此形成一个继承体系。体系形成后,发现MyTextReader效率不高,于是就需要对MyTextReader加入缓冲技术提高其效率MyBufferTextReader。而MyMediaReader也要提高效率,有MyBufferMediaReader。如下图:
问题,如果还有一个数据读取形式,就需要再写一个Reader类和其缓冲技术,所以体系会变得很复杂和臃肿。
发现所有扩展出来的都是使用缓冲技术,所以可以单独定义缓冲区,哪个类需要缓冲区传入哪个类即可。如下:
但是发现,上图中的类扩展性极差,找到其参数的共同类型,通过多态形式,可以提高其扩展性。如下:
该类作为MyReader的增强类,说明其拥有增强功能的读取行为:
最后形成的体系:
装饰模式比继承要灵活,避免了继承模式的臃肿,而且降低了类与类之间的关系。装饰类因为是增强已有对象,具备的功能和已有对象是相同的,只不过提供了更强的功能。所以装饰类和被装饰类通常都属于同一个体系中。(组合结构)
15.自定义装饰类
package com.vnb.javabase.base.io;
import java.io.FileReader;
import java.io.IOException;
public class MyBufferedReader {
private FileReader fr;
MyBufferedReader(FileReader fr){
this.fr = fr;
}
//可以一次读一行数据的方法
public String myReadLine() throws IOException{
//定义一个临时容器,原BufferedReader封装的是字符数组,为了演示方便定义一个StringBuilder容器,因为最终还是要将数据变成字符串
StringBuilder sb = new StringBuilder();
int ch = 0;
while((ch=fr.read())!=-1){
if(ch=='\r'){
continue;
}
//读到行末,返回字符串
if(ch=='\n'){
return sb.toString();
}else{
sb.append((char)ch);
}
}
if(sb.length()!=0){
return sb.toString();
}
return null;
}
//定义缓冲区关闭方法
public void myClose() throws IOException{
//缓冲区使用的是FileReader,因此使用的使用不再使用FileReader的close方法
fr.close();
}
}
根据上述原理,针对自定义的缓冲类,自定义的读取一行newLine方法的类,进行修改。
首先继承字符流的父类读取类:MyBufferedReader extends Reader
然后将要增强的类的父类Reader传入(多态:使用时传入要增强的类即可)
private Reader fr;
MyBufferedReader(Reader fr){
this.fr = fr;
}
问题:发现继承了Reader,就必须要复写Reader类中的方法,但是每个流对象调用的close()方法的对象不一样,且read(char[] char,int offset,int len)方法不好复写。
修改好后,如下:
package com.vnb.javabase.base.io;
import java.io.IOException;
import java.io.Reader;
public class MyBufferedReader extends Reader{
private Reader fr;
MyBufferedReader(Reader fr){
this.fr = fr;
}
//可以一次读一行数据的方法
public String myReadLine() throws IOException{
//定义一个临时容器,原BufferedReader封装的是字符数组,为了演示方便定义一个StringBuilder容器,因为最终还是要将数据变成字符串
StringBuilder sb = new StringBuilder();
int ch = 0;
while((ch=fr.read())!=-1){
if(ch=='\r'){
continue;
}
//读到行末,返回字符串
if(ch=='\n'){
return sb.toString();
}else{
sb.append((char)ch);
}
}
if(sb.length()!=0){
return sb.toString();
}
return null;
}
//定义缓冲区关闭方法
public void myClose() throws IOException{
//缓冲区使用的是FileReader,因此使用的使用不再使用FileReader的close方法
fr.close();
}
@Override
public int read(char[] cbuf, int off, int len) throws IOException {
return fr.read(cbuf, off, len);
}
@Override
public void close() throws IOException {
//缓冲区使用的是FileReader,因此使用的使用不再使用FileReader的close方法
fr.close();
}
}
16.LineNumberReader
跟踪行号的缓冲字符输入流,此类定义了方法setLineNumber()和getLineNumber()(可以写出文件时加上行号,读取文件时读取行号)。
package com.vnb.javabase.base.io;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.LineNumberReader;
public class LineNumberReaderDemo {
public static void main(String[] args) {
FileReader fr = null;
LineNumberReader lnr = null;
try {
fr = new FileReader("D:/EclipseWork/SpringmvcQuartz/CopyFileTest_copy.java");
lnr = new LineNumberReader(fr);
String line = null;
try {
while((line=lnr.readLine())!=null){
System.out.println(lnr.getLineNumber()+":"+line);
}
} catch (IOException e) {
e.printStackTrace();
}
} catch (FileNotFoundException e) {
e.printStackTrace();
}finally{
if(lnr!=null){
try {
lnr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
结果:
1:package com.vnb.javabase.base.io;
2:
3:import java.io.FileReader;
4:import java.io.FileWriter;
5:import java.io.IOException;
6:
7:public class CopyFileTest {
8: public static void main(String[] args) {
9: copy_1();
10: }
11: //从C盘读一个字符就往D盘写一个字符
12: public static void copy_1(){
13: FileReader fr = null;
14: FileWriter fw = null;
15: try {
16: fr = new FileReader("D:/EclipseWork/SpringmvcQuartz/src/com/vnb/javabase/base/io/FileReaderDemo.java");
17: fw = new FileWriter("C:/Users/Administrator/Desktop/backup/FileReaderDemo_copy.java");
18: int ch = 0;
19: while((ch = fr.read())!=-1){
20: fw.write(ch);
21: }
22: } catch (IOException e) {
23: e.printStackTrace();
24: }finally{
25: try {
26: if(fr!=null){
27: fr.close();
28: }
29: if(fw!=null){
30: fw.close();
31: }
32: } catch (IOException e) {
33: e.printStackTrace();
34: }
35: }
36: }
37:}
设置//把行号设置成从100开始lnr.setLineNumber(100);后结果如下:
101:package com.vnb.javabase.base.io;
102:
103:import java.io.FileReader;
104:import java.io.FileWriter;
105:import java.io.IOException;
106:
107:public class CopyFileTest {
108: public static void main(String[] args) {
109: copy_1();
110: }
111: //从C盘读一个字符就往D盘写一个字符
112: public static void copy_1(){
113: FileReader fr = null;
114: FileWriter fw = null;
115: try {
116: fr = new FileReader("D:/EclipseWork/SpringmvcQuartz/src/com/vnb/javabase/base/io/FileReaderDemo.java");
117: fw = new FileWriter("C:/Users/Administrator/Desktop/backup/FileReaderDemo_copy.java");
118: int ch = 0;
119: while((ch = fr.read())!=-1){
120: fw.write(ch);
121: }
122: } catch (IOException e) {
123: e.printStackTrace();
124: }finally{
125: try {
126: if(fr!=null){
127: fr.close();
128: }
129: if(fw!=null){
130: fw.close();
131: }
132: } catch (IOException e) {
133: e.printStackTrace();
134: }
135: }
136: }
137:}
17.MyLineNumberReader
自定义一个带行号的流输出类。其实,其内部一个计数器。行号:读一行才自增一行。
package com.vnb.javabase.base.io;
import java.io.IOException;
import java.io.Reader;
public class MyLineNumberReader{
private Reader r;
private int lineNumber;
MyLineNumberReader(Reader r){
this.r = r;
}
public String myReadLine(){
//默认lineNumber为0,且只有在调用myReadLine时才会行号增加
lineNumber++;
StringBuilder sb = new StringBuilder();
int ch;
try {
while((ch = r.read())!=-1){
if(ch == '\r'){
continue;
}
if(ch == '\n'){
return sb.toString();
}else{
sb.append((char)ch);
}
}
} catch (IOException e) {
e.printStackTrace();
}
if(sb.length()!=0){
return sb.toString();
}
return null;
}
public void mySetLineNumber(int lineNumber){
this.lineNumber = lineNumber;
}
public int myGetLineNumber(){
return this.lineNumber;
}
public void myClose() throws IOException {
r.close();
}
}
package com.vnb.javabase.base.io;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
public class MyLineNumberReaderTest {
public static void main(String[] args) {
FileReader fr = null;
MyLineNumberReader myLnr = null;
try {
fr = new FileReader("D:/EclipseWork/SpringmvcQuartz/CopyFileTest_copy.java");
myLnr = new MyLineNumberReader(fr);
String line = "";
myLnr.mySetLineNumber(20);
while((line=myLnr.myReadLine())!=null){
System.out.println(myLnr.myGetLineNumber()+":"+line);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
}finally{
if(myLnr!=null){
try {
myLnr.myClose();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
结果:
21:package com.vnb.javabase.base.io;
22:
23:import java.io.FileReader;
24:import java.io.FileWriter;
25:import java.io.IOException;
26:
27:public class CopyFileTest {
28: public static void main(String[] args) {
29: copy_1();
30: }
31: //从C盘读一个字符就往D盘写一个字符
32: public static void copy_1(){
33: FileReader fr = null;
34: FileWriter fw = null;
35: try {
36: fr = new FileReader("D:/EclipseWork/SpringmvcQuartz/src/com/vnb/javabase/base/io/FileReaderDemo.java");
37: fw = new FileWriter("C:/Users/Administrator/Desktop/backup/FileReaderDemo_copy.java");
38: int ch = 0;
39: while((ch = fr.read())!=-1){
40: fw.write(ch);
41: }
42: } catch (IOException e) {
43: e.printStackTrace();
44: }finally{
45: try {
46: if(fr!=null){
47: fr.close();
48: }
49: if(fw!=null){
50: fw.close();
51: }
52: } catch (IOException e) {
53: e.printStackTrace();
54: }
55: }
56: }
57:}
问题:发现自定义类MyLineNumberReader和自定义类MyBufferedReader有很多重复的地方,且LineNumberReader是继承自BufferedReader类的,那么为什么LineNumberReader没有使用父类BufferedReader类的readLine()方法而是自己写呢?
MyBufferedReader本身已经有readLine()了MyLineNumberReader就不需要写了,直接继承MyBufferedReader类再复写里面的readLine()增加行数增加内容即可;且MyBufferedReader类已经继承了Reader类,就不需要再继承Reader类了,直接调用父类的Reader即可。
MyLineNumberReader(Reader r){
super(r);
}
public String myReadLine() throws IOException{
//默认lineNumber为0,且只有在调用myReadLine时才会行号增加
lineNumber++;
return super.myReadLine();
}
修改完成后:
package com.vnb.javabase.base.io;
import java.io.IOException;
import java.io.Reader;
public class MyLineNumberReader extends MyBufferedReader{
private int lineNumber;
MyLineNumberReader(Reader r){
super(r);
}
public String myReadLine() throws IOException{
//默认lineNumber为0,且只有在调用myReadLine时才会行号增加
lineNumber++;
return super.myReadLine();
}
public void mySetLineNumber(int lineNumber){
this.lineNumber = lineNumber;
}
public int myGetLineNumber(){
return this.lineNumber;
}
}
测试类同上
结果:
21:package com.vnb.javabase.base.io;
22:
23:import java.io.FileReader;
……
81: try {
82: if(fw!=null){
83: fw.close();
84: }
85: } catch (IOException e) {
86: e.printStackTrace();
87: }
88: }
89: }
90:}
18.字节流File读写操作
以上学习字符流、字符流缓冲区:FileReader、FileWriter、BufferedReader、BufferedWriter。
需求:想要操作图片数据,就要用到字节流。即InputStream、OutputStream。
OutputStream:
字符流用的是字符数组char[],字节流使用的是字节数组byte[]。
package com.vnb.javabase.base.io.byteio;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
public class FileStream {
public static void main(String[] args) {
writeFile();
}
public static void writeFile(){
FileOutputStream fos = null;
try {
fos = new FileOutputStream("fos.txt");
try {
fos.write("adfasdf".getBytes());
} catch (IOException e) {
e.printStackTrace();
}
} catch (FileNotFoundException e) {
e.printStackTrace();
}finally{
if(fos!=null){
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
结果:
发现:
- 字符串转化为字节数组:"adfasdf".getBytes();
- 字符串转化为字符数组:"adfasdf".toCharArray();
- 字节流中不使用flush()和close()方法也可以将字节输出。
使用字符流时,其实一样是底层使用字节流,但是会先把字节(文字)临时存储起来(如中文,读完一个字节后临时存储起来,再读一个字节后,然后再去对应字符表里对应);而直接使用字节流时没有指定缓冲区时,会直接把字节写到目标文件里去,所以字节流不需要使用flush()或者close()来将文件写入目标文件,但是要使用close()方法关闭流资源。
package com.vnb.javabase.base.io.byteio;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
public class FileStream {
public static void main(String[] args) {
//writeFile();
//readFile_1();
//readFile_2();
readFile_3();
}
public static void writeFile(){
FileOutputStream fos = null;
try {
fos = new FileOutputStream("fos.txt");
try {
fos.write("adfasdf".getBytes());
} catch (IOException e) {
e.printStackTrace();
}
} catch (FileNotFoundException e) {
e.printStackTrace();
}finally{
if(fos!=null){
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
//一个一个字节的读
public static void readFile_1(){
FileInputStream fis = null;
try {
fis = new FileInputStream("fos.txt");
int ch = 0;
try {
while((ch=fis.read())!=-1){
System.out.println((char)ch);
}
} catch (IOException e) {
e.printStackTrace();
}
} catch (FileNotFoundException e1) {
e1.printStackTrace();
}finally{
if(fis!=null){
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
//读完所有的再打印 结果:adfasdf
public static void readFile_2(){
FileInputStream fis = null;
try {
fis = new FileInputStream("fos.txt");
byte[] bytes = new byte[1024];
int len = 0;
try {
while((len=fis.read(bytes))!=-1){
System.out.println(new String(bytes,0,len));
}
} catch (IOException e) {
e.printStackTrace();
}
} catch (FileNotFoundException e1) {
e1.printStackTrace();
}finally{
if(fis!=null){
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
//字节流特殊读法avalible(),返回从此输入流中可以读取(或跳过)的剩余字节数的估计值,而不会被下一次调用此输入流的方法阻塞。
//此方法会将\r\n也作为2个字节返回
public static void readFile_3(){
FileInputStream fis = null;
try {
fis = new FileInputStream("fos.txt");
//因为不知道要处理的数据有多大,如果要使用read(byte[])必须要设定其大小为1024的整数倍,而通过fis.available()得到字节数后,就可以定义一个刚刚好的缓冲区,就不需要循环了
try {
//虚拟机启动默认分配64兆,如果new byte时,数据量太大,而内存又小会出现内存溢出,所以该方法慎用。
byte[] buf = new byte[fis.available()];
fis.read(buf);
System.out.println(new String(buf));
} catch (IOException e) {
e.printStackTrace();
}
} catch (FileNotFoundException e1) {
e1.printStackTrace();
}finally{
if(fis!=null){
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
方法1结果:
a
d
f
a
s
d
f
方法2和方法3结果:
adfasdf
注意:字节流特殊读取方法avalible(),返回从此输入流中可以读取(或跳过)的剩余字节数的估计值,而不会被下一次调用此输入流的方法阻塞。此方法会将\r\n也作为2个字节返回。因为不知道要处理的数据有多大,如果要使用read(byte[])必须要设定其大小为1024的整数倍,而通过fis.available()得到字节数后,就可以定义一个刚刚好的缓冲区,就不需要循环了。但是,虚拟机启动默认分配64兆,如果new byte时,数据量太大,而内存又小会出现内存溢出,所以available()方法要慎用。
available()方法:因为不知道要处理的数据有多大,如果要使用read(byte[])必须要设定其大小为1024的整数倍,而通过fis.available()得到字节数后,就可以定义一个刚刚好的缓冲区,就不需要循环了。但是,虚拟机启动默认分配64兆,如果new byte时,数据量太大,而内存又小会出现内存溢出,所以该方法慎用。
19.拷贝图片
思路:
- 用字节读取流对象和图片关联
- 用字节写入流对象创建一个图片文件。用于存储获取到的图片数据
- 通过循环读写,完成数据的存储
- 关闭资源
package com.vnb.javabase.base.io.byteio;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
public class CopyPic {
public static void main(String[] args) {
FileOutputStream fos = null;
FileInputStream fis = null;
try {
fis = new FileInputStream("D:/EclipseWork/SpringmvcQuartz/Lighthouse.jpg");
fos = new FileOutputStream("D:/EclipseWork/SpringmvcQuartz/Lighthouse_copy.jpg");
byte[] picBye = new byte[1024];
int len = 0;
try {
while((len=fis.read(picBye))!=-1){
fos.write(picBye, 0, len);
}
} catch (IOException e) {
e.printStackTrace();
}
} catch (FileNotFoundException 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();
}
}
}
}
}
结果:
字符流可以复制图片吗?可以复制,但是使用字符流赋值图片后,有可能会打不开,因为图片数据读完后读一段会去查编码表。所以不要用字符流拷贝媒体文件。
20.字节流的缓冲区
BufferedOutputStream、BufferedInputStream。
需求:通过mp3的赋值,演示字节流的缓冲区
package com.vnb.javabase.base.io.byteio;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
public class CopyMp3Stream {
public static void main(String[] args) {
//copy_2();
copy_1();
}
public static void copy_1(){
BufferedInputStream bis = null;
BufferedOutputStream bos = null;
try {
bis = new BufferedInputStream(new FileInputStream("D:/EclipseWork/SpringmvcQuartz/mp3.mp3"));
bos = new BufferedOutputStream(new FileOutputStream("D:/EclipseWork/SpringmvcQuartz/mp3_copy1.mp3"));
int len = 0;
try {
while((len=bis.read())!=-1){
bos.write(len);
}
} catch (IOException e) {
throw new RuntimeException("复制mp3文件失败");
}
} catch (FileNotFoundException e) {
throw new RuntimeException("未找到该mp3文件");
}finally{
if(bis!=null){
try {
bis.close();
} catch (IOException e) {
throw new RuntimeException("bis输入流关闭出错");
}
}
if(bos!=null){
try {
bos.close();
} catch (IOException e) {
throw new RuntimeException("bos输出流关闭出错");
}
}
}
}
//通过字节流的缓冲区完成复制
public static void copy_2(){
BufferedInputStream bis = null;
BufferedOutputStream bos = null;
try {
bis = new BufferedInputStream(new FileInputStream("D:/EclipseWork/SpringmvcQuartz/mp3.mp3"));
bos = new BufferedOutputStream(new FileOutputStream("D:/EclipseWork/SpringmvcQuartz/mp3_copy.mp3"));
byte[] mp3Byte = new byte[1024];
int len = 0;
try {
while((len=bis.read(mp3Byte))!=-1){
bos.write(mp3Byte, 0, len);
}
} catch (IOException e) {
throw new RuntimeException("复制mp3文件失败");
}
} catch (FileNotFoundException e) {
throw new RuntimeException("未找到该mp3文件");
}finally{
if(bis!=null){
try {
bis.close();
} catch (IOException e) {
throw new RuntimeException("bis输入流关闭出错");
}
}
if(bos!=null){
try {
bos.close();
} catch (IOException e) {
throw new RuntimeException("bos输出流关闭出错");
}
}
}
}
}
结果:
21.自定义字节流的缓冲区—read和write的特点
自定义字节流缓冲区的基本原理:
分析:先把硬盘数据放进缓冲区,再从缓冲区中取数据。使用FileInputStream将一批数据从硬盘中读到缓冲区中的数组byte[] buf 1024里,然后通过BufferedInputStream的read()方法从缓冲区中的字节数组中一个一个字节的往外取,而在将数据读到缓冲区时能记录下数据的长度(1024),而把数据从缓冲区中往外取时使用指针读一次指针自增一次;取完缓冲区中的所有1024的数据时,再从硬盘上取一批数据到缓冲区,并重复进行如上操作;当最后一次往缓冲区里取最后一批数据时,数据可能没有1024个,怎么代表数据取完了?1024代表第一次往缓冲区取的字节个数,通过指针往缓冲区中取一次代表缓冲区里的数据被取走了,就将1024自减1,取完了即为0,代表数据取完了。例如,如果是最后一次取,加入还有234个字节,取到234个字节时,再取时就会返回-1。
package com.vnb.javabase.base.io.byteio;
import java.io.IOException;
import java.io.InputStream;
public class MyBufferedInputStream {
//定义指针和计数器
private int pos = 0,count = 0;
//缓冲区字节数组
private byte[] buf = new byte[1024];
private InputStream is;
MyBufferedInputStream(InputStream is){
this.is = is;
}
//从缓冲区(字节数组中)一次读一个字节
public int myRead() throws IOException{
//在count为0时,往缓冲区存数据(缓冲区中的数据取光之后才会再次往缓冲区中存数据)
if(count==0){
//通过is输入流对象读取硬盘上的数据,并存储在buf缓冲区中
count = is.read(buf);
//取到最后取不到数据时返回-1
if(count<0){
return -1;
}
//每一次重新到硬盘上往缓冲区中取数据,指针都要归零
pos = 0;
//取到buf缓冲区中的第一个元素
byte b= buf[pos];
count--;
pos++;
return b;
}else if(count>0){
byte b = buf[pos];
count--;
pos++;
return b;
}
return -1;
}
public void myClose() throws IOException{
is.close();
}
}
package com.vnb.javabase.base.io.byteio;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
public class MyBufferedInputStreamTest {
public static void main(String[] args) {
MyBufferedInputStream bis = null;
BufferedOutputStream bos = null;
try {
bis = new MyBufferedInputStream(new FileInputStream("D:/EclipseWork/SpringmvcQuartz/mp3.mp3"));
bos = new BufferedOutputStream(new FileOutputStream("D:/EclipseWork/SpringmvcQuartz/mp3_copy2.mp3"));
int len = 0;
try {
while((len=bis.myRead())!=-1){
bos.write(len);
}
} catch (IOException e) {
throw new RuntimeException("复制mp3文件失败");
}
} catch (FileNotFoundException e) {
throw new RuntimeException("未找到该mp3文件");
}finally{
if(bos!=null){
try {
bos.close();
} catch (IOException e) {
throw new RuntimeException("bos输出流关闭出错");
}
}
if(bis!=null){
try {
bis.myClose();
} catch (IOException e) {
throw new RuntimeException("bis输入流关闭出错");
}
}
}
}
}
结果:发现复制后的数据是0KB。
分析:mp3文件都是二进制数据1010等组成。通过bufis.myRead()发现读到的数据为-1,即一开始就读到-1不会往下进行读取。读一个字节相当于8个二进制位,-1的二进制表示即为1111-1111,当取到一个字节为-1为byte类型的,而接收的是int类型的-1,所以-1的类型被提升了(byte -1—》int -1变成了4个字节存放,即1111 1111 1111 1111),但是还是-1所以len=bis.myRead())!=-1还是会导致循环停止。解决:byte -1—》int -1在转换时,前面8位用0000 0000来补全,即0000 0000 0000 0000….. 1111 1111 即为255不是int类型的-1。与上一个最低4为&上15,取一个数最低8位&上255,即可。
11111111—》提升为int类型后,还是-1,因为8个1前面补全的是1导致,所以只要让8个1前面补0即可,保证原字节数据不变,补0:
11111111 111111111 11111111 11111111
00000000 000000000 00000000 11111111
00000000 000000000 00000000 11111111 与完后的结果
即return b&255即可。
package com.vnb.javabase.base.io.byteio;
import java.io.IOException;
import java.io.InputStream;
public class MyBufferedInputStream {
//定义指针和计数器
private int pos = 0,count = 0;
//缓冲区字节数组
private byte[] buf = new byte[1024];
private InputStream is;
MyBufferedInputStream(InputStream is){
this.is = is;
}
//从缓冲区(字节数组中)一次读一个字节
public int myRead() throws IOException{
//在count为0时,往缓冲区存数据(缓冲区中的数据取光之后才会再次往缓冲区中存数据)
if(count==0){
//通过is输入流对象读取硬盘上的数据,并存储在buf缓冲区中
count = is.read(buf);
//取到最后取不到数据时返回-1
if(count<0){
return -1;
}
//每一次重新到硬盘上往缓冲区中取数据,指针都要归零
pos = 0;
//取到buf缓冲区中的第一个元素
byte b= buf[pos];
count--;
pos++;
return b&255;
}else if(count>0){
byte b = buf[pos];
count--;
pos++;
return b&0xff;//0xff为255的16进制表现形式
}
return -1;
}
public void myClose() throws IOException{
is.close();
}
}
结果:文件复制成功。
读一个字节提升成4个字节了,往文件里写的时候也是4个字节?read()方法是将最低8为写入文件,即写入文件的还是原数据(read方法做了强转)。
以上字节流和字符流的8个主要的类已经学完:
22.读取键盘录入
需求:读取键盘录入。
System.out:对应的是标准的输出设备,控制台
System.in:(标准的输入流)对应的是标准的输入设备:键盘
package com.vnb.javabase.base.io.byteio;
import java.io.IOException;
import java.io.InputStream;
public class KeyboardinDemo {
public static void main(String[] args) {
InputStream is = System.in;
int by1;
int by2;
int by3;
try {
//read方法为阻塞式方法没有录入数据,就会一直等待
by1 = is.read();
System.out.println(by1);
by2 = is.read();
System.out.println(by2);
by3 = is.read();
System.out.println(by3);
} catch (IOException e) {
e.printStackTrace();
}
}
}
结果:
abc
97
98
99
read方法每一次读一个字符,但读三个只输入一个字符时:
a
97
13
10
发现还是输出三个。因为回车符\r和\n也是代表字符集13和10。
System.out.println('\r'+0);
System.out.println('\n'+0);
结果:输出
13
10
需求:通过键盘录入数据,当录入一行数据后,就将该行数据进行打印;如果录入数据时over,就停止录入。
System.in结束标记为Ctrl+c会返回结束标记,而使用-1不会结束而只是会返回对应字符编码。输入一次存一次(存到缓冲里),当回车时就打印。注意:sb.delete(0, sb.length());每次回车后都要清空缓冲区StringBuilder。
package com.vnb.javabase.base.io.byteio;
import java.io.IOException;
import java.io.InputStream;
public class KeyboardinDemo {
//需求:通过键盘录入数据,当录入一行数据后,就将该行数据进行打印;如果录入数据时over,就停止录入。
public static void main(String[] args) {
InputStream is = System.in;
//定义缓冲区,用于存储回车前所有输入的字符
StringBuilder sb = new StringBuilder();
int by;
try {
//System.in结束标记为Ctrl+c会返回结束标记,而使用-1不会结束而只是会返回对应字符编码
while(true){
int ch = is.read();
if(ch == '\r'){
continue;
}
if(ch == '\n'){
String s = sb.toString();
if("over".equals(s)){
break;
}
System.out.println(s.toUpperCase());
//每次回车后清空缓冲区
sb.delete(0, sb.length());
}else{
sb.append((char)ch);
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
结果:
aasdf
AASDF
asdf
ASDF
adsfc
ADSFC
ujuu
UJUU
over
23.读取转换流
上例中出现的问题:通过上例中的键盘录入一行数据并打印其大写,发现其实就是读一行数据的原理,即readLine()方法的原理,那么能否使用readLine()方法来完成键盘录入的一行数据的读取。
问题:readLine方法是字符流BufferedReader的方法,而键盘录入的read方法时字节流InputStream的方法。那么能不能字节流转化成字节流再使用字符流缓冲区的readLine方法呢?InputStreamReader:专门用于操作字节流的字符流对象。
步骤:
- 获取键盘录入对象
- 将字节流对象转成字符流对象,使用转换流InputStreamReader
- 为了提高效率将字符流进行缓冲区技术的高效操作,使用BufferedReader
package com.vnb.javabase.base.io.byteio;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
public class TransStreamDemo {
public static void main(String[] args) {
//获取键盘录入对象
InputStream is = System.in;
//将字节流对象转成字符流对象,使用转换流InputStreamReader
InputStreamReader isr = new InputStreamReader(is);
//为了提高效率,将字符流进行缓冲区技术的高效操作,BufferedReader
BufferedReader br = new BufferedReader(isr);
String line = null;
try {
while((line = br.readLine())!=null){
if("over".equals(line)){
break;
}
System.out.println(line.toUpperCase());
}
} catch (IOException e) {
e.printStackTrace();
}
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
结果:
asdf
ASDF
sdbvf
SDBVF
afbet
AFBET
ret4
RET4
over
24.写入转换流
用于操作字符的字节流对象,OutputStreamWriter:专门用于操作字符流的字节流对象。
- 获取键盘输出
- 为了使输出换行,使用缓冲区的newLine()方法
- 使用缓冲区BufferedWriter进行输出换行
- 字符流必须使用flush才能输出到文件
package com.vnb.javabase.base.io.byteio;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
public class TransStreamDemo {
public static void main(String[] args) {
//获取键盘录入对象
InputStream is = System.in;
//将字节流对象转成字符流对象,使用转换流InputStreamReader
InputStreamReader isr = new InputStreamReader(is);
//为了提高效率,将字符流进行缓冲区技术的高效操作,BufferedReader
BufferedReader br = new BufferedReader(isr);
//获取键盘输出
OutputStream out = System.out;
OutputStreamWriter osw = new OutputStreamWriter(out);
//为了使输出换行,使用缓冲区的newLine()方法
BufferedWriter bufw = new BufferedWriter(osw);
String line = null;
try {
while((line = br.readLine())!=null){
if("over".equals(line)){
break;
}
//System.out.println(line.toUpperCase());
bufw.write(line.toUpperCase());
//使用缓冲区BufferedWriter进行输出换行
bufw.newLine();
//字符流必须使用flush才能输出到文件
bufw.flush();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
结果:
werg
WERG
aSdf
ASDF
over
如上,发现写法麻烦,可以简写:
package com.vnb.javabase.base.io.byteio;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
public class TransStreamDemo {
public static void main(String[] args) {
//获取键盘录入对象
//将字节流对象转成字符流对象,使用转换流InputStreamReader
//为了提高效率,将字符流进行缓冲区技术的高效操作,BufferedReader
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
//获取键盘输出
//为了使输出换行,使用缓冲区的newLine()方法
BufferedWriter bufw = new BufferedWriter(new OutputStreamWriter(System.out));
String line = null;
try {
while((line = br.readLine())!=null){
if("over".equals(line)){
break;
}
//System.out.println(line.toUpperCase());
bufw.write(line.toUpperCase());
//使用缓冲区BufferedWriter进行输出换行
bufw.newLine();
//字符流必须使用flush才能输出到文件
bufw.flush();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
结果:
adsf
ADSF
cgn
CGN
over
25.流操作规律-1
上例中System.in时数据源(键盘录入),System.out是数据的目的(目标文件,控制台)。
需求:想把键盘录入的数据存储到一个文件中。
源:键盘;目的:文件(字节输出流(操作文件:FileOutputStream))
package com.vnb.javabase.base.io.byteio;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
public class TransStreamDemo1 {
public static void main(String[] args) {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
BufferedWriter bufw;
try {
bufw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("out.txt")));
String line = null;
try {
while((line = br.readLine())!=null){
if("over".equals(line)){
break;
}
//System.out.println(line.toUpperCase());
bufw.write(line.toUpperCase());
//使用缓冲区BufferedWriter进行输出换行
bufw.newLine();
//字符流必须使用flush才能输出到文件
bufw.flush();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
} catch (FileNotFoundException e1) {
e1.printStackTrace();
}
}
}
结果:输出到文件成功
需求:想要将一个文件的数据打印在控制台上。
源:文件;目的:控制台
package com.vnb.javabase.base.io.byteio;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
public class TransStreamDemo1 {
public static void main(String[] args) {
BufferedReader br;
try {
br = new BufferedReader(new InputStreamReader(new FileInputStream("out.txt")));
BufferedWriter bufw;
bufw = new BufferedWriter(new OutputStreamWriter(System.out));
String line = null;
try {
while((line = br.readLine())!=null){
if("over".equals(line)){
break;
}
//System.out.println(line.toUpperCase());
bufw.write(line.toUpperCase());
//使用缓冲区BufferedWriter进行输出换行
bufw.newLine();
//字符流必须使用flush才能输出到文件
bufw.flush();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
} catch (FileNotFoundException e2) {
e2.printStackTrace();
}
}
}
结果:控制台输出文件成功
ASDF
ADSG
ZXV
AFHGR5
SFVV
总结:流操作的基本规律:流对象有很多,要使用哪个?
通过以下几点来确定:
- 源和目的?源即输入流(InputStream、Reader),目的即输出流(OutputStream、Writer);
- 操作的数据是否是纯文本?如果是纯文本用字符流,不是则使用字节流。
- 当体系明确后,再明确要使用哪个具体的对象?通过设备来进行区分。源设备包括内存(ByteArray等)、硬盘(File)、键盘(System);目的设备包括内存、硬盘、控制台。
- 是否需要提高效率?是则使用对应的缓冲区(BufferedReader、BufferedWriter;BufferedInputStream、BufferedOutputStream)
需求:将一个文本文件中数据存储到另一个文件中(复制文件)。
源(输入):
- 因为是源所以使用读取流:InputStream Reader;
- 是操作文本文件,所以使用字符流Reader;
- 这样体系就明确了,接下来明确要使用该体系中的哪个对象?明确设备即硬盘上的一个文件。Reader体系中可以操作文件的对象时FileReader。再根据是否需要提高效率?是则加入Reader体系中的缓冲区BufferedReader。
目的(输出):
- 是否是纯文本?是,所以使用Writer
- 设备?硬盘,一个文件。Writer体系中可以操作文件的对象为FileWriter。再根据是否需要提高效率?是则加入Writer体系中的缓冲区BufferedWriter。
- 明确源和目的即可选择要使用的流对象。
再根据是否需要提高效率?是则加入Reader体系中的缓冲区BufferedReader或者Writer体系中的缓冲区BufferedWriter。
练习需求:将一个图片文件中的数据存储到另一个文件中。复制文件。
26.流操作规律-2
需求:将键盘录入的数据保存到一个文件中
这个需求中源和目的都存在。
源:
- InputStream、Reader
- 是不是纯文本?是,所以使用Reader
- 设备?键盘录入。对应的对象时System.in。体系时Reader,但是System.in是字节流,那么要如何选择?为了操作键盘的文件数据方便,可以将字节流转成字符流按照字符串操作是最方便的。所以既然明确了Reader,那么就将System.in转换成字符流。即Reader体系中的转换流InputStreamReader。所以就确定了流对象:InputStreamReader isr = new InputStreamReader(System.in);
- 需要提高效率吗?需要,所以需要使用BufferedReader。所以确定了缓冲区对象:BufferedReader bufr = new BufferedTeader(isr);
目的:
- OutputStream、Writer
- 是否是纯文本?是,使用Writer
- 设备?硬盘,一个文件。使用FileWriter。FileWriter fw = new FileWriter(“c.txt”); FileWriter使用的是默认的编码表GBK(根据自己的设定不同)。
- 需要提高效率吗?需要,使用BufferedWriter。BufferedWriter bufw = new BufferedWriter(fw);
扩展,想要把录入的数据按照指定的编码表(utf-8),将数据存到文件中。按照之前的步骤,使用的系统默认的字符编码。如果要指定编码要使用转换流。构造方法OutputStreamWriter(OutputStream out,String charsetName):创建使用指定字符集的OutputStreamWriter。
目的:
- OutputStream、Writer
- 是否是纯文本?是,使用Writer
- 设备?硬盘,一个文件。使用FileWriter。FileWriter fw = new FileWriter(“c.txt”);
存储时,要加入指定编码表,而指定的编码表只有转换流可以指定,所以要使用的对象是OutputStreamWriter,而该转换流对象要接收一个字节输出流,而且还可以操作的文件的字节输出流FileOutputStream。OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(“d.txt”),”UTF-8”);
- 需要提高效率吗?需要,使用BufferedWriter。BufferedWriter bufw = new BufferedWriter(osw);
转换流什么时候使用?字符和字节之间的桥梁,通常涉及到字符编码转换时,需要使用到转换流。
package com.vnb.javabase.base.io.byteio;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
public class TransStreamDemo1 {
public static void main(String[] args) {
BufferedReader br;
try {
br = new BufferedReader(new InputStreamReader(System.in));
BufferedWriter bufw;
try {
bufw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("d2.txt"),"utf-8"));
String line = null;
try {
while((line = br.readLine())!=null){
if("over".equals(line)){
break;
}
bufw.write(line.toUpperCase());
//使用缓冲区BufferedWriter进行输出换行
bufw.newLine();
//字符流必须使用flush才能输出到文件
bufw.flush();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
} catch (UnsupportedEncodingException e1) {
e1.printStackTrace();
}
} catch (FileNotFoundException e2) {
e2.printStackTrace();
}
}
}
对于转换流:
bufw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("d.txt")));
bufw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("d1.txt"),"GBK"));
bufw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("d2.txt"),"utf-8"));
结果:
由上图可知,根据指定的编码不同,打印的“你好”输出结果的字节数不同。而当前系统默认的字符编码为utf-8。所以要使用FileWriter(默认编码为系统编码),如果要读取GBK的“你好”,就需要进行转换。而文件转换流InputStreamReader的子类FileReader是直接操作文件的字符流对象。为什么FileReader是直接操作文件的字符流对象?因为其能直接操作字符文件,意味着其可以拿到相应的字符去查表,对于GBK中的“你好”的6个字节,先拿到“你”字的三个字节就会去查表。而FileReader虽然能操作字符文件,但是只能使用默认的字符编码去操作。想要按照指定的编码去读取时就不能使用FileReader了,所以InputStreamReader会比较通用,可以通过构造方法指定字符编码。
练习:将一个文本数据打印在控制台上
27.改变标准输入输出设备
package com.vnb.javabase.base.io.byteio;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
public class TransStreamDemo1 {
public static void main(String[] args) {
BufferedReader br;
br = new BufferedReader(new InputStreamReader(System.in));
BufferedWriter bufw;
bufw = new BufferedWriter(new OutputStreamWriter(System.out));
String line = null;
try {
while ((line = br.readLine()) != null) {
if ("over".equals(line)) {
break;
}
bufw.write(line.toUpperCase());
// 使用缓冲区BufferedWriter进行输出换行
bufw.newLine();
// 字符流必须使用flush才能输出到文件
bufw.flush();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
对于上例,将键盘录入打印在控制台上。
需求:通过setIn(),setOut()可以改变输入输出设备。在setIn()里将键盘录入改为文件就可以将源改为文件。setOut()也可以将目的转为文件。
package com.vnb.javabase.base.io.byteio;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintStream;
public class TransStreamDemo1 {
public static void main(String[] args) {
try {
System.setIn(new FileInputStream("d2.txt"));
System.setOut(new PrintStream("d3.txt"));
} catch (FileNotFoundException e1) {
e1.printStackTrace();
}
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
BufferedWriter bufw = new BufferedWriter(new OutputStreamWriter(System.out));
String line = null;
try {
while ((line = br.readLine()) != null) {
if ("over".equals(line)) {
break;
}
bufw.write(line.toUpperCase());
// 使用缓冲区BufferedWriter进行输出换行
bufw.newLine();
// 字符流必须使用flush才能输出到文件
bufw.flush();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
结果:
28.异常的日志信息
需求:将异常信息输出到文件
思路:e.printStackTrace(PrintStream s):调用printStackTrace()方法,并往里面传入System.out。
方法一:使用:e.printStackTrace(new PrintStream("exception.log"));即可将异常输出到文件
package com.vnb.javabase.base.io.byteio;
import java.io.FileNotFoundException;
import java.io.PrintStream;
public class ExceptionInfo {
public static void main(String[] args) {
try{
int[] arr = new int[2];
System.out.println(arr[3]);
}catch(Exception e){
//目的默认是控制台,可以将其改成一个文件
//e.printStackTrace(System.out);
try {
e.printStackTrace(new PrintStream("exception.log"));
} catch (FileNotFoundException e1) {
e1.printStackTrace();
}
}
}
}
结果:异常信息成功输出到文件
方法二:使用System.setOut(new PrintStream("exception1.log"));输出异常信息到文件。
package com.vnb.javabase.base.io.byteio;
import java.io.FileNotFoundException;
import java.io.PrintStream;
public class ExceptionInfo {
public static void main(String[] args) {
try{
int[] arr = new int[2];
System.out.println(arr[3]);
}catch(Exception e){
//目的默认是控制台,可以将其改成一个文件
//e.printStackTrace(System.out);
try {
System.setOut(new PrintStream("exception1.log"));
} catch (FileNotFoundException e1) {
throw new RuntimeException("日志文件创建失败");
}
e.printStackTrace(new PrintStream(System.out));
}
}
}
结果:异常成功输出到文件
方法三:加上日志文件产生的日期和时间
package com.vnb.javabase.base.io.byteio;
import java.io.FileNotFoundException;
import java.io.PrintStream;
import java.text.SimpleDateFormat;
import java.util.Date;
public class ExceptionInfo {
public static void main(String[] args) {
try{
int[] arr = new int[2];
System.out.println(arr[3]);
}catch(Exception e){
//目的默认是控制台,可以将其改成一个文件
//e.printStackTrace(System.out);
try {
Date d = new Date();
//HH 0-23 hh:1-12
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String dateStr = sdf.format(d);
PrintStream ps = new PrintStream("exception1.log");
ps.println(dateStr);
System.setOut(ps);
} catch (FileNotFoundException e1) {
throw new RuntimeException("日志文件创建失败");
}
e.printStackTrace(new PrintStream(System.out));
}
}
}
结果:
29.系统信息
通过System中的setProperties()方法可以返回系统信息。
Properties是和流相关的集合即List(PrintStream s)
需求:将所有的信息打印到文本文件中。
package com.vnb.javabase.base.io.byteio;
import java.io.FileNotFoundException;
import java.io.PrintStream;
import java.util.Properties;
public class SystemInfo {
public static void main(String[] args) {
Properties prop = System.getProperties();
try {
prop.list(new PrintStream("sysinfo.txt"));
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
}
结果:
30.File流
30.1File概述
流操作的就是数据,而数据最明显的体现形式是文件。文件又包含很多属性和信息,所以会将其进行描述并封装成文件类File类。流只能操作数据,而想要操作文件对象必须使用File类。
File类:
- 用来将文件或者文件夹封装成对象
- 方便对文件与文件夹进行操作
- File对象可以作为参数传递给流的构造函数
package com.vnb.javabase.base.io.file;
import java.io.File;
public class FileDemo {
public static void main(String[] args) {
//将a.txt封装成file对象,可以将已有的和未出现的文件或文件夹封装成对象
File f1 = new File("a.txt");
//参数父级目录,文件。好处文件可以以参数进行传递
File f2 = new File("D:/EclipseWork/SpringmvcQuartz","b.txt");
File d = new File("D:/EclipseWork/SpringmvcQuartz");
File f3 = new File(d,"c.txt");
//打印出的是路径
System.out.println("f1:"+f1);
System.out.println("f2:"+f2);
System.out.println("f3:"+f3);
//目录分割父 /和\\都可以,但是跨平台系统下,使用separator():与系统相关的默认名称分隔符,为了方便它被表示成一个字符串
File f4 = new File("D:"+File.separator+"EclipseWork"+File.separator+"SpringmvcQuartz"+File.separator+"d.txt");
System.out.println("f4:"+f4);
}
}
结果:
f1:a.txt
f2:D:\EclipseWork\SpringmvcQuartz\b.txt
f3:D:\EclipseWork\SpringmvcQuartz\c.txt
f4:D:\EclipseWork\SpringmvcQuartz\d.txt
30.2File对象功能—创建和删除
File类的常见方法:
- 创建:
-
Boolean createNewFile():调用底层资源创建一个文件。如果该文件已经存在,则不创建,返回false,和输出流不一样,输出流对象一建立就创建未见,而且文件已经存在会覆盖。
-
static File createTempFile(String prefix,String suffix):创建临时文件,指定前缀后后缀
-
static File createTempFile(String prefix,String suffix,File directory):在指定目录下创建临时文件
-
boolean mkdir():创建文件夹。只能创建一级目录,已经存在或者创建多级目录返回false
-
boolean mkdirs():创建多级文件夹。
- 删除:
- Boolean delete():删除失败返回假
- void deleteOnExit():在退出时(虚拟机终止时)删除(某些文件在正在应用时删不了,所以即使放在finally里也删不了,所以需要在虚拟机终止时再删除)
package com.vnb.javabase.base.io.file;
import java.io.File;
import java.io.IOException;
public class FileDemo1 {
public static void main(String[] args) {
method_1();
}
public static void method_1(){
File f = new File("file.txt");
try {
System.out.println("f:"+f.createNewFile());
//创建文件夹
File f5 = new File("abc");
//获取当前目录
System.out.println("mkdir:"+f5.mkdir());
//创建多级文件夹
File f6 = new File("abc\\sdf\\sdf");
f6.mkdirs();
//删除失败返回假
System.out.println("delete f:"+f.delete());
//在虚拟机终止时删除指定文件
f.deleteOnExit();
} catch (IOException e) {
e.printStackTrace();
}
}
}
结果:
f:true
delete f:true
mkdir:true
30.3File对象功能—判断
- 判断:
- canExecute():判断该文件是否可执行,文件存在则可执行,不存在即不可执行。如果判断这个文件可执行,可以通过Runtime对象打开所有的文件。
- canWriter():可写入
- int compareTo(File pathname):文件名按照字母大小进行比较。也可以自定义比较器。
- boolean exists():判断文件是否存在
判断是文件还是目录:
- isFile():判断是不是一个文件
- isDirectory():判断是不是一个文件夹
- isHidden():判断文件是否是一个隐藏文件
- isAbsolute():是否是绝对文件路径(是则返回true)(即使文件不存在也可以判断)。
在判断文件对象是否是文件或者目录时,必须要判断文件对象封装的内容是否存在(通过exists()判断),否则即使是文件或者目录也会返回false。
package com.vnb.javabase.base.io.file;
import java.io.File;
public class FileDemo2 {
public static void main(String[] args) {
method_1();
}
public static void method_1(){
File f = new File("file.txt");
System.out.println("excute:"+f.canExecute());
System.out.println("exists:"+f.exists());
//判断是否是文件还是目录
//在判断文件对象是否是文件或者目录时,必须要判断文件对象封装的内容是否存在(通过exists()判断),否则即使是文件或者目录也会返回false
System.out.println("isDirectory:"+f.isDirectory());
System.out.println("isFile:"+f.isFile());
}
}
结果:
excute:true
exists:true
isDirectory:false
isFile:true
30.4File对象功能—获取
- 获取信息:
- getName():获取名称
- getPath():获取路径
- getParent:获取父目录,该方法返回的是绝对路径中的文件父目录,如果获取的是相对路径则返回null(文件不存在也会返回指定的绝对路径),如果相对路径中有上一层目录那么该目录就是返回结果
- String getAbsolutePath():获取绝对路径。无论是相对的或是决定的,都是返回绝对路径
- File getAbsoluteFile():将返回的绝对路径封装成了对象
- long lastModified():文件最后修改的时间。只创建没有修改过返回0。
- long length():文件的大小。字节读取流有个方法avalible()可以返回读到的字节数,这个也可以用length()获取。但是avalible()为int型,超过长度了就不往里面装了;输出流一创建会建立文件,如果该文件已创建就会覆盖。
- renameTo():重命名。该方法会将文件重命名到目的路径,并将原文件删除,类似于剪切。
package com.vnb.javabase.base.io.file;
import java.io.File;
import java.io.IOException;
public class FileDemo3 {
public static void main(String[] args) {
method_1();
}
public static void method_1(){
File f = new File("file3.txt");
try {
f.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
System.out.println("getName:"+f.getName());
//封装的什么路径就返回什么路径
System.out.println("getPath:"+f.getPath());
//该方法返回的是绝对路径中的文件父目录
System.out.println("getParent:"+f.getParent());
//无论是相对的或是决定的,都是返回绝对路径
System.out.println("getAbsolutePath:"+f.getAbsolutePath());
System.out.println("lastModified:"+f.lastModified());
System.out.println("length:"+f.length());
//文件重命名
System.out.println("重命名:"+f.renameTo(new File("D:/EclipseWork/SpringmvcQuartz/file_rename.txt")));
}
}
结果:
getName:file3.txt
getPath:file3.txt
getParent:null
getAbsolutePath:D:\EclipseWork\SpringmvcQuartz\file3.txt
lastModified:1536807629830
length:0
重命名:true
30.5File对象功能—文件列表
- static File[] listRoots():列出系统里的有效盘根路径。返回的是File数组。作用:需要获取到盘符,创建文件时需要把文件创建到有效盘符上,再进行创建。或者想要将文件存在装系统的文件夹中。但是现在Windows系统可以将系统装到非C盘下。就可以用此方法获取。
- String[] list():返回数组。返回当前文件夹下所有的文件夹和文件名称。包含隐藏文件和文件夹。如果将文件封装成对象,文件里没有东西,数组为null,则不能继续遍历,会返回空指针异常。所以调用list方法的file对象必须是封装了一个目录,且该目录必须存在。
- String[] list(FilenameFilter filter):过滤某个文件(比如只获取文件夹下所有的java文件)
package com.vnb.javabase.base.io.file;
import java.io.File;
public class FileDemo4 {
public static void main(String[] args) {
method_1();
}
public static void method_1(){
File[] files = File.listRoots();
for(File f : files){
System.out.println(f);
}
File f = new File("D:/EclipseWork/SpringmvcQuartz/");
String[] names = f.list();
for(String name : names){
System.out.println("name:"+name);
}
}
}
结果:
C:\
D:\
E:\
F:\
name:.classpath
name:.project
name:.settings
name:abc
name:BufferdWriterDemo.txt
name:build
name:file.txt
name:file_rename.txt
name:src
name:WebContent
File f = new File("D:/EclipseWork/SpringmvcQuartz/");
结果:
Exception in thread "main" java.lang.NullPointerException
at com.vnb.javabase.base.io.file.FileDemo4.method_1(FileDemo4.java:17)
at com.vnb.javabase.base.io.file.FileDemo4.main(FileDemo4.java:8)
30.6File对象功能—文件列表2
- String[] list(FilenameFilter filter):过滤某个文件(比如只获取文件夹下所有的java文件)。FilenameFilter为接口,只有boolean accept(File dir,String name)一个方法,表示过滤指定目录下的某个文件
- String[] list(FilenameFilter filter)根据FilenameFilter中的accept方法的返回值来判断返回的文件是不是想要的文件。
-
File[] listFiles():返回的当前目录下的文件夹和文件对象。有对象就可以通过getPath、getName()等获取文件的信息。list()方法只返回当前目录下的文件夹和文件,而listFiles()返回的当前目录下的文件夹和文件对象。
需求:返回某个目录下的所有java文件
package com.vnb.javabase.base.io.file;
import java.io.File;
import java.io.FilenameFilter;
public class FileDemo5 {
public static void main(String[] args) {
method_1();
}
public static void method_1(){
File dir = new File("D:/EclipseWork/SpringmvcQuartz/");
String[] arr = dir.list(new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
//dir为需要过滤的目录,name为目录下所有的文件名,只需要对那么进行判断过滤即可
System.out.println(dir+":"+name);
//return true;
return name.endsWith(".txt");
}
});
for(String name : arr){
System.out.println("该文件夹下所有文件及文件名:"+name);
}
}
}
结果:
D:\EclipseWork\SpringmvcQuartz:.classpath
D:\EclipseWork\SpringmvcQuartz:.project
D:\EclipseWork\SpringmvcQuartz:.settings
D:\EclipseWork\SpringmvcQuartz:abc
D:\EclipseWork\SpringmvcQuartz:BufferdWriterDemo.txt
D:\EclipseWork\SpringmvcQuartz:build
D:\EclipseWork\SpringmvcQuartz:file.txt
D:\EclipseWork\SpringmvcQuartz:file_rename.txt
D:\EclipseWork\SpringmvcQuartz:src
D:\EclipseWork\SpringmvcQuartz:WebContent
该文件夹下所有文件及文件名:BufferdWriterDemo.txt
该文件夹下所有文件及文件名:file.txt
该文件夹下所有文件及文件名:file_rename.txt
File[] listFiles():返回的当前目录下的文件夹和文件对象。有对象就可以通过getPath、getName()等获取文件的信息。list()方法只返回当前目录下的文件夹和文件,而listFiles()返回的当前目录下的文件夹和文件对象。
package com.vnb.javabase.base.io.file;
import java.io.File;
public class FileDemo6 {
public static void main(String[] args) {
method_1();
}
public static void method_1(){
File dir = new File("D:/EclipseWork/SpringmvcQuartz/");
File[] files = dir.listFiles();
for(File f : files){
System.out.println(f.getName()+"::"+f.length());
}
}
}
结果:
.classpath::2598
.project::1048
.settings::4096
abc::0
BufferdWriterDemo.txt::27
build::0
file.txt::0
file_rename.txt::0
src::4096
WebContent::0
30.7列出目录下所有内容—递归
需求:获取当前文件夹下的文件夹和文件,及其子目录中的文件及文件夹。
使用递归:定义了一个遍历文件夹的功能,当遍历文件夹中还有文件夹还可以使用该功能。
package com.vnb.javabase.base.io.file;
import java.io.File;
public class FileDemo7 {
public static void main(String[] args) {
File dir = new File("D:/EclipseWork/SpringmvcQuartz/");
showDir(dir);
}
public static void showDir(File dir){
File[] files = dir.listFiles();
System.out.println(dir);
for(int x = 0;x<files.length;x++){
//判断出该文件时目录则回调自己再去遍历该文件夹
if(files[x].isDirectory()){
showDir(files[x]);
}else{
System.out.println(files[x]);
}
}
}
}
结果:
D:\EclipseWork\SpringmvcQuartz\src\com\vnb\javabase\base\collection
D:\EclipseWork\SpringmvcQuartz\src\com\vnb\javabase\base\collection\ArrayListTest.java
D:\EclipseWork\SpringmvcQuartz\src\com\vnb\javabase\base\collection\ArrayListTest2.java
D:\EclipseWork\SpringmvcQuartz\src\com\vnb\javabase\base\collection\ArraysDemo.java
……
D:\EclipseWork\SpringmvcQuartz\src\com\vnb\javabase\base\io
D:\EclipseWork\SpringmvcQuartz\src\com\vnb\javabase\base\io\byteio
D:\EclipseWork\SpringmvcQuartz\src\com\vnb\javabase\base\io\byteio\CopyMp3Stream.java
D:\EclipseWork\SpringmvcQuartz\src\com\vnb\javabase\base\io\byteio\CopyPic.java
因为目录中还有目录只要使用同一个列出目录功能的函数完成即可,在列出过程中还是目录的话,可以再次调用本功能,也就是函数自身调用自身,这个表现形式或者编程手法称为递归。
递归需注意:
限定条件;要注意递归的次数尽量避免内存溢出。
求和的递归:
内存分析:
因为栈内存中的方法太多且都没有执行完,所以很容易内存溢出。
30.8对出目录下所有内容—带层次
需求:让上例中的列出目录下所有内容的带有层级
package com.vnb.javabase.base.io.file;
import java.io.File;
public class FileDemo9 {
public static void main(String[] args) {
File dir = new File("D:/EclipseWork/SpringmvcQuartz/");
showDir(dir,0);
}
//递归一次函数被调用一次,调用一次层级会变一次
public static void showDir(File dir,int level){
System.out.println(getLevel(level)+dir.getName());
File[] files = dir.listFiles();
level++;
System.out.println(dir);
for(int x = 0;x<files.length;x++){
//判断出该文件时目录则回调自己再去遍历该文件夹
if(files[x].isDirectory()){
showDir(files[x],level);
}else{
System.out.println(files[x]);
}
}
}
public static String getLevel(int level){
StringBuilder sb = new StringBuilder();
sb.append("|--");
for (int i = 0; i < level; i++) {
//sb.append(" ");
sb.insert(0,"| ");
}
return sb.toString();
}
}
结果:
D:\EclipseWork\SpringmvcQuartz\build\classes
| | | |--com
D:\EclipseWork\SpringmvcQuartz\build\classes\com
| | | | |--vnb
D:\EclipseWork\SpringmvcQuartz\build\classes\com\vnb
| | | | | |--boc
D:\EclipseWork\SpringmvcQuartz\build\classes\com\vnb\boc
| | | | | |--collision
D:\EclipseWork\SpringmvcQuartz\build\classes\com\vnb\collision
D:\EclipseWork\SpringmvcQuartz\build\classes\com\vnb\collision\Collision.class
D:\EclipseWork\SpringmvcQuartz\build\classes\com\vnb\collision\CollisionResult.class
30.9删除带内容的目录
windows这种删除原理:从文件夹里往外删,文件夹本来是空的则直接删除。既然是从里往外删除,就需要用到递归。
package com.vnb.javabase.base.io.file;
import java.io.File;
public class FileDemo10 {
public static void main(String[] args) {
File dir = new File("D:/EclipseWork/SpringmvcQuartz/abc");
removeDir(dir);
}
//递归一次函数被调用一次,调用一次层级会变一次
public static void removeDir(File dir){
File[] files = dir.listFiles();
for (int i = 0; i < files.length; i++) {
if(!files[i].isHidden() && files[i].isDirectory())
removeDir(files[i]);
}else{
System.out.println(files[i].toString()+":file:"+files[i].delete());
}
}
//文件删除完毕后再删除文件夹
System.out.println("dir:"+dir.delete());
}
}
结果:
D:\EclipseWork\SpringmvcQuartz\abc\file.txt:file:true
D:\EclipseWork\SpringmvcQuartz\abc\file_rename.txt:file:true
D:\EclipseWork\SpringmvcQuartz\abc\sdf\file.txt:file:true
D:\EclipseWork\SpringmvcQuartz\abc\sdf\file_rename.txt:file:true
D:\EclipseWork\SpringmvcQuartz\abc\sdf\sdf\file.txt:file:true
D:\EclipseWork\SpringmvcQuartz\abc\sdf\sdf\file_rename.txt:file:true
dir:true
dir:true
dir:true
隐藏目录无法返回的目录,java删除不了。java删除后不会将删除的文件放到回收站,所以删除了在回收站中找不到文件。
一般要判断主目录是否存在.
30.10创建java文件列表
需求:将一个指定目录下的java文件的绝对路径,存储到一个文本文件中。建立一个java文件列表文件。
思路:
- 对指定的目录进行递归
- 获取递归过程中所有的java文件的路径
- 将这些路径存储到集合中
- 将集合中的数据写入到一个文件中
package com.vnb.javabase.base.io.file;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
public class FileToList {
public static void main(String[] args) {
File dir = new File("D:/EclipseWork/SpringmvcQuartz");
List<File> list = new ArrayList<File>();
fileToList(dir,list);
System.out.println(list.size());
File file = new File(dir,"javaListFile.txt");
writeToFile(list, file.toString());
}
public static void fileToList(File dir,List<File> list){
File[] files = dir.listFiles();
for (File file : files) {
if(file.isDirectory()){
fileToList(file,list);
}else{
if(file.getName().endsWith(".java")){
list.add(file);
}
}
}
}
//将文件持久化到硬盘中(将集合中的java文件,存到目的文件中,字符流:BufferedWriter)
public static void writeToFile(List<File> list,String javaListFile){
BufferedWriter bufw = null;
try {
bufw = new BufferedWriter(new FileWriter(javaListFile));
for(File f : list){
String path = f.getAbsolutePath();
bufw.write(path);
bufw.newLine();
bufw.flush();
}
} catch (IOException e) {
throw new RuntimeException("文件写出失败");
}finally{
if(bufw!=null){
try {
bufw.close();
} catch (IOException e) {
throw new RuntimeException("文件流关闭失败");
}
}
}
}
}
结果:
31.Properties
31.1Properties简述
- Properties是HashTable的子类,具备map集合的特点,而且它里面存储的键值对都是字符串,不需要范型。Properties是集合中和IO技术相结合的集合容器。该对象的特点是可以用于键值对形式的配置文件。
- void load(InputStream instream)可以从流中加载键值对信息
32.2Properties存取
- getProperties()、setProperties():设置和获取元素
- Set<String> stringPropertyNames():把map转成set集合,再取出来
package com.vnb.javabase.base.io.file;
import java.util.Properties;
import java.util.Set;
public class PropertiesDemo {
public static void main(String[] args) {
Properties prop = new Properties();
prop.setProperty("zhangsan", "23");
prop.setProperty("lisi", "24");
System.out.println(prop);
String zs = prop.get("zhangsan").toString();
String ls = prop.get("lisi").toString();
System.out.println("zs:"+zs);
System.out.println("ls:"+ls);
System.out.println("----------------");
Set<String> names = prop.stringPropertyNames();
for(String name : names){
System.out.println(name+":"+prop.getProperty(name));
}
}
}
结果:
{zhangsan=23, lisi=24}
zs:23
ls:24
----------------
zhangsan:23
lisi:24
31.3Properties存取配置文件
需求:如何将流中的数据存储到集合中。想要将info.txt中键值数据存到集合中进行操作。
- 用一个流和info.txt文件关联
- 读取一行数据,将该行数据用“=”进行切割
- 等号左边作为键,右边作为值,存入到Properties集合中即可
package com.vnb.javabase.base.io.file;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.util.Properties;
public class PropertiesDemo {
public static void main(String[] args) {
method_1();
System.out.println("----------");
method_2();
}
public static void method_1(){
BufferedReader bufr;
try {
bufr = new BufferedReader(new FileReader("info.txt"));
String line = null;
Properties prop = new Properties();
try {
while((line = bufr.readLine())!=null){
String[] arr = line.split("=");
prop.setProperty(arr[0], arr[1]);
}
System.out.println(prop);
} catch (IOException e) {
e.printStackTrace();
}
} catch (FileNotFoundException e1) {
e1.printStackTrace();
}
}
//方法一需要一个一个的设置,比较麻烦,通过load()方法Properties集合可以直接加载字节流或者字符流
public static void method_2(){
Properties prop = new Properties();
FileInputStream fis = null;
FileOutputStream fos = null;
try {
fis = new FileInputStream("info.txt");
try {
prop.load(fis);
System.out.println(prop);
//发现文档里某个值错误,需要对某个值进行修改
prop.setProperty("lisi", "24");
System.out.println("------");
System.out.println(prop);
//但是发现虽然能加载并修改,但是并没有将指定文件info.txt中的list对应的值进行更改。
fos = new FileOutputStream("info.txt");
prop.store(fos, "set the value of lisi");
System.out.println("------");
System.out.println(prop);
} catch (IOException e) {
e.printStackTrace();
}
} catch (FileNotFoundException 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();
}
}
}
}
}
结果:
{zhangsan=30, lisi=39}
----------
{zhangsan=30, lisi=39}
------
{zhangsan=30, lisi=24}
------
{zhangsan=30, lisi=24}
总结:Properties对象可以用于键值对形式的配置文件,在加载数据时,需要数据有固定格式,即键=值。
31.4Properties练习
需求:用于记录应用程序运行次数,如果使用次数已到,那么给出注册提示。
思路:可使用计数器,但是该计数器定义在程序中,随着程序的运行而在内存中存在,并进行自增。但是随着该应用程序的退出,该计数器也在内存中消失了,下次再启动该程序又重新开始从0计数。这样不是我们想要的。我们要的是即使程序结束,该计数器的值也存在,下次程序启动会先加载该计数器的值并加1后重新存储起来。所以要建立一个配置文件,用于记录该软件的使用次数。该配置文件使用键值对的形式,便于阅读和操作数据。键值对数据时map集合,数据是以文件形式存储,使用IO技术。map+IO即Properties集合,配置文件可以实现应用程序数据的共享。
package com.vnb.javabase.base.io.file;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Properties;
public class PropertiesDemo {
public static void main(String[] args) {
method_2();
}
public static void method_2(){
Properties prop = new Properties();
File file = new File("count.properties");
if(!file.exists()){
try {
file.createNewFile();
} catch (IOException e) {
throw new RuntimeException("创建文件失败");
}
}
FileInputStream fis = null;
FileOutputStream fos = null;
try {
fis = new FileInputStream(file);
try {
prop.load(fis);
int count = 0;
String value = prop.getProperty("time");
if(value!=null){
count = Integer.parseInt(value);
//count>=5次时,次数使用完毕
if(count>=5){
System.out.println("5次使用完毕");
return;
}
}
count++;
prop.setProperty("time", count+"");
fos = new FileOutputStream(file);
prop.store(fos, "set the value of time");
System.out.println(prop);
} catch (IOException e) {
e.printStackTrace();
}
} catch (FileNotFoundException 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();
}
}
}
}
}
结果:
XML的格式:
java可以使用XML文档的对象Document接口,但是获取麻烦,因此有了Dom4j工具(dom for java)
32.PrintWriter
- 打印流:PrintWriter与PrintStream:可以直接操作输入流和文件。
- 序列流:SequenceInputStream:对多个流进行合并
- 操作对象:ObjectInputStream与ObjectOutputStream:被操作的对象需要实现Serializable(标记接口)。
PrintStream:OutputStream的子类,操作的是字节。为其他输出流添加了功能。能方便的打印各种数据值表现形式。各种写,打印方法(可对基本数据类型进行直接操作,保证数据原样性)。
PrintStream和PrintWriter区别:
- PrintStream:字节打印流,构造方法能直接操作文件,PrintStream(File file);构造函数可以接收的参数类型:file对象File,字符串路径,字节输出流。
- PrintWriter:字符打印流。非常常用。
package com.vnb.javabase.base.io.file;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
public class PrintStreamDemo {
public static void main(String[] args) {
BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));
//PrintWriter out = new PrintWriter(System.out,true);
//将数据存储到文件中,写File对象或者字符串都可以;将文件封装到流里才能刷新文件
PrintWriter out;
try {
out = new PrintWriter(new BufferedWriter(new FileWriter("a.txt")),true);
String line = null;
try {
while((line = bufr.readLine())!=null){
if("over".equals(line)){
break;
}
//BufferedOutputStream才有newLine()方法,PrintWriter中自带println用于换行
out.println(line.toUpperCase());
//PrintWriter(OutputStream out,boolean autoFlush):当autoFlush为true时,则println、printf、format方法将刷新输出缓冲区,即autoFlush后不再需要使用flush方法
//out.flush();
}
} catch (IOException e) {
e.printStackTrace();
}
} catch (IOException e1) {
e1.printStackTrace();
}
}
}
测试数据:
asdf
inta
asdf
over//用于结束标记
结果:
33.SequenceInputStream(序列流)合并流
SequenceInputStream表示其他输入流的逻辑串联,从输入流的有序集合开始,并从第一个输入流开始读取,直到到达文件末尾,接着从第二个输入流读取,依次类推,直到到达包含的最后一个输入流的文件末尾为止。即将对各流对象拼成一个流对象。
SequenceInputStream(InputStream s1,InputStream s2)
示例:如下图1,2,3三个txt文件里有不同的内容,现需要将三个文件的内容合并在一起。使用SequenceInputStream(Enumeration<? extends InputStream> e)构造函数。
package com.vnb.javabase.base.io.file;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.SequenceInputStream;
import java.util.Enumeration;
import java.util.Vector;
public class SequenceInputStreamDemo {
public static void main(String[] args) {
Vector<FileInputStream> v = new Vector<FileInputStream>();
try {
v.add(new FileInputStream("D:/EclipseWork/SpringmvcQuartz/1.txt"));
v.add(new FileInputStream("D:/EclipseWork/SpringmvcQuartz/2.txt"));
v.add(new FileInputStream("D:/EclipseWork/SpringmvcQuartz/3.txt"));
} catch (FileNotFoundException e) {
throw new RuntimeException("找不到文件");
}
Enumeration<FileInputStream> en = v.elements();
SequenceInputStream sis = new SequenceInputStream(en);
FileOutputStream fos = null;
try {
fos = new FileOutputStream("D:/EclipseWork/SpringmvcQuartz/4.txt");
int len = 0;
byte[] bytes = new byte[1024];
try {
while((len=sis.read(bytes))!=-1){
fos.write(bytes, 0, len);
}
} catch (IOException e) {
e.printStackTrace();
}
} catch (FileNotFoundException e1) {
throw new RuntimeException("文件输出错误");
}finally{
if(fos!=null){
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(sis!=null){
try {
sis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
结果:
34.切割文件
需求:将图片文件切割成分段文件到另一个文件夹。
package com.vnb.javabase.base.io.file;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
public class SplitFile {
public static void main(String[] args) {
FileInputStream fis = null;
FileOutputStream fos = null;
try {
fis = new FileInputStream("D:/EclipseWork/SpringmvcQuartz/1.jpg");
int len = 0;
int count = 1;
byte[] bytes = new byte[1024*258];
try {
while((len=fis.read(bytes))!=-1){
//图片碎片不是jpg不能打开,所以随便定义一个后缀
fos = new FileOutputStream("D:/EclipseWork/SpringmvcQuartz/"+(count++)+".part");
fos.write(bytes, 0, len);
}
} catch (IOException e) {
e.printStackTrace();
}
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
}
结果:
问题:切割后的文件不能查看,所以要将其进行合并才可以查看。可以让SequenceInputStream对象和切割相结合。因为创建Vector效率比较低。所以使用ArrayList存文件数据,再通过Iterator取。
package com.vnb.javabase.base.io.file;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.SequenceInputStream;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Iterator;
public class SplitFile {
public static void main(String[] args) {
spitFile();
merge();
}
public static void spitFile(){
FileInputStream fis = null;
FileOutputStream fos = null;
try {
fis = new FileInputStream("D:/EclipseWork/SpringmvcQuartz/1.jpg");
int len = 0;
int count = 1;
byte[] bytes = new byte[1024*258];
try {
while((len=fis.read(bytes))!=-1){
//图片碎片不是jpg不能打开,所以随便定义一个后缀
fos = new FileOutputStream("D:/EclipseWork/SpringmvcQuartz/"+(count++)+".part");
fos.write(bytes, 0, len);
}
} catch (IOException e) {
e.printStackTrace();
}
} catch (FileNotFoundException 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();
}
}
}
}
public static void merge(){
//因为创建Vector效率比较低。所以使用ArrayList存文件数据,再通过Iterator取
ArrayList<FileInputStream> al = new ArrayList<FileInputStream>();
for (int i = 1; i <= 3; i++) {
try {
al.add(new FileInputStream("D:/EclipseWork/SpringmvcQuartz/"+i+".part"));
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
//因为Enumeration使用的是匿名内部类,所以要对访问的局部变量进行final修饰。
final Iterator<FileInputStream> it = al.iterator();
Enumeration<FileInputStream> en = new Enumeration<FileInputStream>() {
@Override
public boolean hasMoreElements() {
return it.hasNext();
}
@Override
public FileInputStream nextElement() {
return it.next();
}
};
//通过合并流将多个文件碎片合并在一起,并输出到文件
SequenceInputStream sis = new SequenceInputStream(en);
FileOutputStream fos = null;
try {
fos = new FileOutputStream("D:/EclipseWork/SpringmvcQuartz/2.jpg");
int len = 0;
byte[] bytes = new byte[1024];
try {
while((len=sis.read(bytes))!=-1){
fos.write(bytes, 0, len);
}
} catch (IOException e) {
e.printStackTrace();
}
} catch (FileNotFoundException e1) {
throw new RuntimeException("文件输出错误");
}finally{
if(fos!=null){
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(sis!=null){
try {
sis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
结果:
注意:切电影等空间比较大的文件时,不能直接写1024*1024*1024,需要判断当有100兆后再存到另一个文件中,否则很容易内存溢出。
35.对象序列化ObjectInputStream与ObjectOutputStream
直接操作对象的流。对象本身存在堆内存中,但是当程序停止后,堆内存中的数据也就没有了。所以可以通过流对象将对象存到硬盘上。堆内存中的对象都会封装一些数据,这些数据也随着对象存在硬盘上了,当下次需要时,即使程序结束了,直接读取硬盘上的对象即可获得相应的数据。这个过程叫做对象的持久化存储或对象的可串行性或对象的序列化。
- ObjectOutputStream将java对象的基本数据类型和图形写入OutputStream。可以使用ObjectOutputStream读取(重构)对象。
- ObjectOutputStream(OutputStream out):构造方法,让流和对象相结合。
- write():可以直接操作基本数据类型,如writeByte()、writeInt(int val)等
write(Int val)和writeInt(int val)区别:
- write(Int val):写入一个字节(int型的最低八位)
- writeInt(int val):写入4个字节(4个八位即32位)int型数据
- writeObject(Object obj):将指定对象写入ObjectOutputStream
package com.vnb.javabase.base.io.other;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
public class ObjectStreamDemo {
public static void main(String[] args) {
writeObj();
}
public static void writeObj(){
//把对象数据写到文件里,该数据不是纯文本,对象里的都是字节码数据。直接操作对象的流对象即ObjectOutputStream
ObjectOutputStream oos = null;
try {
oos = new ObjectOutputStream(new FileOutputStream("obj.txt"));
oos.writeObject(new Person("lisi",20));
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally{
if(oos!=null){
try {
oos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
package com.vnb.javabase.base.io.other;
public class Person {
String name;
int age;
Person(String name,int age){
this.name = name;
this.age = age;
}
@Override
public String toString() {
return name+""+age;
}
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;
}
}
结果:捕捉到异常
java.io.NotSerializableException: com.vnb.javabase.base.io.other.Person
at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)
at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
at com.vnb.javabase.base.io.other.ObjectStreamDemo.writeObj(ObjectStreamDemo.java:18)
at com.vnb.javabase.base.io.other.ObjectStreamDemo.main(ObjectStreamDemo.java:11)
分析:NotSerializableException表示某个要序列化的对象不能实现java.io.Serializable接口。即要对对象进行序列化,必须实现Serializable接口,使该对象具备序列化或反序列化的功能。Serializable接口没有要实现的方法,这种接口称为标记接口。
分析:实现Serializable接口的对象在编译时会产生uid,然后产生一个Person对象,接着Person对象被序列化到一个文件中;当类的源文件被改变后,增加id,country等后,再往外读取序列化后的文件时,读取不到id,country了;uid其实是根据类中不同的成员等计算出来的,所以不同的类uid会不同。
package com.vnb.javabase.base.io.other;
import java.io.Serializable;
public class Person implements Serializable{
String name;
int age;
Person(String name,int age){
this.name = name;
this.age = age;
}
@Override
public String toString() {
return name+""+age;
}
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;
}
}
结果:对象被序列化后的文件,记事本对其进行GBK读取后,是看不懂的
读取对象序列化后的文件:
package com.vnb.javabase.base.io.other;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class ObjectStreamDemo {
public static void main(String[] args) {
writeObj();
readObj();
}
public static void writeObj(){
//把对象数据写到文件里,该数据不是纯文本,对象里的都是字节码数据。直接操作对象的流对象即ObjectOutputStream
ObjectOutputStream oos = null;
try {
oos = new ObjectOutputStream(new FileOutputStream("obj.txt"));
oos.writeObject(new Person("lisi",20));
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally{
if(oos!=null){
try {
oos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
public static void readObj(){
ObjectInputStream ois = null;
try {
ois = new ObjectInputStream(new FileInputStream("obj.txt"));
try {
Person p = (Person)ois.readObject();
System.out.println(p);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally{
if(ois!=null){
try {
ois.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
package com.vnb.javabase.base.io.other;
import java.io.Serializable;
public class Person implements Serializable{
String name;
int age;
Person(String name,int age){
this.name = name;
this.age = age;
}
@Override
public String toString() {
return name+":"+age;
}
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;
}
}
结果:成功读取到对象中的数据
lisi:20
当Person类中给成员加私有后,会产生新的Person文件,当没有写(没有执行writeObj()),而直接读取时(直接执行readObj),
private String name;
//writeObj();
readObj();
结果:前一个Person文件产生的UID和现在改变后的UID序列号不对应。
java.io.InvalidClassException: com.vnb.javabase.base.io.other.Person; local class incompatible: stream classdesc serialVersionUID = -9220828806796382660, local class serialVersionUID = 5404534014922605156
at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:687)
……
解决:可以不让java自动生成UID,而是自己生成固定的UID即可。private static final long serialVersionUID = 1L;
package com.vnb.javabase.base.io.other;
import java.io.Serializable;
public class Person implements Serializable{
private static final long serialVersionUID = 1L;
private String name;
int age;
Person(String name,int age){
this.name = name;
this.age = age;
}
@Override
public String toString() {
return name+":"+age;
}
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;
}
}
结果:无论Person类如何更改,新的类都能操作曾经被序列化的对象。
lisi:20
package com.vnb.javabase.base.io.other;
import java.io.Serializable;
public class Person implements Serializable{
private static final long serialVersionUID = 1L;
private String name;
int age;
static String cuntry = "cn";
Person(String name,int age,String cuntry){
this.name = name;
this.age = age;
}
@Override
public String toString() {
return name+":"+age+":"+cuntry;
}
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 static String getCuntry() {
return cuntry;
}
public static void setCuntry(String cuntry) {
Person.cuntry = cuntry;
}
}
package com.vnb.javabase.base.io.other;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class ObjectStreamDemo {
public static void main(String[] args) {
writeObj();
readObj();
}
public static void writeObj(){
//把对象数据写到文件里,该数据不是纯文本,对象里的都是字节码数据。直接操作对象的流对象即ObjectOutputStream
ObjectOutputStream oos = null;
try {
oos = new ObjectOutputStream(new FileOutputStream("obj.txt"));
oos.writeObject(new Person("lisi",20,"kr"));
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally{
if(oos!=null){
try {
oos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
public static void readObj(){
ObjectInputStream ois = null;
try {
ois = new ObjectInputStream(new FileInputStream("obj.txt"));
try {
Person p = (Person)ois.readObject();
System.out.println(p);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally{
if(ois!=null){
try {
ois.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
结果:发现结果没有任何改变。因此对象中的静态是不能被序列化的。静态在方法区里,但是对象在堆里,序列化可以序列化堆里的数据,但是不能序列化方法区里的数据。
lisi:20:cn
即使在堆内存中,如果对非静态成员也不想进行序列化,则使用关键字transient。
transient int age;
oos.writeObject(new Person("lisi",45,"kr"));
结果:发现使用transient后没有将其进行序列化。
lisi:0:cn
注:一般对序列化的对象不会将其存在txt文件中(即使存储了打开也看不懂,乱码),一般会存成后缀名为.object的文件。
如果同时oos.writeObject(new Person("lisi",45,"kr"));多条语句时,则执行readObject一次就会存一个对象。
36.管道流PipedInputStream和PipedOutputStream
管道流:PipedInputStream和PipedOutputStream:输入输出可以直接进行连接,通过结合线程使用。
管道输入流应该连接到管道输出流;管道输入流提供要写入管道输出流的所有数据字节。通常,数据由某个线程从PipedInputStream对象读取,并由其他线程将其写入到相应的PipedOutputStream。不建议对两个对象尝试使用单个线程,因为这样可能死锁线程。管道输入流包含一个缓冲区,可在缓冲区限定的范围内将读操作和写操作分离开。如果向连接管道输出流提供数据字节的线程不再存在,则认为该管道已损坏。
read(阻塞式方法)一开启,没有数据就会等待。即read方法在被另一个线程操作,没有输入数据read线程就等待,当键盘输入数据时就唤醒了读取线程。
构造方法PipedInputStream(PipedOutputStream src):创建PipedInputStream管道输入流,使其连接到管道输出流src。空构造时,需使用connect(PipedOutputStream src)方法进行连接。
package com.vnb.javabase.base.io.other;
import java.io.IOException;
import java.io.PipedInputStream;
public class PipedStreamReadThread implements Runnable {
private PipedInputStream in;
PipedStreamReadThread(PipedInputStream in){
this.in = in;
}
@Override
public void run() {
byte[] buf = new byte[1024];
int len = 0;
try {
System.out.println("读取前,没有数据阻塞开始");
while((len=in.read(buf))!=-1){
//管道流也是InputStream的子类,所以使用InputStream的方法也可以
System.out.println("读取到数据,阻塞结束");
String s = new String(buf,0,len);
System.out.println(s);
}
} catch (IOException e) {
throw new RuntimeException("管道读取流失败");
}finally{
if(in!=null){
try {
in.close();
} catch (IOException e) {
throw new RuntimeException("管道读取流关闭失败");
}
}
}
}
}
package com.vnb.javabase.base.io.other;
import java.io.IOException;
import java.io.PipedOutputStream;
public class PipedStreamWriteThread implements Runnable {
private PipedOutputStream out;
PipedStreamWriteThread(PipedOutputStream out){
this.out = out;
}
@Override
public void run() {
try {
try {
System.out.println("线程等待6秒");
Thread.sleep(6000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("开始写入数据");
out.write("piped is coming".getBytes());
} catch (IOException e) {
throw new RuntimeException("管道输出流失败");
}finally{
if(out!=null){
try {
out.close();
} catch (IOException e) {
throw new RuntimeException("管道输出流关闭失败");
}
}
}
}
}
package com.vnb.javabase.base.io.other;
import java.io.IOException;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
public class PipedStreamDemo {
public static void main(String[] args) {
PipedInputStream in = new PipedInputStream();
PipedOutputStream out = new PipedOutputStream();
try {
in.connect(out);
PipedStreamReadThread pst = new PipedStreamReadThread(in);
PipedStreamWriteThread pwt = new PipedStreamWriteThread(out);
new Thread(pst).start();
new Thread(pwt).start();
} catch (IOException e) {
e.printStackTrace();
}
}
}
结果:
读取前,没有数据阻塞开始
线程等待6秒
开始写入数据
读取到数据,阻塞结束
piped is coming
分析:对于PipedStreamReadThread和PipedStreamWriteThread两个线程谁先抢到资源都不重要,如果PipedStreamReadThread先抢占到CPU执行权,当执行到len=in.read(buf)时因为没有资源所以会进入等待,从而开始执行PipedStreamWriteThread输入资源写到PipedStreamReadThread管道里,有了资源后后唤醒PipedStreamReadThread继续执行并获取资源。
37.随机访问文件的读写类RandomAccessFile
RandomAccessFile:此类的实例支持对随机访问文件的读取和写入,自身具备读写的方法;随机访问文件的行为类似存储在文件系统中的一个大型byte数组。存在执行该隐含数组的光标或索引,成为文件指针;输入操作从文件指针开始读取字节,并随着对字节的读取而前移此文件指针。如果随机访问文件以读取/写入模式创建,则输入操作也可用;输出操作从文件指针开始写入字节,并随着对字节的写入而前移此文件指针。写入隐含数组的当前末尾之后得输出操作导致该数组扩展。该文件指针可以通过getFilePointer方法读取,并通过seek方法设置。通过skipBytes(int x),seek(int x)来达到随机访问。
该类不算是IO体系中的子类,而是直接继承自Object,但是它是IO包中的成员,因为它具备读和写功能,内部封装了一个数组,而且通过指针对数组中的元素进行操作,可以通过getFilePointer()方法获取指针位置,同时可以通过seek()方法改变指针的位置。
读写的方法:read() readInt() readLine()读一行;write() writeInt()等
读写的原理:内部封装了字节输入流和输出流,所以既能读有能写。
通过构造函数RandomAccessFile(String name,String mode)可以看出,该类只能操作硬盘上的文件。而且操作文件还有模式mode,r只读,rw读写,rws可读写且要求文件的内容和元数据的每个更新都同步写入到底层存储设备,rwd可读写,且要求对文件内容的每个更新都同步写入到底层存储设备。r和rw比较常用,不定义mode会报无效参数异常。
方法:
- read(byte[] b)(InputStream的方法),readInt()等读基本数据类型;
- write(byte[] b),writeInt()等写出基本数据类型
- seek(long pos):指定指针的位置
package com.vnb.javabase.base.io.other;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
public class RandomAccessFileDemo {
public static void main(String[] args) {
RandomAccessFile raf = null;
try {
raf = new RandomAccessFile("ran.txt", "rw");
try {
raf.write("李四".getBytes());
raf.write(345);
} catch (IOException e) {
e.printStackTrace();
}
} catch (FileNotFoundException e) {
e.printStackTrace();
}finally{
if(raf!=null){
try {
raf.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
结果:发现数据时错乱的。345的字符码表
write()方法的特点:write方法只写出int类型的最低8位。
即,
System.out.println(Integer.toBinaryString(258));
结果:写出100000010,但是这个表示的是2。
解决:要代表数据的原样性。writeInt(int v)是按照4个字节往外写。
raf.writeInt(345);
结果:发现写出了4个字节
raf.writeInt(97);
读取:
package com.vnb.javabase.base.io.other;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
public class RandomAccessFileDemo {
public static void readFile(){
RandomAccessFile raf = null;
try {
raf = new RandomAccessFile("ran.txt", "r");
byte[] buf = new byte[4];
try {
System.out.println("获取李四信息");
raf.read(buf);
String name = new String(buf);
System.out.println("name:"+name);
int age = raf.readInt();
System.out.println("age:"+age);
System.out.println("获取王五信息");
raf.read(buf);
String name1 = new String(buf);
System.out.println("name1:"+name1);
int age1 = raf.readInt();
System.out.println("age1:"+age1);
} catch (IOException e) {
e.printStackTrace();
}
} catch (FileNotFoundException e) {
e.printStackTrace();
}finally{
if(raf!=null){
try {
raf.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
public static void writeFile(){
RandomAccessFile raf = null;
try {
raf = new RandomAccessFile("ran.txt", "rw");
try {
raf.write("李四".getBytes());
raf.writeInt(24);
raf.write("王五".getBytes());
raf.writeInt(34);
} catch (IOException e) {
e.printStackTrace();
}
} catch (FileNotFoundException e) {
e.printStackTrace();
}finally{
if(raf!=null){
try {
raf.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) {
writeFile();
readFile();
}
}
结果:(乱码可能是由于记事本对字符查了编码表)
当指向获取某个索引上的值时(调整对象中的指针):
package com.vnb.javabase.base.io.other;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
public class RandomAccessFileDemo {
public static void readFile(){
RandomAccessFile raf = null;
try {
raf = new RandomAccessFile("ran.txt", "r");
try {
byte[] buf = new byte[8];
//8位一个字节,去第几个字,就*几
raf.seek(8*1);
raf.read(buf);
String name = new String(buf);
int age = raf.readInt();
System.out.println(name+":"+age);
} catch (IOException e) {
e.printStackTrace();
}
} catch (FileNotFoundException e) {
e.printStackTrace();
}finally{
if(raf!=null){
try {
raf.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
public static void writeFile(){
RandomAccessFile raf = null;
try {
raf = new RandomAccessFile("ran.txt", "rw");
try {
raf.write("李四".getBytes());
raf.writeInt(97);
raf.write("王五".getBytes());
raf.writeInt(99);
} catch (IOException e) {
e.printStackTrace();
}
} catch (FileNotFoundException e) {
e.printStackTrace();
}finally{
if(raf!=null){
try {
raf.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) {
writeFile();
readFile();
}
}
结果:
王五:99
raf.skipBytes(8);可以往下跳不能往回跳,但是seek可以随便指定指针
结果:王五:99
流在操作数据时都是按顺序读写。
也可以写入指定位置:
public static void writeFile_2(){
RandomAccessFile raf = null;
try {
raf = new RandomAccessFile("ran.txt", "rw");
try {
//写入数据时指定指针
raf.seek(8*3);
raf.write("周期".getBytes());
raf.writeInt(103);
} catch (IOException e) {
e.printStackTrace();
}
} catch (FileNotFoundException e) {
e.printStackTrace();
}finally{
if(raf!=null){
try {
raf.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
如果设置成raf.seek(8*0)会修改掉原有的李四的内容:
如果模式为只读r。不会创建文件,会去读取一个已存在文件,如果该文件不存在,则会出现异常。如果模式rw,而且该对象的构造函数要操作的文件不存在,会自动创建,如果存在则不会覆盖原文件中已有内容。
可以随机写入数据,可以修改(覆盖数据),可以实现数据的分段写入(使用多线程,一个线程负责一段)(一般的流是从头往后写,会导致数据虽然都存完,但是读取出来的数据错乱)由此可以实现多线程的文件下载。
38.操作基本数据类型的流对象DataStream
操作基本数据类型:DataInputStream与DataOutputStream
方法write(int b) writeBoolean(boolean v) writeByte(int v) writeInt()等
构造方法:DataInputStream(InputStream in); DataOutputStream(OutputStream out)
package com.vnb.javabase.base.io.other;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
public class DataStreamDemo {
public static void main(String[] args) {
writeData();
readData();
}
public static void writeData(){
DataOutputStream dos = null;
try {
dos = new DataOutputStream(new FileOutputStream("data.txt"));
try {
dos.writeInt(232);
dos.writeBoolean(false);
dos.writeDouble(2334.456);
} catch (IOException e) {
e.printStackTrace();
}
} catch (FileNotFoundException e) {
e.printStackTrace();
}finally{
if(dos!=null){
try {
dos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
public static void readData(){
DataInputStream dis = null;
try {
dis = new DataInputStream(new FileInputStream("data.txt"));
try {
//读取时要按照存的顺序读
int num = dis.readInt();
boolean b = dis.readBoolean();
double d = dis.readDouble();
System.out.println(num+":"+b+":"+d);
} catch (IOException e) {
e.printStackTrace();
}
} catch (FileNotFoundException e) {
e.printStackTrace();
}finally{
if(dis!=null){
try {
dis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
输出:因为记事本文件都是以字符的形式存储会去查询编码表所以会乱码
控制台读取:
232:false:2334.456
writeUTF(String str):以与机器无关方式使用UTF-8修改版编码将一个字符串写入基础输出流。
public static void writeUTFData(){
DataOutputStream dos = null;
try {
dos = new DataOutputStream(new FileOutputStream("utfdata.txt"));
try {
dos.writeUTF("你好");
} catch (IOException e) {
e.printStackTrace();
}
} catch (FileNotFoundException e) {
e.printStackTrace();
}finally{
if(dos!=null){
try {
dos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
结果:
按照传统的输出流:
try {
OutputStreamWriter osw = null;
try {
osw = new OutputStreamWriter(new FileOutputStream("oswdata.txt"),"UTF-8");
try {
osw.write("你好");
} catch (IOException e) {
e.printStackTrace();
}
} catch (FileNotFoundException e) {
e.printStackTrace();
}finally{
if(osw!=null){
try {
osw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
结果:发现输出的是四个字节
gbk编码下:
读取writeUTF()输出的数据:
public static void readUTFData(){
DataInputStream dis = null;
try {
dis = new DataInputStream(new FileInputStream("utfdata.txt"));
try {
String str = dis.readUTF();
System.out.println(str);
} catch (IOException e) {
e.printStackTrace();
}
} catch (FileNotFoundException e) {
e.printStackTrace();
}finally{
if(dis!=null){
try {
dis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
结果:
你好
如果使用这种方式读取普通输出流写出的数据:
java.io.EOFException
at java.io.DataInputStream.readFully(DataInputStream.java:197)
at java.io.DataInputStream.readUTF(DataInputStream.java:609)
at java.io.DataInputStream.readUTF(DataInputStream.java:564)
at com.vnb.javabase.base.io.other.DataStreamDemo.readUTFData(DataStreamDemo.java:122)
at com.vnb.javabase.base.io.other.DataStreamDemo.main(DataStreamDemo.java:18)
会抛出异常。表示如果此输入流在读取所有字节之前到达末尾。即readUTF()是读8个字节的,但是读到4个字节就结尾了,所以抛出异常。
39.ByteArrayStream操作字节数组中数据的流对象
操作字节数组:ByteArrayInputStream与ByteArrayOutputStream
字节流中本身有可以操作数组的方法,那这两个类有什么意义?
ByteArrayInputStream包含一个内部缓冲区,该缓冲区包含从流中读取的字节。内部计数器跟踪read方法要提供的下一个字节。即ByteArrayInputStream负责源,直接将元数据对应的字节存到缓冲区,即建立对象时就要有数据存在。构造时,需要接受数据源,而且数据源是一个字节数组。
方法:read(),read(byte[] b,int off,int len)available()
注意:ByteArrayInputStream没有调用底层资源,而是直接操作数组数据。所以关闭ByteArrayInputStream无效。此类中的方法在关闭中仍可调用,且不会抛出任何IOException异常。
ByteArrayOutputStream:实现了一个输出流,其中的数据被写入一个byte数据。缓冲区会随着数据的不断写入而自动增长(不用自己手动建立1024的字节数据),可使用toByteArray()和toString()获取数据。关闭ByteArrayOutputStream无效,此类中的方法在关闭中仍可调用,且不会抛出任何IOException异常。在构造时,不定义数据目的,因为该对象中已经被捕封装了可变长度的字节数据。这就是数据目的地。构造时的目的已经被封装在了类的内部即可变长度的字节数组。
因为这两个流对象都操作数组,并没有使用系统资源,所以不用进行close关闭。
package com.vnb.javabase.base.io.other;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
public class ByteArrayStreamDemo {
public static void main(String[] args) {
//数据源
ByteArrayInputStream bais = new ByteArrayInputStream("ABCDEFG".getBytes());
//数据目的
ByteArrayOutputStream baos = new ByteArrayOutputStream();
System.out.println(baos.size());
//写出到文件
int by = 0;
while((by=bais.read())!=-1){
baos.write(by);
}
System.out.println(baos.size());
System.out.println(baos.toString());
}
}
输出结果:
0
7
ABCDEFG
在流操作规律中:
- 源设备:键盘(System.in)、硬盘(File)、内存(ArrayStream)
- 目的设备:控制台(System.out)、硬盘(File)、内存(ArrayStream)
为什么要使用ByteArrayInputStream而不自己手动写数组?
ByteArrayInputStream已经封装好了拿来即用;提高封装性、代码的复用性、更简便的功能;对数组的操作只有设置和获取角标值,对应到IO流中的读和写,所以是用流的思想操作数组,且不需要再判断数组的长度。
方法:toByteArray()、toString()、writeTo(OutputStream out)将数据全部存到数组流里,然后再写到一个文件中。
try {
baos.writeTo(new FileOutputStream("ByteArray.txt"));
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
结果:
ByteArrayInputStream和ByteArrayOutputStream都是操作的字节数组,则如何操作字符数组?即CharArrayReader与CharArrayWriter
源创建时就要传入字符数组;目的是一个空的缓冲区。
操作字符数组:CharArrayReader与CharArrayWriter
操作字符串:StringReader与StringWriter
40.转换流的字符编码
字符编码:
- 字符流的出现为了方便操作字符
- 更重要的是键入了编码转换
- 通过子类转换流来完成:InputStreamReader、OutputStream以及PrintReader、PrintWriter(只能打印不能读取)
- 在两个对象进行构造时可以加入字符集
编码表的由来:
- 计算机只能识别二进制数据,早期由来是电信号
- 为了方便应用计算机,让它可以识别各个国家的文字,就将各个国家的文字用数组来表示,并一一对应,形成一张表。这就是编码表。
常见的编码表:
- ASCII:美国标准信息交换码,用一个字节的7位可以表示
- ISO8859-1:拉丁码表,欧洲码表,用一个字节的8位表示
- GB2312:中国的中文编码表
- GBK:中国的中文编码表升级,融合了更多的中文文字符号
- Unicode:国际标准码,融合了多种文字。所有文字都用两个字节来便是,java语言使用的就是Unicode
- UTF-8:最多用三个字节来表示一个字符
- ….
package com.vnb.javabase.base.io.other;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
public class EncodeStream {
public static void main(String[] args) {
writeText();
}
public static void writeText(){
OutputStreamWriter osw = null;
try {
try {
osw = new OutputStreamWriter(new FileOutputStream("gbk.txt"),"GBK");
} catch (UnsupportedEncodingException e1) {
e1.printStackTrace();
}
try {
osw.write("你好");
} catch (IOException e) {
e.printStackTrace();
}
} catch (FileNotFoundException e) {
e.printStackTrace();
}finally{
if(osw!=null){
try {
osw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
osw = new OutputStreamWriter(new FileOutputStream("gbk.txt"),"GBK");
结果:
osw = new OutputStreamWriter(new FileOutputStream("gbk.txt"),"UTF-8");
结果:
字符在码表中变现形式:
存入到文本文件中的都是码表中对应一个的数组,在文本文件打开时会自动去查询对应的码表将文字显示出来。
读取字符:
public static void readTxt(){
InputStreamReader isr = null;
try {
isr = new InputStreamReader(new FileInputStream("gbk.txt"),"GBK");
char[] buf = new char[10];
int len;
try {
len = isr.read(buf);
String str = new String(buf,0,len);
System.out.println(str);
} catch (IOException e) {
e.printStackTrace();
}
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
结果:
你好
取数据分析图示:
但是当,编码表的字符出错时:
isr = new InputStreamReader(new FileInputStream("gbk.txt"),"UTF-8");
结果:字符出现乱码
��
分析图示:
更改正确时:
isr = new InputStreamReader(new FileInputStream("UTF-8.txt"),"UTF-8");
结果:
你好
如果编码错误(使用gbk取查找utf-8的编码表):
isr = new InputStreamReader(new FileInputStream("UTF-8.txt"),"gbk");
结果:
浣犲ソ
分析图:在gbk码表中确实存在对应码对应的中文
41.字符编码
编码:字符串变成字节数组,String-->byte[] str.getBytes()
解码:字节数组变成字符串,byte[]-->String new String(byte[])
String(byte[] byte)
String(byte[] byte,String charset)
getBytes()可按照默认编码(编译环境的编码)或者指定的编码进行转码。
package com.vnb.javabase.base.io.other;
import java.io.UnsupportedEncodingException;
import java.util.Arrays;
public class EncodeDemo {
public static void main(String[] args) {
String s = "你好";
//编码
byte[] b1 = s.getBytes();
System.out.println("编码:"+Arrays.toString(b1));
//解码
try {
System.out.println("解码后:"+new String(b1,"utf-8"));
} catch (UnsupportedEncodingException e1) {
e1.printStackTrace();
}
try {
byte[] b2 = s.getBytes("GBK");
System.out.println("编码:"+Arrays.toString(b2));
System.out.println("解码后:"+new String(b2,"GBK"));
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
//存在硬盘上是变成字节,即编码
}
}
结果:
编码:[-28, -67, -96, -27, -91, -67]
解码后:你好
编码:[-60, -29, -70, -61]
解码后:你好
本编译环境默认编码为utf-8。
如果编码编错了即使用不识别中文的编码表,则解码不出来。
try {
byte[] b2 = s.getBytes("ISO8859-1");
System.out.println("编码:"+Arrays.toString(b2));
System.out.println("解码后:"+new String(b2,"ISO8859-1"));
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
结果:
编码:[63, 63]
解码后:??
但是如果编码编对了,但是解码解错了,会使用未知字符表示:
try {
byte[] b2 = s.getBytes("GBK");
System.out.println("编码:"+Arrays.toString(b2));
System.out.println("解码后:"+new String(b2,"ISO8859-1"));
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
结果:
编码:[-60, -29, -70, -61]
解码后:ÄãºÃ
则乱码已经产生了,要怎么解决?
解决:重新获取乱码后的编码值,再到致使错误的的编码表中查询到编码值,再通过正确的编码表进行解码。
解析图示(解析结果不正确,但是编码的数字并未改变):
try {
byte[] b2 = s.getBytes("GBK");
System.out.println("编码:"+Arrays.toString(b2));
System.out.println("解码后:"+new String(b2,"ISO8859-1"));
//对s进行ISO8859-1的编码
byte[] b3 = s.getBytes("ISO8859-1");
System.out.println("乱码后再解码:"+new String(b2,"GBK"));
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
结果:
编码:[-60, -29, -70, -61]
解码后:ÄãºÃ
乱码后再解码:你好
开发中可能遇到的上述状况:
服务器中默认编码是iso8859-1,当有中文字符时就必须通过iso8859-1先编码,再通过浏览器中的编码编解码才能解决乱码问题。
如果是utf-8解码错误,在使用,在使用utf-8重新编码时,再使用正确的编码表解码时,会有问题:
往回重新编码时,分别对应三个字节。在utf-8编码里也支持连续三个负数所对应的文字,然后使用三个文字去编码里查,发现没查找到,于是使用码表中的未知字符区域显示。这是由于GBK和utf-8都识别中文造成。
42.字符编码—联通
新建一个test.txt文件,输入你好,再次打开内容不变,但是将文字改成“联通”后,再次打开,发现内容乱码。
原因:联通四个字节写入了文件,当打开记事本时,记事本(默认GBK)会去进行解码。
GBK两个字节代表一个字符,utf-8是三个字节代表一个字符,但是都是负数(中文的字节用负数表示),到底是读两个还是三个字符来确定这个字符是什么呢?
分析图示:
第一个当读到110表示头时,会再往下读一个字节即10开头的字节;第二个当读到010标识头时不再继续往下读;读到1110时继续往下再读两个字节即10开头的字节和10开头的字节。
本来字符流读一次读到2个字节。但是utf-8是3个字节读一次。所以utf-8编码给每个字节加了标识头信息,依据自己的标识头就可以知道一次读几个字节。
package com.vnb.javabase.base.io.other;
import java.io.UnsupportedEncodingException;
public class EncodeDemo2 {
public static void main(String[] args) {
String s = "联通";
try {
byte[] by = s.getBytes("gbk");
for(byte b : by){
//&255表示只取最后8位二进制
System.out.println(Integer.toBinaryString(b&255));
}
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
}
结果:
11000001
10101010
11001101
10101000
发现使用的是GBK编码,但是编码形式和utf-8一致。如果是记事本查到该编码会直接使用utf-8表示,于是会乱码。只有这两个字会出现这种情况,如果加入其它中文字符就不会出现此种情况。
43.练习
需求:有五个学生,每个学生有3们课的成绩。从键盘输入以上数据(包括姓名,三门课成绩);输入格式:如,zhangsan,30,40,60;计算出总成绩并把学生的信息和计算出的总分数按照高低顺序存放在磁盘文件“stud.txt”中。
思路:
描述学生对象
定义一个可以操作学生的工具类
思想:
- 通过获取键盘录入一行数据,并将改行的信息取出封装成学生对象
- 因为学生对象有很多,那么就需要存储,则使用到集合,因为要对学生的总分排序,所以可以使用TreeSet集合
- 将集合中的信息写入到一个文件中。
对外提供带比较器即强行逆转比较器(分数从高到低排序)和默认自然排序的方法。
Comparator<Student> cmp = Collections.reverseOrder();
package com.vnb.javabase.base.io.other;
import java.util.Comparator;
public class Student implements Comparable<Student> {
private String name;
private int ma,cn,en;
private int sum;
Student(String name,int ma,int cn,int en){
this.name = name;
this.ma = ma;
this.cn = cn;
this.en = en;
sum = ma+cn+en;
}
@Override
public int compareTo(Student s) {
int num = new Integer(this.sum).compareTo(new Integer(s.sum));
if(num==0){
return this.name.compareTo(s.name);
}
return num;
}
@Override
public int hashCode() {
return name.hashCode()+sum*70;
}
@Override
public boolean equals(Object obj) {
if(!(obj instanceof Student)){
throw new ClassCastException("对象类型不匹配");
}
Student s = (Student)obj;
return this.name.equals(s.name) && this.sum==s.sum;
}
@Override
public String toString() {
return "Student["+name+","+ma+","+cn+","+en+"]";
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getMa() {
return ma;
}
public void setMa(int ma) {
this.ma = ma;
}
public int getCn() {
return cn;
}
public void setCn(int cn) {
this.cn = cn;
}
public int getEn() {
return en;
}
public void setEn(int en) {
this.en = en;
}
public int getSum() {
return sum;
}
public void setSum(int sum) {
this.sum = sum;
}
}
工具类:
package com.vnb.javabase.base.io.other;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Comparator;
import java.util.Set;
import java.util.TreeSet;
public class StudentInfoTools {
//带强制转换器读取
public static Set<Student> readStuInfo(Comparator<Student> cmp){
Set<Student> stuSet = null;
//InputStreamReader字节流和字符流转换的桥梁
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String line = null;
//根据是否传入强制转换器,确定使用哪种TreeSet构造函数
if(cmp!=null){
stuSet = new TreeSet<Student>(cmp);
}else{
stuSet = new TreeSet<Student>();
}
try {
while((line=br.readLine())!=null){
//line就是读取到的数据,需要判断何时结束输入
if("over".equals(line)){
break;
}
String[] arr = line.split(",");
Student s = new Student(arr[0],Integer.parseInt(arr[1]),Integer.parseInt(arr[2]),Integer.parseInt(arr[3]));
stuSet.add(s);
}
} catch (IOException e) {
e.printStackTrace();
}
return stuSet;
}
//不带强制转换器读取
public static Set<Student> readStuInfo2(){
return readStuInfo(null);
}
//写入文件
public static void write2File(Set<Student> stuSet){
BufferedWriter bw = null;
try {
//关于字符流的文件处理类FileWriter
bw = new BufferedWriter(new FileWriter("stuinfo.txt"));
for(Student s : stuSet){
bw.write(s.toString()+"\t");
bw.write(s.getSum()+"");
//缓冲区换行且必须刷新
bw.newLine();
bw.flush();
}
} catch (IOException e) {
e.printStackTrace();
}finally{
if(bw!=null){
try {
bw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
测试:
package com.vnb.javabase.base.io.other;
import java.util.Collections;
import java.util.Comparator;
import java.util.Set;
public class StudentInfoTest {
public static void main(String[] args) {
//传入强制转换比较器
Comparator<Student> cmp = Collections.reverseOrder();
Set<Student> stuSet = StudentInfoTools.readStuInfo(cmp);
StudentInfoTools.write2File(stuSet);
}
}
键盘录入数据:
zhangsan,20,30,40
lisi,56,67,78
wangwu,67,89,67
zhaoliu,45,45,23
over
结果:打印且排序成功