《Java面向对象程序设计》学习笔记——第 12 章 输入流与输出流

​笔记汇总:《Java面向对象程序设计》学习笔记

第 12 章 输入流与输出流

12.1 File 类

File 对象主要用来获取文件本身的一些信息,例如文件所在的目录、文件的长度和文件的读/写权限等,不涉及对文件的读/写操作。

创建 File 对象的构造方法有以下 3 个:

File(String filename);
File(String directoryPath,String filename);
File(File f,String filename);

其中, filename 是文件名字 ,directoryPath 是文件的路径, f 是指定成一个目录的文件。默认是当前目录。

12.1.1 文件的属性

常用方法,其他见书P329

  • public String getName() :获取文件的名字。
  • public boolean canRead() :判断文件是否是可读的。
  • public boolean canWrite() :判断文件是否可以被写入。
  • public boolean exists() :判断文件是否存在。
  • public long length() :获取文件的长度(单位是字节)。
// 例1:使用 File 类的一些方注,获取某些文件的信息
package tom.jiafei;
import java.io.*;
public class Example12_1 {
   public static void main(String args[]) {
      System.out.println(E.class.getPackage().getName); // tom.jiafei 输出包名
      File f1=new  File("C:\\chapter12","Example12_1.java");
      File f2=new File("c:\\chapter11");
      System.out.println(f1.getName()+"是可读的吗:"+f1.canRead());
      System.out.println(f1.getName()+"的长度:"+f1.length());
      System.out.println(f1.getName()+"的绝对路径:"+f1.getAbsolutePath());
      System.out.println(f2.getName()+"是目录吗?"+f2.isDirectory());
   }
}
// 例2:列出当前目录(应用程序所在的目录)下全部 java 文件的名字
// Example12_2.java
import java.io.*;
public class Example12_2 {
   public static void main(String args[]) {
      File dir=new File(".");
      FileAccept fileAccept=new FileAccept(); // 过滤
      fileAccept.setExtendName("java"); // 设置文件后缀名
      String fileName[]=dir.list(fileAccept); // 把所有满足条件的文件列出来,存入数组
      for(String name:fileName) {
          System.out.println(name);
      }
   }
}

// FileAccept.java
import java.io.*;
public class FileAccept implements FilenameFilter {
   private String extendName;
   public void setExtendName(String s) {
      extendName="."+s;
   }
   public boolean accept(File dir,String name) { //重写接口中的方法
      return name.endsWith(extendName);
   }              
}

大概率不会考,了解就行

// 例3:Runtime 对象打开 windows 平台上的记事本程序并运行了例子 11.6
import java.io.*;
public class Example12_3 {
   public static void main(String args[]) {
      try{ 
           Runtime ce=Runtime.getRuntime();
           File file=new File("c:/windows","Notepad.exe");
           ce.exec("java Example11_6"); 
           ce.exec(file.getAbsolutePath());
      }
      catch(Exception e) {
           System.out.println(e);
      } 
   } 
}

12.1.2 目录

12.1.3 文件的创建与删除

12.1.4 运行可执行文件

不用看,大概率不会考

12.2 文件字节流

InputStream , OutputStream :字节流。

子类: FileInputStream 和 FileOutputSream 类。

使用输入流通常包括 4 个基本步骤:

  • 设定输入流的源
  • 创建指向源的输入流
  • 让输入流读取源中的数据
  • 关闭输入流
// 使用文件字节输入流读取文件,将文件的内容显示在屏幕上
int b;
byte tom[]=new byte[18];
try{
	File f=new File("Example12_4.java");
	FileInputStream in=new FileInputStream(f);
	// 不等于 -1 表示 当前有数据可以读取
	while((b=in.read(tom,0,18))!=-1) { // 从第 0 个字符开始读,读取 18 个字符,放入 tom 里
		String s=new String (tom,0,b);
		System.out.print(s);
	}
	in.close();
}catch(IOException e) {
	System.out.println("File read Error"+e);
}

使用输出流通常包括 4 个基本步骤:

  • 给出输出流的目的地

  • 创建指向目的地的输出流

  • 让输出流把数据写入到目的地

  • 关闭输入流

// 首先使用具有刷新功能的构造方法创建指向文件 a.txt 的输出流、并向 a.txt 文件写入:新年快乐,然后再选择使用不刷文件的构造方法指向 a.txt ,并向文件写入(即尾加): Happy New Year
byte [] a = "新年快乐".getBytes(); // 8字节
byte [] b = "Happy New Year".getBytes(); // 14字节
File file = new File("a.txt");                         //输出的目的地
try{  
	OutputStream out = new FileOutputStream(file);      //指向目的地的输出流
	System.out.println(file.getName()+"的大小:"+file.length()+"字节");//a.txt的大小:0字节
	out.write(a);                                    //向目的地写数据
	out.close();
	out = new FileOutputStream(file,true);             //准备向文件尾加内容,(true为刷新,false则为不刷新,可以理解为刷新为重置)
	System.out.println(file.getName()+"的大小:"+file.length()+"字节");//a.txt的大小:8字节
	out.write(b,0,b.length);  // 从第 0 个位置开始写,写 b.length 个字节
	System.out.println(file.getName()+"的大小:"+file.length()+"字节");//a.txt的大小:22字节
	out.close();
}catch(IOException e) {
	System.out.println("Error "+e);
}

12.3 文件字符流

Reader 和 Writer 类子类: FileReader 、 FiIeWriter

(字节流不能很好地操作 Unicode 字符,比如,一个汉字在文件中占用 2 个字节,如果使用字节流,读取不当会出现“乱码”现象。)

// 使用字符输入、输出流读写文件。将一段文字加密后存入文件,然后再读取。
char a[]="四月十二日10点发起总攻".toCharArray();   
int n=0,m=0;
try{
	File f=new File("secret.txt");
	for(int i=0;i

12.4 缓冲流

(简单理解就是:源与目的地之间有个缓冲区,而源/目的地与缓冲区各有一个缓冲流用来读取/写入)

BufferedReader 和 BufferedWriter 类

BufferedReader 类和 BufferedWriter 的构造方法分别是:

  • BufferedReader (Reader in)

  • BufferedWriter (Writer out)

BufferedReader 流能够读取文本行,方法是 readLine()

使用输入流通常包括 4 个基本步骤:

  • 设定输入流的源
  • 创建指向源的输入流
  • 输入流读取源中的数据
  • 关闭输入流

使用输出流通常包括 4 个基本步骤:

  • 给出输出流的目的地
  • 创建指向目的地的输出流
  • 让输出流把数据写入到目的地
  • 关闭输出流
// 使用 BufferedWriter 流把字符串按行写入文件,然后再使用 BufferedReader 流按行读取文件
// 目的地 ← 缓冲流1 ← 缓冲区 ← 缓冲流2 ← 源(文件)     关流: 先关缓冲流2再关缓冲流1(先关进再关出)
//    源 → 缓冲流1 → 缓冲区 → 缓冲流2 → 目的地(文件)  关流: 先关缓冲流1再关缓冲流2(先关进再关出)
File file=new File("Student.txt");
String content[]={"天气预报:","北京晴","上海多云,有小雨","大连晴,有时多云"};
try{
	FileWriter outOne=new FileWriter(file);
	BufferedWriter outTwo= new BufferedWriter(outOne);
	for(String str:content) {
		outTwo.write(str);
		outTwo.newLine(); // 换行
	}
	outTwo.close();
	outOne.close();
	FileReader inOne=new FileReader(file);
	BufferedReader inTwo= new BufferedReader(inOne);
	String s=null;
	while((s=inTwo.readLine())!=null) { // readLine()能直接读取一行
		System.out.println(s);
	}
	inOne.close();
	inTwo.close();
}catch(IOException e) {
	System.out.println(e);
}  

12.5 使用文件对话框

应该不考,跳过

12.6 随机流

(即可当输入流,也可当输出流,还可以随机改编读写位置:seek(),相对之前的流,读写方面更加强大)

RandomAccessFiIe流

  • RandomAccessFiIe 类创建的流与前面的输入、输出流不同
  • RandomAccessFile 类既不是 InputStream 类的子类,也不是 OutputStram 类的子类
  • RandomAccessFiIe 类创建的流的指向既可以作为源也可以作为目的地
  • 当准备对一个文件进行读写操作时,可以创建一个指向该文件的随机访问文件流即可,这样既可以从这个流中读取文件的数据,也可以通过这个流写入数据到文件。

RandonAccessFiIe(String name ,String mode)

  • 参数 name 用来确定一个文件名,给出创建的流的源,也是流目的地。
  • 参数 mode 取 r (只读)或 rw (可读写),决定创建的流对文件的访问权利。

RandomAccessFiIe (File file ,String mode)

RandomAccessFile 类中的方法:

  • seek (long a) 方法用来定位 RandomAccessFile 流的读写位置,其中参数 a 确定读写位置距离文件开头的字节个数。

  • 可以调用 getFiIePointer() 方法获取流的当前读写位置。

RandomccessFile 流对文件的读写比顺序读写更为灵活

// 把几个 int 型整数写入到一个名字为 tom.dat 文件中,然后按相反顺序读出这些数据
RandomAccessFile inAndOut=null;
int data[]={1,2,3,4,5,6,7,8,9,10};
try{
	inAndOut=new RandomAccessFile("tom.dat","rw");
	for(int i=0;i=0;i--) { //一个int型数据占4个字节,inAndOut从
		inAndOut.seek(i*4);          //文件的第36个字节读取最后面的一个整数,
		System.out.print(","+inAndOut.readInt()); //每隔4个字节往前读取一个整数
	}
	inAndOut.close();
}catch(IOException e){} 
// RandomAccessFi1e 流使用 readLine() 读取文件
RandomAccessFile in=null;
try{
    in=new RandomAccessFile("Example12_10.java","rw");
    long length=in.length();  //获取文件的长度
    long position=0;
    in.seek(position);       //将读取位置定位到文件的起始 
    while(position

12.7 数组流

流的源和目标除了可以是文件外,还可以是计算机内存。

(写在内存里,关闭程序就没有了)

字节数组输入流

ByteArrayInputStream 和字节数组使用字节数组作为流的源:

ByteArrayInputStream(byte[] buf);

ByteArrayInputStream(byte[] buf , int offset , int length);

第一个构造方法构造的字节数组流的源是参数 buf 指定的数组的全部字节单元

第二个构造方法构造的字节数组流的源是 buf 指定的数组从offset 处按顺序取 length 个字节单元。

字节数组输出流

ByteArrayOutputStream()使用字节数组作为目标。

ByteArrayOutputStream();

ByteArrayOutputStream(int size) ;

第一个构造方法构造的字节数组输出流指向一个默认大小区,如果输出流向缓冲区写入的字节个数大于缓冲区时,缓冲区的容量会自动增加。

第二个构造方法构造的字节数组输出流指向的缓冲区的初始大小由参数 size 指定,如果输出流向缓冲区写入的字节个数大于缓冲区时,缓冲区的容量会自动增加。

调用

public byte[] toByteArray();

方法可以返回输出流写入到缓冲区的全部字节。

与数组字节流对应的是字符数组流 CharArrayReader 和 CharArrayWriter 类,字符数组流分别使用字符数组作为流的源和目标。

// 使用数组流向内存(输出流的缓冲区)写入 "How are you" 和 "您好" ,然后再从内存读取曾写入的数据
try {
    // 创建一个字节数组输出流
    ByteArrayOutputStream outByte = new ByteArrayOutputStream();
    // 将字符串 "How are you" 转换为字节数组
    byte[] byteContent = "How are you".getBytes();
    // 将字节数组写入输出流
    outByte.write(byteContent);

    // 创建一个字节数组输入流,使用之前写入的字节数组作为输入
    ByteArrayInputStream inByte = new ByteArrayInputStream(outByte.toByteArray());
    // 创建一个与输出流大小相同的字节数组,用于接收从输入流读取的数据
    byte backByte[] = new byte[outByte.toByteArray().length];
    // 从输入流读取数据,并将其存储到 backByte 数组中
    inByte.read(backByte);
    // 将读取的字节数组转换为字符串并打印输出
    System.out.println(new String(backByte));

    // 创建一个字符数组输出流
    CharArrayWriter outChar = new CharArrayWriter();
    // 将字符串 "您好" 转换为字符数组
    char[] charContent = "您好".toCharArray();
    // 将字符数组写入输出流
    outChar.write(charContent);

    // 创建一个字符数组输入流,使用之前写入的字符数组作为输入
    CharArrayReader inChar = new CharArrayReader(outChar.toCharArray());
    // 创建一个与输出流大小相同的字符数组,用于接收从输入流读取的数据
    char backChar[] = new char[outChar.toCharArray().length];
    // 从输入流读取数据,并将其存储到 backChar 数组中
    inChar.read(backChar);
    // 将读取的字符数组转换为字符串并打印输出
    System.out.println(new String(backChar));
}
catch (IOException exp) {
    // 捕获可能发生的输入输出异常,并进行处理
}

12.8 数据流

DataInputStream 和 DataOutputStream 类创建的对象称为数据输入流和数据输出流。

(必须指向一个流,不能是文件之类的)

DataInputStream 输入流

DatalnputStream (lnputStream in)创建的数据输入流指向一个由参数 in 指定的底层输入流。

DataOutputStream 输出流

DataOutputStream (OutputStream out)创建的数据输出流指向一个由参数 out 指定的底层输出流。

数据流允许程序按着机器无关的风格读取写 Java 原始数据。当读写时,不必再关心这个数据应当占多少个字节。

// 写几个 Java 类型的数据到一个文件,然后再读出来
File file=new File("apple.txt");
try{
    FileOutputStream fos=new FileOutputStream(file);
    DataOutputStream outData=new DataOutputStream(fos);
    outData.writeInt(100);
    outData.writeLong(123456);  
    outData.writeFloat(3.1415926f);
    outData.writeDouble(987654321.1234);
    outData.writeBoolean(true);
    outData.writeChars("How are you doing ");
}  catch(IOException e){}

try{
    FileInputStream fis=new FileInputStream(file);
    DataInputStream inData=new DataInputStream(fis);
    // 要按顺序(的类型)读取,不然会有问题
    System.out.println(inData.readInt());    //读取int数据
    System.out.println(inData.readLong());   //读取long数据 
    System.out.println(inData.readFloat()); //读取float数据
    System.out.println(inData.readDouble()); //读取double数据
    System.out.println(inData.readBoolean());//读取boolean数据
    char c;
    while((c=inData.readChar())!='\0') {       //'\0'表示空字符。
    	System.out.print(c);
    } 
} catch(IOException e){}

12.9 带进度条的输入流

应该不考,跳过

12.10 对象流

ObjectInputStream 和 ObjectOutputStream 类创建的对象称为对象输入流和对象输出流。

(通过构造方法可看出,目的地和源必须是个流)

ObjectInputStream 和 ObjectOutputStream 类的构造方法:

  • ObjectInputStream (InputStream in)

  • ObjectOutputStream (OutputStream out)

对象输出流使用 writeObject (Object obj) 方法将一个对象 obj 写入到一个文件。

对象输入流使用 readObject() 读取一个对象到程序中。

序列化对象

  • 当使用对象流写入或读入对象时,要保证对象是序列化的。这是为了保证能把对象写入到文件,并能再把对象正确读回到程序中的缘故

  • 一个类如果实现了 serializable 接口,那么这个类创建的对象就是所谓序列化的对象。

    serializable 接口中没有方法,因此实现该接口的类不需要实现额外的方法。

    需要注意的是,使用对象流把一个对象写入到文件时不仅要保证该对象是序列化的,而且该对象的成员对象也必须是序列化的。

  • Java 类库提供给我们的绝大多数对象都是所谓序列化的,比如组件等。

// 使用对象流读写 Student 创建的对象
public class Student implements Serializable { //实现Serializable接口
   String name=null;
   double height; 
   public void setName(String name) {
      this.name=name;
   }
   public void setHeight (double height) {
     this.height=height;
   }
}

public class Example12_14 {
   public static void main(String args[]) {
      Student zhang=new Student();
      zhang.setName("张三");
      zhang.setHeight(1.77); 
      File file=new File("people.txt");
      try{ 
          // 创建一个文件输出流,将数据写入指定的文件
          FileOutputStream fileOut=new FileOutputStream(file);
          // 创建一个对象输出流,用于将对象写入文件输出流
          ObjectOutputStream objectOut=new ObjectOutputStream(fileOut);
          // 将对象 zhang 写入对象输出流,将其序列化并保存到文件中
          objectOut.writeObject(zhang); 
          // 关闭对象输出流
          objectOut.close(); 
          // 创建一个文件输入流,用于从指定文件读取数据
          FileInputStream fileIn=new FileInputStream(file);
          // 创建一个对象输入流,用于从文件输入流读取对象数据
          ObjectInputStream objectIn=new ObjectInputStream(fileIn);
          // 从对象输入流中读取对象数据,并将其反序列化为 Student 类型的对象
          Student li=(Student)objectIn.readObject(); // 一定要强制转换,因为所有所有被写入文件的认为是object对象,序列化了,得强制转换为原来的类型
          // 修改对象 li 的姓名为 "李四"
          li.setName("李四");
          // 关闭对象输入流
          objectIn.close();
          // 修改对象 li 的身高为 1.88
          li.setHeight(1.88); 
          // 打印对象 zhang 的姓名和身高
          System.out.println(zhang.name+" 身高是:"+zhang.height);
          // 打印对象 li 的姓名和身高
          System.out.println(li.name+" 身高是:"+li.height); // li 改变了姓名和身高不影响 zhang
       }
       catch(ClassNotFoundException event) {
          System.out.println("不能读出对象");
       }
       catch(IOException event) {
          System.out.println(event);
       }
   }
}

12.11 序列化与对象克隆

一个类的两个对象如果具有相同的引用,那么他们就具有相同的实体和功能。

比如

A one=new A();
A two=one;
// 假设 A 类有名字为 x 的 int 型成员变量,那么,如果进行如下的操作
two.x = 100;
// 那么 one.x 的值也会是 100

再比如,某个方法的参数是 People 类型

public void f (People p) {
	p.x = 200;
}
// 如果调用该方法时,将 Peop1e 的某个对象的引用,比如 zhang ,传递给参数 p ,那么该方法执行后,zhang.x 的值也将是 200

有时想得到对象的一个“复制品”,复制品实体的变化不会引起原对象实体发生变化,反之亦然。这样的复制品称为原对象的一个克隆对象或简称克隆。

使用对象流很容易获取一个序列化对象的克隆,只需将该对象写入对象输出流指向的目的地,然后将该目的地作为一个对象输入流的源,那么该对象输入流从源中读回的对象一定是原对象的一个克隆,即对象输入流通过对象的序列化信息来得到当前对象的一个克隆。

12.12 文件锁

应该不太会考,看看就行

经常出现几个程序处理同一个文件的情景,比如同时更新或读取文件。应对这样的问题作出处理,否则可能发生混乱。

JDK 1.4 版本后, Java 提供了文件锁功能,可以帮助解决这样的问题。

RondomAccessFile 创建的流在读写文件时可以使用文件锁,那么只要不解除该锁,其它程序无法操作被锁定的文件。

使用文件锁的步骤如下:

// 1. 创建一个随机访问文件对象 input,指定要打开的文件为 "Example.java",以读写模式打开
RandomAccessFile input=new RandomAccessFile ("Examp1e.java","rw");
// 2. 获取文件对象 input 的文件通道 channel
FileChannel channel=input.getChannel();
// 3. 尝试对文件通道进行加锁,获取文件锁 lock
FileLock lock=channel.tryLock();

对一个文件加锁之后,如果想读、写文件必须让 FileLock 对象调用 release() 释放文件锁。

例如:lock.release();

12.13 使用 Scanner 类解析文件

第 9 章的有关知识(见 9.3 和 9. 5 ):解析所需要的内容,其优点是处理速度快,但如果读入的内容较大将消耗较多的内存,即以空间换取时间。

本节介绍怎样借助 Scanner 类和正则表达式来解析文件,比如,要解析出文件中的特殊单词,数字等信息。

使用 Scanner 类和正则表达式来解析文件的特点是以时间换取空间,即解析的速度相对较慢,但节省内存。

读取文件中商品价格并计算平均价格

// goods.txt    商品清单文件,包含商品名称和价格
电视机 2989.98元
洗衣机 5678.876元
冰箱   6589.99元
import java.io.*;
import java.util.*;

public class Example12_18 {
    public static void main(String args[]) {
        // 创建一个 File 对象,指定要读取的文件为 "goods.txt"
        File file = new File("goods.txt");
        // 创建一个 Scanner 对象,用于读取文件内容
        Scanner sc = null;
        // 记录商品数量的变量
        int count = 0;
        // 记录商品总价的变量
        double sum = 0;

        try {
            // 记录读取到的商品价格的变量
            double price = 0;
            // 将文件与 Scanner 对象关联
            sc = new Scanner(file);
            // 设置 Scanner 对象的分隔符,用于提取价格信息
            sc.useDelimiter("[^0123456789.]+");
            // 循环读取文件中的下一个 double 类型的值
            while (sc.hasNextDouble()) {
                // 读取商品价格
                price = sc.nextDouble();
                // 商品数量加1
                count++;
                // 累加商品价格
                sum = sum + price;
                // 打印商品价格
                System.out.println(price);
            }
            // 计算商品平均价格
            double aver = sum / count;
            // 打印商品平均价格
            System.out.println("平均价格:" + aver);
        } catch (FileNotFoundException exp) {
        	// 捕获并处理文件未找到异常
        	System.out.println(exp);
        }
    }
}

12.14 小结

《Java面向对象程序设计》学习笔记——第 12 章 输入流与输出流_第1张图片

你可能感兴趣的:(java,学习,笔记)