• 一个中文乱码的问题

    前两天同事问了我一个中文乱码的问题。他写了一个json请求的接口,通过浏览器请求接口的时候,返回的json中包含的中文显示了乱码。后来使用jmeter进行测试时也发现返回的是乱码。 之前也遇到过几次Java/JSP中文乱码的问题,知道乱码通常发生在字节流和字符相互转换的过程中。检查了一些这个接口涉及到的几次字节流转换的地方都没发现问题。我这个同事还是挺细心的,注意了统一使用UTF8进行编码,并且在输出接口内容时也添加了“response.setCharacterEncoding(“UTF-8”);”这样的代码。所以尝试将问题定位在浏览器显式前后:在PrintWriter输出前打印日志,如果中文在日志上可以正确显示那么问题就应该是出在浏览器端了,否则就从继续从后端找原因。结果还没观察日志呢,另一个同事查看了一下浏览器的内容编码设置,发现浏览器的内容编码设置的是GBK。问题就这样解决了:将浏览器的内容编码设置改为UTF8后问题就解决了。 问题是解决了,但是产生了一个疑问:为什么我们的程序没有提示浏览器该使用什么样的编码来解析接收到的信息呢。查了一些资料知道了该怎么设置就能完美解决这个问题了。这涉及到ServletResponse设置编码的几个方法。ServletResponse设置编码有如下三个方法: response.setCharacterEncoding(“UTF-8”); response.setContentType(“text/html;charset=UTF-8”); response.setLocale(new java.util.Locale(“zh”,”CN”)); 简单解释下这几个方法及其优先级: 第一种方法只能用来设置out输出流中所采用的编码,但是它的优先权最高,可以覆盖后面两种方法中的设置; 第二中方法可以设置out输出流中字符的编码方式,也可以设置浏览器接收到这些字符后以什么编码方式来解码,它的优先权低于第一种方法,但高于第三种方法; 第三种方法只能用来设置out输出流中字符的编码方式,但是它的优先权最低,在已经使用前两种方法中的一个设置了编码方式以后,它就被覆盖而不起作用了。 所以,使用response.setContentType可以完美解决前面提到的那个问题。 此外,还需要注意使用的ContentType,因为我同事写的接口返回的是json,所以他使用的content-type应该是“application/json”。 就这样! ########

    [阅读更多...]
  • Spark快速入门

    这是整理的Spark官网的《QuickStart》教程。在这篇教程里我们会先使用shell初步接触一下spark,然后再编写一个spark应用。我这里会优先使用scala来完成这些工作。如果想使用python或者java请直接移步原文。 shell操作 基础 sprak shell可以让我们快速的熟悉相关的API,同时它也是一个强大的交互式数据分析工具。目前spark shell只支持scala和python两种语言,这里我们只使用scala。在spark根目录下执行如下语句: spark最重要的抽象是一个分布式数据集合,叫做RDD( Resilient Distributed Dataset,弹性分布式数据集)。可以从hadoop的InputFormats(比如HDFS文件)中创建RDD,也可以转译其他的RDD为新的RDD。现在我们使用spark根目录下的README文件中的文本创建一个新的RDD: RDD有两大类数据操作(也有人称之为算子,算子是RDD中定义的函数,可以对RDD中的数据进行转换和操作):分别是action(行为)和transformation(转换)。其中action返回的是值,transformation返回的是指向新的RDD的指针。 我们先从几个action开始: 然后我们体验一下transformation,这里我们将会使用filter来返回README.md文件中所有包含“Spark”字样的行作为一个新的RDD。 我们也可以将transformation和action连起来用: 关于RDD的更多操作 RDD的action和transformation还可以用来做一些比较复杂的计算。比如说我们想找出单词最多的一行: 前面的map运算将计算出每行单词的个数,创建了一个新的RDD。然后在这个RDD上调用reduce方法来找出单词数最多的一个行。map和reduce的参数都是scala的函数值(闭包),可以使用任何语言特性或者是java或scala的库。举个例子说:我们可以简单调用其他地方声明的函数。下面我们使用Math.max()来让代码更容易理解些: 一个常见的数据流模型是MapReduce,也是因Hadoop而流行起来的。Spark可以很容易地实现MapReduce流: 这里我们结合了flatMap、map和reduceByKey三种transformation来计算文件中每个单词出现的次数,并生成了一个类型为(String, int)对的RDD。要获取RDD中每个单词出现的次数可以使用collect这个action: Caching Spark也支持从集群范围的内存缓存中获取数据。这在需要重复访问数据时是很有用的,比如需要频繁重复查询一个的小型数据集时,或者进行类似PageRank这样的迭代运算时。下面我们演示下如何缓存并使用之前获取的linesWithSpark数据集: 使用Spark来分析和缓存一个只有100行的文本文件看起来有点儿傻。真正重要的是:同样的函数也可以用在非常巨大的数据集上,即使是在由数十个或者在数百个节点的集群上也可以使用。可以按照这里的教程体验一下如何在集群上使用Spark的spark-shell。 自定义应用 这里我们会使用Spark API来编写一个自定义应用。示例程序使用Scala(使用了sbt)编写。这个应用会非常简单,实际上,应用名就叫做SimpleApp.scala: 注意:这里应该定义一个main()方法,不要使用继承scala.App这样的方式。scala.App的子类可能不会正常工作。 这个程序只是分别统计了Spark README文件中包含单词‘a’和单词‘b’的行的总数。还得记得需要将程序中的YOUR_SPARK_HOME替换为你计算机上Spark程序安装的位置。 不像前面说的那些使用Spark shell的例子,它们使用的是Spark shell的SparkContext实例,我们初始化了一个SparkContext实例作为程序的一部分。 这里我们先定义了一个SparkConf对象,在这个对象里包含我们的应用的一些信息。然后我们将这个SparkConf对象传递给SparkContext构造器。 我们的应用需要依赖Spark API,所以我们还需要添加一个sbt配置文件:simple.sbt。在这文件中添加了Spark依赖: 为了让sbt正确工作,我们需要按照正确的目录结构放置SimpleApp.scala和simple.sbt这两个文件。当一切都安排妥当后,我么可以创建一个包含应用代码的JAR包,然后使用spark-submit脚本来运行我们的程序: 参考文档 spark算子的作用:http://www.jianshu.com/p/4ff6afbbafe4 ##########

    [阅读更多...]
  • Python网络爬虫7 – 使用cookie

    很多时候,我们要查看的内容必须要先登录才能找到,比如知乎的回答,QQ空间的好友列表、微博上关注的人和粉丝等。要使用爬虫直接登录抓取这些信息时,有一个不太好解决的难题,就是这些网站设置的登录规则以及登录时的验证码识别。不过,我们可以想办法绕过去,思路是这样的:先使用浏览器登录,从浏览器获取登录后的“凭证”,然后将这个“凭证”放到爬虫里,模拟用户的行为继续抓取。这里,我们要获取的凭证就是cookie信息。 这次我们尝试使用python和cookie来抓取QQ空间上的好友列表。使用的工具是FireFox浏览器、FireBug和Python。 获取cookie 打开FireFox浏览器,登录QQ空间,启动FireBug,选择FireBug中的Cookies页签,点击页签中的cookies按钮菜单,选择“导出本站点的cookie”即可完成cookie的导出。 导出cookie会以一个名为cookies.txt文本文件形式存在。 程序实现 然后我们会使用获取的cookie新建一个opener来替换之前请求时使用的默认的opener。将获取的cookies拷贝到程序目录下,编写脚本如下: 因为我们使用的是FireFox浏览器导出的cookie文件,所以这里使用的cookieJar是MozillaCookieJar。 执行脚本…然而报错了: 问题出在cookies文件上,说是不像一个Netscape格式的cookie文件。不过也好解决,只需要在cookies文件开始一行添加如下内容即可: 通过这行内容提示python cookie解析器这是一个FireFox浏览器适用的cookie。 再次执行,还是会报错,因为比较长我就只贴关键的部分出来: 意思是cookie中某些行存在格式错误。具体错在哪儿,需要先了解下FireFox浏览器的cookie格式。MozillaCookieJar认为每行cookie需要包含以下信息,每条信息以制表符分隔: 名称 domain domain_specified path secure expires name value 类型 字符串 布尔型 字符串 布尔型 长整型 字符串 字符串 说明 域名 — 适用路径 是否使用安全协议 过期时间 名称 值 其中domain_specified是什么意思我不很清楚,以后弄明白了再补上。再来看看我们获取的cookie的部分行: 前两行格式是错误的,后两行格式是正确的。前两行缺少“expires”属性。该怎么办呢——补上就好了呗。在其他的cookie中随意选一个时间补上就OK了。 补全cookie后,再次执行是正常的,没有报错。但是没有如预期的打印出好友信息,因为网址错了。使用firebug可以找出正确的网址: 这样就抓取到好友列表了。好友列表是一个json字符串。 至于如何解析json,会在下一节进行说明。 动态获取cookie cookie是有过期时间的。如果想长时间抓取网页,就需要每隔一段时间就更新一次cookie。如果都是从FireFox浏览器来手动获取显得有些笨了。从浏览器获取的cookie只是作为一个入口,之后再进行请求还是要依靠python主动获取cookie。下面是一段获取cookie的程序: 在示例程序中演示了如何获取cookie,并打印了cookie的name和value两项属性。通过实例可以看到每次执行http请求都会重新获取cookie,因此可以将我们的程序调整一下:执行第一次请求时使用我们通过浏览器获取的cookie,之后的每次请求都可以使用上次请求时获取的cookie。调整后的程序: 就这样。 其他 其实在登录QQ空间时使用cookie还有另一种法子——通过观察,也可以在http 请求头中添加cookie信息。 获取请求头中cookie的方式:打开FireFox浏览器,打开FireBug并激活FireBug的network页签,在FireFox浏览器上登录QQ空间,然后在FireBug中找到登录页请求,然后就可以找到请求头中的cookie信息了。 将cookie信息整理成一行,添加到请求头中就可以直接访问了。这个方法相对简单,减少了修改cookie文件的步骤。 此外,在一篇博客文章中还找到了直接登录QQ空间的方案。这算是已知最好的法子了,只要腾讯不改变登录规则就能很简单的执行请求获取cookie。

    [阅读更多...]
  • HttpClient多线程并发

    说明:以下的代码基于httpclient4.5.2实现。 我们要使用java的HttpClient实现get请求抓取网页是一件比较容易实现的工作: 要多线程执行get请求时上面的方法也堪用。不过这种多线程请求是基于在每次调用get方法时创建一个HttpClient实例实现的。每个HttpClient实例使用一次即被回收,也意味着每次调用这里的get方法都需要重新创建连接。这显然不是一种最优的实现。 HttpClient提供了多线程请求方案,可以查看官方文档的《Pooling connection manager》这一节。HttpCLient实现多线程请求是基于内置的连接池实现的,其中有一个关键的类即PoolingHttpClientConnectionManager,这个类负责管理HttpClient连接池。在PoolingHttpClientConnectionManager中提供了两个关键的方法:setMaxTotal和setDefaultMaxPerRoute。setMaxTotal设置连接池的最大连接数,setDefaultMaxPerRoute设置每个路由上的默认连接个数。此外还有一个方法setMaxPerRoute——单独为某个站点设置最大连接个数,像这样: 根据文档稍稍调整下我们的get请求实现: 这样就差不多了。不过对于我自己而言,我更喜欢httpclient的fluent实现,比如我们刚才实现的http get请求完全可以这样简单的实现: 我们要做的只是将以前的httpclient依赖替换为fluent-hc依赖: 并且这个fluent实现天然就是采用PoolingHttpClientConnectionManager完成的。它设置的maxTotal和defaultMaxPerRoute的值分别是200和100: 唯一一点让人不爽的就是Executor没有提供调整这两个值的方法。不过这也完全够用了,实在不行的话,还可以考虑重写Executor方法,然后直接使用Executor执行get请求: 就这样! ####

    [阅读更多...]
  • Java Fork/Join

    Fork/Join框架是ExecutorService接口的一个实现,通过它我们可以实现多进程。Fork/Join可以用来将一个大任务递归的拆分为多个小任务,目标是充分利用所有的资源尽可能增强应用的性能。 和任何ExecutorService接口的实现一样,Fork/Join也会使用线程池来分布式的管理工作线程。Fork/Join框架的独特之处在于它使用了work-stealing(工作窃取)算法。通过这个算法,工作线程在无事可做时可以窃取其它正在繁忙的线程的任务来执行。 Fork/Join框架的核心是ForkJoinPool类,一个AbstractExecutorService类的子类。ForkJoinPool实现了核心的work-stealing算法并可以执行ForkJoinTask处理。 基础用法 使用Fork/Join框架的第一步是编写执行碎片任务的代码。要编写的代码类似如下伪代码: 使用ForkJoinTask子类来封装如上的代码,通常会使用一些JDK提供的类,使用的有RecursiveTask(这个类会返回一个结果)和RecursiveAction两个类。 在准备好ForkJoinTask子类后,创建一个代表所有任务的对象,并将之传递给一个ForkJoinPool实例的invoke()方法。 由模糊到清晰 为了辅助理解Fork/Join框架是如何工作的,我们使用一个案例来进行说明:比如对一张图片进行模糊处理。我们用一个整型数组表示图片,其中的每个数值代表一个像素的颜色。被模糊的图片也用一个同等长度的数组来表示。 执行模糊是通过对代表图片的每个像素进行处理实现的。计算每个像素与其周围像素的均值(红黄蓝三原色的均值),计算生成的结果数组就是模糊后的图片。由于代表图像的通常都是一个大数组,整个处理过程需要通常会需要很多时间。可以使用Fork/Join框架利用多处理器系统上的并发处理优势来进行提速。下面是一个可能的实现: 现在实现抽象方法compute(),在这个方法中既实现了模糊操作,也实现了将一个任务拆分成两个小任务。这里仅是简单依据数组长度来决定是直接执行任务还是将之拆分成两个小任务: 因为上面这些方法的实现是定义在RecursiveAction的一个子类中,可以直接在一个ForkJoinPool中创建并运行任务。具体步骤如下: 1. 创建一个代表要执行的任务的对象: 2. 创建一个运行任务的ForkJoinPool实例: 3. 运行任务: 在源代码中还包含了一些创建目标图片的代码。具体参考ForkBlur示例。 标准实现 要使用Fork/Join框架按自定义的算法在多核系统上执行并发任务当然需要实现自定义的类了(比如之前我们实现的ForkBlur类)。除此之外,在JavaSE中已经在广泛使用Fork/Join框架的一些特性了。比如Java8中的java.util.Arrays类的parallelSort()方法就使用了Fork/Join框架。具体可以参考Java API文档。 Fork/Join框架的另一个实现在java.util.streams包下,这也是java8的Lambda特性的一部分。 参考文档 https://docs.oracle.com/javase/tutorial/essential/concurrency/forkjoin.html http://www.infoq.com/cn/articles/fork-join-introduction/

    [阅读更多...]