java基础之I/O

时间:2023-02-14 23:43:39

一、概述

I/O的本质是通信。

有多种源端和接收端:文件(硬盘)、键盘/控制台、网络链接等
有多种不同的通信方式:顺序、随机存取、缓冲、二进制、按字符、按行、按字等。
java设计了大量的类来解决 这个通信问题。

在电脑上的数据有三种存储方式,一种是外存,一种是内存,一种是缓存。缓存用于提升计算机工作效率。
将数据冲外存中读取到内存中的称为输入流,将数据从内存写入外存中的称为输出流。

I/O类图:
java基础之I/O

二:File类

1、概述:
既能代表一个文件的名称,也能代表一个目录。
File f1 = new File(“c:\java\demo.txt”); File f2 = new File(“c:\java”);

2、各种方法:
1:创建。

    boolean createNewFile():在指定目录下创建文件,如果该文件已存在,则不创建。而对操作文件的输出流而言,输出流对象已建立,就会创建文件,如果文件已存在,会覆盖。除非续写。
    boolean mkdir():创建此抽象路径名指定的目录。 boolean mkdirs() :创建多级目录。

2 :删除。

    boolean delete():删除此抽象路径名表示的文件或目录。
    void deleteOnExit():在虚拟机退出时删除。
    注意:在删除文件夹时,必须保证这个文件夹中没有任何内容,才可以将该文件夹用 delete删除。
    window的删除动作,是从里往外删。 注意:java删除文件不走回收站。要慎用。

3:获取.

    long length():获取文件大小。
    String getName():返回由此抽象路径名表示的文件或目录的名称。
    String getPath():将此抽象路径名转换为一个路径名字符串。
    String getAbsolutePath():返回此抽象路径名的绝对路径名字符串。
    String getParent():返回此抽象路径名父目录的抽象路径名,如果此路径名没有指定父目录,则返回 null。
    long lastModified():返回此抽象路径名表示的文件最后一次被修改的时间。
    File.pathSeparator:返回当前系统默认的路径分隔符, windows默认为 “;”。
    File.Separator:返回当前系统默认的目录分隔符, windows默认为 “\”。

4 :判断:

    boolean exists():判断文件或者文件夹是否存在。
    boolean isDirectory():测试此抽象路径名表示的文件是否是一个目录。
    boolean isFile():测试此抽象路径名表示的文件是否是一个标准文件。
    boolean isHidden():测试此抽象路径名指定的文件是否是一个隐藏文件。
    boolean isAbsolute():测试此抽象路径名是否为绝对路径名。

5 :重命名。

    boolean renameTo(File dest) :可以实现移动的效果: 剪切 +重命名。

6.目录的操作:

    当file对象代表目录时,实质上是代表该目录下一组文件的名称,是一个文件集合,可以用listFiles方法获得一个File类型的数组:
    File[ ] f2list = f2.listFiles( );

    当要获取所有文件目录包括子目录时,可以采用递归的方式:

     public static void showDir(File dir,int level)
     {        
          System.out.println(getLevel(level)+dir.getName());
          level++;
          File[] files = dir.listFiles();
          for(int x=0; x<files.length; x++)
          {
               if(files[x].isDirectory())
                    showDir(files[x],level);
               else
                    System.out.println(getLevel(level)+files[x]);
          }
     }

     public static String getLevel(int level)
     {
          StringBuilder sb = new StringBuilder();
          sb.append("|--");
          for(int x=0; x<level; x++)
          {
               //sb.append("|--");
               sb.insert(0,"| ");
          }
          return sb.toString();
     }     

三、RandomAcessFile类:

非流类,主要提供随机访问文件的功能,内部封装了格式化输入输出流(DataInputStream和DataOutputStream),必须文件的结构是已知的,才能使用本类来操作。

使用方法:通过getFilePointer获取指针位置,利用seek()方法在文件中移动指针
可以指定操作模式:只读或读写。读写时,如果文件存在,不会像File,PrintWriter那样覆盖。

提供了如下方法:
(1)RandomAccessFile raf = new RandomAccessFile(“file”,”r或者rw”);
(2)raf.seek(long pos); 把文件指针设定在pos处
(3)long raf.getFilePointer():返回文件指针
(4)long raf.length();返回长度
(5)raf.skipBytes(long);

四、字节流

InputStream接口提供的方法:

    (1)int read(); 读取1个字节数据,然后返回该字节转换成的0-255范围的int值,读取完为-1
    注意:这里read方法中的byte转换成int和一般类型转换的byte转int是不同的,这里的int范围一定要在0-255之间。
    (2)int read(byte[]b); 读取流中数据写入字节数组,返回本次读入的长度int值
    (3)int read(byte[]b,int off,int len) 读取len长度的字节数,写入b的off开始的字节数组中
    注意:如果考虑效率方面,则可以使用(2)(3)的方法,因为能够批量读取。
    (4)void close(); 关闭流

OutputStream提供了如下方法:

    (1)write(int b); 将b先转型成byte即截取低八位字节,并写入输出流,例如如果b=257,则实际写入的只是1
    (2)write(byte[]b); 将byte数组写入到输出流中。
    (3)write(byte[]b,int off,int len);
    (4)void flush();
    (5)void close();关闭前会自行刷新一次

五、字符流

与字节流的对比:

    字符流处理的单元为2个字节的Unicode字符,分别操作字符、字符数组或字符串;而字节流处理单元为1个字节, 操作字节和字节数组。
    所有文件的储存是都是字节(byte)的储存,在磁盘上保留的并不是文件的字符而是先把字符编码成字节,再储存这些字节到磁盘。在读取文件时,也是一个字节一个字节地读取以形成字节序列.
    字节流可用于任何类型的对象,包括二进制对象,而字符流只能处理字符或者字符串; 2. 字节流提供了处理任何类型的IO操作的功能,但它不能直接处理Unicode字符,而字符流就可以。

Reader:用于读取字符流的抽象类。子类必须实现的方法只有 read(char[], int, int) 和 close()。

    read()返回的值为int型,是读入该char[]数组的字符数,不同于字节流的单个字节的int值。

Writer:写入字符流的抽象类。子类必须实现的方法仅有 write(char[], int, int)、flush() 和 close()。


编码问题:

在Java中,字符串用统一的Unicode编码,每个字符占用两个字节,与编码有关的两个主要函数为:

    1)将字符串用指定的编码集合解析成字节数组,完成Unicode-〉charsetName转换
    public byte[] getBytes(String charsetName)
    2)将字节数组以指定的编码集合构造成字符串,完成charsetName-〉Unicode转换
    public new String(byte[] bytes, String charsetName)

流编码过程:

    从文件读取:源bytes–>根据指定的或默认的编码表编码后的字符–>Unicode字符(java中最终的char)
    写入到文件:与String.getBytes([encode])同理:Unicode字符–>encode字符–>bytes。

Unicode统一采用2个字节编码,UTF-8是Unicode的改进,原本ASCII码的字符还是一个字节。

Unicode与各编码之间的直接转换

    Unicode和GBK
    每个汉字转换为两个字节,且是可逆的,即通过字节可以转换回字符串
    Unicode和UTF-8
    测试结果如下,每个汉字转换为三个字节,且是可逆的,即通过字节可以转换回字符串
    Unicode和ISO-8859-1
    测试结果如下,当存在汉字时转换失败,非可逆,即通过字节不能再转换回字符串

Unicode与各编码之间的交叉转换
在上面直接转换中,由字符串(Unicode)生成的字节数组,在构造回字符串时,使用的是正确的编码集合,如果使用的不是正确的编码集合会怎样呢?会正确构造吗?如果不能正确构造能有办法恢复吗?会信息丢失吗?
能够正确显示的中间不正确转换

    String-GBK〉ByteArray-ISO-8859-1〉String-ISO-8859-1〉ByteArray-GBK〉String
    String-UTF-8〉ByteArray-ISO-8859-1〉String-ISO-8859-1〉ByteArray-UTF-8〉String
    String-UTF-8〉ByteArray-GBK〉String-GBK〉ByteArray-UTF-8〉String

六、装饰器之转换流

InputStreamReader和InputStreamWriterInputStreamReader:
以字节流为数据源,经过编码后变成字符。编码方式如未指定,使用的是系统默认的编码表。

InputStreamReader in2 = new InputStreamReader(new FileInputStream("Reader.txt"),"UTF-8");

七、装饰器之缓冲功能:

BufferedInputStream和BufferedOutputStream:

缓冲流作为过滤流的中间层,构建缓冲区,提高效率

BufferedInputStream in = new BufferedInputStream(new FileInputStream("1.txt"));
BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream("1.txt"));

BufferedReader 和BufferedWriter :

特点:提供了readLine()方法,可以一行一行地读取文本。

    FileReader fr = new FileReader("bufdemo.txt");
    BufferedReader bufr  = new BufferedReader(fr);
    String line = null;
    while((line=bufr.readLine())!=null)
    { 
        System.out.println(line); //readLine方法返回的时候是不带换行符的。
    }
    bufr.close();



    FileWriter fw = new FileWriter("bufdemo.txt");
    BufferedWriter bufw = new BufferedWriter(fw);//让缓冲区和指定流相关联。
    for(int x=0; x<4; x++)
    {
        bufw.write(x+"abc");
        bufw.newLine(); //写入一个换行符,这个换行符可以依据平台的不同写入不同的换行符。
        bufw.flush();//对缓冲区进行刷新,可以让数据到目的地中。
    }
    bufw.close();//关闭缓冲区,其实就是在关闭具体的流。

八、文件的读写:文件流:

根据文件的类型选择相应的流对象:

    文本文件选用字符流,其他选择字节流。且为提高效率,多用缓冲功能。输出流多采用打印流简化书写。

字节流:FileInputStream和FileOutputStream:

BufferedInputStream in = new BufferedInputStream(new FileInputStream("c:\\1.bmp"));
PrintStream out = new PrintStream("d:\\1.bmp",true);

字符流:FileReader和FileWriter
写文本文件时,若需指定编码表,则使用FileWriter+转换流,若无需指定,使用PrintWriter简化书写。

BufferedReader bufr = new BufferedReader(new FileReader("c:\\1.txt"));
PrintWriter pw = new PrintWriter(new FileWriter("d:\\1.txt"));//使用默认编码表编码,若文件存在,则覆盖。

九、标准输入输出:

System.in:键盘录入封装成一个InputStream,常用转换流转换成字符,一行行读取
System.out: 控制台输出,封装成一个OutputStream,提供了print和println方法。

十、数据格式化输入输出流:

DataInputStream和DataOutputStream

实现了DataInput和DataOutput接口的类。
DataInput提供了如下方法:
(1)Xxx readXxx();读取基本数据类型
(2)int read(byte[]b);读取至字节数组中,返回实际读取的长度
(3)readChar()读取一个字符即两个字节
(4)String readUTF();

DataOutput提供了如下方法:
(1)void wrtieXxx(Xxx )写入基本数据类型
(2)void writeBytes(String)能够以字节方式写入String,随后可以用read读取。
(3)void writeChars(String)以字符方式写入,一个字符是两个字节
(4)void writeUTF(String );

十一、打印流

PrintStream和PrintWriter: 简化了书写,且指定输出流时,可定义自动刷新

PrintStream out = new PrintStream(OutputStream o,boolean autoflush);
提供了方法:
out.print(Xxx);
out.println(Xxx);

十二、序列流

序列流,作用就是将多个读取流合并成一个读取流。实现数据合并。
表示其他输入流的逻辑串联。它从输入流的有序集合开始,并从第一个输入流开始读取,直到到达文件末尾,接着从第二个输入流读取,依次类推,直到到达包含的最后一个输入流的文件末尾为止。
这样做,可以更方便的操作多个读取流,其实这个序列流内部会有一个有序的集合容器,用于存储多个读取流对象。

SequenceInputStream(InputStream s1, InputStream s2)
SequenceInputStream(Enumeration<? extends InputStream> e) 

对象的构造函数参数是枚举,想要获取枚举,需要有Vector集合,但不高效。需用ArrayList,但ArrayList中没有枚举,只有自己去创建枚举对象。
但是方法怎么实现呢?因为枚举操作的是具体集合中的元素,所以无法具体实现,但是枚举和迭代器是功能一样的,所以,可以用迭代替代枚举。

合并原理:多个读取流对应一个输出流。
切割原理:一个读取流对应多个输出流。

十二、操作对象的流与对象序列化

目的:将一个具体的对象进行持久化,写入到硬盘上。
注意:静态数据不能被序列化,因为静态数据不在堆内存中,是存储在静态方法区中。
如何将非静态的数据不进行序列化?用transient 关键字修饰此变量即可。

Serializable:用于启动对象的序列化功能,可以强制让指定类具备序列化功能,该接口中没有成员,这是一个标记接口。这个标记接口用于给序列化类提供UID。这个uid是依据类中的成员的数字签名进行运行获取的。如果不需要自动获取一个uid,可以在类中,手动指定一个名称为serialVersionUID id号。依据编译器的不同,或者对信息的高度敏感性。最好每一个序列化的类都进行手动显示的UID的指定。

相关流:ObjectInputStream和ObjectOutputStream:
对象流实现了DataInput和DataOutput接口
对于对象:
(1)writeObject(Object);
(2)Object readObject(); 读取后需要强制类型转换
对于基本类型:使用readXxx和writeXxx方法

十三、管道流

管道读取流和管道写入流可以像管道一样对接上,管道读取流就可以读取管道写入流写入的数据。
注意:需要加入多线程技术,因为单线程,先执行read,会发生死锁,因为read方法是阻塞式的,没有数据的read方法会让线程等待。

    public static void main(String[] args) throws IOException
    {
        PipedInputStream pipin = new PipedInputStream();
        PipedOutputStream pipout = new PipedOutputStream();
        pipin.connect(pipout);
        new Thread(new Input(pipin)).start();
        new Thread(new Output(pipout)).start();
    }