Java基础系列6:计时器Timer与新闻的定时自动采集

一 Timer类与TimerTask类

在Java中要实现定时执行某项任务就需要用到Timer类和TimerTask类。其中,Timer类可以实现在某一刻时间或某一段时间后安排某一个任务执行一次或定期重复执行,该功能需要与TimerTask类配合使用。TimerTask类表示由Timer类安排的一次或多次重复执行的那个任务。

Timer类中的常用方法:

方法 描述
void cancel() 终止此计时器,丢弃所有当前已安排的任务,对当前正在执行的任务没有影响
int purge() 从此计时器的任务队列中移除所有已取消的任务,一般用来释放内存空间
void schedule(TimerTask task, Date time) 安排在指定的时间执行指定的任务
void schedule(TimerTask task, Date firstTime, long period) 安排指定的任务在指定的时间开始进行重复的固定延迟执行
void schedule(TimerTask task, long delay)  安排在指定延迟后执行指定的任务
void schedule(TimerTask task, long delay, long period) 安排指定的任务从指定的延迟后开始进行重复的固定延迟执行
void scheduleAtFixedRate(TimerTask task, Date firstTime, long period) 安排指定的任务在指定的时间开始进行重复的固定速率执行
void scheduleAtFixedRate(TimerTask task, long delay, long period)  安排指定的任务在指定的延迟后开始进行重复的固定速率执行

注:1)上面提到的时间的单位都是毫秒

2)schedule()方法和scheduleAtFixedRate()方法的区别在于重复执行任务时对于时间间隔出现延迟的情况的处理:schedule()方法的执行时间间隔永远是固定的,如果之前出现了延迟情况,那么之后也会继续按照设定好的时间间隔来执行;scheduleAtFixedRate()方法在出现延迟情况时,则将快速连续地出现两次或更多的执行,从而使后续执行能够“追赶上来”。从长远来看,执行的频率将正好是指定周期的倒数(假定 Object.wait(long) 所依靠的系统时钟是准确的)

二 一个定时任务范例

package javase.timer;

import java.text.Format;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;

class MyTask extends TimerTask{
	public void run() {
		Format format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS");
		System.out.println("当前时间:" + format.format(new Date()));
	}

}

public class Demo {

	public static void main(String[] args) {
		Timer timer = new Timer();
		timer.schedule(new MyTask(), 1000, 100);  //1秒后执行,并且每隔100毫秒重复执行

		try {
			Thread.sleep(2000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}

		timer.cancel();  //终止计时器,放弃所有已安排的任务
		timer.purge();  //释放内存
	}

}

输出:

当前时间:2016-03-01 15:02:36:451
当前时间:2016-03-01 15:02:36:477
当前时间:2016-03-01 15:02:36:578
当前时间:2016-03-01 15:02:36:678
当前时间:2016-03-01 15:02:36:778
当前时间:2016-03-01 15:02:36:878
当前时间:2016-03-01 15:02:36:978
当前时间:2016-03-01 15:02:37:078
当前时间:2016-03-01 15:02:37:178

从上面的代码可以看出,操作步骤跟线程是差不多的,首先是用一个类实现TimerTask接口,然后在run()方法里定义具体的任务,最后通过一个Timer实例定时调用这个任务执行(PS:实际上TimerTask实现了runnable接口,因此就少了我们很多的操作)

三 使用Timer实现的新闻定时自动采集

在这里,我选用的测试目标是“中国新闻网”的滚动新闻,链接是:http://www.chinanews.com/scroll-news/news1.html 。然后接下来的步骤就很明确了,设置定时任务定时去访问这个网页,然后每次访问时用正则表达式将我们需要的最新新闻的标题、URL、类型和时间提取出来,最后是保存到数据库中。完整代码如下:

package javase.timer;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

class CollectionTask extends TimerTask {

	public void run() {
		synchronized (this) {
			List<String[]> news = getCurrentNews();
			if (news.size() > 0)
				saveNews(news);
		}

	}

	/**
	 * 获取最新的“中国新闻网”的滚动新闻
	 * 
	 * @return 最新滚动新闻的list列表
	 * */
	private List<String[]> getCurrentNews() {
		List<String[]> newsList = new ArrayList<String[]>();

		try {
			URL url = new URL("http://www.chinanews.com/scroll-news/news1.html");
			HttpURLConnection connection = (HttpURLConnection) url
					.openConnection();
			connection.setRequestMethod("GET");
			connection.setConnectTimeout(5000);
			connection.setReadTimeout(5000);

			if (connection.getResponseCode() == 200) {
				InputStream inputStream = url.openStream();
				// 注意编码问题,因为目标网页用的是gb2312,因此我这里也设置成gb2312,不然容易导致乱码
				BufferedReader reader = new BufferedReader(
						new InputStreamReader(inputStream, "gb2312"));
				String line = "";
				Pattern pattern = Pattern
						.compile("<li><div class=\"dd_lm\">\\[<a href=.*?>(.+?)</a>\\]</div>.*?<div class=\"dd_bt\"><a href=\"([^\"]+)\">(.+)?</a></div><div class=\"dd_time\">(.+)?</div></li>");
				Matcher matcher = null;
				while ((line = reader.readLine()) != null) {
					// System.out.println(line);
					matcher = pattern.matcher(line);
					if (matcher.find()) {
						String[] news = new String[4]; // 存储每条新闻的各个元素的数组
						news[0] = matcher.group(1); // 类型
						news[1] = matcher.group(2); // URL
						news[2] = matcher.group(3); // 标题
						news[3] = matcher.group(4); // 时间

						newsList.add(news);
					}
				}
				// for(int i=0;i<newsList.size();i++){
				// String[] temp = newsList.get(i);
				// System.out.println("分类:" + temp[0] + "  标题:" + temp[2] +
				// "  URL:" + temp[1] + "  时间:" + temp[3]);
				// }
				reader.close();
				inputStream.close();
				connection.disconnect();
				return newsList;
			}

		} catch (MalformedURLException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}

		return newsList;
	}

	/**
	 * 保存新闻列表到数据库
	 * 
	 * @param news
	 *            待保存的新闻列表
	 * */
	private void saveNews(List<String[]> news) {
		Connection connection = JDBCDemo.getConnection(); // 获取数据库连接
		try {
			PreparedStatement pStatement = null;

			// 遍历,然后判断该条新闻是否已经在数据库中存在,不存在则保存
			for (String[] temp : news) {
				// System.out.println("分类:" + temp[0] + "  标题:" + temp[2] +
				// "  URL:"
				// + temp[1] + "  时间:" + temp[3]);

				pStatement = connection
						.prepareStatement("select id from rollnews where url = ?");
				pStatement.setString(1, temp[1]);
				ResultSet resultSet = pStatement.executeQuery();
				if (resultSet.next())
					continue;
				else {
					pStatement = connection
							.prepareStatement("insert into rollnews(title,url,type,time) values(?,?,?,?)");
					pStatement.setString(1, temp[2]);
					pStatement.setString(2, temp[1]);
					pStatement.setString(3, temp[0]);
					pStatement.setString(4, temp[3]);

					pStatement.executeUpdate();
				}

			}
			pStatement.close();
			connection.close();
		} catch (SQLException e) {
			e.printStackTrace();
		}

	}

}

public class NewsCollection {

	public static void main(String[] args) {
		Timer timer = new Timer();
		// timer.schedule(new CollectionTask(), 1000, 120000);
		timer.scheduleAtFixedRate(new CollectionTask(), 1000, 120000);
	}

}

附:

(1)JDBCDemo.java:

package javase.timer;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

public class JDBCDemo {

	public static Connection getConnection(){
		try {
			Class.forName("com.mysql.jdbc.Driver");

			return DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/news?useUnicode=true&characterEncoding=utf-8", "root", "root");
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		} catch (SQLException e) {
			e.printStackTrace();
		}
		return null;
	}

}

(2)SQL结构:

/*
Navicat MySQL Data Transfer

Source Server         : sel
Source Server Version : 50519
Source Host           : localhost:3306
Source Database       : news

Target Server Type    : MYSQL
Target Server Version : 50519
File Encoding         : 65001

Date: 2016-03-01 15:35:06
*/

SET FOREIGN_KEY_CHECKS=0;

-- ----------------------------
-- Table structure for rollnews
-- ----------------------------
DROP TABLE IF EXISTS `rollnews`;
CREATE TABLE `rollnews` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `title` varchar(255) NOT NULL,
  `url` varchar(255) NOT NULL,
  `type` varchar(255) NOT NULL,
  `time` varchar(255) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=300 DEFAULT CHARSET=utf8;

最后的效果如下:

每隔2分钟会访问目标网页一次,然后将获取到的新的新闻保存到数据库中

时间: 2024-08-08 07:47:35

Java基础系列6:计时器Timer与新闻的定时自动采集的相关文章

《Java 基础系列》初步整理

<Java 基础系列>初步整理大概有 12 篇,主要内容为.: 抽象类和接口内部类修饰符装箱拆箱注解反射泛型异常集合IO字符串其他第一篇我们来聊聊抽象类和接口. "抽象类和接口"听起来是非常普遍的东西,有些朋友会觉得:这个太基础了吧,有啥好说的,你又来糊弄我. 这里写图片描述 事实上我在面试中不仅一次被问到相关的问题: 抽象类和接口之间的区别?什么时候创建抽象类?什么时候创建接口?设计框架时该如何选择?我比较喜欢这样的问题,答案可深可浅,体现了我们对日常工作的思考. 我们什

夯实Java基础系列4:一文了解final关键字的特性、使用方法,以及实现原理

目录 final使用 final变量 final修饰基本数据类型变量和引用 final类 final关键字的知识点 final关键字的最佳实践 final的用法 关于空白final final内存分配 使用final修饰方法会提高速度和效率吗 使用final修饰变量会让变量的值不能被改变吗: 如何保证数组内部不被修改 final方法的三条规则 final 和 jvm的关系 写 final 域的重排序规则 读 final 域的重排序规则 如果 final 域是引用类型 参考文章 微信公众号 Jav

夯实Java基础系列6:一文搞懂抽象类和接口,从基础到面试题,揭秘其本质区别!

目录 抽象类介绍 为什么要用抽象类 一个抽象类小故事 一个抽象类小游戏 接口介绍 接口与类相似点: 接口与类的区别: 接口特性 抽象类和接口的区别 接口的使用: 接口最佳实践:设计模式中的工厂模式 接口与抽象类的本质区别是什么? 基本语法区别 设计思想区别 如何回答面试题:接口和抽象类的区别? 参考文章 微信公众号 Java技术江湖 个人公众号:黄小斜 本系列文章将整理到我在GitHub上的<Java面试指南>仓库,更多精彩内容请到我的仓库里查看 https://github.com/h2pl

夯实Java基础系列7:一文读懂Java 代码块和执行顺序

目录 Java中的构造方法 构造方法简介 构造方法实例 例 1 例 2 Java中的几种构造方法详解 普通构造方法 默认构造方法 重载构造方法 java子类构造方法调用父类构造方法 Java中的代码块简介 Java代码块使用 局部代码块 构造代码块 静态代码块 Java代码块.构造方法(包含继承关系)的执行顺序 参考文章 微信公众号 Java技术江湖 个人公众号:黄小斜 本系列文章将整理到我在GitHub上的<Java面试指南>仓库,更多精彩内容请到我的仓库里查看 https://github

夯实Java基础系列9:深入理解Class类和Object类

目录 Java中Class类及用法 Class类原理 如何获得一个Class类对象 使用Class类的对象来生成目标类的实例 Object类 类构造器public Object(); registerNatives()方法; Clone()方法实现浅拷贝 getClass()方法 equals()方法 hashCode()方法; toString()方法 wait() notify() notifAll() finalize()方法 CLass类和Object类的关系 参考文章 微信公众号 Ja

夯实Java基础系列10:深入理解Java中的异常体系

目录 为什么要使用异常 异常基本定义 异常体系 初识异常 异常和错误 异常的处理方式 "不负责任"的throws 纠结的finally throw : JRE也使用的关键字 异常调用链 自定义异常 异常的注意事项 当finally遇上return JAVA异常常见面试题 参考文章 微信公众号 Java技术江湖 个人公众号:黄小斜 - Java异常 本系列文章将整理到我在GitHub上的<Java面试指南>仓库,更多精彩内容请到我的仓库里查看 https://github.c

夯实Java基础系列13:深入理解Java中的泛型

目录 泛型概述 一个栗子 特性 泛型的使用方式 泛型类 泛型接口 泛型通配符 泛型方法 泛型方法的基本用法 类中的泛型方法 泛型方法与可变参数 静态方法与泛型 泛型方法总结 泛型上下边界 泛型常见面试题 参考文章 微信公众号 Java技术江湖 个人公众号:黄小斜 本系列文章将整理到我在GitHub上的<Java面试指南>仓库,更多精彩内容请到我的仓库里查看 https://github.com/h2pl/Java-Tutorial 喜欢的话麻烦点下Star.Fork.Watch三连哈,感谢你的

Java基础系列1:深入理解Java数据类型

Java基础系列1:深入理解Java数据类型 当初学习计算机的时候,教科书中对程序的定义是:程序=数据结构+算法,Java基础系列第一篇就聊聊Java中的数据类型. 本篇聊Java数据类型主要包括四个内容: Java基本类型 Java封装类型 自动装箱和拆箱 封装类型缓存机制 Java基本类型 Java基本类型分类.大小及表示范围 Java的基本数据类型总共有8种,包括三类:数值型,字符型,布尔型,其中 数值型: 整数类型:byte.short.int.long 浮点类型:float.doubl

Java 基础系列合集

Java特性 Java三大特性:封装,继承,多态 Java 抽象类与接口 Java 浅拷贝和深拷贝 Java static和final Java 内部类.静态内部类.匿名内部类 Java 强制类型转换 java 实现多重继承 Java - equals方法 Java 字符串 Java - 异常解析基础 Java集合系列 Java - 数组解析 Java - 集合 Java - ArrayList源码分析 Java - LinkedList源码分析 Java - HashMap源码解析 Java