Java I/O输入输出流

编码问题

String s = "Java 教程";
byte[] byte1 = s.getBytes();   //使用默认编码。 
byte[] byte2 = s.getBytes("utf-8"); //显示指定编码格式为 utf-8
//把字节(转换成int)以16进制方式显示
// GBK编码 中文占2个字节,英文占1个字节
// UTF-8 编码,中文占用3个字节,英文占用1个字节
// Java 是双字节编码 UTF-16be
for( byte b : byte1) {
    System.out.print(Integer.toHexString(b & 0xff) + "  ");
}

当你的字节序列是某种编码时,在把字节序列变成字符串时,也需要使用该种编码,否则会乱码。

String str = new String(b,"utf-16be");

文本文件就是字节序列,可以是任意编码的字节序列。
如果在中文机器上直接创建的文本文件,那么文本文件只认识ansi编码。
复制的还是按照原来的编码格式

File 类常用API

Java中, java.io.FIle 类用于表示文件(目录),只用于表示文件(目录)的信息(名称、大小),不能用于文件访问。

File file = new File("E:\\java\\javaTest"); // 一个目录
File file2 = new File("E://java//test1.java"); // 一个文件
File file3 = new File("E://java","test2.java") //一个文件

if(!file.exists()) {
    file.mkdir(); //创建一个目录
    file.createNewFile(); //创建一个文件
} else {
    file.delete();
}
if(file.isDirectory()) { }

if(file2.isFile()) { }
//常用的File对象API
file.getAbsolutePath();
file.getName();
file.getParent();

File常用操作:遍历,过滤

//列出指定目录下的目录和文件
String[] filenames = dir.list(); //列出当前目录下的子目录和文件,返回字符串数组。不包含子目录下的内容

// 需要遍历目录下的内容时,就要构造File对象做递归操作。
//File提供了直接返回对象的方法。
File[] files = dir.listFiles(); //返回直接子目录(文件)的对象
if(files != null && files.length > 0) {
    for (file : files) {
        if(file.isDirectory()) {
            //递归
        }
        else {
            //直接返回
        }
    }
}

RandomAccessFile的使用

RandomAcceseeFile 是Java 提供的对文件内容的访问类。
既可以读取文件,也可以写文件。并且可以随机访问文件,可以访问文件的任意位置

java 文件模型

  • 在硬盘上的文件是按 字节 存储的,是数据的集合;

打开文件

  • 两种模式: “rw”“r”。
  • RandomAccessFile raf = new RandomAccessFile(file, “rw”);
  • 文件指针: 打开文件时指针在开头 pointer = 0;

写方法

  • raf.write(int) ; //只写一个字节(后8位),同时指针指向下一个字节的位置,准备再次写入

读方法

  • int b = raf.read(); //只读一个字节

读写文件后一定要关闭文件,否则可能出现不可预测的错误。

File file  = new File("001.dat") {
    if(!file.exist()) {
        file.createNewFile();
    }
    RandomAccessFile raf = new RandomAccessFile(file,"rw");
    int i = 0x7fffffff;
    //用write方法没戏只写一个字节。int4个字节,需要写4次
    raf.write(i >>> 24); 高8位
    raf.write(i >>> 16);
    raf.write(i >>> 8);

    //或是用 writeInt 直接写一个int
    raf.writeInt(i);

    //write 可以直接写一个字节数组
    String s = "中";
    byte[] gbk = s.getBytes("gbk");
    raf.write(gbk); //写入一个字节数组

    //读文件,必须把指针移到头部
    raf.seek(0);
    //一次性读取文件中的所有内容到字节数组中
    byte[] buf = new Byte[(int)raf.length()];
    raf.read(buf);

IO流(输入流,输出流)

字节流的使用

  1. InputStream、OutputStream
    InputStream 抽象了应用程序读取数据的方式
    OutputStream 抽象了应用程序写出数据的方式
  2. EOF == end of file 读到-1表示读到文件结尾
  3. 输入流基本方法:(注意,键盘是输入流。 从键盘读入数据写到文件中)

    • int b = in.read(); // 读取一个字节无符号填充int低8位。 -1 是 EOF
    • `int read(byte[] buf); //读取数据填充到字节数组buf
    • int read(byte[] buf, int start, int size); //读取数据到字节数组buf,从buf 的start位置开始存放size长度的数据
  4. 输出流基本方法

    • out.write(int b); // 写出一个byte到流中,b的低8位
    • out.write(byte[] buf); //将buf字节数组写入到流
    • out.write(byte[] buf, int start, int size); //字节数组buf从start位置开始写size长度的字节到流
  5. FileInputStream 继承InputStream,具体实现了文件上读取数据。

    • FileInputStream in = new FileInputStream("file");
    • int b = in.read();
    • Byte[] buf = new byte[20*1024];
    • int bytes = in.read(buf,0,buf.length);//从in中批量读取字节,放入到buf这个字节数组中,从第0个位置开始放,最多放buf.length个字节。返回的是读到的字节的个数
  6. FileOutputStream 继承了OutputStream,具体实现了向文件中写出byte数据的方法

    • //如果文件不存在,直接创建。如果存在,删除后创建
    • FileOutputStream out = new FileOutputStream(file);

    • //true 表示 append,在文件后面追加.

    • //如果文件不存在,直接创建。如果文件存在,直接在文件末尾写入
    • FileOutputStream out = new FileOutputStream(file,true);
  7. DataInputStream 和 DateOutputStream 数据输入输出流
    是对“流”功能的扩展,可以更加方便的读取int,long,字符等类型数据

    • writeInt()/ writeDouble()/ writeChar()
    • //有OutputStream嵌套生成
    • FileOutputStream fout = new FileOutputStream("E:\\001.txt");
    • DataOutputStream dos = new DataOutputStream(fout);
    • dos.writeInt(10);
    • dos.writeUTF("中国");// 采用utf-8编码写出,每个汉字3个字节
    • dos.writeChars("中国"); //采用utf-16be 编码,每个汉字2个字节
  8. BufferedInputStream & BufferedOutputStream 带缓冲区的字节流操作。
    一般打开文件进行写入或读取操作时,都会加上缓冲,这种流模式提高了IO的性能。

FileOutoutStream ---> write()方法:类似一滴一滴的把水“转移”过去
DataOutputStream ---> writeXxx方法:较方便,类似一瓢一瓢的把水‘转移’过去
BufferedOutputStream ---> write() 方法: 更方便,类似把水先一瓢一瓢放到桶中,在从桶中导入水缸中。
FileOutoutStream ---> write(buf,start,size)方法: 速度最快
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(srcFile));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(destFile));
int c;
while((c = bis.read()) != -1) {
    bos.write(c);
    bos.flush(); //一定要刷新缓冲区,将数据写入文件中
}
bis.close();
bos.close();

字符流的使用

认识文本和文本文件

  • 文本: Java 文本(char)是16位无符号整数,是字符的unicode编码(双字节编码)
  • 文件: 是按照byte存储的数据序列
  • 文本文件: 是文本(char)序列按照某种编码方案(utf-8,utf-16be, gbk) 序列化为byte的存储结构

字符流输入输出流(Reader, Writer) 操作文本文件

字符的处理: 一次处理一个字符
字符的底层仍然是基本的字节序列

字符流的基本实现

  • InputStreamReader : 完成byte流解析为char流,按照编码解析
  • OuputStreamWriter : 提供char流发到byte流,按照编码解析
FileInputStream in = new FileInputStram("001.txt");
InputStreamReader isr = new InputStreamReader(in,"gbk"); //默认按照gbk编码
int c = isr.read();
char[] buffer = new char[8 * 1024];
c = isr.read(buffer, 0, buffer.length); //批量读取字符,放入buffer字符数组,从0位置开始放置,最多放置length个,返回读取到的字符个数
String s = new String(buffer,0,c); //把字符存储到字符串中

另外一种实现: FileReader&FileWriter

注意: 没有编码参数
需要设置编码时,就用InputStreamReader&OutputStreamWriter

FileReader fr = new FileReader("001.txt");
FileWriter fw = new FileWriter("002.txt");
//FileWriter fw = new FileWriter("002.txt","true");//文件后面追加
while((int c = fr.read(buffer,0,buffer,length) != -1) {
    fw.write(buffer,0,c);
    fw.flush();
}
fr.close();
fw.close();

字符流的过滤器 BufferedReader

BufferedReader 除了基本的读取外,可以一次读取一行
BufferedWriter/ PrintWriter 可以一次写一行

BufferedReader br = new BufferedReader(
    new InputStreamReader(
        new FileInputStream("001.txt"[,"utf-8"])));
//或是用FileReader()
//BufferedReader br = new BufferedWriter(FileReader("001.txt"));

BufferedWriter bw = new BufferedWriter(
    new OutputStreamWriter(
        new FileInputStream("002.txt"[,"utf-8"]));
String line;
//一次读取一行,不能识别换行,返回是字符串
while(line = br.readLine() != null) {
    bw.write(line);
    //单独写出换行操作
    bw.newLine(); //换行操作
    bw.flush();
}
br.close();
bw.close();

//  可以直接用PrintWriter 代替 BufferedWriter
// PrintWriter pw = new PrintWriter("003.txt");
// PrintWriter pw1 = new PrintWriter(outputStream, boolean autoFlush); //自动刷新
// pw.print(line); //输出一行,没有换行
// pw.println(line); // 输出一行,有换行

对象的序列化和反序列化

对象的序列化&反序列化

  • 对象的序列化,就是将Object 转换成byte序列
  • 对象的反序列化,就是将byte序列转换成Object

  • 序列化流:ObjectOutputStream, 是过滤流 -> writeObject 方法

  • 反序列化流:ObjectInputStream, -> readObject方法
序列化接口 (Serializable)

对象必须实现序列化接口,才能进行序列化,否则会出现异常;
序列化接口没有任何实现方法,只是一个标准。

ObjectOutputStream oos = new ObjectOutputStream(
    new FileOutputStream("001.txt"));
ObjectInputStream ois = new ObjectInputStream( 
    new FileInputStream("002.txt"));
Student stu = new Student("1001","张三"); // Student 类要实现Serializable 接口,否则出现异常
oos.writeObject(stu);
Student stu2 = (Student)ois.readObject();
oos.flush();
oos.close();
ois.close();

关键字: transient

transient 修饰的元素不会进行JVM默认的序列化
但是可以自己定义给元素的序列化。

Student 类,其中age有transient修饰

自己定义序列化和反序列化方法

例如在ArrayLis中,底层的数组是不一定会被填满的。
所以ArrayList 自己定义数组元素的序列化和反序列化,这样可以舍去空元素,提高性能。

序列化中子类和父类构造函数的调用

父类实现了序列化接口,其子类都能进行序列化。
定义的3个foo类
定义的3个类

序列化时递归调用父类构造函数
序列化时递归调用父类构造函数

反序列化操作1
反序列化操作1

反序列化操作1 结果: 只打印出了对象,没有打印出构造函数调用结果

反序列化操作1 结果: 只打印出了对象,没有打印出构造函数调用结果

另外3个类
bar 3个类

序列化时会打印3个构造函数。

反序列化时会打印父类的构造函数:
反序列化操作 bar 类
反序列化操作 bar 类

说明 对子类对象进行反序列化操作时,如果其父类没有实现序列化接口,那么其父类的构造函数会被调用。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章