ChenPotato

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

Java IO

IO操作在编程中算是重要的知识点了。初学Java,借鉴网上前辈的经验,顺着前辈的思路,自己在demo上做了些修改,方便自己理解。

原博文地址:http://blog.csdn.net/yczz/article/details/38761237

前言

我们知道,操作系统向下对硬件进行抽象,向上为用户提供接口。I/O设备(如Disk)通常具有控制器(controller),控制器的功能在于屏蔽外设自身的细节,向操作系统提供一个相对简单的接口。衔接控制器与操作系统的职责由驱动(driver)完成。驱动中通常包含各种协议,最终落实到I/O设备的控制器是对其寄存器进行读写。这些寄存器构成IO地址空间。读写I/O设备,简而言之就是IO操作。

IO流(Stream) 是提供给用户进行IO操作的一种抽象,是一种概念,是用户希望处理IO的方式,本质上就是对文件的处理(everything is a file).根据处理的需求,比如以什么格式读入,以什么格式写入,需不需要缓存,阻塞or非阻塞等等,按需分为不同的流,在Java里基本的流如下图(此图是Java7的,更新的见官方文档):

如图所示,Java IO流分为两大类,字符流和字节流。字节可以理解为字符的超集。本质上所有文件都是字节组成,文件包括文本文件(text)和二进制文件(bin)。文本文件是对人类使用的字符进行特定的编码(如ANSIC,UNICODE,UTF-8等)得到的文件。二进制文件则是一些视音频数据,可执行文件之类。字符流可以理解为基于文本的操作(text-base),字节流则基于原始字节的操作(raw byte).进入正片之前,先写个demo了解个大概。

code_0.0.0

 1 import java.io.*;
 2 public class ChineseDemo {
 3     public static void main(String[] args){
 4         String filepath = "E:/Java/ChineseDemo.txt";
 5         byte[] readbuf = new byte[512];
 6         int ret;
 7         InputStream  in = null;
 8         OutputStream out = null;
 9         BufferedReader br = null;
10         String readString = null;
11         
12         try{
13             out = new FileOutputStream(filepath); 
14             out.write("中文文件".getBytes());
15             out.close();
16             
17             //FileInputStream 字节形式读取 
18             System.out.println("Byte Approach:");
19             in = new FileInputStream(filepath);
20             while((ret = in.read(readbuf))!= -1){                
21                 System.out.println("read " + ret + "bytes");
22                 for(int i = 0;i < ret; i++){
23                     System.out.printf("0X%02X ",readbuf[i]);   //打印读出来的字节
24                 }
25                 readString = new String(readbuf,0,ret);       //将byte[] 转换为String
26                 System.out.println("\n" + readString);            
27             } 
28             in.close();
29             
30             //InputStreamReader 字符形式读取(系统默认编码格式) 
31             System.out.println("\ntext-base:");
32             br =  new BufferedReader(new InputStreamReader(new FileInputStream(filepath))); 
33             while((readString = br.readLine()) != null){
34                 System.out.println(readString);
35             }
36             br.close();
37             
38             //InputStreamReader 字符形式读取(以GBK编码格式去读取)
39             System.out.println("\ntext-base decode with GBK:");
40             br =  new BufferedReader(new InputStreamReader(new FileInputStream(filepath) ,"gbk"));     //译为GBK码
41             while((readString = br.readLine()) != null){
42                 System.out.println(readString);
43             }    
44             br.close();
45         }catch(IOException e){
46             e.printStackTrace();
47         }
48     }
49 }

运行结果

Byte Approach:
read 12bytes
0XE4 0XB8 0XAD 0XE6 0X96 0X87 0XE6 0X96 0X87 0XE4 0XBB 0XB6
中文文件

text-base:
中文文件

text-base decode with GBK:
涓枃鏂囦欢

上面的代码,先创建文件E:/Java/ChineseDemo.txt,并以utf-8编码格式(默认)写入“中文文件”,对应的字节编码为:

0XE4 0XB8 0XAD 0XE6 0X96 0X87 0XE6 0X96 0X87 0XE4 0XBB 0XB6

demo里按三种方式读取

1、字节流直接读取原始字节,然后自己将byte[]转为String

2、字符流读取(系统默认编码格式),读取正确

3、字符流读取(指定为GBK编码格式),乱码, 0xE4 0XB8 对应 ' 欢 ',依次同理

 

 

大概了解后,下面开始按字节流和字符流两大块开始展开。到时再回过头分析demo

 

一、字节流

1、InputStream & OutputStream

code_1.1.0

//文件读写测试
package C10;
import java.io.*;
public class CreatFile {
	
	private String filepath ;
	public CreatFile(String s){
		filepath = s;
	}
	public  void Createfile(){
		File f = new File(filepath);
		try{
			f.createNewFile();   //原子操作,当且仅当文件不存在时创建
			System.out.println("该分区大小  "+f.getTotalSpace()/(1024*1024*1024)+"G");    //返回文件所在分区大小
			f.mkdir();
			System.out.println("文件名  "+f.getName());
			
			System.out.println("文件父目录  "+f.getParent());
		}catch(Exception e){
			e.printStackTrace();
		}
	}
	
	public void StreamRead(){
		  int cnt = 0;
		  int ret = 0;
	  InputStream StreamReader = null; // InputStream  是所有输入流的基类
		try {
			StreamReader = new FileInputStream(new File(filepath));   
		} catch (FileNotFoundException e1) {
			e1.printStackTrace();
		}

		try{
			while( ( ret = StreamReader.read()) != -1){
				
				System.out.printf("%c",ret);
				cnt++;
			}
			System.out.println("文件字节: " + cnt);
		}catch(IOException e){
			System.out.print(e);
			e.printStackTrace();
		}finally{
			try{
				StreamReader.close();
			}catch(IOException e){
				System.out.println(e);
				e.printStackTrace();
			}
		}
	}
	
	public void StreamWrite(String s){
		FileOutputStream StreamWriter = null;
		try{
			StreamWriter = new FileOutputStream(new File(filepath));   //创建输出流
			StreamWriter.write(s.getBytes());                   //写入字符串
			StreamWriter.close(); 
		}catch(IOException e){
			e.printStackTrace();
		}
	}
	public static void main(String[] args){
		String filepath = "E:/Java/CreatFile.txt";
		CreatFile f = new CreatFile(filepath);
		f.Createfile();
		f.StreamWrite("Welocome to Java IO\n");   
		f.StreamRead();
	}
}

 上面代码只是简单的创建文件,按字节写入字符串,然后再读出来。

 

InputStream:

This abstract class is the superclass of all classes representing an input stream of bytes.

Applications that need to define a subclass of InputStream must always provide a method that returns the next byte of input.

InputStream 是所有字节输入流的基类,所有InputStream的派生类必须具备返回输入中下一字节的方法(即必须能读取下一字节)

 

FileOutputStream

is meant for writing streams of raw bytes such as image data. For writing streams of characters, consider using FileWriter.

FileOutputStream 是所有字节输出流的基类,如果需要使用字符写入,则使用FileWriter.

 

StreamReader = new FileInputStream(new File(filepath)) 创建一个新的FileInputStream

其中FileInputStream的原型

public FileInputStream(File file) throws FileNotFoundException

Creates a FileInputStream by opening a connection to an actual file, the file named by the File object file in the file system. A new FileDescriptor object is created to represent this file connection.

将流与具体的文件关联起来,FileDescriptor ,文件描述符,(Magic number?)

public FileInputStream(File file)构造方法找不到文件时会报错,找不到文件FileNotFoundException,相反,创建FileOutputStream,如果文件不存在,但是文件名合法,则会自动创建。

code_1.1中在读去文件时每次只读一个Byte,这样效率太低,当文件比较大时涉及到大量的IO操作,而IO操作的系统开销是比较大的。如果每次IO读时能读多一点,则大大提高效率。Java肯定也是有相关的方法的。

 

code_1.1.1 进行文件复制

import java.io.*;
public class FileCopy {
	public static void main(String[] args){
		String infile = "E:/Java/CreatFile.txt";
		String outfile = "E:/Java/OutFile.txt";
		FileInputStream in = null;
		FileOutputStream out = null;
		byte[] readbuf = new byte[512];         //512 bytes buffer
		int ret;
		try{
			in = new FileInputStream(infile);
		}catch(FileNotFoundException e){
			System.out.println(e);
			e.printStackTrace();
		}
		
		try{
			out = new FileOutputStream(outfile);
		}catch(FileNotFoundException e){
			System.out.println(e);
			e.printStackTrace();
		}
	try	{
		
		//读取一个文件并写入另外一个文件
		while((ret = (in.read(readbuf))) != -1){                      
				System.out.print(new String(readbuf));         
				try{
					out.write(readbuf,0,ret);       //将之前读取到buffer里的字节写入文件
				}catch(IOException e){
					System.out.println(e);
					e.printStackTrace();
				}
				        
		}
	}catch(IOException e){
		System.out.println(e);
		e.printStackTrace();
	}finally{
		try{
			in.close();
			out.close();
		}catch(IOException e){
			System.out.println(e);
			e.printStackTrace();
		}	
	}
	
	
	System.out.println("Now read " + outfile );      //开始读取刚刚写入的文件,查看是否正确写入	

	try{
		in = new FileInputStream(outfile);
	}catch(IOException e){
		System.out.println(e);
		e.printStackTrace();
	}
	
	try{
		while((ret = in.read(readbuf)) != -1){
			System.out.print(new String(readbuf));    // 将byte[] 转换为 String 并打印读取的结果。
			readbuf = new byte[512];             //readbuf 重新引用以达到清零的效果        
		}
	}catch(IOException e){
		System.out.println(e);
		e.printStackTrace();
	}finally{
		try{
			in.close();
		}catch(IOException e){
			System.out.println(e);
			e.printStackTrace();
		}
		
	}	
  }
}

 

2、ObjectInputStream  & ObjectOutputStream

读写对象:ObjectInputStream 和ObjectOutputStream ,该流允许读取或写入用户自定义的类,但是要实现这种功能,被读取和写入的类必须实现Serializable接口,其实该接口并没有什么方法,可能相当 于一个标记而已,但是确实不合缺少的。实例代码如下:

code_1.2.0

import org.apache.commons.logging.*;
import java.io.*;

class Student implements Serializable{
	
	private String name;
	private int age;

	public Student(String name , int age){
		super();
		this.name = name;
		this.age = age;
	}
	
	public String toString(){
		return "Student [name = " + name + ", age = " + age+"]"; 
	}
}


public class ObjectStream {
	private static Log logger = LogFactory.getLog(ObjectStream.class);
	
	public static void ObjectStreamRW(String file){
		logger.info("ObjectStreamRw() called"); 
		ObjectOutputStream objectwriter = null;
		ObjectInputStream  objectreader = null;
		
		try{
			objectwriter = new ObjectOutputStream(new FileOutputStream(file));    //FileOutputStream 的构造方法 , file不存在则会去创建
			objectwriter.writeObject(new Student("Eric" , 25));        //向ObjectOutputStream写入两个Student 类
			objectwriter.writeObject(new Student("Chen", 26));
			
			objectreader = new ObjectInputStream(new FileInputStream(file));  //FileInputStream 的构造方法,file如果不存在则会去创建
			System.out.println(objectreader.readObject());            //从ObjectInputStream中读取刚刚写入的类
			System.out.println(objectreader.readObject());
			
		}catch(IOException |  ClassNotFoundException e){
			e.printStackTrace();
		}finally{
			try{
				objectwriter.close();
				objectreader.close();
			}catch(IOException e){
				e.printStackTrace();
			}
		}	
	}
	
	public static void main(String[] args){
		String filename = "E:/Java/ObjectStream.txt";
		ObjectStreamRW(filename);
	}
}

 运行结果:

C10.ObjectStream.ObjectStreamRW(ObjectStream.java:27) [main] INFO  C10.ObjectStream  - ObjectStreamRw() called
Student [name = Eric, age = 25]
Student [name = Chen, age = 26]

 

3、DataInputStream、DataOutputStream

有时没有必要存储整个对象的信息,而只是要存储一个对象的成员数据,成员数据的类型假设都是Java的基本数据类型,这样的需求不必使用 到与Object输入、输出相关的流对象,可以使用DataInputStream、DataOutputStream来写入或读出数据。下面是一个例 子:(DataInputStream的好处在于在从文件读出数据时,不用费心地自行判断读入字符串时或读入int类型时何时将停止,使用对应的readUTF()和readInt()方法就可以正确地读入完整的类型数据)

code_1.3.0

import java.io.*;
import org.apache.commons.logging.*;

class StudentInfo{
	String name;
	int age;
	int score;
	
	public StudentInfo(){
		name = "Eric Chen";
		age = 25;
		score = 47;
	}
	
	public void setName(String name){
		this.name = name;
	}
	public String getName(){
		return name;
	}
	
	public void setAge(int age){
		this.age = age;
	}
	public int getAge(){
		return age;
	}
	
	public void setScore(int score){
		this.score = score;
	}
	public int getScore(){
		return score;
	}
	
	public String toString(){
		return "Studnet[name = " + name + ", age = " + age + ", score = " + score + " ]"; 
	}
}

public class DataStream {
	
	public static void DataStreamRW(String file){
		StudentInfo aStudent = new StudentInfo();
		DataInputStream DataStreamReader = null;
		DataOutputStream DataStreamWriter = null;
		try{
			DataStreamWriter = new DataOutputStream(new FileOutputStream(file));
			DataStreamReader = new DataInputStream(new FileInputStream(file));
			System.out.println("original :");
			System.out.println(aStudent);
			
			DataStreamWriter.writeUTF(aStudent.getName());   //save original information
			DataStreamWriter.writeInt(aStudent.getAge());
			DataStreamWriter.writeInt(aStudent.getScore());
			
			aStudent.setName("Lisa");
			aStudent.setAge(20);
			aStudent.setScore(100);
			
			System.out.println("modified");
			System.out.println(aStudent);
			
			aStudent.setName(DataStreamReader.readUTF());
			aStudent.setAge(DataStreamReader.readInt());
			aStudent.setScore(DataStreamReader.readInt());
			
			System.out.println("Read back:");
			System.out.println(aStudent);
			
		}catch(IOException e){
			e.printStackTrace();
		}finally{
			try{
				DataStreamWriter.close();
				DataStreamReader.close();
			}catch(IOException e){
				e.printStackTrace();
			}	
		}
	}
	
	public static void main(String[] args){
		String filepath = "E:/Java/DataStream.txt";
		DataStreamRW(filepath);
	}
	

}

 运行结果:

original :
Studnet[name = Eric Chen, age = 25, score = 47 ]
modified
Studnet[name = Lisa, age = 20, score = 100 ]
Read back:
Studnet[name = Eric Chen, age = 25, score = 47 ]

4、PushbackInputStream

PushbackInputStream类继承了FilterInputStream类是iputStream类的修饰者。提供可以将数 据插入到输入流前端的能力(当然也可以做其他操作)。简而言之PushbackInputStream类的作用就是能够在读取缓冲区的时候提前知道下一个 字节是什么,其实质是读取到下一个字符后回退的做法,这之间可以进行很多操作,这有点向你把读取缓冲区的过程当成一个数组的遍历,遍历到某个字符的时候可以进行的操作,当然,如果要插入,能够插入的最大字节数是与推回缓冲区的大小相关的,插入字符肯定不能大于缓冲区吧!下面是一个示例。

code_1.4.0

import java.io.*;

public class PushBackInputStreamDemo {
	public static void main(String[] args) throws IOException{
		String str = "Hello world,welcome to JAVA world !";
		PushbackInputStream push = null;
		ByteArrayInputStream bat = null;
		
		bat  = new ByteArrayInputStream(str.getBytes());
		push = new PushbackInputStream(bat);
		
		int ret = 0;
		
		while((ret = push.read()) != -1){
			if(ret == ','){
				push.unread(ret);          //遇到","则回退
				ret = push.read();
				System.out.print("(Back " + (char)ret+ ")");
			}else{
				System.out.print((char)ret);
			}
		}
		
	}
}

 

 运行结果:

Hello world(Back ,)welcome to JAVA world !

 

5、SequenceInputStream

有些情况下,当我们需要从多个输入流中向程序读入数据。此时,可以使用合并流,将多个输入流 合并成一个SequenceInputStream流对象。SequenceInputStream会将与之相连接的流集组合成一个输入流并从第一个输入 流开始读取,直到到达文件末尾,接着从第二个输入流读取,依次类推,直到到达包含的最后一个输入流的文件末尾为止。 合并流的作用是将多个源合并合一个源。其可接收枚举类所封闭的多个字节流对象。

code_1.5.0

import java.io.*;
import java.util.*;
public class SequenceInputStreamDemo {
	private static void doSequence(){
		SequenceInputStream sis = null;
		BufferedOutputStream bos = null;
		FileOutputStream fos = null; 
		String file_1 = "E:/Java/SequenceInputStream_1.txt";
		String file_2 = "E:/Java/SequenceInputStream_2.txt";
		String file_3 = "E:/Java/SequenceInputStream_3.txt";        //三个input
		String file_4 = "E:/Java/SequenceInputStream_4.txt";        //output
		
		
		try{
			fos = new FileOutputStream(file_1);
			fos.write((file_1+"\n").getBytes(),0,(file_1+"\n").length());
			fos.close();
			
			fos = new FileOutputStream(file_2);		
			fos.write((file_2+"\n").getBytes(),0,(file_2+"\n").length());
			fos.close();	

			fos = new FileOutputStream(file_3);		
			fos.write((file_3+"\n").getBytes(),0,(file_3+"\n").length());
			fos.close();

			
			Vector<InputStream> vector = new Vector<InputStream>();
			vector.addElement(new FileInputStream(file_1));
			vector.addElement(new FileInputStream(file_2));
			vector.addElement(new FileInputStream(file_3));
			
			Enumeration<InputStream> e = vector.elements();
			
			sis = new SequenceInputStream(e);          //三个input按顺序合并为一个流
			
			bos = new BufferedOutputStream(new FileOutputStream(file_4));
			
			byte[] readbuf = new byte[1024];
			int len = 0;
			while((len = sis.read(readbuf)) != -1){
				bos.write(readbuf,0,len);
				bos.flush();             
			}
		}catch(IOException e){
			e.printStackTrace();
		}
		finally{
			try{
				if(sis != null){
					sis.close();
				}
				if(bos != null){
					bos.close();
				}
			}catch(IOException e){
				e.printStackTrace();
			}
			

		}
	}
	
	public static void main(String[] args){
		doSequence();
		System.out.println("Done");
	}
}

 code_1.5.0代码中需要注意的是,使用BufferedOutputStream,在write后使用flush刷新缓存区以确保缓存中的数据完全被写入。附上flush()的说明

public void flush()
           throws IOException
Flushes this buffered output stream. This forces any buffered output bytes to be written out to the underlying output stream.

5、PrintStream

public class PrintStream
extends FilterOutputStream
implements Appendable, Closeable

A PrintStream adds functionality to another output stream, namely the ability to print representations of various data values conveniently. Two other features are provided as well. Unlike other output streams, a PrintStream never throws an IOException; instead, exceptional situations merely set an internal flag that can be tested via the checkError method. Optionally, a PrintStream can be created so as to flush automatically; this means that the flush method is automatically invoked after a byte array is written, one of the println methods is invoked, or a newline character or byte ('\n') is written.

PrintStream,字节流格式化输出,不抛异常,而是内部实现标识,通过checkError来查看。在byte 数组写入完成后或写入‘\n’后自动刷新输出缓存。System.out就是PrintStream类。

 

二、字符流(顾名思义,就是操作字符文件的流)

1.java 使用Unicode存储字符串,在写入字符流时我们都可以指定写入的字符串的编码。前面介绍了不用抛异常的处理字节型数据的流 ByteArrayOutputStream,与之对应的操作字符类的类就是CharArrayReader,CharArrayWriter类,这里也 会用到缓冲区,不过是字符缓冲区,一般将字符串放入到操作字符的io流一般方法是

CharArrayReaderreader=mew CharArrayReader(str.toCharArray()); 一旦会去到CharArrayReader实例就可以使用CharArrayReader访问字符串的各个元素以执行进一步读取操作。不做例子

2.FileReader &PrintWriter

code_2.2.0

 1 import java.io.*;
 2 
 3 public class PrintStreamDemo {
 4     public static void main(String[] args){
 5         String file = "E:/Java/SequenceInputStream_1.txt";
 6         char[] buffer = new char[512];
 7         int ret = 0;
 8         FileReader reader = null;
 9         PrintWriter writer = null;
10         
11         try{
12             reader = new FileReader(file);
13             writer = new PrintWriter(System.out);
14             
15             while((ret = reader.read(buffer) ) != -1){
16                 writer.write("Read " + ret + " bytes\n");
17                 writer.write(buffer,0,ret);
18              }
19             }catch(IOException e){
20                 e.printStackTrace();
21             }finally{
22                 
23                 try{
24                     reader.close();
25                 }catch(IOException e){
26                     e.printStackTrace();
27                 }
28                 
29                 writer.close();
30             }
31             
32         }
33 } 

运行结果:

Read 34 bytes
E:/Java/SequenceInputStream_1.txt

PrintWriter主要用于向字符输出流产生格式化的输出。

Prints formatted representations of objects to a text-output stream. This class implements all of the print methods found in PrintStream. It does not contain methods for writing raw bytes, for which a program should use unencoded byte streams.

3 、BufferedReader &  InputStreamReader

回到前言中的demo code_0.0.0中,创建字符流时 br = new BufferedReader(new InputStreamReader(new FileInputStream(filepath) ,"gbk"));

引用手册

public class InputStreamReader
extends Reader
An InputStreamReader is a bridge from byte streams to character streams: It reads bytes and decodes them into characters using a specified charset. The charset that it uses may be specified by name or may be given explicitly, or the platform's default charset may be accepted.

Each invocation of one of an InputStreamReader's read() methods may cause one or more bytes to be read from the underlying byte-input stream. To enable the efficient conversion of bytes to characters, more bytes may be read ahead from the underlying stream than are necessary to satisfy the current read operation.

For top efficiency, consider wrapping an InputStreamReader within a BufferedReader. For example:

 BufferedReader in
   = new BufferedReader(new InputStreamReader(System.in));
 

InputStreamReader作为字节流到字符流之间的桥梁,器构造方法可接收InputStream和编码方式。为提供读取效率,建议使用BufferedReader进行封装。亦称链接流

4、StreamTokenizer

StreamTokenizer对字符输入流进行解析,每次调用nextToken()方法读取一次,这里的nextToken() 和C中的scanf() 的工作原理类似,遇到空格停止。

StreamTokenizer 的对字符流的解析结果由其内部的各种静态属性来标识。比如nval,sval , TT_EOF等。

下面对E:/Java/StreamTokenizerDemo.txt 进行解析,StreamTokenizerDemo.txt 以UTF-8 编码格式写入以下内容:

Hello World
~!@#$%^&*?
中文100Eric.Chen
100 1.2345

code_2.4.0

 1 import java.io.*;
 2 import java.nio.*;
 3 import java.util.*;
 4 public class StreamTokenizerDemo{
 5     
 6     public static void main(String[] args){
 7         String filepath = "E:/Java/StreamTokenizerDemo.txt";
 8         
 9         int charCnt = 0;
10         int wordCnt = 0;
11         int numCnt  = 0;
12         
13         ArrayList<Character> charList = new ArrayList<Character>();    
14         ArrayList<String> wordList = new ArrayList<String>();
15         ArrayList<Double> numList = new ArrayList<Double>();          
16         StreamTokenizer st = null;
17         try{
18             BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(filepath)));
19             st = new StreamTokenizer(br);
20             while(st.nextToken() != StreamTokenizer.TT_EOF){
21                 switch(st.ttype){
22                     case StreamTokenizer.TT_NUMBER:
23                         numList.add(st.nval);
24                         numCnt++;
25                         break;
26                     case StreamTokenizer.TT_WORD:
27                         wordList.add(st.sval);
28                         wordCnt++;
29                         break;
30                     case StreamTokenizer.TT_EOL:
31                         break;
32                 default:
33                         charList.add((char)st.ttype);
34                         charCnt++;
35                     break;
36                 }    
37             }
38             
39             br.close();
40             
41             System.out.println(wordCnt+" Word:");
42             for(String s : wordList){
43                 System.out.print(s + " ");
44             }
45             
46             System.out.println("\n\n" + numCnt +" Number:");
47             for(Double f:numList){
48                 System.out.print(f + " ");
49             }
50             
51             System.out.println("\n\n" + charCnt + " Character:");
52             for(Character c: charList){
53                 System.out.print(c + " ");
54             }
55         }catch(IOException e){
56             e.printStackTrace();
57         }
58     }
59 }

运行结果:

3 Word:
Hello World 中文100Eric.Chen

2 Number:
100.0 1.2345

10 Character:
~ ! @ # $ % ^ & * ?

 

待续。

 

posted on 2016-08-11 17:56  ChenPotato  阅读(192)  评论(0编辑  收藏  举报