java基础(5)

java基础(5)

1.异常

概述:程序出现了不正常的情况

程序的异常:Throwable,包含java中所有的问题和异常

  • 严重问题:Error,我们不处理,这种问题一般都是很严重的,比如说内存溢出
  • 一般问题:Exception
    • 编译期问题:不是RuntimeException的异常,必须进行处理,因为不处理,编译就不能通过
    • 运行期问题:RuntimeException 这种问题我们不处理,因为是你的问题,而且这个问题出现肯定是我们的代码不够严谨,需要修正代码的

JVM默认是如何处理异常的

如果程序出现了问题,我们没有做任何处理,最终jvm就会做出默认的处理:把异常的名称,原因及出现问题的行号等信息输出在控制台,同时会结束程序

自己如何处理异常

1.try...catch...finally:

一个异常的处理格式为:

try{
	可能出现问题的代码;
}catch(异常名 变量名){
	针对问题的处理;
}finally{
	一定要执行的语句体;
}

过程:在try里面发现问题后,jvm会帮我们生成一个异常对象,然后把这个异常抛出,和catch里面的异常类进行匹配,如果该对象是某个类型的,就会执行该catch里面的处理信息

注意:

  • try里面的代码越少越好
  • catch里面必须要有东西,不然就不是捕捉异常,而是在隐藏异常了

多个异常的处理格式为:

A方法:每一个异常写一个try...catch

B方法:
try{
	...
}catch(异常名 变量名){
	...
}catch(){
	...
}...

C方法(jdk7才有的):
try{
...
}catch(异常名1|异常名2|异常名3... 变量名){
	...
}
C方法的两个弊端:
1.处理方式是一致的,针对的是同一类型的问题,给出同一个处理
2.多个异常间必须是平级关系

注意:

  • 能明确的尽量明确,不要用大的异常类来处理
  • 平级关系的异常谁前谁后无所谓,但是如果是子父关系,夫必须在后面

2.throws(常用于方法的异常抛出)

概述:有些时候,我们时可以对异常进行处理的,但是又有些时候,我们根本就没有权限去处理某个异常,或者说,我处理不了,我就不处理了,为了解决这种情况,java就提供了另一种处理方案:抛出(throws)

格式:throws 异常类名(可以多个)

注意:

  • 尽量不要在main方法商抛出异常
  • 编译期异常抛出,将来调用者必须处理,运行期异常抛出,调用者可以不用处理

编译期异常与运行期异常的区别

编译期异常:java程序必须显示处理(try...catch...捕捉异常),否则程序就会发生错误,无法通过编译;

运行时异常:无需显示处理,也可以和编译异常一样处理(try...catch...捕捉异常)

Throwable的几个常见方法

1.public String getMessage():返回该异常的消息字符串

2.public String toString():返回该异常的简单信息描述,信息描述部分由以下几部分构成:

  • 此对象的类名(包括包名的全路径类名)
  • ": "(冒号和一个空格)
  • 调用次对象的getLocalizedMessage()方法的结果(默认返回的是getMessage()的内容)

3.public void printStackTrace():获取异常名和异常信息,以及异常出现在程序中的位置,把消息输出在控制台,它与由jvm抛出异常显示在控制台的显示内容一样,但是通过这个方法,后面的程序仍可以继续走

throw关键字

概述:如果出现了异常情况,我们可以把该异常抛出,这个时候的抛出的应该是异常的对象(只能一个),而不是类名

throws与throw的区别:

throws:

  • 用在方法声明后面,跟的是异常类名
  • 可以跟多个异常类名,用逗号隔开
  • 表示异常抛出,由该方法的调用者来处理
  • throws表示的是出现异常的一种可能性,并不一定会发生

throw:

  • 用在方法体内,跟的是异常对象名
  • 只能抛出一个异常
  • 表示抛出异常,由方法体内的语句处理
  • throw表示抛出异常,执行throw则一定抛出了某种异常

finally关键字

概述:被finally控制的语句体一定会执行,但是在执行finally之前jvm推出了(比如System.exit(0))

finally的作用:用于释放资源,在IO流操作和数据库操作中可以见到

finally面试题:

1.final,finally,finalize的区别?

答:final:可以修饰类,成员变量,成员方法,修饰类,类不能被继承,修饰成员变量,变量是常量,修饰成员方法时,方法不能被重写

finally:是异常处理的一部分,用于释放资源,一般来说,代码肯定会执行,特殊情况下,如在执行到finally之前jvm推出了

finalize:是Object类的一个方法,用于垃圾回收

2.如果catch里面有return语句,请问finally里面的代码还会执行么?如果会,请问是return前,还是return后?

答:会,在return前(准确的说是在中间)

看这个面试题的示例代码:

public class Demo2 {
	public static void main(String[] args) {
		System.out.println(method());//30
	}
	public static int method(){
		int a = 10;
		try{
			System.out.println(a / 0);
		}catch(ArithmeticException e){
			a = 30;
			/*
			 * return a 在程序执行到这一步的时候,这里不是return a而是return 30,
			 * 这个返回路径就形成了,但是后面又看到了finally,所有继续执行finally的内容,a = 40
			 */
			return a;
		}finally{
			a = 40;
		}
		return a;
	}
}

//结果:30

自定义异常

概述:java不可能对所有情况都考虑到,所以,在实际的开发中,我们可能需要自己定义异常,而我们随意写的一个类,是不能作为异常类来看的,要想你的类是一个异常类,就必须继承自Exception或RuntimeException

自定义异常示例:

public class MyException extends Exception{
public MyException(){
}
public MyException(String message){
	super(message);
}
}

异常的注意事项:

1.子类重写父类方法时,子类的方法必须抛出相同的异常或父类异常的子类

2.如果父类抛出了多个异常,子类重写父类时,只能抛出相同的异常或者是它的子类,子类不能抛出父类没有的异常

3.如果被重写的方法没有异常抛出,那么子类的方法绝对不可以抛出异常,如果子类方法内有异常,那么子类只能try,不能throws

2.File类

概述:我们要想实现IO的操作,就必须知道硬盘上文件的表现形式,而java就提供了一个类File供我们使用

File:文件和目录路径名的抽象表示方法

构造方法

1.File (String pathname):根据一个路径得到File对象

2.File (String parent, String child):根据一个目录和一个子文件得到File对象

3.File (File parent, String child):根据一个父File对象和一个子文件得到File对象

注意:如果你创建文件或者文件夹的时候没有填写盘符路径,那么,默认在项目路径下

成员方法

1.创建功能:

  • public boolean createNewFile():创建文件,如果文件存在,就不创建了,返回false
  • public boolean mkdir():创建文件夹,如果存在这样的文件夹,就不创建了,返回false
  • public boolean mkdirs():创建多层次文件夹,如果父文件夹不存在,也会帮你创建出来,如果存在这样的文件夹,就不创建了,返回false

2.删除功能

  • public boolean delete():可以删除文件或文件夹,java中的删除不走回收站,如果要删除一个文件夹,文件夹里面不能有文件或者文件夹

3 .重命名功能

  • public boolean renameTo(File dest):如果路径相同,就是改名,如果路径不同,就是改名并剪切

4 . 判断功能

  • public boolean isDirectory():判断是否是目录
  • public boolean isFile():判断是否是文件
  • public boolean exists():判断是否存在
  • public boolean canRead():判断是否可读
  • public boolean canWrite():判断是否可写
  • public boolean isHidden():判断是否隐藏

5.获取功能

  • public String getAbsolutePath():获取绝对路径
  • public String getPath():获取相对路径(相对于当前项目的路径)
  • public String getName():获取名称
  • public String length:获取长度,字节数
  • public long lastModified():获取最后一次的修改机会,毫秒值
  • public String[] list():获取指定目录下的所有文件或者文件夹的名称数组,list()括号里面还可以放一个文件过滤器对象,实现的是FilenameFilter接口
  • public File[] listFiles():获取指定目录下的所有文件或者文件夹得File数组,listFiles()括号里面也可以放一个文件过滤器对象,实现的是FilenameFilter接口

案例代码(获取d盘下的.jpg文件):

/*
 * 获取d盘下的.jpg文件
 */
//第一种做法:先遍历所有的文件或问价夹再挑出满足条件的,再输出到控制台

import java.io.File;

public class Demo1 {
	public static void main(String[] args) {
		//封装d盘目录
		File file = new File("d:\");
		//获取该目录下所有文件或者文件夹的File对象
		File[] fileArray = file.listFiles();
		
		for(File f:fileArray){
			//判断是否是文件
			if(f.isFile()){
				//判断文件是否以.jpg结尾
				if(f.getName().endsWith(".jpg")){
					System.out.println(f.getName());
				}
			}
		}
	}
}

//第二种方法:先过滤出满足条件的,在显示到控制台

import java.io.File;
import java.io.FilenameFilter;

public class Demo2 {
	public static void main(String[] args) {
		File file = new File("d:\");
		//通过文件名称过滤器实现
		String[] strArray = file.list(new FilenameFilter(){
			
			@Override
			public boolean accept(File dir, String name) {
				//这两个条件都为true才会把文件或文件夹添加到数组中,这里的dir是盘符路径,后面的name为文件名
				return new File(dir, name).isFile() && name.endsWith(".jpg");
			}
			
		});
		
		for(String s: strArray){
			System.out.println(s);
		}
	}
}

3.递归

概述:递归就是方法定义中调用方法本身的现象,需要注意的是方法的嵌套不是递归

注意事项:

  • 递归一定要有出口,否则就是死递归
  • 递归的次数不能太多,否则就内存溢出
  • 构造方法不能递归使用

如何实现递归?

  • 找规律和出口条件
  • 写一个方法
  • 写出口条件

代码示例1(求阶乘):

public class Demo3 {
	public static void main(String[] args) {
		System.out.println("5的阶乘为:" + factorial(5));
	}
	public static int factorial(int n){
		if(n == 1){
			return 1;
		}else{
			return n * factorial(n - 1);
		}
	}
}

代码示例2(斐波那契数列):

public class Demo4 {
	public static void main(String[] args) {
		System.out.println("第20个斐波那契数列值为:" + fib(20));//第20个斐波那契数列值为:6765
	}
	
	public static int fib(int n){
		if(n == 1 || n == 2){
			return 1;
		}else{
			return fib(n - 1) + fib(n - 2);
		}
	}
}

代码示例3(递归输出指定目录下的所有.java文件的绝对路径):

import java.io.File;

public class Demo5 {
	public static void main(String[] args) {
		//封装目录
		File file = new File("d:\javaProj");
		//递归功能实现
		getAllJavaFilePaths(file);
	}
	public static void getAllJavaFilePaths(File source){
		//获取该目录下所有文件或者文件夹的File数组
		File[] fileArray = source.listFiles();
		
		for(File f: fileArray){
			if(f.isDirectory()){
				//如果是文件夹,就调用自身方法
				getAllJavaFilePaths(f);
			}else {
				if(f.getName().endsWith(".java")){
					System.out.println(f.getAbsolutePath());
				}
			}
		}
	}
}

4.IO流

概述:用来设备间的数据传输问题

IO流的分类

按流向分:

1.输出流:指的是从内存写内容到硬盘中

2.输入流:指的是从硬盘读取内容到内存

按数据类型分:

1.字节流:可以处理任何一种文件数据(文本,图片,声音,视频等),字节流分为字节输入流和字节输出流

  • 字节输入流:读取数据:InputStream(抽象类)
  • 字节输出流:写数据:OutputStream(抽象类)

2.字符流:只能处理纯文本文件,字符流也分为字符输入流和字符输出流

  • 字符输入流:读取数据:Reader(抽象类)
  • 字符输出流:写数据:Writer(抽象类)

5.字节流操作

FileOutputStream写入文件

概述:使用字节流往文件里写数据

创建字节输出流对象做了几件事情:

  • 调用系统功能去创建文件(如果没有,则创建文件,有则直接使用)
  • 创建字节输出流对象对象
  • 把该字节输出流对象对象指向这个文件

构造方法:

  • FileOutputStream(File file)
  • FileOutputStream(String name)

字节输出流的操作步骤:

  • 创建字节输出流对象
  • 写数据
  • 释放资源
    • 让流对象变成垃圾,这样就可以被垃圾回收器回收了
    • 通知系统去释放跟该文件相关的资源

字节输出流的三个write方法:

  • public void write(int b):写一个字节
  • public void write(byte[] b):写一个字节数组
  • public void write(byte[] b, int offset, int len):写一个字节数组的一部分

写数据时如何实现换行呢?

换行其实也是一种字符,叫做“换行符”

不同的操作系统换行符的表示也不同:

  • Windows:
  • macOS:早期为 ,现在是
  • Linux:

因此,我们在Windows上开发程序时,往文件里写入字符“ ”即可

如何实现追加写入?

我们的构造方法创建出来的字节输出流对象,使用这个字节输出流对象写数据时默认是在文件开始处写内容,这样就是重写了文件的内容,而其实我们的构造方法还有第二个为boolean值的参数,true
代表的是对文件进行追加操作,即在文件末尾处写入内容,false则是默认的重写

代码示例:

//没有做异常处理的版本
import java.io.FileOutputStream;
import java.io.IOException;

public class Demo6 {
	public static void main(String[] args) throws IOException {
		//创建字节流输出对象
		FileOutputStream fos = new FileOutputStream("text.txt");
		//往文件里写内容
		fos.write("Hello, world".getBytes());
		//释放资源
		fos.close();
	}
}

//有做异常处理的版本

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

public class Demo6 {
	public static void main(String[] args) throws IOException {
		
		FileOutputStream fos = null;
		try{
			//创建字节流输出对象
			fos = new FileOutputStream("text.txt");
			//往文件里写内容
			fos.write("Hello, world,java".getBytes());
		}catch(FileNotFoundException e){
			e.printStackTrace();
		}finally{
			try{
				//释放资源
				if(fos != null){
					fos.close();
				}
			}catch(IOException e){
				e.printStackTrace();
			}
			
		}
		
	}
}

FileInputStream读取文件

概述:用于读取文件,可以类比FileOutputStream来学习

使用步骤:

  • 创建字节流输入流对象
  • 调用read()方法读取数据,并把数据显示到控制台
  • 释放资源

两个read方法:

  • int read()
  • int read(byte[] b):返回实际读取字符的个数

示例代码1(读取文件):

import java.io.FileInputStream;
import java.io.IOException;

public class Demo7 {
	public static void main(String[] args) throws IOException {
		FileInputStream fis = new FileInputStream("text.txt");
		
		int by = 0;
		//如果读取不到数据了,就会返回-1
		while((by = fis.read()) != -1){
			//得到的是一个int类型的数字,需要把他转换为char类型,但是这里只能转换非中文字符,因为中文字符在gbk中用两个字节表示
			System.out.print((char)by);
		}
		
		fis.close();
	}
}

示例代码2(复制文本到另一个文件):

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class Demo8 {
	public static void main(String[] args) throws IOException {
		FileOutputStream fos = new FileOutputStream("text2.txt");
		FileInputStream fis = new FileInputStream("text1.txt");
		int by = 0;
		/*
		 * 这里读取并写入的文件里面的内容既可以是英文,也可以是中文,
		 * 因为gbk编码存储中文是用两个字节的,而第一个字节肯定是负数,
		 * 第二个字节常见的是负数,可能是整数,但是没影响;
		 */
		while((by = fis.read()) != -1){
			fos.write(by);
		}
		
		//谁先close无所谓
		fos.close();
		fis.close();
	}
}

示例代码3(一次读取多个字节)

import java.io.FileInputStream;
import java.io.IOException;

public class Demo9 {
	public static void main(String[] args) throws IOException {
		FileInputStream fis = new FileInputStream("text1.txt");
		//这里的 byte数组长度最好为1024或者1024的倍数
		byte[] bys = new byte[1024];
		int len = 0;
		//读取完了之后,返回的读取的长度为-1
		while((len = fis.read(bys)) != -1){
			System.out.println(new String(bys, 0, len));
		}
		fis.close();
	}
}

缓冲区类

概述:通过定义数组的方式确实比以前一次读取一个字节的方式快很多,所以,看来有一个缓冲区还是非常好的,既然这样,那么java开始设计的时候,他也考虑到了这一点,因此提供了我们带缓冲区的字节类,这种类被称为缓冲区类(高效类)

写数据:BufferedOutputStream

  • 构造方法:BufferedOutputStream(OutputStream out)

读数据:BufferedInputStream

  • 构造方法:BufferedInputStream(InputStream in)

以上这两个缓冲区类的构造方法可以指定缓冲区大小,但是我们一般用不上,因为默认缓冲区大小就足够了,构造方法里传一个OutputStream或一个InputStream对象

为什么不传递一个具体的文件或者文件路径,而是传一个OutputStream对象呢?

答:因为字节缓冲区流仅仅提供缓冲区,为高效而设计的,但是,真正的读写操作还得靠基本的流对象实现

实例代码(四种方式实现复制文本到另一个文件的耗时):

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class Demo10 {
	public static void main(String[] args) throws IOException {
		long start = System.currentTimeMillis();
		method1("video1.mp4", "video2.mp4");
		//method2("video1.mp4", "video2.mp4");
		//method3("video1.mp4", "video2.mp4");
		//method4("video1.mp4", "video2.mp4");
		long end = System.currentTimeMillis();
		System.out.println("花费了" + (end - start) + "s");
	}
	public static void method1(String srcString, String destString) throws IOException{
		FileOutputStream fos = new FileOutputStream(destString);
		FileInputStream fis = new FileInputStream(srcString);
		
		int by = 0;
		while((by = fis.read()) != -1){
			fos.write(by);
		}
		fos.close();
		fis.close();
	};
	public static void method2(String srcString, String destString) throws IOException{
		FileOutputStream fos = new FileOutputStream(destString);
		FileInputStream fis = new FileInputStream(srcString);
		
		byte[] bys = new byte[1024];
		int len = 0;
		while((len = fis.read(bys)) != -1){
			fos.write(bys, 0, len);
		}
		fos.close();
		fis.close();
	};
	public static void method3(String srcString, String destString) throws IOException{
		FileOutputStream fos = new FileOutputStream(destString);
		FileInputStream fis = new FileInputStream(srcString);
		BufferedOutputStream bos = new BufferedOutputStream(fos);
		BufferedInputStream bis = new BufferedInputStream(fis);
		int by = 0;
		while((by = bis.read()) != -1){
			bos.write(by);
		}
		bos.close();
		bis.close();
	};
	public static void method4(String srcString, String destString) throws IOException{
		FileOutputStream fos = new FileOutputStream(destString);
		FileInputStream fis = new FileInputStream(srcString);
		BufferedOutputStream bos = new BufferedOutputStream(fos);
		BufferedInputStream bis = new BufferedInputStream(fis);
		byte[] bys = new byte[1024];
		int len = 0;
		while((len = bis.read(bys)) != -1){
			bos.write(bys, 0, len);
		}
		bos.close();
		bis.close();
	}
}

6.转换流

概述:由于字节流操作中文不是特别方便,所有,java就提供了转换流

字符流 = 字节流 + 编码表

编码表

概述:由现实世界的字符和对应的数值组成的一张表

ASCII码表:用一个字节表示一个字符,最高位为符号位,其余为数值位

ISO-8859-1:拉丁码表,8位表示一个数据

GB2312:中国的中文编码表

GBK:中国的中文编码升级,融合了更多的中文文字符号

GBK18030:GBK的取代版本

BIG-5码:通行于*,香港地区的一个繁体字编码方案,俗称“大五码”

Unicode:国际标准码,融合了多种文字,所有文字都用两个字节来表示,java语言使用的就是unicode

UTF-8:最多用三个字节来表示一个字符,能用一个的就用一个(ASICC兼容),一个表示不了,就用两个,两个不行,就用三个

字符串的编码和解码问题

编码:从文字图形转化为二进制数字的过程

解码:从二进制数字转化为文字图形的过程

主要编码和解码的格式一样,就不会出现乱码等问题

代码示例:

import java.io.UnsupportedEncodingException;

public class Demo11 {
	public static void main(String[] args) throws UnsupportedEncodingException {
		String s = "你好";
		byte[] bys = s.getBytes("utf-8");
		//byte[] bys = s.getBytes("gbk");
		//String ss = new String(bys, "gbk");
		String ss = new String(bys, "utf-8");
		
		System.out.println(ss);
	}
}

转换流OutputStreamWriter和InputStreamReader的使用

概述:使用转换流可以指定编码,就可以很方便的往文件里写中文了,转换流本身也是字符流

OutputStreamWriter的五种写数据的方式

  • public void write(int c):写一个字符
  • public void write(char[] cbuf):写一个字符数组
  • public void write(char[] cbuf, int off, int len):写一个字符数组的一部分
  • public void write(String str):写一个字符串
  • public void write(String str, int off, int len):写一个字符串的一部分

close()和flush()方法的区别:close()关闭流对象,但是先刷新一次缓冲区,关闭之后,流对象不可以继续使用;flush()方法仅仅刷新缓冲区,刷新之后,仍然可以使用

OutputStreamWriter的使用实例:

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;

public class Demo12 {
	public static void main(String[] args) throws IOException {
		//指定了编码为utf-8
		OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(
				"text1.txt"), "utf-8");
		
		osw.write("中国");
		
		osw.close();
		
	}
}

InputStreamReader的两种读数据的方式:

  • int read():一次读取一个字符
  • int read(char[] chs):一次读取一个字符数组

InputStreamReader的使用实例:

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;

public class Demo12 {
	public static void main(String[] args) throws IOException {
		//指定了编码为utf-8
		InputStreamReader isr = new InputStreamReader(new FileInputStream(
				"text1.txt"), "utf-8");
		int ch = 0;
		while((ch = isr.read()) != -1){
			System.out.print((char)ch);
		}
		
		
		isr.close();
		
	}
}

转换流的子类FileWriter和FileReader(本身就是字符流)

概述:由于我们常见的操作都是使用本地默认编码,所以,不用指定编码,而转换流的名称有点长,所以,java就提供了其子类(FileWriter和FileReader)供我们使用

OutputStreamWriter = FileOutputStream + 编码表(默认是gbk)

FileWriter = FileOutputStream + 编码表(默认是gbk)

InputStreamReader = FileInputStream +编码表(默认是gbk)

FileReader = FileInputStream +编码表(默认是gbk)

FileWriter和FileReader的使用代码示例:

import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;

public class Demo13 {
	public static void main(String[] args) throws IOException {
		FileWriter fw = new FileWriter("text2.txt");
		FileReader fr = new FileReader("text1.txt");
		
		/*int ch = 0;
		while((ch = fr.read()) != -1){
			fw.write(ch);
		}*/
		
		char[] chs = new char[1024];
		int len = 0;
		while((len = fr.read(chs)) != -1){
			fw.write(chs, 0, len);
			fw.flush()
		}
		
		fw.close();
		fr.close();
	}
}

字符缓冲流(BufferedReader和BufferedWriter)

概述:从字符流中读取数据或写数据,缓冲各个数据,从而实现字符、数组和行的高效读取或写数据,可以指定缓冲区大小,或者可以使用默认的大小,大多数情况下,默认值就足够大了。可以类比字节缓冲流的BufferedOutputStream和BufferedInputStream来学习

代码示例(使用字符缓冲流复制文本到另一个文件):

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;

public class Demo14 {
	public static void main(String[] args) throws IOException {
		BufferedWriter bw = new BufferedWriter(new FileWriter("text2.txt"));
		BufferedReader br = new BufferedReader(new FileReader("text1.txt"));
		
		char[] chs = new char[1024];
		int len = 0;
		while((len = br.read(chs)) != -1){
			bw.write(chs, 0, len);
			bw.flush();
		}
		
		bw.close();
		br.close();
	}
}

字符缓冲流的特殊功能:

BufferedWriter:

  • public void newLine():根据系统来决定换行符

BufferedReader:

  • public void readLine():一次读取一行数据,读到文件末尾时,返回null,而不是-1

代码示例1(使用特殊功能复制文本到另一个文件):

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;

public class Demo15 {
	public static void main(String[] args) throws IOException {
		BufferedWriter bw = new BufferedWriter(new FileWriter("text2.txt"));
		BufferedReader br = new BufferedReader(new FileReader("text1.txt"));
		
		String line = null;
		while((line = br.readLine()) != null){
			bw.write(line);
			bw.newLine();
			bw.flush();
		}
		
		bw.close();
		br.close();
	}
}

代码示例2(复制多级文件夹)

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;

public class Demo16 {
	public static void main(String[] args) throws IOException {
		//封装数据源File
		File srcFile = new File("D:\java学习资料\day01\code");
		//封装目的地File
		File destFile = new File("D:\");
		//复制文件夹
		copyFolder(srcFile, destFile);
		
		System.out.println("操作成功");
	}
	//复制文件夹的方法
	public static void copyFolder(File srcFile, File destFile) throws IOException {
		if(srcFile.isDirectory()){
			//判断是否为一个文件夹
			File newFolder = new File(destFile, srcFile.getName());
			newFolder.mkdir();
			//获取目标盘符下的所有文件和文件夹
			File[] fileArray = srcFile.listFiles();
			
			for(File f:fileArray){
				//遍历获取到的所有文件和文件夹,调用自己
				copyFolder(f, newFolder);
			}
		}else{
			File newFile = new File(destFile, srcFile.getName());
			//调用复制文件的方法
			copyFile(srcFile, newFile);
		}
	}
	//复制文件到目标目录的方法
	public static void copyFile(File srcFile, File newFile) throws IOException{
		BufferedReader br = new BufferedReader(new FileReader(srcFile));
		BufferedWriter bw = new BufferedWriter(new FileWriter(newFile));
		
		String line = null;
		while((line = br.readLine()) != null){
			bw.write(line);
			bw.newLine();
			bw.flush();
		}
		
		bw.close();
		br.close();
	}
}

LineNumberReader获取行号:BufferedReader的子类

  • public int getLineNumber():获取当前行号
  • pbblic void setLineNumber(int lineNumber):设置当前行号从lineNumber + 1开始

7.其他流(了解)

数据输入输出流

概述:可以对基本数据类型进行读写操作

数据输出流:DataOutputStream

  • 构造方法:DataOutputStream(OutputStream out)

数据输入流:DataIntputStream

  • 构造方法:DataInputStream(InputStream in)

示例代码:

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class Demo17 {
	public static void main(String[] args) throws IOException {
		write();
		//read();
	
	}
	private static void read() throws IOException {
		DataInputStream dis = new DataInputStream(new FileInputStream("text1.txt"));
		short s = dis.readShort();
		int i = dis.readInt();
		long l = dis.readLong();
		byte by = dis.readByte();
		float f = dis.readFloat();
		double d = dis.readDouble();
		char c = dis.readChar();
		boolean b = dis.readBoolean();
		
		dis.close();
		
		System.out.println(s);
		System.out.println(i);
		System.out.println(l);
		System.out.println(by);
		System.out.println(f);
		System.out.println(f);
		System.out.println(c);
		System.out.println(b);
	}
	public static void write() throws IOException{
		DataOutputStream dos = new DataOutputStream(new FileOutputStream("text1.txt"));
		dos.writeShort(100);
		dos.writeInt(100);
		dos.writeLong(1000);
		dos.writeByte(97);
		dos.writeFloat(10.0f);
		dos.writeDouble(100.00);
		dos.writeChar('a');
		dos.writeBoolean(true);
		
		dos.close();
	}
}

内存操作流

概述:用于处理临时存储信息的,程序结束,数据就从内存消失,内存操作流的close方法没有什么作用,可以不写close方法

内存操作流可以操作一下内容:

字节数组:

  • ByteArrayInputStream
  • ByteArrayOutputStream

字符数组:

  • CharArrayReader
  • CharArrayWriter

字符串:

  • StringReader
  • StringWriter

打印流

特点:

  • 只有写数据,没有读数据,只能操作目的地,不能操作数据源
  • 可以操作任意类型的数据
  • 如果启动了自动刷新,就可以不用手动刷新了
  • 该流是可以直接操作文本文件的(查看流对象的构造方法,如果同时有File类型和String类型的参数,一般来说就是可以直接操作文件的)

打印流:

  • 字节流打印流:PrintStream,构造方法的第二个参数为true时,可以实现自动刷新(使用println方法才能自动刷新才能达到效果)
  • 字符打印流:PrintWriter,构造方法的第二个参数为true时,可以实现自动刷新(使用println方法自动刷新才能达到效果)

实例代码(复制文件打印流版):

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;

public class Demo18 {
	public static void main(String[] args) throws IOException {
		BufferedReader br = new BufferedReader(new FileReader("text1.txt"));
		
		PrintWriter pw = new PrintWriter(new FileWriter("text2.txt"), true);
		
		String line = null;
		while((line = br.readLine()) != null){
			pw.println(line);
		}
		
		pw.close();
	}
}

标准输入输出流

System类中的两个成员变量:

  • public static final InputStream in:标准输入流
  • public static final PrintStream out:标准输出流

System.in其实就是一个
InputStream对象,而System.out则是一个PrintStream对象,由此,可以得知,输出语句的本质其实就是IO流操作,把数据输出到控制台

实现控制台输出的另一种方式:

  • BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
  • bw.write("hello");
  • bw.flush();

三种方式实现键盘录入

  • main方法的args接收参数
  • Scanner(JDK之后的)
  • 通过字符缓冲流包装标准输入流实现
    • BufferedReader br = new BufferedReader(new InputStreamReader(System.in))
    • br.nextLine()

随机访问流RandomAccessFile

概述:RandomAccessFile类不属于流,是Object类的子类,但他融合了InputStream和OutputStream的功能,支持对随机访问文件的读取和写

构造方法:

  • public RandomAccessFile(String name, String mode):第一个参数是文件路径,第二个参数是操作文件的模式,模式有四种,我们最常用的是“rw
    ”,这种方式表示我们既可以写数据,也可以读数据

代码示例:

import java.io.IOException;
import java.io.RandomAccessFile;

public class Demo20 {
	public static void main(String[] args) throws IOException {
		//write();
		read();
	}

	private static void read() throws IOException {
		RandomAccessFile raf = new RandomAccessFile("text1.txt", "rw");
		String s = raf.readUTF();
		System.out.println(s);//中国
		//utf8编码中中文一个字符占三个字节,而又因为readUTF()方法需要多加两个字节,所以指针指向为8
		System.out.println("此时指针的位置在:" + raf.getFilePointer());//8
		int i = raf.readInt();
		System.out.println(i);//97
		//int类型需要4个字节,加前面的8,所以为12
		System.out.println("此时指针的位置在:" + raf.getFilePointer());//12
	}

	private static void write() throws IOException {
		RandomAccessFile raf = new RandomAccessFile("text1.txt", "rw");
		raf.writeUTF("中国");
		raf.writeInt(97);
		
		raf.close();
	}
}

合并流SequenceInputStream

概述:SequenceInputStream类可以将多个输入流串流在一起,合并为一个输入流

构造方法:

  • SequenceInputStream(InputStream s1, InputStream s2):针对两个流合并
  • SequenceInputStream(Enumeration<? extends InputStream> e):针对多个流合并

示例代码1(合并流读取两个文件的内容复制到一个文件中):

import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.SequenceInputStream;

public class Demo21 {
	public static void main(String[] args) throws IOException {
		InputStream is1 = new FileInputStream("text1.txt");
		InputStream is2 = new FileInputStream("text2.txt");
		
		SequenceInputStream sis = new SequenceInputStream(is1, is2);
		BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("text3.txt"));
		
		byte[] bys = new byte[1024];
		int len = 0;
		while((len = sis.read(bys)) != -1){
			bos.write(bys, 0, len);
		}
		
		bos.close();
		sis.close();
	}
}

示例代码2(合并流读取多个文件的内容复制到一个文件中):

import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.SequenceInputStream;
import java.util.Enumeration;
import java.util.Vector;

public class Demo22 {
	public static void main(String[] args) throws IOException {
		Vector<InputStream> v = new Vector<InputStream>();
		InputStream is1 = new FileInputStream("text1.txt");
		InputStream is2 = new FileInputStream("text2.txt");
		InputStream is3 = new FileInputStream("text3.txt");
		
		v.add(is1);
		v.add(is2);
		v.add(is3);
		
		Enumeration<InputStream> en = v.elements();
		
		SequenceInputStream sis= new SequenceInputStream(en);
		BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("text4.txt"));
		
		byte[] bys = new byte[1024];
		int len = 0;
		while((len = sis.read(bys)) != -1){
			bos.write(bys, 0, len);
		}
		
		bos.close();
		sis.close();
	}
}

序列化流ObjectOutputStream和ObjectInputStream

概述:把对象按照流一样的方式存入文本文件或在网络中传输,写和读取的是一个对象,而且这个对象的对应的类需要具有序列化标记接口Serializable,不然在使用的时候,就会抛出NotSerializableException异常

构造方法

  • public ObjectOutputStream(OutputStream out)
  • public ObjectInputStream(InputStream in)

transient关键字:被这个关键字修饰的变量就不会被序列化了

使用实例:

//Student.java

import java.io.Serializable;

//实现Serializable接口
public class Student implements Serializable{
	//生成id值防止下次改动java文件时没有重写写入而导致错误
	private static final long serialVersionUID = -7128679398892529111L;
	private String name;
	private int age;
	public Student(){};
	public Student(String name, int age){
		this.name = name;
		this.age = age;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		this.age = age;
	}
	@Override
	public String toString() {
		return "Student [name=" + name + ", age=" + age + "]";
	}
	
}

//Demo23.java

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public class Demo23 {
	public static void main(String[] args) throws IOException, ClassNotFoundException {
		//write();
		read();
	}

	private static void read() throws IOException, ClassNotFoundException{
		ObjectInputStream ois = new ObjectInputStream(new FileInputStream("text1.txt"));
		
		Object obj = ois.readObject();
		
		ois.close();
		
		System.out.println(obj);
	}

	private static void write() throws IOException, IOException {
		ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("text1.txt"));
		
		Student stu1 = new Student("luyi", 19);
		
		oos.writeObject(stu1);
		
		oos.close();
	}
	
}

注意:当我们修改Student.java文件中的内容时,当我们再次读取的时候,就会报错,这是因为Student类实现了序列化接口,那么它本身也应该有一个标记值,对class文件进行标记,而当我们修改了Student类的内容后,它的标记值就会发生改变,此时,如果你没有重写写一次,而是直接读取的话,就会报错,所以我们如果修改了Student类的内容的时候,就需要重新写一次;但我们在实际开发中,可能需要使用以前写过的数据,不能重新写入,怎么办呢?这时候就可以让这个标记值在java文件中是一个固定的值,这样,你修改文件的时候,这个标记值就不会发生改变了。而生成这个固定的标记值只需把鼠标移动到Student类上黄色警告线上,就可以点击生成了

8.Properties集合

概述:属性集合类,是一个可以和IO流相结合使用的集合类,可以保存在流中或从流中加载,属性列表中每个键及其对应值都是一个字符串,它是Hashtable的子类,说明它是一个Map集合,也可以作为一个Map集合来使用,但是它不是一个泛型类,不能加泛型

Properties自己独有的特殊方法:

  • public Object setProperty(String key, String value):添加元素
  • public String getProperty(String key):获取元素
  • public Set stringPropertyNames():获取所有的键的集合
  • public void load(Reader reader):把文件中的数据读取到集合中
  • public void store(Writer writer, String comments):把集合中的数据存储到文件中

代码示例1(Properties存储元素并遍历):

import java.util.Properties;
import java.util.Set;

public class Demo24 {
	public static void main(String[] args) {
		Properties prop = new Properties();
		
		prop.setProperty("1", "卢一");
		prop.setProperty("2", "黄伊");
		prop.setProperty("3", "赵露思");
		
		Set<String> set = prop.stringPropertyNames();
		
		for(String key : set){
			String value = prop.getProperty(key);
			System.out.println(key + "----" + value);
		}
	}
}

代码示例2(load功能和store功能实现):

import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Reader;
import java.io.Writer;
import java.util.Properties;

public class Demo25 {
	 public static void main(String[] args) throws IOException {
		myLoad();
		//myStore();
	}

	private static void myStore() throws IOException {
		Properties prop = new Properties();
		
		prop.setProperty("1", "卢一");
		prop.setProperty("2", "黄伊");
		prop.setProperty("3", "赵露思");
		
		Writer w = new FileWriter("text1.txt");
		
		prop.store(w, "这是描述信息");
		
		w.close();
	}

	private static void myLoad() throws IOException {
		Properties prop =  new Properties();
		Reader r = new FileReader("text1.txt");
	
		prop.load(r);	
		r.close();
		System.out.println("prop:" + prop);
	}
}

9.NIO(new IO,java4之后有)

概述:JDK4之后出现了NIO,新IO和传统IO有相同的目的,都是用于进行输入输出,但新IO使用了不同的方式来处理输入输出,采用内存映射文件的方式,将文件或者文件的一段区域映射到内存中,就可以像访问内存一样来访问文件了,这种方法效率比IO高效,但是目前很多地方我们看到的还是旧IO的引用

JDK7的NIO增加的需要了解的类

  • Path:与平台无关的路径
  • Paths:包含了返回Path的静态方法
    • public static Path get(URI uri):根据给定的URI来确定文件路径
  • Files:操作文件的工具类,提供了大量的方法
    • public static long copy(Path source, OutputStream out):复制文件
    • public static Path write(Path path,Interable<? extends CharSequence> lines, Charset cs, OpenOption...options):写操作

代码示例:

import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;

public class Demo26 {
	public static void main(String[] args) throws IOException {
		//复制文件到指定目录
		//Files.copy(Paths.get("text1.txt"), new FileOutputStream("copy.txt"));
		
		//写操作
		ArrayList<String> array = new ArrayList<String>();
		array.add("hello");
		array.add("world");
		array.add("java");
		
		Files.write(Paths.get("array.txt"), array, Charset.forName("GBK"));
	}
}

综合案例:五人游戏

游戏场景:

1.有五个人都在玩游戏

2.这五个人组成了一支队伍

3.这支队伍进行游戏

4.队伍成员彼此欣赏,决定以后还要组队一起游戏

5.一段时间后大家再次组队游戏

整体思路:

查找老朋友(加载文件到集合)->判断有没有老朋友(查看集合中是否有数据)->有老朋友则直接进行游戏(使用集合)->没有老朋友的话则找五个新朋友(新建五个对象)->玩游戏(使用集合)->判断是否是新朋友->是的话留联系方式(保存文件,结束程序)->不是的话直接程序结束

代码实现

//Hero.java
public class Hero {
	private String name;//英雄的名字
	private int attack;//英雄的攻击力
	private String type;//英雄的类型
	public Hero(){
		
	}
	public Hero(String name, int attack, String type) {
		super();
		this.name = name;
		this.attack = attack;
		this.type = type;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public int getAttack() {
		return attack;
	}
	public void setAttack(int attack) {
		this.attack = attack;
	}
	public String getType() {
		return type;
	}
	public void setType(String type) {
		this.type = type;
	}
	
}

//DemoGame.java
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Scanner;

public class DemoGame {

	public static void main(String[] args) throws IOException {
		//1. 首先,创建一个集合,用来存储五个英雄
		ArrayList <Hero> list = new ArrayList<Hero>();
		//2.读文件,把数据加载到集合
		loadFileToList(list);
		//3.判断一下集合当中是否有内容(判断是不是新的队伍)
		boolean isNew = list.size() == 0;
		System.out.println("判断集合中是否没有内容:" + isNew);
		//4.1如果是新的队伍,没有人,就需要创建五个对象加入到集合当中
		if(isNew){
			//调用方法,往集合添加英雄
			addFileHeros(list);
		}
		//5.遍历集合,输出其中每一个对象的具体信息
		showHeros(list);
		//6.统计一下总战斗力
		getTotalAttack(list);
		//7.把集合当中的新的队伍写入文件当中去
		saveToFile(list);
		System.out.println("退出游戏!");

	}
	/*
	 * 定义一个方法用来读取文件,将数据添加到集合当中
	 * 返回值:void
	 * 方法名称:loadFileToList
	 * 参数列表:ArrayList<Hero> list
	 */
	public static void loadFileToList(ArrayList<Hero> list) throws IOException{
		BufferedReader br = new BufferedReader(new FileReader("gameFile.txt"));
		String line;
		while((line = br.readLine()) != null){
			//根据逗号切割字符串数据并添加到array数组中,切割后得到的就是一组数组
			System.out.println(line.split(","));
			String[] array = line.split(",");
			String name = array[0];
			//把字符串类型的数据转为int类型的数据
			int attack = Integer.parseInt(array[1]);
			String type = array[2];
			Hero hero = new Hero(name, attack, type);
			list.add(hero);
		}
		br.close();
	}
	/*
	 * 定义一个添加英雄到集合的方法
	 * 返回值:void
	 * 方法名称:addFileHeros
	 * 参数列表:ArrayList<Hero> list
	 */
	public static void addFileHeros(ArrayList<Hero> list){
		Scanner sc = new Scanner(System.in);
		for(int i = 1; i <= 5; i ++){
			System.out.println("请输入第" + i +"个英雄的名字");
			String name = sc.next();
			System.out.println("请输入第" + i +"个英雄的攻击力");
			int attack = sc.nextInt();
			System.out.println("请输入第" + i +"个英雄的类型");
			String type = sc.next();
			Hero hero = new Hero(name, attack, type);
			list.add(hero);//把英雄添加到集合当中
		}
	}
	
	/*定义一个遍历英雄集合的方法
	 * 返回值:void
	 * 方法名:showHeros
	 * 参数列表:ArrayList<Hero> list
	 */
	public static void showHeros(ArrayList<Hero> list){
		for(int i = 0; i < list.size(); i ++){
			Hero hero = list.get(i);
			System.out.println("英雄的名字为:" + hero.getName() + ",英雄的攻击力为" + hero.getAttack() + ",英雄的类型为" + hero.getType());
		}
	}
	/*定义一个统计英雄总攻击力的方法
	 * 返回值:int
	 * 方法名:getTotalAttack
	 * 参数列表:ArrayList<Hero> list
	 */
	public static int getTotalAttack(ArrayList<Hero> list){
		int total = 0;
		for(int i = 0; i < list.size(); i ++){
			Hero hero = list.get(i);
			total += hero.getAttack();
		}
		return total;
	}
	/*定义一个方法,用来将集合当中的对象数据全部写到文件里
	 * 返回值:void
	 * 方法名称:saveToFile
	 * 参数列表:ArrayList<Hero>
	 */
	public static void saveToFile(ArrayList<Hero> list) throws IOException{
		BufferedWriter bw = new BufferedWriter(new FileWriter("gameFile.txt"));
		for(int i = 0; i < list.size(); i ++){
			Hero hero = list.get(i);
			//需要把一个Hero对象转换为字符串,将三个成员变量拼接成一个字符串
			String str = hero.getName() + "," + hero.getAttack() + "," + hero.getType();
			bw.write(str);
			bw.newLine();//换行
		}
		bw.close();
	}
}