黑马程序员_Java_IO

---------- android培训java培训、期待与您交流! ----------

在Java程序中,对于数据的输入/输出操作以“流”方式进行,Java提供了各种各样的“流”类,可以分为两种,输入有关的所有类都从InputStream 继承,从InputStream(输入流)衍生的所有类都拥有名为read()的基本方法,用于读取单个字节或者

字节数组;而与输出有关的所有类都从OutputStream 继承,从OutputStream 衍生的所有类都拥有基本方法write(),用于写入单个字节或者字节数组。

可以从不同的角度对Java的IO进行分类:

1、按照数据流的方向不同,可以分为输入流和输出流

2、按处理数据单位不同,可以分为字节流和字符流

3、按照功能的不同可以分为节点流和处理流(节点流可以从一个特定的数据源读写数据,而处理流是建立在已存在的流的基础上,提供了更为强大的读写功能,在Java中是使用装饰模式进行处理的)

 

位于java.io包内的类都分别继承自四种抽象流类型

字节输入流InputStream、字节输出流OutputStream、字符输入流Reader、字节输出流Writer

可以用树状结构表示java.io包内的类的继承关系

字节输入流InputStream

            -- FileInputStream

            -- PipedInputStream

InputStream -- FilterInputStream --------- BufferedInputStream、LineNumberinputStream、DataInputStream、PushbackInputStream

            -- ByteArrayInputStream

            -- SequenceInputStream

            -- StringBufferInputStream

            -- ObjectInputStream

 

字节输出流OutputStream:

             -- FileOutputStream

             -- PipedOutputStream

OutputStream -- FilterInputStream ------ BufferedOutputStream、DataOutputStream、PrintStream

             -- ByteArrayOutputStream 

             -- ObjectOutputStream

字符输入流Reader

         -- BufferedReader    ----- LineNumberReader

         -- CharArrayReader

Reader   -- InputStreamReader ----- FileReader

         -- FilterReader      ----- PushbackReader

         -- PipedReader

         -- StringReader     

字节输出流Writer

         -- BufferedWriter   

         -- CharArrayWriter

Writer   -- OutputStreamReader ----- FileWriter

         -- FilterWriter     

         -- PipedWriter

         -- StringWriter  

从上面这四个树状图可以看出,java的io类基本上都是成对出现的,这样可以方便我们记忆。

JavaIO中有一个非常重要的类,叫File,File代表文件和目录路径名的抽象表示形式,它的常用构造方法是用一个路径创建File对象,下面这些代码介绍了FIle类常用的方法

创建一个绝对路径名为d:/java/Test.java的文件,当new出这个File对象时只是存在于内存中,而不是存在于磁盘上,只要调用createNewFile方法时才会真正在磁盘上创建,此时创建出来的时一个空文件,这个方法会抛出IOException异常

public static void main(String[] args) { 

  //在内存里创建这个文件,分隔符使用/,这样保证了既可以在windows上运行,也可以在linux上运行
  File file = new File("d:/java/Test.java");
  //判断此文件在磁盘上是否存在
  if (!file.exists()) {
   try {
    //不存在的话就创建它
    file.createNewFile();
   } catch (IOException e) {
    e.printStackTrace();
   }
  }
 }

创建一个目录d:/java,与创建文件类似,当new出这个File对象时只是存在于内存中,而不是存在于磁盘上,只要调用mkdir方法时才会真正在磁盘上创建这个目录

//创建一个目录
 
public static void main(String[] args) { 
  //在内存里创建这个文件
  File file = new File("d:/java");
  //判断此文件在磁盘上是否存在
  if (!file.exists()) {
   //不存在的话就创建它
   file.mkdir();
  }
 }

File的路径分隔符,路径分割符是用来分割路径的,如:windows中"d:\java\test.java",符号"\"就是路径分割符,由于不同操作系统的路径分隔符使用的字符不同,所以为了保证Java的跨平台这一特性,所以Java在File类中,提供了一个与操作系统无关的分隔符:File.separator。

这句代码

System.out.println(File.separator)

在windows下运行结果是 \   而在linux下运行结果是 /  ,所以在我们的程序中路径分隔符不要硬编码,尽量使用File.separator作为分隔符,如果想省事的话就是用/,windows也支持这种,可以保证在windows和linux上都可以运行

 

得到一个指定文件的详细信息

public static void main(String[] args) { 
  File file = new File("d:/java/Test.java");
  System.out.println(file.canRead());//
测试应用程序是否可以读取此抽象路径名表示的文件
 
System.out.println(file.canWrite());//测试应用程序是否可以修改此抽象路径名表示的文件
  System.out.println(file.canExecute());//测试应用程序是否可以执行此抽象路径名表示的文件
  System.out.println(file.isFile());//测试此抽象路径名表示的文件是否是一个标准文件
  System.out.println(file.isDirectory());//测试此抽象路径名表示的文件是否是一个目录
  System.out.println(file.length());//返回由此抽象路径名表示的文件的长度
 }

递归的获得一个目录下的所有文件与目录,并打印出树状结构

public static void main(String[] args) { 
  File file = new File("d:/java");
  list(file,0);
 }
 
 public static void list(File file,int level) {
  //根据层次拼出,打印file时前面出现的空白字符,单线程下使用StringBuilder速度快且安全
  StringBuilder tree = new StringBuilder("");
  for (int i = 0;i <= level;i++) {
   tree.append("  ");
  }
 
  //如果参数file表示的不是一个目录方法不执行
  if (file.isDirectory()) {
   //获得一个目录下的所有文件和目录
   File[] children = file.listFiles();
   //使用高级循环遍历children
   for (File child : children) {
    //打印child,前面加上拼出来的空白字符
    System.out.println(tree.toString() + child);
    //如果child为目录的话,递归调用,并把级别加上1
    if (child.isDirectory()) {   
     list(child,level + 1);
    }
   }
  } 
 }

上面使用FIle这个类只能对文件进行简单的操作,还不能读写文件。

字节流

FIleInputStream和FileOutputStream都属于节点流,他们的数据源都是硬盘上的一个具体文件。为打开一个文件以便输入,需要使用FileInputStream,同时使用String或File对象作为文件名使用。同样的输出到文件需要FileOutputStream。要注意的是,构造FIleInputStream,对应的文件必须是可读的,而构造FIleOutputStream时,若输出文件已存在,则必须是可覆盖的。写一个测试程序,使用这两个类,完成把复制文件的功能(把d:/java/Person.java复制到d:/Person.java)

 

public static void main(String[] args) {
 
FileInputStream fis = null;//
输入流
 
FileOutputStream fos = null;//输出流
  String srcFileName = "d:/java/Person.java";
  try {
   //通过打开一个到实际文件的连接来创建一个 FileInputStream
   fis = new FileInputStream(srcFileName);
   // 创建一个向具有指定名称的文件中写入数据的输出文件流
   fos = new FileOutputStream("d:/Person.java");
   int i = 0;
   while (( i = fis.read()) != -1) {
    fos.write(i);
   }
  } catch (FileNotFoundException e) {  
   System.out.println("找不到文件" + srcFileName);
  } catch (IOException e) {  
   System.out.println("出现了错误");
  } finally {
   try {
    //一定要关闭,否则日积月累会造成内存泄露
    fis.close();//关闭输入流
    fos.close();//关闭输出流
   } catch (IOException e) {   
    e.printStackTrace();
   }
  }
 }

需要注意的是当我们使用完流时,一定要关闭,不然的话日积月累会造成内存泄露,这点对于我们自己的电脑无所谓,但是对于服务器端的应用程序来说就至关重要了,因为服务器是不经常重启的。关闭的代码最好写在finally里面,这样保证了无论如何这段代码都会被执行。

 

字符流   

字符流主要是用来处理字符的。Java采用16位的Unicode来表示字符。字符流类有两个基类Reader和Writer,继承自Reader的流都是用于向程序中输入数据,且数据的单位为字符。继承自Writer的流都是用于输出单位为字符的数据。可以把上面那个复制文件的程序,改为使用FIleReader和FileWriter这两个字符流类来完成

//复制文件,使用字符流
 
public static void main(String[] args) {
  FileReader fr = null;//输入流
  FileWriter fw = null;//输出流
  String srcFileName = "d:/java/Person.java";
  try {
   //通过打开一个到实际文件的连接来创建一个 FileReader
   fr = new FileReader(srcFileName);
   // 创建一个向具有指定名称的文件中写入数据的输出字符流
   fw = new FileWriter("d:/Person.java");
   int i = 0;
   while (( i = fr.read()) != -1) {
    fw.write(i);
   }
  } catch (FileNotFoundException e) {  
   System.out.println("找不到文件" + srcFileName);
  } catch (IOException e) {  
   System.out.println("出现了错误");
  } finally {
   try {
    //一定要关闭,否则日积月累会造成内存泄露
    fr.close();//关闭输入流
    fw.close();//关闭输出流
   } catch (IOException e) {   
    e.printStackTrace();
   }
  }
 }

这个程序和上面那个使用FIleInputStream和FileOutputStream进行文件复制,看似差不多,但是根本区别在于底层,第一个数据传输的单位是字节,第二个程序的传输单位是字符

缓冲流

缓冲流要套接在相应的节点流之上,对读写的数据提供了缓冲功能,可以减少对硬盘的读写次数,即保护了硬盘,也提高了读写的效率,同时增加了一些新的方法。Java为InputStream、OutputStream、Reader、Writer都提供了相应的缓冲流。就以BufferedInputStream和BufferedOutputStream这两个类为例来说明缓冲流,把复制文件的例子进行改写,把字节流套接在缓冲流之上 

//复制文件,使用缓冲流
 
public static void main(String[] args) {
  FileInputStream fis = null;//输入流
  FileOutputStream fos = null;//输出流
  BufferedInputStream bis = null;//带缓冲的输入流
  BufferedOutputStream bos = null;//带缓冲的输出流
  String targetFileName = "d:/java/Person.java";
  try {
   //通过打开一个到实际文件的连接来创建一个 FileInputStream
   fis = new FileInputStream(targetFileName);
   //使用装饰模式,把字节流fis套接在缓冲流之上,为其提供缓冲
   bis = new BufferedInputStream(fis);
   // 创建一个向具有指定名称的文件中写入数据的输出文件流
   fos = new FileOutputStream("d:/Person.java");
   //使用装饰模式,把字节流fos套接在缓冲流之上,为其提供缓冲
   bos = new BufferedOutputStream(fos);
   int i = 0;
   while (( i = bis.read()) != -1) {
    bos.write(i);
   }
  } catch (FileNotFoundException e) {  
   System.out.println("找不到文件" + targetFileName);
  } catch (IOException e) {  
   System.out.println("出现了错误");
  } finally {
   try {
    //把缓冲流关闭,套接在缓冲流之上的字节流也随着被关闭
    bis.close();
    bos.close();
   } catch (IOException e) {   
    e.printStackTrace();
   }
  }
 }

转换流

在程序中有时候,需要把字节流转换为字符流使用,Java为我们提供了这样的类。InputStreamReader和OutputStreamWriter用于字节数据到字符数据之间的转换,以 InputStreamReader 为例,写个把字节输入流转化为字符输入流使用的测试程序,功能是从键盘上读取一行输入并打印出来,具体代码:

//转换流,把从键盘上输入的字节流,转换为带缓冲的字符流使用
 
public static void main(String[] args) {
  BufferedReader br = null;
  try {
   System.out.println("输入一行数据:");
   br = new BufferedReader(
     new InputStreamReader(System.in));
   System.out.println("你输入的数据为:" + br.readLine());
  } catch (IOException e) {
   e.printStackTrace();
  } finally {
   try {
    br.close();
   } catch (IOException e) {
    e.printStackTrace();
   }
  } 
 }

程序中 InputStreamReader 作为字节输入流 System.in 和 带缓冲的字符输入流之间的桥梁,通过包装完成流转换

数据流

数据流用到了两个类DataInputStream和DataOutputStream,需要套接在InputStream和OutPutStream类型的节点流之上,他们提供了读写Java的原始数据类型的方法,如:readInt()、  readDouble() 、writeInt()、writeDouble()。数据流在网络程序中使用的较多,比如:在联机的网络游戏中,代表自己在画面中的位置用int类型的数字表示,我们可以把自己的位置写出去,另外一台机器就可以根据读取的int值把相应的人物在界面中画出来。下面是一个测试程序,有一个server,一个cllient,client发送给server一个int类型数字,server接收这个int类型数字并打印出来。

//server

public class JavaSocketServer { 
 public static void main(String[] args) throws IOException { 
  //创建绑定到6666端口的服务器套接字
  ServerSocket ss = new ServerSocket(6666);
  DataInputStream dis = null;
  //客户端套接字
  Socket sk = null;
  //阻塞的接受客户端的连接
  while((sk = ss.accept()) != null) {  
   System.out.println("A client connection");
   //把数据输入流套接在sk的字节输入流上
   dis = new DataInputStream(sk.getInputStream());    
   System.out.println("接受到了一个int类型的数字" + dis.readInt());
  }
  dis.close();
  sk.close();
  ss.close();
 }
}

//client

public class JavaSocketClient {
 

 public static void main(String[] args) throws IOException { 
  Socket sk = null;
  DataOutputStream dos = null; 
  //创建一个流套接字并将其连接到指定 IP 地址的指定端口号
  sk = new Socket("127.0.0.1",6666);
  //把数据输出流套接在sk的字节输出流上
     dos = new DataOutputStream(sk.getOutputStream());
     int i = 999999999;
     //把i发送出去
     dos.writeInt(i);
     System.out.println("成功的发送了int类型的数字" + i);
     //关闭资源
     dos.close();
     sk.close();
 }
}

Print

Print流主要有两个类PrintWriter和printStream,分别针对字符和字节。Print流汉族要用于往控制台还有网页中写数据,System.out 就是一个PrintStream的静态对象,它可以往控制台中打印出所有基本数据类型以及String 对象。Print流还用于向网页中写html,在servlet中的response对象中有个方法叫getWriter,它返回的就是一个PrintWriter对象,利用这个对象可以向客户端的网页中写入html数据,如:

public class HelloServlet extends HttpServlet {
 
public void doGet(HttpServletRequest request, HttpServletResponse response)
   throws ServletException, IOException {
  response.setContentType("text/html");
  PrintWriter out = response.getWriter();//获得PrintWriter对象
  out.println("Hello 黑马程序员训练营");
  out.flush();
  out.close();
 }
 
 public void doPost(HttpServletRequest request, HttpServletResponse response)
   throws ServletException, IOException {
  doGet(request,response);
 }
}

当客户端浏览器请求这个servlet时,这个servlet就会像网页中写入 "Hello 黑马程序员训练营"这个话,使用的就是PrintWriter对象

对象流(Object流)

使用Object流,提供了读写Java对象的功能,被读写的Java对象必须实现Serializable接口,这个接口只是一个标记性接口,本身不包括任何方法,当jvm看到一个类实现了这个接口时,就代表这个类可以被序列化。下面用一个实例进行测试,程序的功能是server读取从客户端发送过来的Point并打印出来。

//Point

public class Point implements Serializable {
 
private int x;
 private int y;
 
 public Point(int x,int y) {
  this.x = x;
  this.y = y;
 }

 public String toString() {
  return "x = " + x + "," + "y = " + y;
 }
}

//服务器端

public class JavaSocketServer {
 

 public static void main(String[] args) throws Exception { 
  //创建绑定到6666端口的服务器套接字
  ServerSocket ss = new ServerSocket(6666);
  ObjectInputStream ois = null;
  //客户端套接字
  Socket sk = null;
  //阻塞的接受客户端的连接
  while((sk = ss.accept()) != null) {  
   System.out.println("A client connection");
   //把数据输入流套接在sk的字节输入流上
   ois = new ObjectInputStream(sk.getInputStream());
   Point p = (Point)ois.readObject();
   System.out.println("接受了一个Point对象 ");  
   System.out.println(p);
  }
  ois.close();
  sk.close();
  ss.close();
 }
}

//客户端

public class JavaSocketClient {
 

 public static void main(String[] args) throws IOException { 
  Socket sk = null;  
  //创建一个流套接字并将其连接到指定 IP 地址的指定端口号
  sk = new Socket("127.0.0.1",6666);
  //把对象输出流套接在sk的字节输出流上  
  ObjectOutputStream oos = new ObjectOutputStream(sk.getOutputStream());
  //实例化一个Point对象
  Point p = new Point(520,909);
  //把对象p发功出去
     oos.writeObject(p);
     System.out.println("发功了一个Point对象");
     System.out.println(p);
     //关闭资源
     oos.close();
     sk.close();
 }
}

服务器端的打印结果为:

A client connection

接受了一个Point对象 

x = 520,y = 909

成功的接收了这个对象,这个过程就是,客户端中表示这个对象的那块内存区域的内容打包发送给Server,server接收后把这个对象还原出来。

 关于java的io这里只是说了一小部分,Java的io设计初衷是设计的小巧一点,但事实上非常庞大,这点Thank in Java这个书中也有说到,初学时很头痛,慢慢的用的多了就能熟练的运用了。

---------- android培训java培训、期待与您交流! ---------- 
详细请查看:
http://edu.csdn.net/

你可能感兴趣的:(黑马程序员_Java_IO)