1、系统类对IO的支持
在我们学习PriteWriter、PrintStream里面的方法print、println的时候是否观察到其与我们之前一直使用的系统输出很相似呢?其实我们使用的系统输出就是采用IO模式完成的。
在System中定义了三个操作的常量:
a、标准输出:(System.out) public final static PrintStream out
b、错误输出:(System.err) public final static PrintStream err
c、标准输入:(System.in) public final static InputStream in
(1)系统输出
out和err常量都为系统输出,都是PrintStream类对象。
两者之间的区别在于:out输出的内容是希望用户看到的内容而err输出的是不希望用户看到的。其中err只是一个保留的属性,现在几乎用不到。
由其结构可以看出System.out 是 PrintStream 的实例化对象,而由类图可得 PrintStream 是 OutputStream 的子类,所以可以直接使用 System.out 直接为 OutputStream 实例化,这个时候的 OutputStream 输出的位置将变为屏幕。/* * 系统输出 * */ public class Test3{ public static void main(String[] args) throws IOException { OutputStream outputStream = System.out; outputStream.write("你好!".getBytes()); } }
(2)系统输入
与前面两者不同的是System.in是InputStream的对象。而这种输入操作是指用户通过键盘输入(用户输入)。Java本身并没有直接的用户输入,所以要想实现这样的操作,还得借助java.io模式来完成。
/* * 系统输入 * */ public class Test3{ public static void main(String[] args) throws IOException { InputStream inputStream = System.in; byte[] data = new byte[1024]; System.out.println("请输入:"); //将数据读取到字节数组中 int temp = inputStream.read(data); System.out.println("输出为:"+new String(data,0,temp)); inputStream.close(); } }
通过程序我们可以发现:当用户输入信息时程序需要暂停执行,这是因为程序进入了阻塞状态,直至我们输入操作完成后按回车,程序才可以继续向下进行。且以上程序中我们是采用开辟固定长度的字节数组,当我们输入的字节大于该固定长度时就会出现读取部分数据的结果。这是因为一次读取不完造成的结果。所以我们将引入内存流对其进行改进,先将这些数据保存在内存流中然后一次性进行取出。
/* * 输入字节数大于开辟数组的大小 * */ public class Test3{ public static void main(String[] args) throws IOException { InputStream inputStream = System.in; byte[] data = new byte[16]; System.out.println("请输入:"); //将数据读取到字节数组中 int temp = inputStream.read(data); System.out.println("输出为:"+new String(data,0,temp)); inputStream.close(); } }
/* * 内存流进行改进操作 * */ public class Test3{ public static void main(String[] args) throws IOException { InputStream inputStream = System.in; ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); byte[] data = new byte[16]; System.out.println("请输入:"); int temp = 0; while((inputStream.read(data)) != -1) { //保存数据到内存输出流中 byteArrayOutputStream.write(data,0,temp); //用户判断输入结束 if(temp < data.length) { break; } break; } inputStream.close(); byteArrayOutputStream.close(); System.out.println("输出为:"+new String(byteArrayOutputStream.toByteArray())); } }
由此我们可以发现虽然实现了用键盘输入数据的功能,但是使得整体的实现逻辑过于混乱,所以说java提供的System.in并不好用,还要结合内存流来完成,复杂度很高。
如果要想在IO中进行中文的处理,最好的做法是将所有输入的数据保存在一起再处理,这样才可以保证不出现乱码。
由此我们可以引入java提供的两种输入流:
(3)输入流(BufferedReader和Scanner)
A、BufferedReader类
BufferReader类是一个缓冲输入流,且是字符流的操作对象。
缓冲流也可分为两类:字节缓冲流((BufferedInputStream)和字符缓冲流(BufferedReader)。
我们之所以选择BufferReader类操作,是因为在该类中提供有如下方法(读取一行数据)
public String readLine() throws IOException这个方法可以直接读取一行数据,以回车为换行符。
但是当我们观察BufferReader类及其构造函数:
public class BufferedReader extends Reader
public BufferedReader(Reader in)而我们使用的System.in是InputStream的子类和Reader并没有直接的关系,所以我们需要建立他们之间的联系:利用InputStreamReader类。
/* * 利用BufferedReader类实现键盘输入 * */ public class Test3{ public static void main(String[] args) throws IOException { BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in)); System.out.println("请输入:"); //默认使用回车换行,默认的换行模式也是BufferReader的缺点 String string = bufferedReader.readLine(); System.out.println("输出为:"+string); } }
使用以上形式实现的键盘输入还有一个大特点,由于接收的数据类型为String,可以使用String类的各种操作进行数据处理并且可以变为各种常见数据类型。且该类在jdk1.5后被java.util.Scanner所替代。
B、Scanner类
Scanner类在java.util包下;
打印流解决的是OutputStream类的缺陷,BefferedReader解决的是InputStream类的缺陷。而Scanner解决的是 BefferedReader类的缺陷(替换了BefferedReader类)
Scanner是一个专门进行输入流处理的程序类,利用这个类可以方便处理各种数据类型,同时也可以直接结合正则表达式进行各项处理,在这个类中主要关注以下方法:
1. 判断是否有指定类型数据: public boolean hasNextXxx()
2. 取得指定类型的数据: public 数据类型 nextXxx() 只要有数据输入,遇到第一个空格、回车、制表符都会终止读取数据。
public 数据类型 nextLine()只以回车符终止(遇到空格、制表符仍然继续进行读取)
3. 定义分隔符:public Scanner useDelimiter(Pattern pattern)
4. 构造方法:public Scanner(InputStream source)
/* * 使用Scanner类实现数据输入 * */ public class Test3{ public static void main(String[] args) { Scanner scanner = new Scanner(System.in); System.out.println("请输入:"); //有输入内容不判断空字符串 if(scanner.hasNext()) { //遇到空格、制表符、回车均停止数据的读取 // System.out.println("输入内容为:"+scanner.next()); //只有遇到回车才停止数据的读取 System.out.println("输入内容为:"+scanner.nextLine()); } scanner.close(); } }
通过上述运行结果我们可以验证nextXXX()方法进行相应数据类型数据的读取时,只要遇到空格就停止读取数据。
最为重要的是,Scanner可以对接收的数据类型使用正则表达式判断。
/* * 自定义格式输出 * */ public class Test3{ public static void main(String[] args) { Scanner scanner = new Scanner(System.in); System.out.println("请输入出生日期:"); if(scanner.hasNext("\\d{4}-\\d{2}-\\d{2}")) { String string = scanner.nextLine(); System.out.println("出生日期为:"+string); }else { System.out.println("输入格式错误"); } } }
使用Scanner本身能够接收的是一个InputStream对象,那么也就意味着可以接收任意输入流,例如:文件输入流;Scanner完美的替代了BufferedReader,而且更好的实现了InputStream的操作。
/* * 使用Scanner操作文件 * */ public class Test3{ public static void main(String[] args) throws FileNotFoundException { String singal = File.separator; Scanner scanner = new Scanner(new FileInputStream( new File("C:"+singal+"Users"+singal+"lenovo"+singal+"Desktop"+singal+"Test2.txt"))); //自定义分隔符 scanner.useDelimiter("\n"); while(scanner.hasNext()) { System.out.println(scanner.next()); } scanner.close(); } }
所以以后除了二进制文件拷贝的处理之外,只要是针对程序的信息输出都是用打印流(PrintStream、 PrintWriter),信息输出使用Scanner。
2、序列化
(1)概念
所谓的序列化指的是将程序中的属性以二进制流的形式在网络上传输或者保存在文件中。
可进行序列化的要求:并不意味着所有类的对象都可以被序列化,需要被序列化的类对象往往需要传输使用,同时这个类必须实现 java.io.Serializable接口。这个接口并没有任何的方法定义,只是一个标识而已。
(2)序列化与反序列化的实现
如果要想实现序列化与反序列化的对象操作,在java.io包中提供有两个处理类:ObjectOutputStream、ObjectInputStream
/* * 实现序列化与反序列化 * */ //定义要被序列化的类 class Student implements Serializable{ private String name = "lemon"; //要使用包装类 private Integer age = 20; Student(String name, Integer age) { super(); this.name = name; this.age = age; } //覆写toString方法 public String toString() { return "Student [name=" + name + ", age=" + age + "]"; } } public class Test3{ static String singal = File.separator; public static final File FILE = new File( "C:"+singal+"Users"+singal+"lenovo"+singal+"Desktop"+singal+"Test2.txt"); public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException { achieveSer(new Student("lemon",10)); opsiteSer(); } //实现序列化操作 public static void achieveSer(Object object) throws FileNotFoundException, IOException { ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(FILE)); objectOutputStream.writeObject(object); objectOutputStream.close(); } //反序列化操作 public static void opsiteSer() throws FileNotFoundException, IOException, ClassNotFoundException { ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(FILE)); System.out.println(objectInputStream.readObject()); objectInputStream.close(); } }实现序列化:
实现反序列化:
(3)transient关键字
由于Serializable默认会将对象中所有属性进行序列化保存,如果现在某些属性不希望被保存了,那么就可以 使用transient关键字。
修改上述代码(在age属性上加transient关键字)
//定义要被序列化的类 class Student implements Serializable{ private String name = "lemon"; //要使用包装类 private transient Integer age = 20; Student(String name, Integer age) { super(); this.name = name; this.age = age; } //覆写toString方法 public String toString() { return "Student [name=" + name + ", age=" + age + "]"; } } public class Test3{ static String singal = File.separator; public static final File FILE = new File( "C:"+singal+"Users"+singal+"lenovo"+singal+"Desktop"+singal+"Test2.txt"); public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException { achieveSer(new Student("lemon",10)); opsiteSer(); } //实现序列化操作 public static void achieveSer(Object object) throws FileNotFoundException, IOException { ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(FILE)); objectOutputStream.writeObject(object); objectOutputStream.close(); } //反序列化操作 public static void opsiteSer() throws FileNotFoundException, IOException, ClassNotFoundException { ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(FILE)); System.out.println(objectInputStream.readObject()); objectInputStream.close(); } }
对age属性使用transient关键字后的序列化结果:
对age属性使用transient关键字后的序列化结果: