IO操作二 : IO操作深入、输入与输出支持 - Java高级特性 11

目录

IO操作深入

字符编码

内存操作流

管道流

RandomAccessFile

输入输出支持

打印流

System类对IO的支持

BufferedReader输入流

Scanner扫描流


IO操作深入

字符编码

在计算机的世界只认0、1的数据,如果要想描述一些文字的编码就需要对这些二进制的数据进行组合,所以才有现在可以看见的中文,但是进行编码的时候如果要想正确显示出内容则一定需要有解码,所以编码和解码肯定要采用统一的标准,那么如果统一的时候就会出现乱码。

那么在实际开发之中对于常用的编码有如下几种:

  • GBK/GB2312:国标编码,可以描述中文信息,其中GB2312只描述简体中文,而GBK包含有简体中文与繁体中文;

  • ISO8859-1:国际通用编码,可以用其描述所有字母的信息,如果是象形文字就需要进行编码处理;

  • UNICODE编码:采用十六进制的方式存储,可以描述所有的文字信息;

  • UTF编码:象形文字部分使用十六进制编码,而普通的字母采用的是ISO8859-1编码,它的优势在于适合于快速的传输,节约带宽,这样也就成为在开发之中首选的编码。主要使用UTF-8编码。

如果要想知道当前系统中支持的编码规则,则可以采用如下代码列出全部的本机属性。

范例:列出本机属性

public class JavaAPIDemo {
	public static void main(String[] args) throws Exception {
		System.getProperties().list(System.out);
	}
}
文件的路径分隔符file.separator=\
文件默认编码file.encoding=UTF-8

也就是说如果现在什么都不设置的话,则采用的编码就是UTF-8。

范例:编写程序

package cn.ren.demo;

import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStream;

public class JavaAPIDemo {
	public static void main(String[] args) throws Exception {
		OutputStream output = new FileOutputStream("D:" + File.separator + "ren.txt") ; 
		output.write("中华人民".getBytes());
		output.close();
	}
}

此时为默认的处理操作,设置编码的时候就将采用默认的编码方式进行。

范例:强制性设置编码

package cn.ren.demo;

import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStream;

public class JavaAPIDemo {
	public static void main(String[] args) throws Exception {
		OutputStream output = new FileOutputStream("D:" + File.separator + "ren.txt") ; 
		output.write("中华人民".getBytes("ISO8859-1"));
		output.close();
	}
}

项目中出现的乱码问题就是编码和解码标准不统一,而最好的解决乱码方式,所有的编码都采用UTF-8。

内存操作流

在之前使用的都是文件操作流,文件操作流特点,程序利用InputStream读取文件内容,而后程序利用OutputStream向文件输出内容。所有的操作都是以文件为终端的。

假设现在需要实现IO操作,可是又不希望产生文件(临时文件)则就可以以内存为终端进行处理,这个时候的流程。

注意写是InputStream,读是OutputStream; 内存流以内存为中心记,ByteArrayinputStream()实例化后放在内存中了

在Java里面提供有两类的内存操作流:

  • 字节内存操作流:ByteArrayOutputStream、ByteArrayintputStream;
  • 字符内存操作流:CharArrayWriter、CharArrayReader;

IO里面需要把类的关系搞明白,才能掌握。

下面ByteArrayOutputStream和ByteArrayInputStram为主进行内存的使用分析,首先来分析构造方法:

  • ByteArrayInputStream构造:public ByteArrayInputStream​(byte[] buf);“现在把一块数据放在内存理然后抓出来”
  • ByteArrayOutputStream构造:public ByteArrayOutputStream​()

在ByteArrayOutputStream类里面有一个重要的方法,这个方法可以获取全部保存在内存流中的数据信息,该方法为:

  • 获取数据: public byte[] toByteArray​()
  • 使用字符串形式获取数据:public String toString​()

范例:利用内存流实现一个小写字母转大写的操作  

package cn.ren.demo;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.OutputStream;

public class JavaAPIDemo {
	public static void main(String[] args) throws Exception {
		String str = "hello ren" ;   // 小写字母
		InputStream input = new ByteArrayInputStream(str.getBytes()) ;   //将数据保存到内存流
		OutputStream output = new ByteArrayOutputStream() ; // 读取内存中的数据
		int data = 0 ;
		while((data = input.read()) != -1) { // 每次读取一个字节
			 output.write(Character.toUpperCase((char) data)); // 将数据保存到输出流
		}
		System.out.println(output);
		input.close() ;
		output.close();
	}
}

如果现在不希望只是以字符串的形式返回,因为可能存放的是其它二进制的数,那么此时就可以应用ByteArrayOutputStream子类的新功能获取全部字节数据。

package cn.ren.demo;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.OutputStream;

public class JavaAPIDemo {
	public static void main(String[] args) throws Exception {
		String str = "hello ren" ;   // 小写字母
		InputStream input = new ByteArrayInputStream(str.getBytes()) ;   //将数据保存到内存流
		// 必须使用子类来调用子类的扩展方法
		ByteArrayOutputStream output = new ByteArrayOutputStream() ; // 读取内存中的数据
		int data = 0 ;
		while((data = input.read()) != -1) { // 每次读取一个字节
			 output.write(Character.toUpperCase((char) data)); // 将数据保存到输出流
		}
		byte result [] = output.toByteArray() ;  // 获取全部数据
		System.out.println(new String(result));    //自己处理字节数据
		input.close() ;
		output.close();
	}
}

在最初的时候可以利用ByteArrayOutputStream实现大规模文本文件的读取。

管道流

管道流主要的功能是实现两个线程之间的IO处理操作。

对于管道流也是分为两类:

              |- 连接处理:public void connect​(PipedInputStream snk) throws IOException

              |- 连接处理:public void connect​(PipedWriter src) throws IOException

 

管道流的继承关系与内存流类似。

范例:实现管道操作
 

package cn.ren.demo;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;

public class JavaAPIDemo {
	public static void main(String[] args) throws Exception {
		SendThread send = new SendThread();
		ReceiveThread recive = new ReceiveThread();
		send.getOutput().connect(recive.getInput()); // 进行管道连接
		new Thread(send, "消息发送线程-").start();
		new Thread(recive, "消息接收线程-").start();
	}
}

class SendThread implements Runnable {
	private PipedOutputStream output; // 管道的输出流

	public SendThread() {
		this.output = new PipedOutputStream(); // 实例化管道输出流
	}

	@Override
	public void run() {
		for (int x = 0; x  < 10; x++) {
			try {
				this.output.write((Thread.currentThread().getName() + "信息发送第" +(x +1) + "次: hello!\n").getBytes()); // 写出去
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		try {
			this.output.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	public PipedOutputStream getOutput() {
		return output;
	}
}

class ReceiveThread implements Runnable {
	private PipedInputStream input;

	public ReceiveThread() {
		this.input = new PipedInputStream();
	}

	@Override
	public void run() {
		byte data[] = new byte[1024];
		int len = 0;
		ByteArrayOutputStream bos = new ByteArrayOutputStream() ; // 所有的数据保存到内存输出流
		try {
			while( (len = this.input.read(data)) != -1 ) {   //读了放到data里面 
				bos.write(data, 0, len);   // 所有数据保存在内存流,写到内存流
			}
			System.out.println("(" + Thread.currentThread().getName() + "接收消息)\n" + new String(bos.toByteArray()));
			bos.close();
		} catch (IOException e) {
			e.printStackTrace();
		}		
		try {
			this.input.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	public PipedInputStream getInput() {
		return input;
	}
}

管道一个只负责发送,一个只负责接收,中间靠一个管道连接。

RandomAccessFile

对于文件内容的操作主要是InputStream(Reader)、OutputStream(Writer)来实现的,但是利用这些类是心啊的内容读取,只能够将数据部分部分读取进来。如果现在有这样的要求,给你一个非常大的文件,这个文件大小有20G,如果此时按照传统的IO操作进行读取与分析是不可能完成,所以这种情况下,在java.io包里面就有一个RandomAccessFile类,这个类就可以实现文件的跳跃式读取,可以只读取中间的部分内容(前提:需要有一个完善的保存形式),数据的保存位数要确定好 。

RandomAccessFile类中定义有如下的操作方法:

构造方法:public RandomAccessFile​(File file, String mode) throws FileNotFoundException

|- 文件的处理模式:r、rw

范例:实现文件的保存

package cn.ren.demo;

import java.io.File;
import java.io.RandomAccessFile;

public class JavaAPIDemo {
	public static void main(String[] args) throws Exception {
		File file = new File("d:" + File.separator + "ren.txt") ;   // 定义操作文件
		RandomAccessFile raf = new RandomAccessFile(file, "rw") ; 	// 读写模式
		String[] names = new String[] {"zhangsan", "wangwu  ", "lisi    "} ;
		int[] ages = new int[] {30, 20, 16} ;
		for (int x = 0; x < ages.length; x ++) {
			raf.write(names[x].getBytes());  // 写入字符串
			raf.writeInt(ages[x]); 
		}
		raf.close();
	}
}

RandomAccessFile最大的特点在于读取数据上,因为所有的数据按照固定的长度进行保存,读取的时候就可以进行跳字节读取:

向下跳:public int skipBytes​(int n) throws IOException

向会跳:public void seek​(long pos) throws IOException

范例:读取数据

 

package cn.ren.demo;

import java.io.File;
import java.io.RandomAccessFile;

public class JavaAPIDemo {
	public static void main(String[] args) throws Exception {
		File file = new File("d:" + File.separator + "ren.txt") ;   // 定义操作文件
		RandomAccessFile raf = new RandomAccessFile(file, "rw") ; 	// 读写模式
		{
			// 读取李四的数据,跳过24位
			raf.skipBytes(24) ;
			byte data [] = new byte[8] ;
			int len = raf.read(data) ;
			System.out.println("姓名:" + new String(data, 0, len).trim() + "、年龄:" + raf.readInt());
		}
		{
			// 读取王五的数据,回跳12位
			raf.seek(12) ;
			byte data [] = new byte[8] ;
			int len = raf.read(data) ;
			System.out.println("姓名:" + new String(data, 0, len).trim() + "、年龄:" + raf.readInt());
		}
		{
			// 读取张三的数据,回跳到头
			raf.seek(0) ;
			byte data [] = new byte[8] ;
			int len = raf.read(data) ;
			System.out.println("姓名:" + new String(data, 0, len).trim() + "、年龄:" + raf.readInt());
		}
		raf.close();
	}
}

整体的使用之中,由用户自行定义要读取的位置按照指定的结构进行数据的读取。这里的seek定位规则未知?

输入输出支持

打印流

如果现在要想通过程序实现内容的输出,核心本质一定要依靠OutputStream类完成,但是OutputStream类有一个最大的缺点,这个类的数据输出操作功能有限:public void write​(byte[] b) throws IOException,所有的数据要转为字节数组后才能输出,假设说你的项目里面可能输出的是long、double、Date,那么在这样的情况下就必须将这些数据变为字节的形式来处理,这样的处理麻烦,所以在开发之中最初为了解决此类的重复操作,往往会有开发者自行定义一些功能类以简化输出功能。

范例:打印流的设计思想

package cn.ren.demo;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;

class PrintUtil implements AutoCloseable { // 实现一些常用数据的输出
	private OutputStream output; // 不管如何操作输出的本质就是OutputStream

	public PrintUtil(OutputStream output) {   // 由外部决定输出的位置
		this.output = output;
	}
	@Override
	public void close() throws Exception {
		this.output.close();		
	}
	public void println(long num) {
		this.println(String.valueOf(num));
	}
	public void print(long num) {
		this.print(String.valueOf(num));
	}
	public void print(String str) { // 输出字符串
		try {
			this.output.write(str.getBytes());
		} catch (IOException e) {
			e.printStackTrace();
		} // 输出
	}
	public void println(String str) {
		this.print(str + "\r\n") ;
	}
}

public class JavaAPIDemo {
	public static void main(String[] args) throws Exception {
		File file = new File("d:" + File.separator + "ren.txt"); // 定义操作文件
		PrintUtil pu = new PrintUtil(new FileOutputStream(file)) ;
		pu.println("姓名:张三" );
		pu.print("年龄:") ;
		pu.println(78);
		pu.close();
	}
}

在整个的操作过程之中打印流的设计思想的本质在于:提高已有类的功能,例如:OutputStream是唯一可以实现输出的操作的标准类,所以应该以其为核心根本,但是这个类输出的操作功能有限,所以不方便进行输出各个数据类型,那么就为它做出一层包装,所以此时采用的思想就是“装饰设计模式”。

但是既然所有的开发者都已经发现了原始中的OutputStream功能的不足,所以设计者一定能发现,所以为了解决输出问题,在java.io包里面提供有打印流:PrintStream、PrintWriter。

PrintStreamPrintWriter
public class PrintStream
extends FilterOutputStream
implements Appendable, Closeable
public class PrintWriter extends Writer
public PrintStream(OutputStream out)
public PrintWriter(OutputStream out);
public PrintWriter​(Writer out)

代理设计与这里的装饰设计的区别,代理是围绕接口展开的,如果是代理设计PrintStream调用的接口中的方法,而装饰设计的时候调用的不是父类定义的方法。

下面使用PrintWriter实现数据的输出操作:

范例:数据输出

package cn.ren.demo;

import java.io.File;
import java.io.FileOutputStream;
import java.io.PrintWriter;

public class JavaAPIDemo {
	public static void main(String[] args) throws Exception {
		File file = new File("d:" + File.separator + "ren.txt"); // 定义操作文件
		PrintWriter pu = new PrintWriter(new FileOutputStream(file)) ;
		pu.println("姓名:张三" );
		pu.print("年龄:") ;
		pu.println(79);
		pu.close();
	}
}

从JDK1.5开始PrintWriter类里面追加有格式化输出的操作支持:public PrintWriter printf​(String format, Object... args) ;

范例:格式化输出 

package cn.ren.demo;

import java.io.File;
import java.io.FileOutputStream;
import java.io.PrintWriter;

public class JavaAPIDemo {
	public static void main(String[] args) throws Exception {
		File file = new File("d:" + File.separator + "ren.txt"); // 定义操作文件
		PrintWriter pu = new PrintWriter(new FileOutputStream(file)) ;
		String name = "张三" ;
		int age = 78 ;
		double salary = 78936.20746 ;
		pu.printf("姓名:%s、年龄:%d、收入:%9.2f", name, age, salary) ;
		pu.close();
	}
}

比起直接使用OutputStream类,那么使用PrintWriter、PrintStream类的操作会更加的简单。以后只要是程序进行内容输出的时候全部使用打印流。

System类对IO的支持

System类是一个系统类,而且是一个从头到尾一直都在使用的系统类,而在这个系统类之中提供有三个常量:

  • 标准输出(显示器输出):public static final PrintStream out

  • 错误输出:public static final PrintStream err

  • 标准输入(键盘):public static final InputStream in

范例:观察输出

 

package cn.ren.demo;

public class JavaAPIDemo {
	public static void main(String[] args) throws Exception {
		try {
			Integer.parseInt("a") ;
		} catch (Exception e) {
			System.out.println(e);
			System.err.println(e);
		}
	}
}

System.out和System.err都是同一种类型的,如果使用Eclipse则在进行使用System.err输出的时候会使用红色字体,System.out会使用黑色字体。

最早设置两个输出操作的是有目的的:System.out是输出那些希望用户可以看见的信息、Syatem.err是输出那些不希望用户看见的信息。

  • 修改out输出位置:public static void setOut​(PrintStream out)

  • 修改err输出位置:public static void setErr​(PrintStream err)

范例:修改System.err的位置(不用,这里只做演示)

package cn.ren.demo;

import java.io.File;
import java.io.FileOutputStream;
import java.io.PrintStream;

public class JavaAPIDemo {
	public static void main(String[] args) throws Exception {
		System.setErr(new PrintStream(new FileOutputStream(new File("d:"  + File.separator + "ren.txt"))));
		try {
			Integer.parseInt("a") ;
		} catch (Exception e) {
			System.out.println(e);
			System.err.println(e);  // 输出到文件里
		}
	}
}

在System类里面还提供有一个in常量,而这个常量对应的是标准输入键盘的输入,可以实现键盘数据输入。在正常的Java开发中使用不上的。

范例:实现键盘输入(不是重点)

package cn.ren.demo;
import java.io.InputStream;
public class JavaAPIDemo {
	public static void main(String[] args) throws Exception {
		InputStream input = System.in ; // 此时的输入流为键盘输入
		System.out.println("请输入:");
		byte [] data = new byte [1024] ;
		int len = input.read(data) ;
		System.out.println("输入内容为:" + new String(data,0,len));
	}
}

这样的键盘输入处理本身是有缺陷的,如果你现在输入的长度不足,那么只能够接收部分数据,所以输入就需要进行重复的输入流接收,而且在接收的时候还有可能牵扯到输入中文的情况,如果处理不当可能出现乱码的操作。

BufferedReader输入流

BufferedReader类提供的是缓冲字符输入流,也就是说其可以很好的解决输入流数据的读取问题。这个来是在最初的时候提供最完善的数据输入的处理(JDK1.5之前),JDK1.5之后被Scanner代替。之所以使用这个类来处理提供了一个重要的方法:

  • 读取一行数据:public String readLine​() throws IOException,以换行为分割符

将利用这个类实现键盘输入数据的标准化定义。

范例:实现键盘数据的输入

 

package cn.ren.demo;

import java.io.BufferedReader;
import java.io.InputStreamReader;

public class JavaAPIDemo {
	public static void main(String[] args) throws Exception {
		BufferedReader input = new BufferedReader(new InputStreamReader(System.in)) ;
		System.out.println("请输入信息:");
		String msg = input.readLine() ; // 接收输入信息
		System.out.println("输入内容为:" + msg) ;
	}
}

缓冲的目的:将输入的数据放在一起,用的时候一起读进来。

在以后开发的过程之中,经常会会遇到输入数据的情况,而所有输入数据的类型都是通过String来描述的,那么这样就方便了接收者进行各种处理。

范例:接收整型输入并且验证

package cn.ren.demo;

import java.io.BufferedReader;
import java.io.InputStreamReader;

public class JavaAPIDemo {
	public static void main(String[] args) throws Exception {
		BufferedReader input = new BufferedReader(new InputStreamReader(System.in)) ;
		System.out.println("请输入你的年龄:");
		String msg = input.readLine() ; // 接收输入信息
		if (msg.matches("\\d{1,3}")) {   // 是否由数字组成
			int age = Integer.parseInt(msg) ;
			System.out.println("年龄:" + age);
		} else {
			System.out.println("输入格式错误");
		}
        input.close() ;
	}
}

对于现代的Java开发,由键盘输入数据的情况并不常见,但是作为一些基础的逻辑训练,还是可以使用的,而键盘输入的标准实现做法(JDK1.5之前)就是上面的操作。实际开发中所有的输入的数据都是字符串,这样可以方面验证和字符串的复杂处理。

Scanner扫描流

java.util.Scanner是从JDK1.5之后追加的一个程序类,其主要的目的是为了解决输入流访问问题的,其可以理解为BufferedReader的替代功能类,在Scanner类里面有如下几种核心的操作方法:

构造:public Scanner​(InputStream source)

判断是否有数据:public boolean hasNext​()

取出数据:public String next​()

设置分割符:public Scanner useDelimiter​(String pattern)

范例:使用Scanner实现键盘数据输入

package cn.ren.demo;

import java.util.Scanner;

public class JavaAPIDemo {
	public static void main(String[] args) throws Exception {
		Scanner scan = new Scanner(System.in) ;
		System.out.println("请输入年龄:");
		if (scan.hasNextInt()) { // 判断是否有整数输入
			int age = scan.nextInt(); // 直接获取int数据
			System.out.println("年龄:" + age);
		} else {
			System.out.println("输入格式不对");
		}
        scan.close() ;
	}
}

此时可以明显感到Scanner的处理更加简单。

范例:输入一个字符串

package cn.ren.demo;

import java.util.Scanner;

public class JavaAPIDemo {
	public static void main(String[] args) throws Exception {
		Scanner scan = new Scanner(System.in) ;
		System.out.println("请输入信息:");
		if (scan.hasNext()) {
			String msg = scan.next() ;
			System.out.println("输入信息为:" + msg);
		} else {
			System.out.println("输入格式不对");
		}
        scan.close() ;
	}
}

这里直接输入回车不会终止。

使用Scanner输入数据还有一个最大的特点是可以利用正则进行验证判断。

范例:输入一个人的生日(yy-MM-dd)

package cn.ren.demo;

import java.text.SimpleDateFormat;
import java.util.Scanner;

public class JavaAPIDemo {
	public static void main(String[] args) throws Exception {
		Scanner scan = new Scanner(System.in) ;
		System.out.println("请输入你的生日:");
		if (scan.hasNext("\\d{4}-\\d{2}-\\d{2}")) {
			String msg = scan.next("\\d{4}-\\d{2}-\\d{2}") ;
			System.out.println("你的生日:" + new SimpleDateFormat("yy-MM-dd").parse(msg));
		} else {
			System.out.println("输入格式不对");
		}
        scan.close() ;
	}
}

现在可以发现Scanner的整体设计要好于BufferedReader,而且要比直接使用InputStream类读取方便;例如,现在读取一个文本中的所有的内容信息,如果采用的是InputStream类,那么就必须依靠存入输出流保存,而且还要判断内容是否换行。

范例:使用Scanner读取

package cn.ren.demo;

import java.io.File;
import java.util.Scanner;

public class JavaAPIDemo {
	public static void main(String[] args) throws Exception {
		Scanner scan = new Scanner(new File("d:" + File.separator + "ren.txt")) ;
		scan.useDelimiter("\n") ; //设置读取分隔符
		while(scan.hasNext()) {
			System.out.println(scan.next());
		}
		scan.close();
	}
}

在以后的开发过程之中,如果程序需要输出数据一定使用打印流,输入数据使用scanner(BufferedReader)。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值