<2> FileStreams && NetworkStreams

[b]四、File Streams[/b]
写个玩具程序,通常用System.in或System.out来输入输出。但实际中更多是使用文件或网络作为数据源来读写。可以使用java.io.FileInputStream和java.io.FileOutputStream类来读写文件(java.io.InputStream和java.io.OutputStream的子类)。

[b]4.1 Reading Files[/b]
java.io.FileInputStream是java.io.InputStream的子类,it provides an input stream connected to a particular file.

public class FileInputStream extends InputStream
public native int read() throws IOException
public int read(byte[] data) throws IOException
public int read(byte[] data, int offset, int length) throws IOException
public native long skip(long n) throws IOException
public native int available() throws IOException
public native void close() throws IOException

它提供了InputStream提供的所有方法,而且这些方法除了两个read方法外其余都是[color=red]native[/color]的。在java2中,read(byte[] data)调用的read(byte[] data, int offset, int length)方法也是native的,所以所有方法都是native的了。

FileInputStream有3个构造方法,[b]只是文件被指定的方式不同[/b]:
public FileInputStream(String fileName) throws IOException 
public FileInputStream(File file) throws FileNotFoundException
public FileInputStream(FileDescriptor fdObj)

第一个构造函数使用表示文件路径(相对路径或绝对路径)的字符串作为参数。
第二个构造函数使用File对象,
第三个构造函数使用java.io.FileDescriptior对象作为参数。文件路径应该是平台独立的,所以最好[color=red]不要使用硬编码[/color]。更推荐使用后面两个构造函数。示例:
try
{
FileInputStream fis = new FileInputStream("README.TXT"); //相对路径
int n;
while ((n = fis.available()) > 0)
{
byte[] b = new byte[n];
int result = fis.read(b);

if (result == -1)
{
break;
}

String s = new String(b);
System.out.print(s);
} // End while
} // End try
catch (IOException e)
{
System.err.println(e);
}

System.out.println();


这种方式指定文件,如果准备要读的[b]文件不存在[/b],那么使用FileInputStream构造函数时会抛出[color=red][b]FileNotFoundException[/b][/color](FileOutputStream这时不会抛一异常,后面会介绍)。如果某些原因导致不能读取文件(比如没有读权限),则会抛出其他异常。如果使用上面介绍的第二个构造函数,可以先获取File对象,通过File对象来判断文件是否存在,是否可读,则可以避免这些情况。

FileInputStream类有一个在父类InputStream里没声明的方法,[color=red]getFD()[/color].
public final FileDescriptor getFD() throws IOException


这个方法返回跟这个FileInputStream关联的java.io.FileDescriptor对象。我们可以使用这个对象通过上面提到的第三个构造函数来构造FileInputStream对象。

同时对同一个文件打开多个流是允许的,虽然很少这样做。每一个流都维持一个独立的指针指示文件中的位置,因为读文件不会改变文件内容,所以这是安全的。

[b]4.2 Writing Files[/b]
java.io.FileOutputStream类时java.io.OutputStream的具体子类,可以对文件进行写操作。
public class FileOutputStream extends OutputStream
这个类有所有output streams应有的方法,比如write(),flush()和close().

public native void write(int b) throws IOException 
public void write(byte[] data) throws IOException
public void write(byte[] data, int offset, int length) throws IOException
public native void close() throws IOException


除了两个写多字节的writer()方法其他都是native实现,但是这两个写多字节的方法也是调用native方法writeBytes()实现的,所以这些方法效率都很高。

FileOutputStream有三个主要的构造方法,主要是指定文件的方式不同:
[b]public FileOutputStream(String filename) throws IOException
public FileOutputStream(File file) throws IOException
public FileOutputStream(FileDescriptor fd)[/b]

如果指定的[color=red][b]文件不存在[/b][/color],这三个构造方法都会[color=red][b]先创建文件再输出[/b][/color](注意,FileInputStream的构造方法不会创建不存在的文件,而是抛出异常),如果指定的文件存在,则之前文件的内容会被覆盖。这显然不太灵活,这时就有第四个构造方法:
public FileOutputStream(String name, boolean append) throws IOException

可以通过append来指定对已存在的文件是覆盖原有内容还是[b]追加[/b]在后面。

FileOutputStream类有一个在父类java.io.OutputStream里没有声明的方法,getFD():
public final FileDescriptor getFD() throws IOException

这个方法返回跟这个输出流关联的对象java.io.FileDescriptor。

[b]五、Network Streams[/b]
java是第一种对network IO提供的支持和对file IO的支持一样多的语言,也许对network IO的支持更多,比如java的URL,URLConnection,Socket和ServerSocket都是文件IO没有的。The exact type of the stream used by a network connection is typically hidden inside the undocumented sun classes. 因此network I/O主要依赖InputStream和OutputStream的方法,根据需要可以使用高级的子类来封装实现,比如带缓存的,压缩的等等。

[b]5.1 URL[/b]
java.net.URL类代表统一资源定位符,比如http://metalab.unc.edu/javafaq/。每个URL独一无二的标识Internet上一个资源的位置。URL有四个构造方法,所有的声明都抛出MalfomedURLException异常(IOException的子类)。
public URL(String u) throws MalformedURLException 
public URL(String protocol, String host, String file) throws MalformedURLException
public URL(String protocol, String host, int port, String file) throws MalformedURLException
public URL(URL context, String u) throws MalformedURLException


如果构造方法没有指定一个[b]有效的URL[/b]则会抛出[b]MalformedURLException[/b]异常。
构造方法的使用:
1.
URL u = null;
try {
u = new URL("http://www.poly.edu/schedule/fall97/bgrad.html#cs");
}
catch (MalformedURLException e) { }
2.
URL u = null;
try {
u = new URL("http", "www.poly.edu", "/schedule/fall97/bgrad.html#cs");
}
catch (MalformedURLException e) { }
3.
URL u = null;
try {
u = new URL("http", "www.poly.edu", 80,"/schedule/fall97/bgrad.html#cs");
}
catch (MalformedURLException e) { }
4.
URL u1, u2;
try {
u1 = new URL("http://metalab.unc.edu/javafaq/course/week12/07.html");
u2 = new URL(u1, "08.html");
}
catch (MalformedURLException e) { }


URL对象创建好之后,有两种方法从中获取数据:
1.openStream()方法从URL中返回bytes形式的原始流。
2.getContent()方法返回java object类型的数据。调用getContent()方法时,java会寻找一个跟数据的MIME类型匹配的handler来处理。

openStream()方法会跟URL指定的server和port创建一个socket连接,返回一个InputStream供从server下载数据, you get only raw data。
public final InputStream openStream() throws IOException

可以使用stream和reader来读取数据。

try {
URL u = new URL("http://www.amnesty.org/");
InputStream in = u.openStream();
int b;
while ((b = in.read()) != -1) {
System.out.write(b);
}
}
catch (MalformedURLException e) {System.err.println(e);}
catch (IOException e) {System.err.println(e);}


网络连接的数据源往往比文件数据源要慢而且不可靠,提高性能的方法就是缓存数据,比如BufferedInputStream类就可以做到这点。

[b]5.2 URL Connections[/b]
URL connections 跟URL紧密相连。我们可以通过URL对象的[b]openConnection[/b]()方法获取URLConnection对象的引用。很多情况下,URL类只是URLConnection类的包装(wrapper)。然而,[color=red][b]URL connections对client和server的通信提供了更多的控制[/b][/color]。特别是,URL connections不仅控制客户端从服务器读数据,而且能控制客户端向服务器发送数据。
java.net.URLConnection类是处理与不同类型服务器(比如FTP服务器,web服务器等)通信的抽象类。URLConnection的具体协议的子类被隐藏在sun的类里处理不同类型的服务器。

[b]5.2.1 Reading Data from URL Connections[/b]

使用URL connections需要5步:
1.构造URL对象
2.使用URL对象的openConnection()方法创建URLConnection对象。
3.设置connection的参数和客户端到服务器的请求属性。
4.使用connect()方法开启客户端到服务器的连接,可能是使用socket创建一个网络连接,也可能是使用文件流创建一个本地连接。同时可以从服务端获取响应头。
5.从这个打开的连接上可以通过调用getInputStream()或getContent()获取数据。也可以通过getOutputStream()返回的outputStream向服务器发送数据。

这上面的步骤是基于http/1.0协议的。这并不适用其他协议的交互,比如FTP和http/1.1。比如:
try { 
URL u = new URL("http://www.digitalthink.com/");
URLConnection uc = u.openConnection();
uc.connect();
InputStream in = uc.getInputStream();
//...
}
catch (IOException e) { //...

如果connection打不开(可能是远程主机不可达)就会抛出IOException.

[b]5.2.2 Writing Data on URL Connections[/b]
通过URLConnection写数据和读数据比较类似。下面是写数据的步骤:
1.构造URL对象
2.调用URL对象的openConnection()方法创建URLConnection对象。
3.设置setDoOutput(true)为true,表示URLConnection可以用来写数据,默认不可以。
4.如果设置了可以写数据,但同时还要读数据可以设置setDoInput(true)表示也可以读数据,默认可读。
5.构造想要发送的数据,最好是byte数组。
6.调用getOutputStream()输出数据。[b]此处getOutputStream会隐含的进行connect(即:如同调用上面的connect()方法,所以在开发中不调用上述的connect()也可以)。[/b]
7.关闭outputStream
8.调用getInputStream()获取输入流对象,可以正常进行读写。

通过URLConnection发送[color=red]get还是post[/color]请求可以参考:
[url]http://blog.163.com/ydmx_lei/blog/static/77053405201241631341871/[/url]

[b]总结:[/b]
a:) HttpURLConnection的connect()函数,实际上只是建立了一个与服务器的tcp连接,并没有实际发送http请求。
无论是post还是get,http请求实际上直到HttpURLConnection的getInputStream()这个函数里面才正式发送出去。

b:) 在用POST方式发送URL请求时,URL请求参数的设定顺序是重中之重,
对connection对象的一切配置(一堆set函数都必须要在connect()函数执行之前完成。而对outputStream的写操作,又必须要在inputStream的读操作之前。
这些顺序实际上是由http请求的格式决定的。
如果inputStream读操作在outputStream的写操作之前,会抛出例外:
java.net.ProtocolException: Cannot write output after reading input.......

c:) http请求实际上由两部分组成,
一个是http头,所有关于此次http请求的配置都在http头里面定义, 一个是正文content。
connect()函数会根据HttpURLConnection对象的配置值生成http头部信息,因此在调用connect函数之前,
就必须把所有的配置准备好。

d:) 在http头后面紧跟着的是http请求的正文,正文的内容是通过outputStream流写入的,
实际上outputStream不是一个网络流,充其量是个字符串流,往里面写入的东西不会立即发送到网络, 而是存在于内存缓冲区中,待outputStream流关闭时,根据输入的内容生成http正文。
至此,http请求的东西已经全部准备就绪。在getInputStream()函数调用的时候,就会把准备好的http请求 正式发送到服务器了,然后返回一个输入流,用于读取服务器对于此次http请求的返回信息。由于http 请求在getInputStream的时候已经发送出去了(包括http头和正文),因此在getInputStream()函数之后对connection对象进行设置(对http头的信息进行修改)或者写入outputStream(对正文进行修改) 都是没有意义的了,执行这些操作会导致异常的发生。

HttpURLConnection是基于HTTP协议的,其底层通过socket通信实现。如果[color=red]不设置超时(timeout)[/color],在网络异常的情况下,可能会导致程序僵死而不继续往下执行。[b][color=red]比如spring的定时任务里有道网络上爬取网页,如果不设置超时则可能运行一段时间后定时任务都不执行了。因为定时任务是由线程池调度的,当所有线程都僵死在那后就没有可以调度的线程了,本人遇到这个问题很久之后才定位到这里。[/color][/b]
比如:
URL url = createURL(sourceUrl);
HttpURLConnection conn = (HttpURLConnection)url.openConnection();
conn.setConnectTimeout(20000);// 设置超时
conn.setReadTimeout(20000);// 设置超时
input = new InputStreamReader(conn.getInputStream(), "utf-8");


推荐参考:[url]http://duanfei.iteye.com/blog/1719997[/url]

[b]5.3 Socket[/b]
当数据在网络的一端被发送到另一端之前,会被切分成不同大小但大小有限的数据包。小数据包传输中可以重发或重排序。
幸运的是这个数据包的切分和组装对java程序员是透明的,java程序员只能看到高度抽象数据结构Socket。socket代表两台网络服务器之间可靠的连接。socket支持4个基本的操作:
1.连接远程主机
2.发送数据
3.接收数据
4.关闭连接
socket不能同时连接多台主机,但可以同时从连接的主机上发送和接收数据。

java.net.Socket类时java抽象的类。可以调用Socket的构造方法指定想要连接的主机来创建连接。
public Socket(String host, int port) throws UnknownHostException, IOException 
public Socket(InetAddress address, int port) throws IOException
public Socket(String host, int port, InetAddress localAddr, int localPort) throws IOException
public Socket(InetAddress address, int port, InetAddress localAddr, int localPort) throws IOException

host参数是类似”www.oreilly.com”或”metalab.unc.edu”的字符串指定要连接的服务器,也可以是”199.1.32.90”类似的ip,同时也可以传递java.net.InetAddress对象。
port参数是要连接的远程主机的端口。由host和port就唯一确定了要通信的主机位置。

通过socket关联的inputStream和OutputStream可以实现发送和接收数据。
public InputStream getInputStream() throws IOException 
public OutputStream getOutputStream() throws IOException


还有关闭socket的方法:
public synchronized void close() throws IOException


[b]5.4 Server Socket[/b]
每个连接都有两个端点:client, 客户端表示连接的发起点;server, 服务端对连接进行相应。服务端需要一直等待客户端的连接,server socket要绑定到服务器上的特定端口上,绑定成功后就一致监听端口是否有连接请求,一旦检测到连接就accept连接,也就是创建一个socket来负责client和server的通信(当然是使用另外的端口,并不是server socket绑定的端口,但这个操作不需要java程序员关注)。

java.net.ServerSocket类代表一个server socket。它有三个构造方法,可以指明需要绑定的端口和等待连接的队列的长度和ip地址:
public ServerSocket(int port) throws IOException 
public ServerSocket(int port, int backlog) throws IOException
public ServerSocket(int port, int backlog, InetAddress bindAddr) throws IOException


通常,我们只需指定需要监听的端口:
try { 
ServerSocket ss = new ServerSocket(80);
}
catch (IOException e) {System.err.println(e);}

这样创建ServerSocket的时候,试图绑定的是本机指定的port。如果有其他任何程序(不仅是java程序)已经绑定了这个端口,就会抛出java.net.BindException。

[color=red][b]0是一个特殊的端口,当指定的port是0时它告诉java程序自己选择一个可用的端口,我们可以调用getLocalPort()方法获取最终绑定的是哪个端口。[/b][/color]
public int getLocalPort()

当创建了ServerSocket之后,就需要等待连接,可以调用accept()方法,这个方法一直阻塞直到有连接到来,accept会返回一个可以用来与client通信的Socket。close()方法可以关闭ServerSocket.
public Socket accept() throws IOException 
public void close() throws IOException



参考:
《java I/O》 Elliotte Rusty Harold
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值