JAVA进阶知识——I/O流

JAVA进阶知识——I/O流(输入输出流)

流的分类

从Java不同版本上来说,流可以分为BIO、NIO和AIO三大类

BIO适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,JDK1.4以前的唯一选择,但程序直观简单易理解

NIO适用于连接数目比较多且连接比较短的架构,比如聊天服务器,并发局限于应用中,编程比较复杂,JDK1.4开始支持

AIO方式适用于连接数目多且连接比较长的架构,比如相册服务器,充分调用OS参与并发操作,编程比较复杂,JDK7开始支持。

  • 根据流的方向:输入流和输出流
  • 根据读写的字节数:字节流和字符流
  • 根据读写的目标:节点流和过滤流

File类

java.io.File用于封装和平台无关的文件夹和文件夹对象。构建File对象时,没有要求对应的文件或者文件夹必须存在

构造器1: public File(String pathName)

//参数是文件或者文件夹的路径,可以是绝对路径,也可以是相对路径
  File file=new File("bbb.txt");  
   //使用绝对路径指定一个具体的文件,注意\\和/的写法
  File ff = new File("c:\\windows\\win.ini");
  //相对路径,相对路径是从项目的根路径开始计算
  File ff=new File("bbb/ccc.txt"); 
  //获取相对路径的起始点
  File file = new File("");//参数为"."也可以
  System.out.println(file.getAbsolutePath());

构造器2:public File(String parent, String child)

File ff = new File("c:\\windows\\win.ini");
//⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇等价于⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇
File ff=new File("c:\\windows","win.ini")

常见方法:

  • getAbsolutePath():String 获取绝对路径

  • length():long 获取文件的大小,单位字节

  • File[] files = file.listFiles(); 获取file对象的子对象

  • isFile():boolean 判断文件对象是否为文件

  • isDirectory():boolean 判断文件对象是否为文件夹

  • exists():boolean 判断文件夹是否存在,存在true,不存在false

  • mkdirs() 创建多级目录,例如d:/aa/bb/cc;mkdir()则只能创建一级,不能创建多级

  • delete()可以删除文件或者文件夹,但是删除文件夹时要求文件夹为空

字节流

面向对象编程的核心是面向抽象编程,所以能用接口则不只用具体类。在学习IO流时,大家掌握的应该是接口中的方法,而不是具体实现类中的方法

输入流输出流
字节流InputStreamOutputStream
字符流ReaderWriter

基本上所有的输入输出流方法上都有IOException,所以标准写法为

InputStream is=null;
try{
    is=获取具体的实现类对象;
    ... 调用读写方法 ...
}catch(IOException e){
    异常处理,常见的是打印报错信息
}finally{
    if(is!=null)
        try{
        	is.close();  关闭操作,释放资源
        } catch(Exception e){
            e.printStackTrace();
        }
}

try-resources写法

字节输入流

InputStream

public abstract class InputStream implements Closeable

实现了Closeable接口,所以可以使用try/catch/finally结构的简化写法try-resources[JDK1.7+]

  • public abstract int read() throws IOException; 一次读取一个字节数据,返回值是一个0到255之间的整数,返回-1表示到达流末尾
  • public int read(byte b[]) throws IOException; 一次最多读取b.length个字节数据,并存储到b数组中,返回读取的字节个数
  • public int read(byte b[], int off, int len) throws IOException; 读取多个字节的数据,存储到b数组中,从off下标位置开始,最多len个字节
  • close() 关闭输入流

字节输出流

public abstract class OutputStream implements Closeable, Flushable 

实现了Closeable接口,所以可以使用try/catch/finally结构的简化写法try-resources[JDK1.7+]

  • public abstract void write(int b) throws IOException; 写出一个字节,int参数中多余内容会直接丢失
  • public void write(byte b[]) throws IOException;将字节数组b中的所有数据写出
  • public void write(byte b[], int off, int len) throws IOException 将字节数组b中的数据,从off开始,共len个字节写出
  • public void close() throws IOException

文件字节流的使用

文件字节输入流用于以字节的方式读取文件中的内容 FileInputStream

  • public FileInputStream(String name) throws FileNotFoundException
  • public FileInputStream(File file) throws FileNotFoundException
public class Test2 {
    public static void main(String[] args) {
        try (
                InputStream is = new FileInputStream("c:/bb/win.ini")
        ) {
            int bb=-1;
            while((bb=is.read())!=-1) {
                System.out.print((char) bb);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    } } 

一次读取一个字节,效率比较低【实际上所有的流都有缓存】,引入字节数组充当缓存,一次性读取多个内容

public class Test3 {
    public static void main(String[] args) {
        InputStream is = null;
        // try-catch-finally结构在实际应用中并不建议,因为太繁琐了
        try {  
            is = new FileInputStream("c:/bb/win.ini");
            byte[] arr=new byte[8192];
            int len=0;
            //读取到文件结尾返回长度为0
            while((len=is.read(arr))>0){ 
                // 将字节数组转换为字符串
                String ss = new String(arr, 0, len); 
                System.out.println(ss);
            }
        } catch (IOException e) {
        	//输出异常信息
            System.out.println(e.getMessage());
        } finally {
            if (is != null) {
                try {
                    is.close();
                } catch (IOException e) {
                	// 打印调用栈
                    e.printStackTrace();  
                }
            }
        }
    }
}

文件字节输出流用于以字节的方式写入文件内容,可以是追加也可以是覆盖

  • public FileOutputStream(String name) throws FileNotFoundException 覆盖
  • public FileOutputStream(String name, boolean append) throws FileNotFoundException 参数append用于定义是否采用追加
  • public FileOutputStream(File file) throws FileNotFoundException
  • public FileOutputStream(File file, boolean append) throws FileNotFoundException

将win.ini拷贝到d:/cc目录下

一次多字节

public class Test {
    public static void main(String[] args) throws Exception {
        try (
                InputStream is = new FileInputStream("c:/bb/win.ini");
                OutputStream os = new FileOutputStream("d:/cc/win.ini", true)
        ) {
            byte[] arr=new byte[8192];
            int len=0;
            while((len=is.read(arr))>0){
                os.write(arr,0,len);
            }
            System.out.println("拷贝完成!");
        }
    }
}

字符流

顶级父抽象类Reader和Writer,一次一字符的操作,实现了Closeable接口。如果涉及中文信息,则需要考虑编码字符集的问题,如果编码字符集错误,则显示乱码

字符输入流

Reader用于封装字符读取操作

  • read():int 返回读取到的字符数据,0-65535 2B,返回-1 表示流末尾
  • read(char[]):int 返回读取的字符个数,流结束返回-1
  • close():void 关闭流

字符输出流

Writer用于封装字符写出操作

  • write(int):void 写出低16位
  • write(char[]数据,int起始下标,int写出的字符数):void
  • close():void 关闭流,释放资源
  • write(String):void 写出一个字符串内容到输出流中

文件字节流的使用

编写一个程序实现如下功能,文件fin.txt是无行结构(无换行符)的汉语文件,从fin中读取字符,写入文件fou.txt中,每40个字符一行(最后一行可能少于40个字)

public class Test1 {
    public static void main(String[] args) throws Exception {
        try(
        Reader r=new FileReader("fin.txt");
        Writer w=new FileWriter("fout.txt");
        ){
            int count=0;
            int kk=0;
            while((kk=r.read())>-1){
                count++;
                w.write(kk);
                if(count%40==0)
                    w.write("\n");
            }
        }
    }
}

节点流

类型字符流字节流
文件FileReader和FileWriterFileInputStream和FileOutputStream
数组CharArrayReader和CharArrayWriterByteArrayInputStream和ByteArrayOutputStream
字符串StringReader和StringWriter
线程之间通讯所使用的管道PipedReader和PipedWriterPipedInputStream和PipedOutputStream

文件流

FileInputStream和FileReader用于从一个文件中读取数据;FileOutputStream和FileWriter用于向一个文件中写出数据

FileInputStream文件输入字节流,FileReader文件输入字符流,用法类似

  • FileInputStream(“文件名称”),如果文件不存在则FileNotFoundException
  • FileInputStream(File)

FileOutputStreram文件输出字节流,FileWriter文件输出字符流,用法类似

  • FileOutputStream(“文件名称”);如果文件不存在,则自动创建;如果文件存在则执行覆盖原文件
    内容
  • FileOutputStream(“文件名称”,boolean是否采用追加方式);如果文件不存在,则自动创建;如果文件存在并且Boolean参数为true则表示采用追加的方式处理

使用字符输入输出文件流的方法和字节一致

内存数组节点

如果是文本字符则使用char[],如果二进制数据则使用byte[]

CharArrayReader和CharArrayWriter、ByteArrayInputStream和ByteArrayOutputStream数据来源或者输出目标为数组,CharArray对应char[],ByteArray对应byte[]

输入流

  • CharArrayReader(char[]) 其中char[]就是数据的来源,也就是说Reader是从char[]中读取数据
  • 不常用 CharArrayReader(char[]数据,int起始下标,int最大长度)

输出流

  • CharArrayWriter用于实现向一个字符数组中写入数据,这个数组大小可以自动调整

  • CharArrayWriter()自动创建一个关联了char[]的输出流

  • CharArrayWriter(int)自动创建一个关联了char[]的输出流,这里的参数就是char[]的初始化大小

  • 特殊方法toCharArray():char[] 获取目标char数组

案例:从键盘读取数据,并写入到char[]中,如果键盘录入quit则退出并显示char[]中的所有内容

public class Test2 {
    public static void main(String[] args) throws Exception {
        Scanner sc = new Scanner(System.in);
        try (
                CharArrayWriter w = new CharArrayWriter()
        ) {
            String str = "";
            while (true) {
                str = sc.nextLine();
                if ("quit".equals(str))
                    break;
                w.write(str + "\n");
            }
            char[] chars = w.toCharArray();
            System.out.println(new String(chars));
        }
        sc.close();
    }
}

过滤流

过滤流的作用就是在节点流的基础上提供额外功能

自定义过滤流

过滤父类FilterReader、FilterInputStream、FilterWriter、FilterOutputStream,实际上就是装饰模式中的抽象装饰类

public abstract class FilterReader extends Reader

案例:循环13加密

public class SecurityReader extends FilterReader {
    public SecurityReader(Reader target){
        super(target);
    }

    @Override
    public int read() throws IOException {
        int kk=super.read();
        if(kk>='a' && kk<='z'){
            kk=(kk-'a'+13)%26+'a';
        }else if(kk>='A' && kk<='Z'){
            kk=(kk-'A'+13)%26+'A';
        }
        return kk;
    }
}

public class Test2 {
    public static void main(String[] args) throws Exception {
        try (
        		//SecurityReader()为自定义过滤流
                Reader r = new SecurityReader(new FileReader("security.txt"));
                Writer w = new FileWriter("security1.txt")
        ) {
            int kk=0;
            while((kk=r.read())!=-1){
                w.write(kk);
            }
            System.out.println("执行完毕!");
        }
    }
}

系统预定义过滤流

处理类型字符流字节流
缓存BufferedReader和BufferedWriterBufferedInputStream和BufferedOutputStream
过滤处理FilterReader和FilterWriterFilterInputStream和FilterOutputStream
桥接处理InputStreamReader和OutputStreamWriter
对象处理/ObjectInputStream和ObjectOutputStream
数据转换/DataInputSteam和DataOutputStream
打印功能PrintWriterPrintStream
行数统计LineNumberReaderLineNumberInputStream
回滚处理PushbackReaderPushbackInputStream
桥接转换流

用途:实现字节流和字符流之间的自动转换,从字节输入流读取字节数据,并按照编码规范转换为字符;向字节输出流写出数据时,先将字符按照编码规范转换为字节,然后再进行输出。

使用桥接流时应该指定编码字符集名称,以便实现流转换,如果不指定,则使用当前默认编码字符集

  • InputStreamReader用于将一个InputStream自动转换为Reader
  • OutputStreamWriter用于将一个 Writer自动转换为OutputStream

InputStreamReader构造器

  • InputStreamReader(InputStream)

  • InputStreamReader(InputStream in, String charsetName) ,例如new InputStreamReader(System.in,“GBK”);

  • InputStreamReader(InputStream in, Charset cs),直接使用charsetName编码字符集名称的方式进行设定编码字符集可能会出错,所以可以使用Charset指定编码字符集名称

Charset gbk = Charset.forName("GBK");  //UnsupportedCharsetException 

没有指定编码字符集则使用系统默认的编码字符集,允许用户自定义编码字符集,如new InputStreamReader(System.in, “iso8859-1”)。iso8859-1是用于欧洲地区的编码字符集,使用的是单字节编码。

OutputStreamWriter构造器和InputStreamReader一致

缓冲流

缓冲流是套接在相应的节点流之上,对读写操作提供缓冲功能,以提高读写的效率,并引入一个新方法

没有使用缓冲流的时间统计 35ms

public class Test3 {
    public static void main(String[] args) throws Exception{
        long start=System.currentTimeMillis();//用于获取从1970-1-1 0:0:0到语句执行时的毫秒值
        Reader r=new FileReader("Test1.java");
        int kk=0;
        while((kk=r.read())!=-1){
            System.out.print((char)kk);
        }
        r.close();
        long end=System.currentTimeMillis();
        System.out.println("执行时间为"+(end-start)+"ms");
    }
}

引入缓冲流:以浪费内存为代价换取高执行效率

public class Test3 {
    public static void main(String[] args) throws Exception{
        long start=System.currentTimeMillis();//用于获取从1970-1-1 0:0:0到语句执行时的毫秒值
        Reader r=new BufferedReader(new FileReader("Test1.java"));
        int kk=0;
        while((kk=r.read())!=-1){
            System.out.print((char)kk);
        }
        r.close();
        long end=System.currentTimeMillis();
        System.out.println("执行时间为"+(end-start)+"ms");
    }
}

缓冲字节流构造器

  • BufferedInputStream(InputStream)不定义缓存大小,则默认8192

  • BufferedInputStream(InputStream,int)人为设置缓冲大小,int单位字节

  • BufferedOutputStream(OutputStream)

  • BufferedOutputStream(OutputStream,int size)自定义缓存区大小

缓冲字符流构造器

  • BufferedReader(Reader)默认缓冲区大小为8192字符

  • BufferedReader(Reader,int)int用于定义缓冲区的大小

  • BufferedWriter(Writer)默认缓冲区大小为8192字符

  • BufferedWriter(Writer,int)int用于自定义缓冲区的大小,单位为字符B

特殊方法

BufferedReader中提供了一个特殊方法readLine():String,但是在BufferedInputStream中并没有这个 方法。可以整行读取数据,以\r或者\n为分隔符(换行符) 如果读取内容为null【 不是-1】,则表示读取到了流末尾 readLine方法的返回值会自动剔除末尾的换行符

BufferedWriter中提供了一个方法newLine可以写入一个换行符

对于输出的缓冲流,写入的数据会受限缓存在内存,使用flush方法可以清空缓存,同时将以前的缓存数 据立即写出

强调

一般使用缓存流的目标

  1. 在于以透明的方式引入缓冲区,以提高代码的执行效率,所以不会使用什么特殊的方法。
  2. 使用Reader/Writer、InputStream/OutputStream之类的父类中提供的方法 。

特殊的是BufferedReader方法readLine 过滤流使用必须有对应的节点流,因为过滤流是用于装饰节点流的,不是有具体的操作目标 。

执行close方法会自动关闭被装饰的对象,所以不需要再关闭FileReader、FileWriter

Reader r=new BufferedReader(new InputStreamReader(System.in));
r.close()
  • 执行flush方法会自动刷新数据到被装饰的流上,但是并没有执行关闭流。针对输出流关闭时会自动先flush然后再关闭。
数据流

DataInputStream和DataOutputStream允许与机器无关的风格读写java原始类型的数据,比较适合于网络上的数据传输

经常在大学java相关课程的期末考试中见到此类编程题目

  • 只有字节流,没有对应的字符流

构造器:

  • DataInputStream(InputStream)
  • DataOutputStream(OutputStream)

练习题:读写一个double类型的数据到文件

FileOutputStream中并没有直接写出double类型数据的方法,所以只能将数据转换为字符串进行输出

public class Test8 {
    public static void main(String[] args) throws Exception {
        double res=123.4567;
        try (
                OutputStream outputStream = new BufferedOutputStream(new FileOutputStream("data.data"));

        ) {
            String ss=""+res;
            final byte[] bytes = ss.getBytes();
            outputStream.write(bytes);
        }
        try(
                InputStream inputStream=new BufferedInputStream(new FileInputStream("data.data"));
                ){
            byte[] bytes=new byte[1024];
            final int len = inputStream.read(bytes);
            final String str = new String(bytes, 0, len);
            Double dd=Double.parseDouble(str);
            System.out.println(dd);
        }
    }
}

数据流针对8种简单类型提供了对应的读写方法:

数据类型输入流DataInputStream输出流DataOutputStream
整数readByte、readShort、readInt、readLongwriteByte、writeShort、writeInt、writeLong
浮点数readFloat、readDoublewriteFloat、writeDouble
布尔型readBooleanwriteBoolean
字符型readCharwriteChar

数据流是通过EOFException用于判断流结束end-of-file

针对字符串类型建议使用readUTF和writeUTF进行读写操作

打印流

打印流都是用于实现打印输出,其中有PrintWriter和PrintStream,分别针对字符和字节,都提供了一 系列重载的print和println方法,可以输出多种类型数据

  • print(Object):void/println(Object):void

输出引用类型:具体实现是通过调用对象的toString()方法将对象转换为String进行输出

注意:PrintWriter和PrintStream的输出操作不会抛出异常,用户可以通过错误状态检查获取错误信息

PrintWriter和PrintStream都有自动的flush功能

特殊构造器

  • public PrintWriter (Writer out)
  • PrintWriter(Writer out,boolean autoFlush) boolean表示是否支持自动刷新
  • PrintWriter(OutputStream out) 直接使用OutputStream时不需要通过桥接处理
  • PrintWriter(OutputStream out, boolean autoFlush)
  • PrintStream(OutputStream out)
  • PrintStream(OutputStream out, boolean autoFlush)
  • PrintStream(String fileName) 参数为文件名称,表示数据直接输出到指定文件中
  • PrintStream(File file)
对象流

Java中引入了对象流ObjectInputStream和ObjectOutputStream可以一次写出一个对象,而不是操作对象中的每个属性。

Java提供了ObjectInputStream和ObjectOutputStream可以直接读写Object对象,实际上还提供了针对 8种简单类型和String类型的读写操作方法

  • ObjectInputStream提供了读取对象的方法readObject():Object
  • ObjectOutputStream提供了写出对象的方法writeObject(Object):void

将java.util.Date类型当前时间写入到now.data文件中,然后再重新从文件读取出来

public class Date implements java.io.Serializable, Cloneable, Comparable<Date>{}

编码实现

public class Test3 {
    public static void main(String[] args) throws Exception {
        Date now=new Date();
        try(
                ObjectOutputStream dos=new ObjectOutputStream(new BufferedOutputStream(new FileOutputStream("now.data")))
                ){
            dos.writeObject(now);
        }
        try(
                ObjectInputStream ois=new ObjectInputStream(new BufferedInputStream(new FileInputStream("now.data")))
                ){
            Object obj=ois.readObject();
            //DateFormat用于对日期类型数据进行格式化
            // yyyy是年MM月dd日E星期HH小时mm分钟ss秒
            DateFormat df=new SimpleDateFormat("yyyy年M月d日E H点m分s秒");
            if(obj instanceof Date) {
                String str = df.format((Date) obj);//针对参数日期应用指定的格式转换为字符串类型
                System.out.println(str);
            }
        }
    }
}
序列化

读写一个对象的前提是这个类型的对象是可序列化的

  • 对象的序列化简单的来说就是将对象可以直接转换为二进制数据流
  • 对象的反序列化将二进制数据流转换为对象

针对对象的序列化和反序列化是通过JVM实现的,编程中只做声明,序列化的目标就是将对象保存到磁盘中或者允许在网络中直接传动

如果需要使用序列化或者反序列化操作,则必须在序列化对象上添加对应的声明,否则NotSerializableException

serialVersionUID用于确保类序列化和反序列化的兼容性问题。编译器推荐2种方式,一种是生成默认的 versioID,还有一种是根据类名、接口名、成员方法和属性生成一个64为的哈希字段

流的选用问题

首先考虑使用DataInputStream和DataOutputStream可以减少数据类型转换的过程,直接运行会有报错。原因是序列化接口的问题。

  • 48
    点赞
  • 39
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值