---------- 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/