输入和输出是所有应用中所必需的组成部分,通过IO可以读取输入数据以及存储数据到外部设备上。Java中的输入和输出是通过java.io来支持的。下面是本人在学习中的归纳和体会。
1. File类和文件过滤器
顾名思义,File类中是有关文件的操作。这里必须明确,文件还包括目录。通过文件或目录路径的字符串作为参数,根据传入的是目录路径还是文件名路径可以分别初始化不同的File对象。在File对象中封装了对文件的操作,例如新建文件,删除文件和重命名等方法。但是它并不能访问文件本身的内容,这需要输入输出流来完成。下面是有关File类的代码。
- public class FileTest
- {
- public static void main(String[] args) throws IOException
- {
-
- File file = new File(".");
-
- System.out.println(file.getName());
-
- System.out.println(file.getParent());
-
- System.out.println(file.getAbsoluteFile());
-
- System.out.println(file.getAbsoluteFile().getParent());
-
- File tmpFile = File.createTempFile("aaa", ".txt", file);
-
- tmpFile.deleteOnExit();
-
- File newFile = new File(System.currentTimeMillis() + "");
- System.out.println("newFile对象是否存在:" + newFile.exists());
-
- newFile.createNewFile();
-
-
- newFile.mkdir();
-
- String[] fileList = file.list();
- System.out.println("======当前路径下所有文件和路径如下=====");
- for (String fileName : fileList)
- {
- System.out.println(fileName);
- }
-
-
- File[] roots = File.listRoots();
- System.out.println("======系统所有根路径如下=====");
- for (File root : roots)
- {
- System.out.println(root);
- }
- }
- }
|
在上面的代码中,使用list()方法可以返回目录下所有文件的路径和名称。使用文件过滤器,可以根据自己的需要选择返回的文件类型。通过一个实现了FilenameFilter接口的类对象作为list()方法的参数,就可以实现文件过滤的作用。过滤的规则在重写FilenameFilter中的accept()方法中定义。代码如下:
- public class FilenameFilterTest
- {
- public static void main(String[] args)
- {
- File file = new File(".");
- String[] nameList = file.list(new MyFilenameFilter());
- for (String name : nameList)
- {
- System.out.println(name);
- }
- }
- }
-
- class MyFilenameFilter implements FilenameFilter
- {
- public boolean accept(File dir, String name)
- {
-
- return name.endsWith(".java")
- || new File(name).isDirectory();
- }
- }
|
2. 输入输出流
在Java中,从不同角度定义了输入输出流的分类。以内存作为流的方向基点,可将其分为输入流和输出流,流向内存的成为输入流,流出内存的成为输出流。从数据操作的最小单元来分类,可将其分为字节流和字符流。根据抽象程度分类,可将其分为节点流和处理流,处理流是连接到实际物理节点的节点流的封装,这样既可以不必去关注节点的来源(文件或者数组),用统一的方法去操作,又可以使用更加方面的方法来实现操作。
在学习中,本人对于输入输出流的总结归纳:
A. 字节流和字符流其实并没有本质的区别,无非是它们二者的操作单元不同。字节流的基类为InputStream和OutputStream,字符流的基类为Reader和Writer。它们所提供的读写方法一样,区别在于参数,需要对应流类型。
B. 输入流中的read系列方法是从输入流中读取数据的操作。空参数将返回所读取的字节(字符)。read方法中可以传入数组参数,数组的类型必须和流的数据类型相匹配,相当于一个竹筒,一次可读取最多等于数组容量的数据,并将所读数据装进数组中。
C. 输出流中的write系列方法是向输出流中写入数据的操作。方法中的参数可以是整型或字符型变量,也可以传入数组参数,数组的类型必须和流的数据类型相匹配,相当于一个竹筒,一次可写入最多等于数组容量的数据。特别地,字符型输出流的write方法可以传入字符串。
D. InputStream和OutputStream,Reader和Writer这四个类作为Java中输入输出流的抽象基类,并不能直接被初始化使用。Java中提供了一些继承了它们的类,用于实现具体的某种流操作。如:
a.File前缀系列的类(如FileInputStream),用于对文件类进行流操作,使用文件名作为构造函数的参数;示例代码如下:
输出类:
- public class FileOutputStreamTest
- {
- public static void main(String[] args) throws IOException
- {
- FileInputStream fis = null;
- FileOutputStream fos = null;
- try
- {
-
- fis = new FileInputStream("FileOutputStreamTest.java");
-
- fos = new FileOutputStream("newFile.txt");
- byte[] bbuf = new byte[32];
- int hasRead = 0;
-
- while ((hasRead = fis.read(bbuf)) > 0 )
- {
-
- fos.write(bbuf , 0 , hasRead);
- }
- }
- catch (IOException ioe)
- {
- ioe.printStackTrace();
- }
- finally
- {
-
- if (fis != null)
- {
- fis.close();
- }
-
- if (fos != null)
- {
- fos.close();
- }
- }
- }
- }
|
输入类:
- public class FileInputStreamTest
- {
- public static void main(String[] args) throws IOException
- {
-
- FileInputStream fis = new FileInputStream("FileInputStreamTest.java");
-
- byte[] bbuf = new byte[1024];
-
- int hasRead = 0;
-
- while ((hasRead = fis.read(bbuf)) > 0 )
- {
-
- System.out.print(new String(bbuf , 0 , hasRead ));
- }
- fis.close();
- }
- }
|
b. 数组前缀系列类(如ByteArrayInputSteam),用于对数组进行流操作,使用数组对象作为构造函数的参数;
c. 转换流InputStreamReader将字节输入流转换为字符输入流;InputStreamReader将字节输出流转换为字符输出流,代码如下:
- public class KeyinTest
- {
- public static void main(String[] args)
- {
- BufferedReader br = null;
- try
- {
-
- InputStreamReader reader = new InputStreamReader(System.in);
-
- br = new BufferedReader(reader);
- String buffer = null;
-
- while ((buffer = br.readLine()) != null)
- {
-
- if (buffer.equals("exit"))
- {
- System.exit(1);
- }
-
- System.out.println("输入内容为:" + buffer);
- }
- }
- catch (IOException ioe)
- {
- ioe.printStackTrace();
- }
-
- finally
- {
- try
- {
- br.close();
- }
- catch (IOException ioe)
- {
- ioe.printStackTrace();
- }
- }
- }
- }
|
d. 字符流的子类有String前缀(StringReader和StringWriter)系列类可以对字符串进行操作,代码如下:
- public class StringNodeTest
- {
- public static void main(String[] args)
- {
- String src = "从明天起,做一个幸福的人\n"
- + "喂马,劈柴,周游世界\n"
- + "从明天起,关心粮食和蔬菜\n"
- + "我有一所房子,面朝大海,春暖花开\n"
- + "从明天起,和每一个亲人通信\n"
- + "告诉他们我的幸福\n";
- StringReader sr = new StringReader(src);
- char[] buffer = new char[32];
- int hasRead = 0;
- try
- {
-
- while((hasRead = sr.read(buffer)) > 0)
- {
- System.out.print(new String(buffer ,0 , hasRead));
- }
- }
- catch (IOException ioe)
- {
- ioe.printStackTrace();
- }
- finally
- {
- sr.close();
- }
-
-
- StringWriter sw = new StringWriter(20);
-
- sw.write("我远离了大海,\n");
- sw.write("看不到春暖花开,\n");
- sw.write("我只有一只小龟,\n");
- sw.write("一样可以闻到馥郁花香\n");
- System.out.println("------下面是sw的字符串节点里的内容------:");
-
- System.out.println(sw.toString());
- }
- }
|
d.推回输入流PushbackInputStream和PushbackReader带有一个推回缓冲区,使用unread(数组)方法可以将一个字节/字符数组推回到推回缓冲区中。推回输入流也有read方法,使用read方法读取输入流时,首先从输入缓冲区中读取,读完之后才从输入流中读。代码如下:
- public class PushbackTest
- {
- public static void main(String[] args)
- {
- PushbackReader pr = null;
- try
- {
-
- pr = new PushbackReader(new FileReader("PushbackTest.java") , 64);
- char[] buf = new char[32];
-
- String lastContent = "";
- int hasRead = 0;
-
- while ((hasRead = pr.read(buf)) > 0)
- {
-
- String content = new String(buf , 0 , hasRead);
- int targetIndex = 0;
-
-
- if ((targetIndex = (lastContent + content).indexOf("new PushbackReader")) > 0)
- {
-
- pr.unread((lastContent + content).toCharArray());
-
- pr.read(buf , 0 , targetIndex);
-
- System.out.print(new String(buf , 0 ,targetIndex));
- System.exit(0);
- }
- else
- {
-
- System.out.print(lastContent);
-
- lastContent = content;
- }
- }
- }
- catch (IOException ioe)
- {
- ioe.printStackTrace();
- }
- finally
- {
- try
- {
- if (pr != null)
- pr.close();
- }
- catch (IOException ioe)
- {
- ioe.printStackTrace();
- }
- }
- }
- }
|
E. 在Java虚拟机中可以通过exec方法执行其他的应用程序,并返回Proess对象。利用该对象的getErrorStream、getInputStream和getOutputStream方法可以分别获得子进程的错误流,输出流和输入流(方向是以程序角度为基点)。
F. RandomAccessFile类提供对文件的随机访问,程序可以直接跳转到文件的任何地方来读取数据。它内部提供了与字节流同法相同的读写方法,另外又加入了自由定位文件记录指针的方法seek。在读写操作时以字节为单位。在构造函数中还需加入一个mode参数由于制定读写权限。
G. 需要注意Scanner对象的使用。Scanner对象用于捕获输入,并将输入内容转换为字符串。
-
- Scanner sc = new Scanner(System.in);
- PrintStream ps = new PrintStream(
- new FileOutputStream("out.txt"));
-
- sc.useDelimiter("\n");
-
- while(sc.hasNext())
- {
-
- ps.println("键盘输入的内容是:" + sc.next());
- }
- ps.close();
|
对于初学者来说一个容易忽略的地方是,需要区别节点流和处理流构造方法中参数的意义。节点流中,构造方法参数是要读取或者输出地物理节点,是“目的地”或者“始发地”,而处理流的构造函数参数是所要包装的处理流对象,包装之后的操作实际上是间接操作节点流,并未对被包装的节点流本身属性做修改。采用处理流包装后,可以不用管节点流的数据类型,而根据处理流的性质传递在节点流中不能传递的内容,例如Obj;同时还可以增加一些便于操作的方法,比如缓冲区。