Java第08次实验(流与文件)实验报告--网络2112-37号

第1次实验
0. 字节流与二进制文件

  1. 使用DataOutputStream与FileOutputStream将Student对象写入二进制文件students.data
    • 二进制文件与文本文件的区别。使用wxMEdit或Visual Studio Code+ Hex Editor插件分析生成的students.data。
    • try...catch...finally注意事项
    • 使用try...with...resouces关闭资源
  2. 使用DataInputStream与FileInputStream从student.data中读取学生信息并组装成Student对象。

首先定义一个Student对象,基本要求:学号,姓名,年龄,成绩。

private int id;
private String name;
private int age;
private double grade;

然后构建所需要的各种方法。

public Student(int id, String name, int age, double grade) {
      this.id = id;
      this.setName(name);
      this.setAge(age);
      this.setGrade(grade);
}

一键生成setter与getter。

public int getId() {
	return id;
}
public void setId(int id) {
	this.id = id;
}
public String getName() {
	return name;
}
public void setName(String name) {
	if (name.length()>10){
		throw new IllegalArgumentException("name's length should <=10 "+name.length());
	}
	this.name = name;
}
public int getAge() {
	return age;
}
public void setAge(int age) {
	if (age<=0){
		hrow new IllegalArgumentException("age should >0 "+age);
	}
	this.age = age;
}
public double getGrade() {
	return grade;
}
public void setGrade(double grade) {
	if (grade<0 || grade >100){
		throw new IllegalArgumentException("grade should be in [0,100] "+grade);
	}
	this.grade = grade;
}

重写tostring()方法。

@Override
public String toString() {
	return "Student [id=" + id + ", name=" + name + ", age=" + age + ", grade=" + grade + "]";
}	
 public static void main(String[] args)
        {

            String fileName="h:\\Students.data";			//输出的文件
            try(DataOutputStream dos=new DataOutputStream(new FileOutputStream(fileName)))//写入文件
            {
                Student[] stu=new Student[3];			//建立Student对象数组
                stu[0]=new Student(37,"hengxin",19,85.0);
                stu[1]=new Student(35,"chunyu",20,95.0);
                stu[2]=new Student(36,"lirong",20,90.0);
                for(Student stu1:stu) {					//for循环将对象写入文件
                    dos.writeInt(stu1.getId());
                    dos.writeUTF(stu1.getName());
                    dos.writeInt(stu1.getAge());
                    dos.writeDouble(stu1.getGrade());
                }

            } catch (FileNotFoundException e) {			//没找到文件的错误
                // TODO Auto-generated catch block
                e.printStackTrace();
                //System.out.println("no");测试代码
            } catch (IOException e) {					//IOException 是在使用流、文件和目录访问信息时引发的异常的基类。
                // TODO Auto-generated catch block
                e.printStackTrace();
               // System.out.println("no2");测试代码
            }
            try(DataInputStream dis=new DataInputStream(new FileInputStream(fileName)))//读取文件
            {
                while(dis.available()!=0) {				//available()读入剩下的所有字节
                    int id=dis.readInt();
                    String name=dis.readUTF();
                    int age=dis.readInt();
                    double grade=dis.readDouble();
                    Student stu=new Student(id,name,age,grade);
                    System.out.println(stu);
                }


            } catch (FileNotFoundException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
               // System.out.println("no3");
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
               // System.out.println("no4");
            }

        }

1. 字符流与文本文件:使用 PrintWriter(写),BufferedReader(读)
任务:

  1. 将Student.txt拷贝到Eclipse项目根目录(右键点击项目-选择paste)。请不要拷贝到src目录。
  2. 使用BufferedReader从编码为UTF-8的文本文件Student.txt中读出学生信息,并组装成对象然后输出。
  • 中文乱码问题(FileReader使用系统默认编码方式读取文件,可能会产生乱码,可使用InputStreamReader解决)。也可将Student.txt编码格式改为GBK,复现乱码。
  • String的split方法使用\s+可以使用多个空格作为分隔符。
  • 进阶:修改Students.txt文件,在正确的数据行中间增加一些错误行(如,每行只有3个数据,或者应该是数字的地方放入其他字符),修改自己的程序,让其可以处理出错的行(报错但可以继续运行)。
  1. 编写public static List readStudents(String fileName)从fileName指定的文本文件中读取所有学生,并将其放入到一个List中后返回。
  2. 使用PrintWriter的println方法将Student对象写入文本文件。基础代码见后。注意:缓冲区问题。
  3. 可选:使用ObjectInputStream/ObjectOutputStream读写学生对象。
public class Mian {

        public static void main(String[] args) throws IOException
        {
            String FileName="H:\\Students.txt";			//文件位置及名称
            BufferedReader br = null;					//缓冲区读取内容,避免中文乱码
            try {
                br = new BufferedReader(new InputStreamReader(new FileInputStream(FileName),"UTF-8"));
                //字符转换流InputStreamReader防止中文乱码
                String line = null;
                while((line=br.readLine())!=null)
                    System.out.println(line);           //打印每一行
            } finally{
                if (br!=null){
                    br.close();							//关闭文件
                }
            }
        }
}

使用GBK输出:

使用UTF-8输出:

public class Mian {

        public static void main(String[] args) throws IOException
        {
            String FileName="H:\\Students.txt";			//文件位置及名称
            BufferedReader br = null;					//缓冲区读取内容,避免中文乱码
                PrintWriter pw=new PrintWriter(new OutputStreamWriter(new FileOutputStream(FileName,true),"UTF-8"));
                //构造方法参数为String类型的对象,值应为文件全路径。若文件不存在,则会先创建文件。
                pw.print("\n4 陈六 21 93");				///n用于换行
                pw.close();								//一定要关闭
            }
        
        public static void ListreadStudents(String fileName) throws IOException{
            ArrayList<Student> StudentList=new ArrayList<Student>();//建立Student链表
            BufferedReader br = null;
            try {
                br = new BufferedReader(new InputStreamReader(new FileInputStream(fileName),"UTF-8"));//读文件
                while(br!=null) {
                    String line=br.readLine();
                    String[] stu=line.split("\\s+");	//正则表达式,用于判断换行等字符,如果遇到更多相同换行则会合并为一个
                    int id=Integer.parseInt(stu[0]);
                    String name=stu[1];
                    int age=Integer.parseInt(stu[2]);
                    double grade=Double.parseDouble(stu[3]);
                    Student Stu=new Student(id,name,age,grade);//建立对象
                    StudentList.add(Stu);				//添加
                }
            } catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} finally{
                if (br!=null){
                    br.close();							//关闭
                }
            }
}
}

运行截图:
无输出所以没有截图。
储存截图:

关于用ObjectInputStream/ObjectOutputStream读写学生对象,不是很理解这两个函数的使用方法,故参考网上相关代码。

public class Mian {

        public static void main(String[] args) throws IOException
        {
            String FileName="H:\\Students.txt";			//文件位置及名称
            try(
                    FileOutputStream fos=new FileOutputStream(FileName);
                    ObjectOutputStream oos=new ObjectOutputStream(fos))
                {
                    Student ts=new Student(5,"asd",14,60);
                    oos.writeObject(ts);
                }
                catch (Exception e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                try(
                    FileInputStream fis=new FileInputStream(FileName);
                    ObjectInputStream ois=new ObjectInputStream(fis))
                {
                    Student newStudent =(Student)ois.readObject();
                    System.out.println(newStudent);
                } catch (IOException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                } catch (ClassNotFoundException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }						
            }
        
        
}

运行截图:

无法成功运行,该错误的原因是没有对象序列化,出现这个问题的时候应该是想要将 该实体类对象保存到某个地方,那么此时必须需要序列化;一个类只有实现了Serializable接口,它的对象才是可序列化的。

解决办法:
在实例化类前面加implements Serializable即可。

2. 缓冲流(结合使用JUint进行测试)

  1. 使用PrintWriter往文件里写入1千万行随机整数,范围在[0,10],随机数种子设置为100.
  2. 从文件将每行读取出来转换成整数后相加。然后依次输出“个数 和 平均值(保留5位小数)”。
  • 对比使用BufferedReader与使用Scanner从该文件中读取数据(只读取,不输出),使用哪种方法快?

测试用例基础代码:

private static int NUM = 10000000;
private static long SEED = 100;
private static String FILENAME = "data.txt";
	
@BeforeAll
void write2File() throws FileNotFoundException {
	Random rnd = new Random(SEED); 
	PrintWriter pw = new PrintWriter(FILENAME);
	for (int i = 0; i < NUM; i++) {
		pw.println(rnd.nextInt(11));
	}
	pw.close();
		
}

注意:

  • 保留5位小数,可使用System.out.format进行格式化输出。
  • 要使用Scanner的hasNextXXX方法来判断是否到文件尾,否则会抛异常。
  • Scanner的hasNextXXX方法应与相对应的nextXXX方法配合使用,否则容易出错。
  • 请删除fail("Not yet implemented");并且在需要测试的方法上使用@Test进行标注。
public class Main {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		String FILENAME = "test.txt";
        double sum=0,aver;
        PrintWriter pw=null;
        try {
            pw = new PrintWriter(FILENAME);				//写入文件
            for(int i = 0;i<10000000;i++){				    //写入1千万行
            int r=new Random().nextInt(10);				//在10以内随机生成数
            sum+=r;
            pw.println(r);
            //System.out.println(r);
        }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }finally{
            pw.close();									//关闭文件
        }
        aver=sum/10000000;    							//生成平均数
        System.out.format("平均数为:%.5f\n", aver);	    	//保留五位小数
    
	    test();											//调用函数测定时间
	    Bufftest();
	}
	
	 public static void test() {
	        String FILENAME = "test.txt";
	        long begin = System.currentTimeMillis();	       //计算任务耗费的毫秒,System.currentTimeMillis()用于获取时间
	        Scanner scanner=null;
	        try {
	            scanner = new Scanner(new File(FILENAME));
	            while(scanner.hasNextLine()){                //只是读出每一行,不做任何处理
	                scanner.nextLine();
	            }
	        } catch (FileNotFoundException e) {
	            e.printStackTrace();
	        }finally{
	            scanner.close();
	        }
	        long end = System.currentTimeMillis();	
	        System.out.print("Scanner方法所用时间为:");
	        System.out.println("last "+(end-begin));	//用最后的时间减去开始的时间,得到这个程序运行的时间
	    }
	  //  @Test
	    public static void Bufftest() {					//同Scanner法
	        String FILENAME = "test.txt";
	        long begin = System.currentTimeMillis();
	        BufferedReader br = null;
	        try {
	             br = new BufferedReader(new FileReader(new File(FILENAME)));
	            while(br.readLine()!=null){};			//只是读出,不进行任何处理
	        } catch (FileNotFoundException e) {
	            e.printStackTrace();
	        } catch (IOException e) {
	            e.printStackTrace();
	        }finally{
	            try {
	                br.close();
	            } catch (IOException e) {
	                e.printStackTrace();
	            }
	        }
	        long end = System.currentTimeMillis();
	        System.out.print("BufferedReader方法所用时间为:");
	        System.out.println("last "+(end-begin));
	    }

}

存储位置:

运行截图:

从运行结果的时间上可以明显看出,Scanner方法比BufferedReader方法慢得多,所以BufferedReader方法更好。

思考题:
为什么以下代码生成的文件大小是0,而只有当count=16384(两倍的默认缓冲区大小)才可以真正写入到文件?

String fileName = "d:/Test.data";
int count = 16383;
PrintWriter pw = null;
try {
	pw = new PrintWriter(fileName);
	
	for (int i = 0; i < count; i++) {
		pw.print(1);
	}
	
} catch (FileNotFoundException e) {
	e.printStackTrace();
}finally{
	//pw.close();
}

答:没有调用flush方法和close方法,这样当向文件中写入数据时,数据流始终先保存到一段缓冲区中,当写入的数据流大于两倍缓冲区而没有进行刷新流操作时,JAVA会自动刷新流,将流写入到文件中。

第2次实验
3. 字节流之对象流
结合使用ObjectOutputStream、ObjectInputStream与FileInputStream、FileOuputStream实现对Student对象的读写。
编写如下两个方法:

  • public static void writeStudent(List stuList)
  • public static List readStudents(String fileName)
public static void writeStudent(List<Student> stuList)
    {
        String fileName="H:\\Student.data";
        try (   FileOutputStream fis=new FileOutputStream(fileName);
                ObjectOutputStream ois=new ObjectOutputStream(fis))
        {
            ois.writeObject(stuList);

        }
        catch (FileNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IOException e1) {
            // TODO Auto-generated catch block
            e1.printStackTrace();
        }
    }
public static List<Student> readStudents(String fileName)
    {
        List<Student> stuList=new ArrayList<>();
        try (   FileInputStream fis=new FileInputStream(fileName);
                ObjectInputStream ois=new ObjectInputStream(fis))
        {
            stuList=(List<Student>)ois.readObject();
        }
        catch (FileNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IOException e1) {
            // TODO Auto-generated catch block
            e1.printStackTrace();
        } catch (ClassNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return stuList;
    }

运行结果:

生成结果为一段乱码,但是这段代码我不是很理解,故借鉴参考代码,而且不理解为什么结果是这样的,就算改变编码格式,也还是乱码,我不理解。

5. 文件操作
编写一个程序,可以根据指定目录和文件名,搜索该目录及子目录下的所有文件,如果没有找到指定文件名,则显示无匹配,否则将所有找到的文件名与文件夹名显示出来。

  1. 编写public static void findFile(Path dir,String fileName)方法.
    以dir指定的路径为根目录,在其目录与子目录下查找所有和filename
    相同的文件名,一旦找到就马上输出到控制台。
public class Main {
	
		public static void main(String[] args) {

		// TODO Auto-generated method stub

		Path dir = Paths.get("H:\\");				//Path get(String first, String... more)文件的根目录

		findFile(dir, "Students.txt");				//找文件

		}

		public static void findFile(Path dir, String fileName) {

		File file = dir.toFile();					//用于返回表示此路径对象的java.io.File对象

		File[] files = file.listFiles();

		for (File now : files) {

		if (now.isFile()) {

		if (now.getName().equals(fileName)) {		//名字相同就打印

		System.out.println(now.getAbsolutePath());

		return;

		}

		} else if (now.isDirectory()) {				//isDirectory()是检查一个对象是否是文件夹

		findFile(now.toPath(), fileName);			//是则去子目录查找

		}

		}

		}

	}
		

运行截图:

  • 没有该文件:

  • 存在该文件:

实验小结

  1. 不是很理解ObjectOutputStream与ObjectInputStream
  • ObjectOutputStream的使用
  • ObjectOutputStream是一个高级流, 将 Java 对象的基本数据类型和图形写入 OutputStream。可以使用 ObjectInputStream 读取(重构)对象。通过在流中使用文件可以实现对象的持久存储。如果流是网络套接字流,则可以在另一台主机上或另一个进程中重构对象。
  • ObjectInputStream的使用
  • ObjectInputStream也是一个高级流,对以前使用 ObjectOutputStream 写入的基本数据和对象进行反序列化。
  • ObjectOutputStream 和 ObjectInputStream 分别与 FileOutputStream 和 FileInputStream 一起使用时,可以为应用程序提供对对象图形的持久存储。ObjectOutputStream用于序列化对象,ObjectInputStream 用于恢复那些以前序列化的对象(反序列化)。其他用途包括使用套接字流在主机之间传递对象,或者用于编组和解组远程通信系统中的实参和形参。
  1. 在第2次的第3个实验中,我还无法理解代码以及不知道为什么一直都是乱码,这个乱码就是正确的格式吗?如何才能不是乱码呢?(未解决)

  2. 关于用ObjectInputStream/ObjectOutputStream读写学生对象,无法进行序列化导致运行失败(未解决)

最后总结:
通过这次实验报告,我深切的感受到了自己的编程能力的不足,而且对于各种方面知识的理解的匮乏,在做报告中我需要查询各种资料,以及jdk文档,找到各种函数的用处,这是对我能力的一次大大的提高,之后我也会对别的实验做出一些练习,提升自己的思维以及编程能力,希望下次实验报告能做的更好。

posted @ 2022-12-25 00:53  网络2112许恒欣=w=  阅读(248)  评论(0编辑  收藏  举报