Java IO使用和总结
上篇文章提到了NIO的使用,这篇总结下IO的使用;不详谈语法,仅分析特点,从而明确它们的使用范围,这样就能够在合适的场合想到并应用它们。
1.IO的数据源和输出目标
IO的数据源和输出目标大致分为以下几类:
1)文件
2)管道
3)网络连接
4)内存缓存
5)系统输入输出
下面逐一分析
2.文件
/**2.文件
* 你可以根据该文件是二进制文件还是文本文件来选择使用FileInputStream或者FileReader
* 如果你需要跳跃式地读取文件其中的某些部分,可以使用RandomAccessFile
* 注意:
* 有时候你可能需要读取文件的信息而不是文件的内容,举个例子,如果你需要知道文件的大小
* 和文件的属性。对于目录来说也是一样的,比如你需要获取某个目录下的文件列表。通过File
* 类可以获取文件和目录的信息。
*/
3.管道
/**3.管道(在上一篇NIO的总结中提到了它,并且举了一个示例)
* Java IO中的管道为运行在同一个JVM中的两个线程提供了通信的能力。
* 你不能利用管道与不同的JVM中的线程通信(不同的进程)。在概念上,Java的管道不同于Unix/Linux
* 系统中的管道。在Unix/Linux中,运行在不同地址空间的两个进程可以通过管道通信。在Java中,通
* 信的双方应该是运行在同一进程中的不同线程。
*
* 除了管道之外,一个JVM中不同线程之间还有许多通信的方式。实际上,线程在大多数情况下会传递
* 完整的对象信息而非原始的字节数据。但是,如果你需要在线程之间传递字节数据,Java IO的管道
* 是一个不错的选择。
*
* 管道的本质是一样的,只是在应用时,在IO和NIO中的语法不同而已
*/
4.网络连接
/**4.IO 网络
* Java网络API用来在不同进程之间建立网络连接,而Java IO则用来在建立了连接之后的进程之间交换数据
* 下面的一个写法,不使用FileInputStream而是InputStream,是因为可以这样的话还可以同时接收来自网络的输入,再
* 延伸一下,java.io.InputStream类是所有Java IO输入流的基类。如果你正在开发一个从流中读取数据的组件,请尝试
* 用InputStream替代任何它的子类(比如FileInputStream)进行开发。这么做能够让你的代码兼容任何类型而非某种确定
* 类型的输入流 ;同样的,OutputStream也是一样。如下面一个例子
*/
/*下面的一个写法,不使用FileInputStream而是InputStream,是因为可以这样的话还可以同时接收来自网络的输入*/
public class MyClass {
public static void main(String[] args) {
InputStream inputStream = new FileInputStream("c:\\myfile.txt");
process(inputStream);
}
public static void process(InputStream input) throws IOException {
//do something with the InputStream
}
}
5.内存缓存
/**5.IO 读取或者获得字节(字符数组) 可以把这个理解为内存缓存
* 5.1 用ByteArrayInputStream或者CharArrayReader封装字节或者字符数组,然后从数组中读取数据成为流,
* 以同样的方式也可以用于读取字符数组,只要把字符数组封装在CharArrayReader上就行了。
* byte[] bytes = new byte[1024];
* InputStream input = new ByteArrayInputStream(bytes);
*
* 5.2 把数据写到ByteArrayOutputStream或者CharArrayWriter中,只要调用toByteArray()或者toCharArray,
* 所有写入的数据就会以数组的形式返回
* OutputStream output = new ByteArrayOutputStream();
* output.write("This text is converted to bytes".getBytes("UTF-8"));
* byte[] bytes = output.toByteArray();
*/
有一个深复制的问题,就可以使用这个思想来做; 如果一个对象数组List<Object>, 如何得到它的一份拷贝,我们所熟悉的add()方法,adllAll()方法,还有system.copy等都不能实现,我们需要的是一个新的List,而且其中的每一个对象都是新的,可以把Object 序列化,然后将Ist.LIst<Object>拷贝到内存中,以ByteArray的形式存放,然后从内存种读取这个 ByteArray到一个新的List<Object>中(反序列化),下面是示例:
/*引出一个深复制的问题,代码如下:*/
public static <T> List<T> deepCopy(List<T> src) throws IOException, ClassNotFoundException {
ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream(byteOut);
out.writeObject(src);
ByteArrayInputStream byteIn = new ByteArrayInputStream(byteOut.toByteArray());
ObjectInputStream in = new ObjectInputStream(byteIn);
@SuppressWarnings("unchecked")
List<T> dest = (List<T>) in.readObject();
return dest;
}
6.系统输入输出
/**6.替换系统流 系统输入输出
* System.in, System.out, System.err本身没什么可讲的。但是有一点;尽管System.in, System.out,
* System.err这3个流是java.lang.System类中的静态成员(译者注:这3个变量均为final static常量),
* 并且已经预先在JVM启动的时候初始化完成,你依然可以更改它们。只需要把一个新的InputStream设
* 置给System.in或者一个新的OutputStream设置给System.out或者System.err,之后的数据都将会在新
* 的流中进行读取、写入。如下案例:
*
* 注意:flush的调用,什么时候调用flush?
* 1)FileOutPutStream继承outputStream,并不提供flush()方法的重写,所以无论内容多少write都会
* 将二进制流直接传递给底层操作系统的I/O,flush无效果
* 而Buffered系列的输入输出流函数单从Buffered这个单词就可以看出他们是使用缓冲区的,应用程序
* 每次IO都要和设备进行通信,效率很低,因此缓冲区为了提高效率,当写入设备时,先写入缓冲区,
* 等到缓冲区有足够多的数据时,就整体写入设备,所以它需要使用flush方法手动强制写出;
* 2)close()方法中包含了flush()的操作,但是马上就关闭流,所以如果想强制刷新缓冲区,然后继续使用
* 流的话,就用flush
*/
OutputStream output = new FileOutputStream("c:\\data\\system.out.txt");
PrintStream printOut = new PrintStream(output);
System.setOut(printOut)
下面讲述一些IO中的其他相关需要学习其特点和注意的地方
7
/**7.PushbackInputStream
* 可以获取输入流之后,处理,然后再反过来放入输入源中。
*/
8
/**8 关于流的整合问题
* Stream可以与Reader或者Writer整合
* Reader或者Writer可以与Buffer整合
*/
9
/**9.之前说到了不在service层try catch,而是在方法体上throws异常:http://blog.csdn.net/Jintao_Ma/article/details/52853865
* 但是如果在service层关闭流的时候可能要用到try catch finally,这个时候,上层如何回复消息给用户?
* 个人认为一个比较好的解决方法是:在service的方法体上加上返回值,然后将service内部的异常信息描
* 述成用户能识别的内容,之后放入返回值中,在controller层捕获异常,
* 1)有异常 2)没有异常,那么有两种情况
* 2.1)service层的异常,2.2)确实没有异常 这三种情况都可以返回给用户
public static void main(String[] args) throws FileNotFoundException, IOException {
try(FileInputStream fileInputStream = new FileInputStream(new File("test.txt"))){
System.out.println("Channel2.main()");
}
}
public BaseResult controllerMethod{ /*假设是controller层次*/
BaseResult baseResult = new BaseResult();
try {
BaseResult = serviceMethod();
} catch (Exception e) {
BaseResult.setIsSuccess(false);
BaseResult.serErrMsg("业务异常");
}finally{
return BaseResult;
}
}
public BaseResult serviceMethod() throws Exception(){
BaseResult baseResult = new BaseResult();
try{
InputStream input = new FileInputStream("c:\\data\\input-text.txt");
doSomethingWithData(input);/*对输入流做一些处理*/
}catch(IOException e){
System.out.println(e.getMessage());
baseResult.setIsSuccess(false);
baseResult.serErrMsg("流异常");
return baseResult; /*这个return 在finally执行后再执行*/
}finally{
input.close();/*关闭流*/
}
doOthers();/*可能会有异常,在方法体上抛出*/
baseResult.setIsSuccess(true);
return baseResult;
}
那么还有没有其他的方法呢,其实是有的,那就是在关闭流的时候不使用finally,这样的话就不用try catch,就可以在方法体上throws异常,从而不影响我们之前文章总结的理论; JDK7及以上就提供了这种方法,称作try with resources, 顾名思义就是在try参数中加上资源,如下:
public static void main(String[] args) throws FileNotFoundException, IOException {
try(FileInputStream fileInputStream = new FileInputStream(new File("test.txt"))){
System.out.println("Channel2.main()");
}
}
另外再提到一点,关于异常处理,在C++中有异常模板,在Java中有异常框架,这一部分后面有时间再探究(这里备注下,提醒还有关于异常处理更标准的方法)
10
/**10.关于RandomAccessFile的使用 可以在任意位置读取文件
* seek 指定一个指针
* getFilePointer 方法
*/
11
/**11.DataInputStream与DataOutputStream可以处理java基本类型的数据
*/
12
/**12.ObjectInputStream与ObjectOutputStream 处理java序列化的对象
*/