Java SE 15th day:Java IO(下)
1、本次课程知识点
1、掌握内存操作流
2、了解管道流
3、字节-字符转换流
4、字符的编码问题
5、打印流
6、System类对IO的支持
7、对象序列化
2、具体内容
2.1 内存操作流(重点)
在之前讲解FileOutputStream和FileInputStream的时候所有的操作的目标是文件,那么现在假设有一些临时的信息要求通过IO操作的话,那么如果将这些临时文件的信息保存再文件之中肯定很不合理,因为操作的最后还要把文件再删除掉,所以此时在IO中就提供了一个内存的操作流,通过内存操作流输入和输出的目标是内存。
对于内存操作流也是分为两类的:
n 字节内存操作流:ByteArrayOutputStream、ByteArrayInputStream
n 字符的内存操作流:CharArrayWriter、CharArrayReader
使用ByteArrayOutputStream和ByteArrayInputStream完成的操作流:
● 在内存操作流中所有的输入和输出都是以内存为操作的源头(注意:这恰与文件的输入输出流相反)
● ByteArrayOutputStream是用于从内存向程序输出的
● ByteArrayInputStream是用于从程序向内存写入的
ByteArrayInputStream的构造方法:publicByteArrayInputStream(byte[] buf)
→ 表示把内容向内存中写入
ByteArrayOutputStream的构造:public ByteArrayOutputStream()
ByteArrayOutputStream,其基本的作用就是与OutputStream一样,一个个的读取数据。
范例:使用内存操作流,完成一个字符串小写字母变为大写字母的操作
package myio;
import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream;
public class ByteArraDemo {
public static void main(String[] args) throws Exception { String str = "hello world"; // 定义字符串,全部由小写字母组成 ByteArrayOutputStream bos = null; // 内存输出流 ByteArrayInputStream bis = null; // 内存输入流 bis = new ByteArrayInputStream(str.getBytes()); // 将内容保存在内存之中 bos = new ByteArrayOutputStream(); // 内存输出流 int temp = 0; while ((temp = bis.read()) != -1) {// 依次读取 char c = (char) temp; // 接收字符 bos.write(Character.toUpperCase(c)); // 输出 } String newStr = bos.toString();// 取出内存输出的内容 bis.close(); bos.close(); System.out.println(newStr); } }
|
HELLO WORLD |
以上程序如果往变量str中传入中文字符,则下面的输出肯定出现乱码,原因之前已经说过:在使用字节流的时候,如果用char数据类型来接收和处理数据时,如果存在中文字符则会出现乱码。解决办法就是在字节流中使用byte来接收和处理数据,或者使用字符流配合char接收数据。
内存操作流现在在java SE阶段是感觉不出有什么作用,但是在学习到java WEB中的AJAX技术的时候,会结合XML解析和JavaScript、AJAX完成一些动态效果的时候使用。
String newStr = bos.toString();// 取出内存输出的内容 |
以上的代码非常的重要,表示内存数据的取出。
但是通过本程序应该可以发现出面向对象的设计问题:父类规定出具体的操作标准,子类根据自己的情况去选择实现,不同的子类标准的实现也肯定不一样,而后会根据实例化父类对象的不同子类,同一方法可以完成不同的功能。
2.2 管道流(了解)
管道流就是进行两个线程间通讯的,使用PipedOutputStream和PipedInputStream两个类完成。但是,这两个类使用的时候基本上都跟OutputStream和InputStream类似,唯一区别的是在于连接管道的操作上:public voidconnect(PipedInputStream snk) throws IOException
范例:进行管道流的操作
package org.lxh.pipeddemo;
import java.io.IOException; import java.io.PipedInputStream; import java.io.PipedOutputStream;
class Send implements Runnable {// 发送数据的线程 private PipedOutputStream output = null;
public Send() { this.output = new PipedOutputStream(); }
public PipedOutputStream getPipedOutputStream() { return this.output; }
public void run() { String str = "hello world!!!";// 要发送的数据 try { this.output.write(str.getBytes());// 发送 } catch (IOException e) { e.printStackTrace(); } try { this.output.close(); // 关闭 } catch (IOException e) { e.printStackTrace(); } }
}
class Receive implements Runnable {// 接收数据的线程 private PipedInputStream input = null;
public Receive() { this.input = new PipedInputStream(); }
public PipedInputStream getPipedInputStream() { return this.input; }
public void run() { byte b[] = new byte[1024]; // 接收内容 int len = 0; try { len = this.input.read(b);// 内容读取 } catch (IOException e) { e.printStackTrace(); } try { this.input.close(); // 关闭 } catch (IOException e) { e.printStackTrace(); } System.out.println("接收的内容为:" + new String(b, 0, len)); } }
public class ThreadConnectDemo { public static void main(String[] args) throws IOException { Send send = new Send(); Receive rec = new Receive(); send.getPipedOutputStream().connect(rec.getPipedInputStream()); // 进行管道连接 // rec.getPipedInputStream().connect(send.getPipedOutputStream()); // 通过测试发现,无论执行以上两行代码中的哪一行,结果都是一样的! new Thread(send).start(); // 启动线程 new Thread(rec).start(); // 启动线程 } } |
接收的内容为:hello world!!! |
2.3 打印流(重点)
使用OutputStream可以完成数据的输出,但是现在如果有一个float型数据就不好输出了!
也就是说现在虽然提供了输出流的操作类,但是这个类本身的输出的支持功能并不是十分强大,所以,如果现在要想进行更方便的输出操作,则可以使用打印流。
打印流分为两种:PrintStream、PrintWriter。
观察打印流的定义:
PrintStream(字节打印流): | PrintWriter(字符打印流): |
java.lang.Object java.io.OutputStream java.io.FilterOutputStream java.io.PrintStream | java.lang.Object java.io.Writer java.io.PrintWriter |
PrintStream是OutputStream的子类,继续观察其构造方法:
public PrintStream(OutputStream out) |
在此方法中要接收OutputStream子类的引用。
通过这个构造和PrintStream类的继承关系,感觉这种操作非常类似于代理设计模式,但是有不完全是代理设计,因为从代理设计的要求有如下两点:
n 第一点:代理设计模式是在接口的基础上建立的;
n 第二点:用户使用代理主题中的方法,肯定是从父类覆写而来的;
但是使用PrintStream的时候并不是使用这些父类中定义的方法,现在PrintSream类中使用的是一系列的print()或println()方法,而不再是OutputStream类中定义的write()方法,所以对于PrintStream的设计实际上属于装饰设计模式,把原本功能不足的类扩充功能,而且最后都使用扩充的方法。
实际上PrintStream属于装饰。也就是说根据实例化PrintStream类对象的不同,输出的位置也不同。
范例:使用PrintSteam向文件输出
package org.lxh.printdemo;
import java.io.File; import java.io.FileOutputStream; import java.io.PrintStream;
public class PrintStreamDemo01 {
public static void main(String[] args) throws Exception { File file = new File("d:" + File.separator + "demo.txt"); PrintStream out = new PrintStream(new FileOutputStream(file)); out.print("hello"); out.println("world"); out.println(19); out.print(20.3); out.close() ; } } |
结论:使用打印流输出最为方便,所以建议以后在输出的时候就使用打印流完成。
范例:使用打印流封装之前编写的内存流代码(修改代码highlight表示)
package myio;
import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.PrintStream;
public class PrintStreamDemo {
public static void main(String[] args) throws Exception { String str = "hello world"; // 定义字符串,全部由小写字母组成 ByteArrayOutputStream bos = null; // 内存输出流 ByteArrayInputStream bis = null; // 内存输入流 bis = new ByteArrayInputStream(str.getBytes()); // 将内容保存在内存之中 bos = new ByteArrayOutputStream(); // 内存输出流 PrintStream ps = new PrintStream(bos);// 只能封装输出流 int temp = 0; while ((temp = bis.read()) != -1) {// 依次读取 char c = (char) temp; // 接收字符 // 现在是借用了PrintStream封装了ByteArrayOutputStream,实际上还是向内存 ps.print(Character.toUpperCase(c)); // 输出 } String newStr = bos.toString();// 取出内存输出的内容 ps.close();// 记得关闭 bis.close(); bos.close(); System.out.println(newStr); } } |
HELLO WORLD |
所以,打印流可以对输出进行封装,并进行一系列方便的操作。
在JDK 1.5 之后对打印流进行了更新,可以使用格式化输出。提供了以下的方法:
public PrintStream format(String format, Object... args) |
可以设置格式和多个参数。
范例:使用PrintStream进行格式化的输出
package org.lxh.printdemo;
import java.io.File; import java.io.FileOutputStream; import java.io.PrintStream;
public class PrintStreamDemo02 {
public static void main(String[] args) throws Exception { File file = new File("d:" + File.separator + "demo.txt"); PrintStream out = new PrintStream(new FileOutputStream(file)); String name = "李兴华"; int age = 3; float score = 99.9f; char sex = 'M'; out.printf("姓名:%s;年龄:%d;成绩:%5.2f;性别:%c。", name, age, score, sex); out.close(); } } |
在打印流中一定要始终记住以下的原则:根据实例化其子类的不同,完成的打印输出功能也不同。
这种格式化输出本身在Java的开发之中,见到的并不是很多,但是讲解它主要的为了引出String的最后一个特点,在String类中有一个新的方法(JDK 1.5之后增加):public static format(String format,Object… args)。
package myio; import java.io.File; import java.io.FileOutputStream; import java.io.PrintStream;
public class PrintStreamDemo02 {
public static void main(String[] args) throws Exception { String name = "吴力"; int age = 23; double score = 98.9764; char sex = 'M'; String str = String.format("姓名:%s;年龄:%d;成绩:%5.2f;性别:%c。", name, age, score, sex); System.out.println(str); } } |
姓名:吴力;年龄:23;成绩:98.98;性别:M。 |
虽然格式化字符串之后,可以对数字进行准确的四舍五入操作,但是这种操作一般不建议使用,四舍五入还是使用BigDecimal完成,因为格式化字符串之后的返回数据不是double型数据,是String。
在以后进行输出流的时候就不要再直接使用OutputStream完成,全部都使用打印流完成。
2.4 System类对IO的支持
No | 常量 | 描述 |
1 | public static final PrintStream out | 表示的是一个标准的输出,输出的位置是显示器 |
2 | public static final PrintStream err | 表示错误,错误的输出 |
3 | public static final PrintStream in | 表示的是键盘的输入,标准输入 |
2.4.1 System.out
System.out是PrintStream的实例,常用的方法就是向屏幕上打印信息,当然如果使用System.out的话也可以直接为OutputStream实例化。
package org.lxh.sytemiodemo; import java.io.OutputStream; public class SysteOutDemo { public static void main(String[] args) throws Exception { OutputStream out = System.out; // 此时具备了向屏幕输出的能力 out.write("hello world".getBytes());// 输出内容 out.close(); } } |
hello world |
2.4.2 System.err
System.err表示的是错误输出。
package org.lxh.sytemiodemo;
public class SystemErrDemo { public static void main(String[] args) { try { Integer.parseInt("hello"); } catch (Exception e) { System.out.println(e); System.err.println(e); } } } |
java.lang.NumberFormatException: For input string: "hello" java.lang.NumberFormatException: For input string: "hello" |
从代码本身观察不出任何的问题,只有再eclipse下才会出现红色的字体,实际上对于以上的两个常量要想区分,只能从概念上讲:
● System.out一般的信息是愿意展示给用户看见的
● System.err一般的信息是不愿意展示给用户看见的
2.4.3 System.in
System.in实际上表示的就是一个键盘的输入,使用此操作,可以完成键盘输入数据的功能。
package org.lxh.sytemiodemo;
import java.io.InputStream;
public class SystemInDemo01 {
public static void main(String[] args) throws Exception { InputStream input = System.in; // 准备键盘输入数据 byte b[] = new byte[20]; // 开辟空间接收内容 System.out.print("请输入内容:"); int len = input.read(b); // 接收内容 System.out.println("输入的内容是:" + new String(b, 0, len)); } } |
请输入内容:世界,你好! 输入的内容是:世界,你好! |
此时已经实现了键盘的输入功能,但是此种程序中在使用时会存在长度的限制,而且输入中文的时候也会存在问题,那么此时可以通过另外一种方式,不指定大小,边读边判断是否结束。
package org.lxh.sytemiodemo; import java.io.InputStream;
public class SystemInDemo03 { public static void main(String[] args) throws Exception { InputStream input = System.in; // 准备键盘输入数据 System.out.print("请输入内容:"); int temp = 0; // 接收内容 StringBuffer buf = new StringBuffer(); while ((temp = input.read()) != -1) { char c = (char) temp; // 转型 if (c == '\n') {// 判断是否是回车 break;// 退出循环 } buf.append(c); } System.out.println("输入的内容是:" + buf); } } |
请输入内容:Hello World 中国! 输入的内容是:Hello World ???ú! |
此时,数据读取的时候没有长度的限制了。
但是再输入中文的时候就无法正确读取了,因为每次读取是一个字节,应该按照整体读取,那么如果想要完成更好的读取操作,则只能使用后续的BufferedReader类完成。
2.4.4 输出、输入重定向
System.out、System.err都有固定的输出目标,都是屏幕。
System.in有固定的输入目标,都是键盘。
但是在System类中提供了一系列的输入输出重定向的方法,可以改变System.out、Ssytem.err、Ssytem.in的输入输出位置。
● Ssytem.out重定向:public static void setOut(PrintStream out)
● Ssytem.err重定向:public static void setErr(PrintStream err)
● Ssytem.in重定向: public static void setIn(InputStream in)
范例:验证输出重定向
package org.lxh.sytemiodemo;
import java.io.File; import java.io.FileOutputStream; Import java.io.PrintStream;
public class RedirectSystemOutDemo { public static void main(String[] args) throws Exception { File file = new File("d:" + File.separator + "demo.txt"); System.setOut(new PrintStream(new FileOutputStream(file))); System.out.println("hello world") ; } } |
此时不再在控制台输出,而是在文件中输出。
范例:验证错误重定向
package org.lxh.sytemiodemo;
import java.io.File; import java.io.FileOutputStream; import java.io.PrintStream;
public class RedirectSystemErrDemo { public static void main(String[] args) throws Exception { File file = new File("d:" + File.separator + "demo.txt"); System.setErr(new PrintStream(new FileOutputStream(file))); System.err.println("hello world") ; } } |
提示:
之前曾经说过System.err是不希望用户看到的,而System.out是希望被用户看到的,所以在开发中不建议改变System.err的输出位置,而只建议修改System.out的输出位置。
package org.lxh.sytemiodemo;
import java.io.File; import java.io.FileOutputStream; import java.io.PrintStream;
public class ErrDemo { public static void main(String[] args) throws Exception { File file = new File("d:" + File.separator + "err.log"); System.setOut(new PrintStream(new FileOutputStream(file))); try { Integer.parseInt("hello"); } catch (Exception e) { System.out.println(e); System.err.println(e); } } } |
java.lang.NumberFormatException: For input string: "hello" |
此时,System.out的输出被重定向到文件中输出,而System.err还是在控制台中输出。
范例:验证输入重定向
package org.lxh.sytemiodemo;
import java.io.File; import java.io.FileInputStream; import java.io.InputStream;
public class RedirectSystemInDemo { public static void main(String[] args) throws Exception { File file = new File("d:" + File.separator + "demo.txt"); System.setIn(new FileInputStream(file)); InputStream input = System.in; byte b[] = new byte[20]; // 开辟空间接收内容 int len = input.read(b); // 接收内容 System.out.println("内容是:" + new String(b, 0, len)); } } |
内容是:hello world |
此时,程序根据已存在的“d:\demo.txt”文件中(如果此文件不存在,则会在执行的时候报错!)读取内容,再在控制台输出。
但是,一般System.in的操作最好不要去修改。
2.5 BufferedReader
实际上表示的是缓冲区读取,可以一次性的将内容全部读取进来。
BufferedReader类的构造方法:
public BufferedReader(Reader in) |
那么,如果想要使用BufferedReader类接收键盘的输入内容的话,则此时就无法直接实例化了,因为System.in属于InputStream类型的。
2.5.1 转换流——OutputStreamWriter和InputStreamReader
这两个类的继承结构如下:
OutputStreamWriter: | InputStreamReader: |
java.io.OutputStreamWriter | java.lang.Object java.io.Reader java.io.InputStreamReader |
这两个都是字符流的操作子类,而这两个类的构造方法中提供了转换功能:
n OutputStreamWriter构造:publicOutputStreamWriter(OutputStream out)
n InputStreamReader构造:publicInputStreamReader(InputStream in)
范例:使用转换流完成输出文件。
package myio;
import java.io.File; import java.io.FileOutputStream; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.Writer;
public class InputStreamWriterDemo { public static void main(String[] args) throws Exception { File file = new File("d:" + File.separator + "demo.txt"); OutputStream output = new FileOutputStream(file); Writer w = new OutputStreamWriter(output);// 转换流 String str = "Hello World!\r\n中国,加油!"; w.write(str); w.close();//这两个关闭一定要按顺序,否则出错! output.close();//后关! } } |
而这种转换的意义基本上不大,因为字节流的操作本身就很多,而讲解此类的主要原因在于之前的文件操作上。
OutputStream有一个子类是FileOutputStream,InputStream,有一个子类是FileInputStream,这两个子类继承如下:
FileOutputStream: | FileInputStream: |
java.lang.Object java.io.OutputStream java.io.FileOutputStream | java.lang.Object java.io.InputStream java.io.FileInputStream |
继续观察FileWriter和FileReader类的继承结构:
FileWriter: | FileReader: |
java.lang.Object java.io.Writer java.io.OutputStreamWriter java.io.FileWriter | java.lang.Object java.io.Reader java.io.InputStreamReader java.io.FileReader |
FileWriter和FileReader并不是Writer和Reader的直接子类,而且在之前也讲解过字符操作流会进行缓冲的操作,而这一点也在类的继承关系上体现出来了。
以后只有必须进行转换的时候才会使用转换流,这种情况一般不出现。下面这个例子就是一个需要使用到转换流的程序。
直接使用转换流的类就可以完成转换功能,使用以下的方法可以读取数据:
public String readLine() throws IOException |
表示一次性读取一行数据,而且一定要记住的是,如果返回的内容是String,则是最好操作的。
范例:使用BufferedReader完成内容的输入
package org.lxh.bufferdemo;
import java.io.BufferedReader; import java.io.InputStreamReader;
public class BufferedReaderDemo {
public static void main(String[] args) throws Exception { BufferedReader buf = null; // 将字节输入流变为字符输入流放在字符流的缓冲区之中 buf = new BufferedReader(new InputStreamReader(System.in)); System.out.print("请输入内容:"); String str = buf.readLine(); System.out.println("输入的内容是:" + str); } } |
请输入内容:Hello World!中国,你好! 输入的内容是:Hello World!中国,你好! |
如果想要完成键盘的输入功能,使用以上的操作是最合适的。也是键盘输入数据的标准格式。
2.5.1 FileWriter和FileReader的说明
观察以下的继承关系
2.6 JDK 1.5新特性——Scanner(重点)
Scanner是Java提供的一个专门负责输入数据的工具类,这个类不在java.io包中定义,而在java.util包中定义,之所以会提供这样的一个工具类,实际上Java的主要目的还是吸引C的开发人员,在C语言中有一个scanf()函数。
Scanner的构造:publicScanner(InputStreamsource),包装InputStream类对象。
在Scanner类的所有方法都必须成对出现,例如:nextXxx()、hasNextXxx()。
2.6.1 Scanner的使用
(其他详见原书p450+scanner章节)
以后使用Java IO操作的时候记住,除了直接文件的操作之外,输入使用Scanner,输出使用PrintStream。
范例:向屏幕中输入如下文件内容。
package myio;
import java.io.File; import java.util.Scanner;
public class ScanerDemo { public static void main(String[] args) throws Exception { File file = new File("d:" + File.separator + "demo.txt"); Scanner scan = new Scanner(file); scan.useDelimiter("\r\n");// 设置换行为分隔符 // System.out.print(scan.delimiter());// 打印scan当前的分隔符 StringBuffer buf = new StringBuffer(); int i = 0; while (scan.hasNext()) { System.out.println(buf.append(scan.next())); System.out.println(i++); } scan.close();//资源操作,记得关闭 } } |
hello world today 0 hello world todayi'm fine. 1 |
将“\r\n”改为“\n”:
…… scan.useDelimiter("\n");// 设置换行为分隔符 …… |
hello world today
0 hello world today i'm fine. 1 |
“\r”与“\n”在实验中效果一样,但与“\r\n”还是有区别的。
范例:控制台输入。
package myio; import java.util.Scanner;
public class ScannerDemo2 { public static void main(String[] args) { Scanner scan = new Scanner(System.in); scan.useDelimiter("\r\n"); String str = scan.next(); System.out.println(str); scan.close(); } } |
中国你好! 中国 你好! |
在这里,“\r”、“\n”与“\r\n”效果都一样。
范例:测试系统默认使用的分隔符。
package firstCourse;
public class NewLineTest { public static void main(String[] args) { if (System.getProperty("line.separator").equals("\r\n")) { System.out.println("for windows"); } if(System.getProperty("line.separator").equals("\n")){ System.out.println("for Linux"); } if(System.getProperty("line.separator").equals("\r")){ System.out.println("for Mac"); } } } |
for windows |
所以在windows中的记事本用的就是“\r\n”,观察之前的例子,如下:
package myio;
import java.io.File; import java.io.PrintStream;
public class ScannerDemo3 { public static void main(String[] args) throws Exception { File file = new File("d:" + File.separator + "demo.txt"); PrintStream ps = new PrintStream(file); String str = "Tara\rSo yeon\r\nCharlene\nChoi"; ps.print(str); ps.close(); } } |
可见,这里只有“\r\n”才能起到换行的效果,但要注意在Linux和Mac中并非这样。
2.6.2 Window下与Linux下换行的探究
我们知道\r表示:carriage return,\n表示:new line,具体不同的系统换行符如下:
\r:Mac
\n:Unix/Linux
\r\n:Windows
所以说,如果在IO操作文件输出时,换行符写成“\n”,则在windows的notepad中是无法显示换行的。下面我们来一一验证。
范例:编写不同的换行符,然后分别在Windows下的notepad和Linux下的vi打开。
package myio;
import java.io.File; import java.io.PrintStream;
public class ScannerDemo3 { public static void main(String[] args) throws Exception { File file = new File("d:" + File.separator + "demo.ecl"); PrintStream ps = new PrintStream(file); String str = "A\rA\nA\r\nA"; ps.print(str); ps.close(); } } |
Notepad中打开:
很明显只有当遇到“\r\n”时,才会换行。在vi中打开:
可以看到,在Linux的vi中,“\r”是被显示为“^M”的,而只有遇到“\n”才会换行。我们在用UltraEdit中打开demo.ecl文件,并以16进制显示:
其中16进制中41表示A,0D表示“\r”,0A表示“\n”。所以以后我们在Linux下编辑文档文件,就不要把后缀名改为“.txt”了,因为如果改为txt后缀名,在Windows下人们会默认用notepad打开,显示的将是一篇文章只有一行。因此我们在Windows下阅读其他系统的文档文件,也不要使用notepad来阅读,而要用专业点的阅读软件,如EditPlus、UltraEdit等等。
2.7 字符编码(了解)
在计算机的世界之中,所有的文字都是通过编码实现的,那么在系统之中,常见的编码有如下几种:
n GBK/GB2312:表示的是中文国际编码,GBK是包含简体中文和繁体中文,而GBK2312包含了简体中文;
n ISO8859-1:是一个国际的通用编码,可以保存任何的文字,但是主要是英文字母操作比较多,中文也可以表示,但是都会经过编码;
n UNICODE:Java的文字编码,采用十六进制,可以表示出世界上的任何一种文字;
n UTF编码:改进了UNICODE编码,中文使用十六进制,而英文使用ISO8859-1,此编码是在开发之中用的较多的编码,但是在学习中暂时不使用此编码。
乱码的造成:编码和解码不统一所造成的。
如果想要在开发之中回避乱码问题,则首先就必须知道当前的语言环境是什么,可以通过如下的代码发现:
package myio;
public class CodeDemo { public static void main(String[] args) { System.getProperties().list(System.out); } } |
-- listing properties -- java.runtime.name=Java(TM) SE Runtime Environment sun.boot.library.path=F:\MyEclipse\Common\binary\com.sun.ja... java.vm.version=11.3-b02 java.vm.vendor=Sun Microsystems Inc. java.vendor.url=http://java.sun.com/ path.separator=; java.vm.name=Java HotSpot(TM) 64-Bit Server VM file.encoding.pkg=sun.io user.country=CN sun.java.launcher=SUN_STANDARD sun.os.patch.level=Service Pack 1 java.vm.specification.name=Java Virtual Machine Specification user.dir=D:\eclipse\MyEclipse 10\ComeBack java.runtime.version=1.6.0_13-b03 java.awt.graphicsenv=sun.awt.Win32GraphicsEnvironment java.endorsed.dirs=F:\MyEclipse\Common\binary\com.sun.ja... os.arch=amd64 java.io.tmpdir=C:\Users\Wolex\AppData\Local\Temp\ line.separator=
java.vm.specification.vendor=Sun Microsystems Inc. user.variant= os.name=Windows Vista sun.jnu.encoding=GBK java.library.path=F:\MyEclipse\Common\binary\com.sun.ja... java.specification.name=Java Platform API Specification java.class.version=50.0 sun.management.compiler=HotSpot 64-Bit Server Compiler os.version=6.1 user.home=C:\Users\Wolex user.timezone= java.awt.printerjob=sun.awt.windows.WPrinterJob file.encoding=GBK java.specification.version=1.6 user.name=Wolex java.class.path=D:\eclipse\MyEclipse 10\ComeBack\bin java.vm.specification.version=1.0 sun.arch.data.model=64 java.home=F:\MyEclipse\Common\binary\com.sun.ja... java.specification.vendor=Sun Microsystems Inc. user.language=zh awt.toolkit=sun.awt.windows.WToolkit java.vm.info=mixed mode java.version=1.6.0_13 java.ext.dirs=F:\MyEclipse\Common\binary\com.sun.ja... sun.boot.class.path=F:\MyEclipse\Common\binary\com.sun.ja... java.vendor=Sun Microsystems Inc. file.separator=\ java.vendor.url.bug=http://java.sun.com/cgi-bin/bugreport... sun.cpu.endian=little sun.io.unicode.encoding=UnicodeLittle sun.desktop=windows sun.cpu.isalist=amd64 |
本程序的功能是列出JVM的所有相关属性,通过查询可以发现本机默认的文字编码“file.enconding=GBK”。
范例:演示乱码的产生。
package myio;
import java.io.File; import java.io.PrintStream;
public class CodeDemo { public static void main(String[] args) throws Exception { File file = new File("d:" + File.separator + "demo.txt"); PrintStream ps = new PrintStream(file); ps.write("Charlene蔡卓妍".getBytes()); ps.close(); } } |
现在输出结果正常:
但是,如果我们强制更改编码,则会出现乱码:
ps.write("Charlene蔡卓妍".getBytes("ISO8859-1"));//强制更新编码 |
结果:
通过以上的程序演示了乱码的产生,所以对于乱码的问题,就是编码和解码不统一所造成的,以后在学习到Java WEB之中还会存在这些乱码问题。
2.8 对象序列化(核心重点)
2.8.1 对象序列化的概念(核心)
所谓的对象序列化指的是将一个内存中保存的对象变为二进制数据流出的形式,即:可以在网络上传输一个对象,或者是向文件中保存一个对象。但是并不是所有类的对象都可以进行序列化操作,如果某一个类的对象要想完成序列化操作实现的话,这个对象所在的类必须实现java.io. Serializable接口,但是在这个接口之中同样也没有任何的方法,所以这种接口属于一个标识接口,作用于Cloneable一样,表示一种能力。
2.8.2 对象输出:ObjectOutputStream
实际上序列化对象的时候所保存的只是类中的属性信息,这种序列化下来的二进制信息只能通过ObjectInputStream进行反序列化操作。
2.8.3 对象输入:ObjectInputStream
实际的开发之中,序列化和发序列化的操作会有一些其他的工具帮助用户实现,此处知道这么回事就行了。
2.8.4 transient关键字(重点)
在默认情况下,当用户操作序列化的时候,会将一个类中的所有属性都进行保存,如果现在某些属性不想保存的话,则可以用transient关键字定义。
private transient String name;// name属性无法被序列化 private int age;// age属性可以被序列化 |
这个关键字一般知道就行了,而且以后被序列化的对象,往往也都是简单Java类。
2.9 练习
2.9.1 完成加法操作
要求输入两个整数,完成两个数字的相加操作,如果输入的不是整数,则要提醒用户继续输入,直到输入正确为止。
DIY |
import java.io.BufferedReader; import java.io.InputStreamReader;
public class Demo { public static void main(String args[]) throws Exception { BufferedReader buf1 = null; int num1 = 0; int num2 = 0; boolean flat = true; while (flat) { buf1 = new BufferedReader(new InputStreamReader(System.in)); System.out.print("请输入第一个数:"); String str1 = buf1.readLine(); try { num1 = Integer.parseInt(str1); flat = false; } catch (Exception e) { System.out.print("输入有误!请重新输入,"); } } flat = true; while (flat) { System.out.print("请输入第二个数:"); String str1 = buf1.readLine(); // buf1.close(); // 不能关闭,否则会出错。上面也一样! try { num2 = Integer.parseInt(str1); flat = false; } catch (Exception e) { System.out.print("输入有误!请重新输入,"); } } System.out.println(num1 + "*" + num2 + "=" + num1 * num2); } } |
请输入第一个数:Hello 输入有误!请重新输入,请输入第一个数:5 请输入第二个数:World 输入有误!请重新输入,请输入第二个数:8 5*8=40 |
Answer |
一、先设置数据输入类,可以接收各种数据 |
package org.lxh.adddemo;
import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date;
public class InputData { private BufferedReader buf = null;
public InputData() { this.buf = new BufferedReader(new InputStreamReader(System.in)); }
public String getString(String info) { String str = null; System.out.print(info);// 打印提示信息 try { str = this.buf.readLine(); } catch (IOException e) { e.printStackTrace(); } return str; }
public int getInt(String info, String err) { int temp = 0; boolean flag = true;// 定义一个标志位 while (flag) { String str = this.getString(info); if (str.matches("\\d+")) { temp = Integer.parseInt(str); flag = false;// 退出循环 } else { System.out.print(err); } } return temp; }
public float getFloat(String info, String err) { float temp = 0.0f; boolean flag = true;// 定义一个标志位 while (flag) { String str = this.getString(info); if (str.matches("\\d+.?\\d+")) { temp = Float.parseFloat(str); flag = false;// 退出循环 } else { System.out.print(err); } } return temp; }
public char getChar(String info, String err) { char temp = ' '; boolean flag = true;// 定义一个标志位 while (flag) { String str = this.getString(info); if (str.matches("\\w")) { temp = str.charAt(0); flag = false;// 退出循环 } else { System.out.print(err); } } return temp; }
public Date getDate(String info, String err) { Date temp = null; boolean flag = true;// 定义一个标志位 while (flag) { String str = this.getString(info); if (str.matches("\\d{4}-\\d{2}-\\d{2}")) { try { temp = new SimpleDateFormat("yyyy-MM-dd").parse(str); } catch (ParseException e) { System.out.print(err); } flag = false;// 退出循环 } else { System.out.print(err); } } return temp; } } |
二、完成加法测试类 |
package org.lxh.adddemo;
public class AddDemo03 {
public static void main(String[] args) { InputData input = new InputData(); int i = input.getInt("请输入第一个数字:", "输入的内容必须是数字,");// 接收第一个数字 int j = input.getInt("请输入第一个数字:", "输入的内容必须是数字,");// 接收第二个数字 System.out.println("数字相加操作:" + i + " + " + j + " = " + (i + j)); } } |
请输入第一个数字:Hello 输入的内容必须是数字,请输入第一个数字:4 请输入第一个数字:World 输入的内容必须是数字,请输入第一个数字:5 数字相加操作:4 + 5 = 9 |
2.9.2 完成一个菜单功能
程序运行之后可以显示如下的系统菜单:
======Xxx系统====== [1]、增加数据 [2]、查看数据 [3]、修改数据 [4]、更新数据 [0]、退出系统 |
在程序开发中,必须始终记住一个原则:拥抱变化。
在本道程序之中,必须考虑:以后有可能为这些菜单加入具体的功能。所以在菜单
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|