本文主要介绍java.net下为网络编程提供的一些基础包,InetAddress代表一个IP协议对象,可以用来获取IP地址,Host name之类的信息。URL和URLConnect可以用来访问web资源,URLDecode和URLEncode用来转换字符串。 本文会写两个例子来演示java网络编程的一些基本用法。
第一个例子,用java实现http get 和 post请求
本例涉及如下知识点,
- HTT协议规范
- 使用URL对象建立HTTP协议的网络连接URLConnection对象conn,本地和网络通信就靠这个对象的getInputStream和getOutputStream方法
- conn返回的inputStream和outputStream都是字节流(byte stream)
- JAVA IO中的BufferedReader,属于reader族,是可以缓存的字符流(characters stream),是更直观的读取方式。
- JAVA IO中的InputStreamReader,也属于reader族,是字节流这字符流的桥梁。 本例中使用它把网络读取的inputStream转换成了BufferedReader。
- 注意在转换字节流到字符流的过程中,需要参考数据源进行编码转换,本例使用utf-8编码
具体实现代码如下, 本例参考了博客 http://www.cnblogs.com/zhuawang/archive/2012/12/08/2809380.html (谢谢, 已对编码部分优化)
1 package network; 2 3 import java.io.BufferedReader; 4 import java.io.InputStreamReader; 5 import java.io.PrintWriter; 6 import java.net.URL; 7 import java.net.URLConnection; 8 import java.nio.charset.Charset; 9 import java.util.List;10 import java.util.Map;11 12 public class HttpRestTest {13 14 public static String sendGet(String url, String param) {15 String result = "";16 BufferedReader in = null;17 try {18 String urlNameString = url + "?" + param;19 URL resultUrl = new URL(urlNameString);20 URLConnection connection = resultUrl.openConnection();21 //设置通用属性22 connection.setRequestProperty("accept", "*/");23 connection.setRequestProperty("connection", "Keep-Alive");24 connection.connect();25 //获取响应头26 Map> map = connection.getHeaderFields();27 28 for(String key:map.keySet()) {29 System.out.println(key+"--->"+map.get(key));30 }31 Charset cs = Charset.forName("utf-8");32 in = new BufferedReader(new InputStreamReader(connection.getInputStream(),cs));33 String line;34 while((line = in.readLine()) != null) {35 result += line;36 }37 } catch (Exception e) {38 e.printStackTrace();39 } finally {40 try {41 if (in != null) {42 in.close();43 }44 } catch (Exception e) {45 e.printStackTrace();46 }47 }48 return result;49 }50 51 public static String sendPost(String url, String param) {52 PrintWriter out = null;53 BufferedReader in = null;54 String result = "";55 try {56 String urlNameString = url + "?" + param;57 URL resultUrl = new URL(urlNameString);58 URLConnection conn = resultUrl.openConnection();59 //设置通用属性60 conn.setRequestProperty("accept", "*/");61 conn.setRequestProperty("connection", "Keep-Alive");62 //发送post请求必须设置下面两行63 conn.setDoOutput(true);64 conn.setDoInput(true);65 out = new PrintWriter(conn.getOutputStream());66 out.print(param);67 out.flush();68 Charset cs = Charset.forName("utf-8");69 in = new BufferedReader(new InputStreamReader(conn.getInputStream(), cs));70 String line;71 while((line = in.readLine()) != null) {72 result += line;73 }74 } catch (Exception e) {75 e.printStackTrace();76 } finally {77 try {78 if (out != null) {79 out.close();80 }81 if (in != null) {82 in.close();83 }84 } catch (Exception e) {85 e.printStackTrace();86 }87 }88 return result;89 }90 91 public static void main(String[] args) {92 String get = HttpRestTest.sendGet("http://fysola.cnblogs.com/", "");93 System.out.println(get);94 System.out.println("================================================");95 String post = HttpRestTest.sendGet("http://fysola.cnblogs.com/", "");96 System.out.println(post);97 98 }99 }
执行结果如下,
1 null--->[HTTP/1.1 200 OK] 2 Vary--->[Accept-Encoding] 3 Date--->[Sat, 19 Nov 2016 04:50:32 GMT] 4 Content-Length--->[14744] 5 Expires--->[Sat, 19 Nov 2016 04:50:41 GMT] 6 X-UA-Compatible--->[IE=10] 7 Last-Modified--->[Sat, 19 Nov 2016 04:50:31 GMT] 8 Connection--->[keep-alive] 9 Content-Type--->[text/html; charset=utf-8]10 Cache-Control--->[private, max-age=10]11fysola - 博客园 12 ================================================13 null--->[HTTP/1.1 200 OK]14 Vary--->[Accept-Encoding]15 Date--->[Sat, 19 Nov 2016 04:50:32 GMT]16 Content-Length--->[14744]17 Expires--->[Sat, 19 Nov 2016 04:50:42 GMT]18 X-UA-Compatible--->[IE=10]19 Last-Modified--->[Sat, 19 Nov 2016 04:50:32 GMT]20 Connection--->[keep-alive]21 Content-Type--->[text/html; charset=utf-8]22 Cache-Control--->[private, max-age=10]23Practice like your performance摘要: 本文主要介绍java.net下为网络编程提供的一些基础包,InetAddress代表一个IP协议对象,可以用来获取IP地址,Host name之类的信息。URL和URLConnect可以用来访问web资源,URLDecode和URLEncode用来转换字符串。 本文会写两个例子来演示java网络编程 阅读全文posted @ 2016-11-19 10:42 fysola 阅读(1) 评论(0) 编辑摘要: 线程池概念 操作系统或者JVM创建一个线程以及销毁一个线程都需要消耗CPU资源,如果创建或者销毁线程的消耗源远远小于执行一个线程的消耗,则可以忽略不计,但是基本相等或者大于执行线程的消耗,而且需要创建大批量这种线程的话,CPU将资源将会大量消耗在创建线程和销毁线程上,这是不能接受的,因此我们需要一个 阅读全文posted @ 2016-11-18 00:33 fysola 阅读(2) 评论(0) 编辑摘要: 线程组 Java中的ThreadGroup类表示线程组,在创建新线程时,可以通过构造函数Thread(group...)来指定线程组。 线程组具有以下特征 如果没有显式指定线程组,则新线程属于默认线程组,默认情况下,与创建线程所在的组相同 一旦确定了线程所在线程组之后,不允许更改线程组,直到线程死亡 阅读全文posted @ 2016-11-17 18:07 fysola 阅读(3) 评论(0) 编辑摘要: 传统的线程通信 Object提供了三个方法wait(), notify(), notifyAll()在线程之间进行通信,以此来解决线程间执行顺序等问题。 wait():释放当前线程的同步监视控制器,并让当前线程进入阻塞状态,直到别的线程发出notify将该线程唤醒。 notify():唤醒在等待控制 阅读全文posted @ 2016-11-17 10:51 fysola 阅读(1) 评论(0) 编辑摘要: 配置虚拟机最少需要修改两个文件,即httpd-vhosts.conf 和 hosts (记住修改任何配置前先备份哦,这才是个好习惯) 第一步,修改httpd-vhosts.conf文件, 文件路径 \apache\conf\extra\ 在文件底部加入下面这段 第二步,修改hosts文件, 文件路径 阅读全文posted @ 2016-11-16 22:53 fysola 阅读(2) 评论(0) 编辑摘要: 线程安全问题 多个线程同时访问同一资源的时候有可能会出现信息不一致的情况,这是线程安全问题,下面是一个例子, Account.class , 定义一个Account模型 DrawThread.class ,定义一个取钱类,用来操作Account DrawTest.class , 写一个测试类 执行结 阅读全文posted @ 2016-11-16 20:33 fysola 阅读(4) 评论(0) 编辑摘要: join线程 在某个线程中调用其他线程的join()方法,就会使当前线程进入阻塞状态,直到被join线程执行完为止。join方法类似于wait, 通常会在主线程中调用别的线程的join方法,这样可以保证在所有的子线程执行结束之后在主线程中完成一些统一的步骤。下面是一个例子, 执行结果,可见当主线程中 阅读全文posted @ 2016-11-16 12:14 fysola 阅读(5) 评论(0) 编辑摘要: 线程有五个状态,分别是新建(New)、就绪(Runnable)、运行(Running)、阻塞(Blocked)和死亡(Dead)。 新建和就绪 程序使用new会新建一个线程,new出的对象跟普通对象一样,JVM会为其分配内存,初始化成员变量等,此时线程并没有运行,而是就是新建状态。 当线程对象调用s 阅读全文posted @ 2016-11-15 16:35 fysola 阅读(5) 评论(0) 编辑摘要: 所有JAVA线程都必须是Thread或其子类的实例。 继承Thread类创建线程 步骤如下, 定义Thead子类并实现run()方法,run()是线程执行体 创建此子类实例对象,即创建了线程对象 调用线程对象的start()方法来启动线程 下面是一个例子, 执行结果, 可见thread-5和thre 阅读全文posted @ 2016-11-15 15:08 fysola 阅读(4) 评论(0) 编辑fysola - 博客园 Practice like your performance摘要: 本文主要介绍java.net下为网络编程提供的一些基础包,InetAddress代表一个IP协议对象,可以用来获取IP地址,Host name之类的信息。URL和URLConnect可以用来访问web资源,URLDecode和URLEncode用来转换字符串。 本文会写两个例子来演示java网络编程 阅读全文posted @ 2016-11-19 10:42 fysola 阅读(1) 评论(0) 编辑摘要: 线程池概念 操作系统或者JVM创建一个线程以及销毁一个线程都需要消耗CPU资源,如果创建或者销毁线程的消耗源远远小于执行一个线程的消耗,则可以忽略不计,但是基本相等或者大于执行线程的消耗,而且需要创建大批量这种线程的话,CPU将资源将会大量消耗在创建线程和销毁线程上,这是不能接受的,因此我们需要一个 阅读全文posted @ 2016-11-18 00:33 fysola 阅读(2) 评论(0) 编辑摘要: 线程组 Java中的ThreadGroup类表示线程组,在创建新线程时,可以通过构造函数Thread(group...)来指定线程组。 线程组具有以下特征 如果没有显式指定线程组,则新线程属于默认线程组,默认情况下,与创建线程所在的组相同 一旦确定了线程所在线程组之后,不允许更改线程组,直到线程死亡 阅读全文posted @ 2016-11-17 18:07 fysola 阅读(3) 评论(0) 编辑摘要: 传统的线程通信 Object提供了三个方法wait(), notify(), notifyAll()在线程之间进行通信,以此来解决线程间执行顺序等问题。 wait():释放当前线程的同步监视控制器,并让当前线程进入阻塞状态,直到别的线程发出notify将该线程唤醒。 notify():唤醒在等待控制 阅读全文posted @ 2016-11-17 10:51 fysola 阅读(1) 评论(0) 编辑摘要: 配置虚拟机最少需要修改两个文件,即httpd-vhosts.conf 和 hosts (记住修改任何配置前先备份哦,这才是个好习惯) 第一步,修改httpd-vhosts.conf文件, 文件路径 \apache\conf\extra\ 在文件底部加入下面这段 第二步,修改hosts文件, 文件路径 阅读全文posted @ 2016-11-16 22:53 fysola 阅读(2) 评论(0) 编辑摘要: 线程安全问题 多个线程同时访问同一资源的时候有可能会出现信息不一致的情况,这是线程安全问题,下面是一个例子, Account.class , 定义一个Account模型 DrawThread.class ,定义一个取钱类,用来操作Account DrawTest.class , 写一个测试类 执行结 阅读全文posted @ 2016-11-16 20:33 fysola 阅读(4) 评论(0) 编辑摘要: join线程 在某个线程中调用其他线程的join()方法,就会使当前线程进入阻塞状态,直到被join线程执行完为止。join方法类似于wait, 通常会在主线程中调用别的线程的join方法,这样可以保证在所有的子线程执行结束之后在主线程中完成一些统一的步骤。下面是一个例子, 执行结果,可见当主线程中 阅读全文posted @ 2016-11-16 12:14 fysola 阅读(5) 评论(0) 编辑摘要: 线程有五个状态,分别是新建(New)、就绪(Runnable)、运行(Running)、阻塞(Blocked)和死亡(Dead)。 新建和就绪 程序使用new会新建一个线程,new出的对象跟普通对象一样,JVM会为其分配内存,初始化成员变量等,此时线程并没有运行,而是就是新建状态。 当线程对象调用s 阅读全文posted @ 2016-11-15 16:35 fysola 阅读(5) 评论(0) 编辑摘要: 所有JAVA线程都必须是Thread或其子类的实例。 继承Thread类创建线程 步骤如下, 定义Thead子类并实现run()方法,run()是线程执行体 创建此子类实例对象,即创建了线程对象 调用线程对象的start()方法来启动线程 下面是一个例子, 执行结果, 可见thread-5和thre 阅读全文posted @ 2016-11-15 15:08 fysola 阅读(4) 评论(0) 编辑
第二个例子,写一个程序用来模拟多线程下载。
本例中用到的技术有,
多线程——多个线程同时读文件写文件,可以加快下载速度,线程池——在本例中线程池不是必须,甚至是多余,只不过是为了演示线程池的简单用法
断点下载/上传——用RandomAccessFile的seek方法可以直接从某个中间位置读取或者写入文件
URLConnection——建立网络连接,读取网络数据的基本方法
ioString——网络IO,建立在URLConnection上的网络IO stream
有几个关键点值得注意,
- 一是使用了URL的实例建立了一个httpURLConnection实例, 通过这个实例的getInputStream(继承自父类)可以返回一个instream对象进行读数据操作
- 二是由于是多线程从远程读文件,每个文件将读取一块文件,文件大小按线程数平分,单个线程文件块 = 文件总大小 / 线程总数,使用RandomAccessFile的方式可以按指定位置读写文件
- 使用RandomAccessFile的seek方法,计算每个线程下载的起始位置
- 使用inStream的skip方法计算每次从网络读取数据的位置
具体实现如下, 先写一个工具类DownUtil, 其中写了一个内部线程类download,还定义了一个线程池,将多个线程类的实例提交给线程池管理,
1 package network; 2 3 import java.io.IOException; 4 import java.io.InputStream; 5 import java.io.RandomAccessFile; 6 import java.net.HttpURLConnection; 7 import java.net.MalformedURLException; 8 import java.net.URL; 9 import java.util.concurrent.ExecutorService; 10 import java.util.concurrent.Executors; 11 12 13 public class DownUtil { 14 //下载资源路径 15 private String path; 16 private String targetFile; 17 //线程数量 18 private int threadNum; 19 //下载线程对象 20 private DownThread[] threads; 21 //下载文件总大小 22 private int fileSize; 23 //定义一个线程池,在构造函数中初始化成具体类型的线程池 24 ExecutorService pool; 25 26 public DownUtil(String path, String targetFile, int threadNum) { 27 this.path = path; 28 this.targetFile = targetFile; 29 this.threadNum = threadNum; 30 this.threads = new DownThread[threadNum]; 31 this.pool = Executors.newFixedThreadPool(threadNum); 32 } 33 34 public void download() throws IOException { 35 URL url = new URL(path); 36 HttpURLConnection conn = (HttpURLConnection)url.openConnection(); 37 conn.setConnectTimeout(5*1000); 38 conn.setRequestMethod("GET"); 39 conn.setRequestProperty("accept", "*/*"); 40 /* 41 conn.setRequestProperty("Accept", "image/gif, image/jpg, image/png, " 42 + "application/x-shockwave-flash, application/xam+xml, " 43 + "application/vnd.ms-xpsdocument, application/x-ms-xbap, " 44 + "application/x-ms-application, application/vnd.ms-excel, " 45 + "application/vnd.ms-powerpoint, application/msword, **"); 46 */ 47 conn.setRequestProperty("Charset", "multipart/form-data"); 48 conn.setRequestProperty("Connection", "Keep-Alive"); 49 //得到文件大小 50 fileSize = conn.getContentLength(); 51 conn.disconnect(); 52 //每个线程要下载的文件部分的大小 53 int currentPartSize = fileSize / threadNum + 1; 54 RandomAccessFile file = new RandomAccessFile(targetFile , "rw"); 55 file.setLength(fileSize); 56 file.close(); 57 for ( int i = 0; i < threadNum; i++ ) { 58 // 计算每个线程下载的开始位置 59 int startPos = i * currentPartSize; 60 //每个线程使用一个RandomAccessFile进行下载 61 RandomAccessFile currentPart = new RandomAccessFile(targetFile, "rw"); 62 //定位该线程的下载位置 63 currentPart.seek(startPos); 64 //创建下载线程 65 threads[i] = new DownThread(startPos, currentPartSize, currentPart); 66 //将单个线程提交到线程池,线程会启动 67 pool.submit(threads[i]); 68 } 69 pool.shutdown(); 70 } 71 72 //获取下载的完成百分比 73 public double getCompleteRate() { 74 //统计多个线程已经下载的总大小 75 int sumSize = 0; 76 for (int i = 0; i< threadNum; i++) { 77 sumSize += threads[i].length; 78 } 79 return sumSize * 1.0 / fileSize ; 80 } 81 82 private class DownThread implements Runnable { 83 //当前线程的下载位置 84 private int startPos; 85 //当前线程负责下载的大小 86 private int currentParSize; 87 //当前线程需要下载的文件块 88 private RandomAccessFile currentPart; 89 //该线程一下载字节数 90 public int length; 91 92 public DownThread(int startPos, int currentPartSize, 93 RandomAccessFile currentPart) { 94 this.startPos = startPos; 95 this.currentParSize = currentPartSize; 96 this.currentPart = currentPart; 97 } 98 99 public void run() {100 try {101 URL url = new URL(path);102 HttpURLConnection conn = (HttpURLConnection)url.openConnection();103 conn.setConnectTimeout(5*1000);104 conn.setRequestMethod("GET");105 conn.setRequestProperty("accept", "*/*");106 /*107 conn.setRequestProperty("Accept", "image/gif, image/jpg, image/png, "108 + "application/x-shockwave-flash, application/xam+xml, "109 + "application/vnd.ms-xpsdocument, application/x-ms-xbap, "110 + "application/x-ms-application, application/vnd.ms-excel, "111 + "application/vnd.ms-powerpoint, application/msword, **"); 112 */113 //conn.setContentType(conn.getContentType());114 conn.setRequestProperty("Charset", "UTF-8");115 conn.setRequestProperty("Connection", "Keep-Alive");116 InputStream inStream = conn.getInputStream();117 //跳过startPos之前的内容118 inStream.skip(this.startPos);119 byte[] buffer = new byte[1024];120 int hasRead = 0;121 // 读取网络数据,写入本地文件122 while (length < currentParSize && (hasRead = inStream.read(buffer)) != -1) {123 currentPart.write(buffer, 0 ,hasRead);124 length += hasRead;125 }126 currentPart.close();127 inStream.close();128 } catch (Exception e) {129 e.printStackTrace();130 }131 }132 }133 }
在主程序中初始化工具类DownUtil,然后每隔固定时间去检查一次下载进度,
1 package network; 2 3 import java.io.IOException; 4 5 public class MultiThreadDown { 6 public static void main(String[] args) throws IOException, InterruptedException { 7 // 初始化DownUtil 8 final DownUtil downUtil = new DownUtil( 9 "http://sw.bos.baidu.com/sw-search-sp/software/7d662d80a3d85/npp_7.2_Installer.exe",10 "notepad.exe",4);11 //开始下载12 downUtil.download();13 Thread monitor = new Thread(new Runnable(){14 15 @Override16 public void run() {17 while(downUtil.getCompleteRate() <= 1) {18 //每隔1秒查看一次完成进度19 System.out.println("已完成:"+downUtil.getCompleteRate());20 try {21 Thread.sleep(1000);22 } catch (InterruptedException e) {23 // TODO Auto-generated catch block24 e.printStackTrace();25 }26 }27 }28 29 });30 monitor.start();31 monitor.join();32 System.out.println("下载完成");33 }34 }
程序是以下载一个QQ程序为例, 下面是执行结果,
1 已完成:0.02 已完成:0.112930721773558593 已完成:0.2783294293973414 已完成:0.44223850190088415 已完成:0.50017188097541226 已完成:0.50017188097541227 已完成:0.65149497784176558 已完成:0.93764446437810729 下载完成
本例中,如果调整线程个数,经过测试,线程数越多,下载反而越慢,估计是因为创建IO时间占用太多,毕竟下载一个文件本来就不用多久。 看来对于小文件下载但是单线程快。