最近在学习PSP,其核心思想是:记录自己的
工作数据,通过数据找出有问题的地方予以改进,通过数据预测自己将来执行某项任务时所需要的时间。
如果衡量开发人员的工作需要真实地记录他们工作的执行情况的话,那么开发人员似乎没有很大的动力做这件事,因为:
他们似乎需要花费大量的时间执行一些和任务本身没有关系的事情。
由于PSP也需要开发人员记录自己代码的缺陷,因此开发人员可能宁可不记录,来让自己显得“不那么笨”。
所以,我必须找到一种激励方法,让他们愿意如实地记录自己代码中注入的缺陷。我们可能不能采用“发现缺陷则惩罚”的方法,因为代码中总是有缺陷的。我们或许可以采取一定的奖励措施,这种奖励措施由“短期的、相对容易实现的目标”和“长期的、不容易实现的目标”组成。前者的目的是引导开发人员经常性地关注自己的代码质量,努力降低缺陷率。由于是短期的且相对容易实现的,则相应的奖励也比较小。而如果在一个较长的时间内开发人员能够始终保持低缺陷率,则第二种奖励便可自动达到。
例如,“短期的、相对容易实现的目标”可以是在一个迭代中“每千行代码包含的缺陷数量低于10个”;“长期的、不容易实现的目标”可以是在连续的12个迭代中至多只有2个迭代的缺陷率没有达到“每千行代码包含的缺陷数量低于10个”。
我们不应该采取“发现缺陷则奖励”的措施,因为这会激励
测试人员去汇报一大堆无关紧要的缺陷。对于测试人员,可以采用“产品发布后,在一定时间内客户没有报出一定数量的缺陷,则奖励测试人员。”
无论是对于开发人员还是测试人员,这种奖励最好是针对团队整体的,或者至少是团队层面和个人层面都有的,而不要仅仅在个人层面。这样做的期望是让每个人都为团队整体的绩效负责,同时在某些人可能明显拖整体后腿的前提下,让一些一直努力的人可以得到奖励。
要让开发人员明白,他们的职责是两点:
按时开发出符合质量要求的产品。
为公司省钱。事实上,这第二点要求是第一点要求的连带产品:只要“按时”和“符合质量”,就为公司省下了钱。
最终的目标:让团队在保证工作质量的前提下,过上朝九晚五的
生活。注意反之是不成立的。
English » | | | | | | | | |
Text-to-speech function is limited to 100 characters
在书场上看到很多有关Java的书籍,但这就像进了瓜地里挑瓜挑的眼花,很多人不知道自己到底该选那本书好。很快精通Java可能只有很少一部分人能实现,那就是他曾经精通过 哪门语言,因为程序设计语言很好学,只要你精通一门语言,就可以做到一通百通。因为每种语言都有其共同点,就拿C语言来说,由于C语言出现的比较早,用的人也比较多,所以人们都习惯了它的语法规则和设计流程,假如现在出现了一门新的语言,而它和C语言的语法规则是天壤之隔,那么它的结果肯定是被淘汰的对 象。道理很简单,这种新语言的语法习惯和人们的编程习惯相差甚远,所以导致很少有人用,而语言的开发就是为了更多的使用才有其价值,如果没人使用也就没有它的价值了。就像Java语言一样,它的出现要比C语言晚,但无论它再怎么新,它的语法规则和C语言基本相差不远,所以人们也喜欢用,这样它才能实现它的实际价值。就像笔者在学习JavaScript一样,由于对Java的
学习比较深入,所以在学习JavaScript只需要不到一个星期就做出了像hao123那样的网页。
而对于大多人来说,他们如果没有精通某种语言,刚开始就学Java,这样连基本的语法规则都没有积累,怎么可能在短期内精通Java?而本书就克服了这个缺点,无论是对于初学者还是大牛,都有其相对应的适应性。根据笔者自学Java两年的经验,笔者在这里毛遂自荐一下,其实精通一门语言很简单:对于初学者,刚开始需要把基本概念过一遍,而本书开始部分的基本概念都是精简版,所以这样就克服了概念吸收慢的缺点。接着就是做后面的程序练习和项目开发。有人可能会问,这样如果有的概念忘记了怎么办?很正常,遇到不懂的概念就回去前面查或者查API文档。就这么简单,精通的过程就是在不断地查和练之间形成的;对于已经接触过一门语言的同学前面的Java概念只需简单过一遍,毕竟每种语言之间虽然有很多相似之处,但也有很多不同之处,所以主要看不同的地方。接着还 是不断地练习和做项目,这样才能不断提高自己。
我在这里不得不提一下另一种古老的学习方式,那就是中学的学习方式。很多人将中学的学习方式带到了大学,而大学的学习方式和中学的学习方式是大相径庭的,无论你学习什么。所以就出现了,很多在中学学习很少拔尖的同学在大学的学习中却很吃力,甚至付出了很多努力,但最后的成绩还是到不得自己预期的水平。在中 学的学习方式是我们花大量的时间来把概念够透彻,尤其对于数学更是这样,就拿笔者来说,笔者在高考时把五本数学书仔仔细细翻了三遍,课后习题一个不落的往后做。而在大学,大学就是一个小社会,它会让你更接近现实,同时进入社会事情肯定也越来越多,怎样高效地处理这些事情就需要另一种学习方式。就像笔者在上面说过的一样,在大学的学习中大多是靠自己自学的,在大学靠老师就等于靠一面快要倒了的墙,你是靠不住的,这样只会耽误一个学生的前途。所以,我们在学习过程中,怎么高效地吸收书本上的知识,很简单,就是通过不断地查和练。
以前在中学时,经常看一些怎样提高学习效率、怎样考高分的书,感觉人家说得在情在理,自己当时也看得是激情澎湃。但在大学的图书馆钻了两年后笔者才发现看 不看这些其实都是一样的。因为无论在哪本学习方法的书里面,都是让你把自己的时间安排的满满的来学习一门知识,这很明显是理想状态,进入社会的人有多少能整体学习一门知识的,就是学生每天也要学习不同的课程,更何况进入社会的我们。其实,话又说回来了,别人的学习方法也不无道理,人各有志,每个人的情况大 相径庭。但无论你无论是借鉴别人的学习方法,还是自己的,只有适合自己的才是最好的。
还有一点,学习方法固然重要,但更重要的是自己的心态,如果一个三天打鱼两天晒网,那么,无论多么科学的学习方法对他来说都无济于事。道理很简单,就像一个人对他的女朋友用心不专一样,那么他还希望他的女朋友能和他相处一辈子吗?
对于初学者来说,笔者建议刚开始练习Java程序的时候用DOS环境来编译和运行,这样也可以提高自己的程序调试水平。笔者承认Eclipse功能很强大 用起来也非常方便,但笔者认为这不适合初学者使用,因为里面很多函数、类、方法等不需要自己写就可以自动生成,这样反而不利于初学者的学习。这个道理也很简单,其实,越方便的东西我们越要警惕,这就和天上掉馅饼是一个道理,它有可能不是圈套就是陷阱。
刚开始学习Java不在多,关键在精。很多人在学习时有这样一种误区,书借了很多,但是都是这本书学一点,那本书学一点,到头来学的知识没有一个整体性,最后给自己的感觉就是好像学了很多,但真正用起来却手足无措。所以,你只需要用一本书把它搞精就OK了。
在这里我们需要明确一个误区:Java的学习是为了项目开发,而不是为了搞研究。所以,我们在学习的时候关键是要知道它怎样用,而不是要深入地知道它到底 是怎么回事。而笔者只所以要写这本书,这也是其中一个原因。缘由笔者在刚开始学习Java的时候也借了很多书,但都是理论搞得过于深刻,这样不但繁琐难懂,而且最后用起来还是写不出来。就拿里面的IO流那章来说,很多书都想把它讲的很清楚这点没错,所以理论搞得非常深厚,但这样只会让人看得一头雾水而不知所云。这样反而会事倍功半,所以笔者在讲这章时,很简单,主要是搞清楚流的去向,如读出就是把文件从内存读出到显示器,写入就是通过键盘把文件写入到内存。搞清楚了最基本的道理,后面的各个函数都是围绕这一点来展开的,学起来就轻而易举地理解它。这就和练功一样,先要把内功练深厚,后面的深奥功夫才能很快练就,反之,就只会走火入魔。
English » | | | | | | | | |
Text-to-speech function is limited to 100 characters
电脑上的许多软件可以监控浏览器发出的HTTP请求, iPhone上有许多连网程序但没有自带软件可以实现监控, 为了方便
测试这些请求是否正确而省去在程序中记录请求日志并逐一查找的麻烦, 可以利用Paros这个监控软件来实现. 以下是实现在Mac上监控iPhone发出的HTTP请求的具体步骤:
1. 将Mac机器的以太网共享给无线局域网:
Mac上: 系统偏好设置->共享->Internet 共享, 将"以太网"共享给"AirPort"
2. 查看无线局域网IP:
返回->网络->AirPort, 查看到AirPort具有IP地址"169.254.242.107"
3. 设置Paros本地代理:
Paros->Tools->Options->Local proxy, Address填上"169.254.242.107", 端口默认为8080
4. 设置iPhone上网代理:
iPhone上: 设置->无线局域网->选上Mac机器共享出去的网络->按"Details"箭头->Http代理->手动->服务器填"169.254.242.107", 端口填"8080"
5. 查看HTTP请求:
iPhone打开访问任何连网程序, 可以在Paros的Sites下看到访问的网站, 右边可以选Reques/Response等信息.
English » | | | | | | | | |
Text-to-speech function is limited to 100 characters
目前公司研发使用
jira软件进行
项目管理,安装了GreenHopper,JIRA Subversion plugin,Links Hierarchy Reports等插件。jira数据库采用oracle 11g。由于历史原因,采用的
操作系统版本为windows
server 2008 32位。
一:新需求汇总:
1: 把jira迁移到windows server 2008 64位的新服务器上
2:新安装fisheye插件并解决授权问题
3: jira,fisheye,svn的整合
4:调整jira的运行内存
二:操作步骤及注意事项
1:新服务器上安装jira软件,安装路径务必同旧服务器保持一致,否则会出现插件缺失的情况!要采用和旧服务器同样的jira软件版本,这里采用jira版本4.4.5
2:下载并安装64位jdk,这里采用
java版本1.6.0_43
C:\Users\Administrator>java -version
java version "1.6.0_43"
Java(TM) SE Runtime Environment (build 1.6.0_43-b01)
Java HotSpot(TM) 64-Bit Server VM (build 20.14-b01, mixed mode)
3:下载并安装fisheye,这里采用fisheye-2.4.3版本,下载完成后解压运行bin目录下的start.bat即可启动。
4:fisheye授权问题,参考
百度文库链接即可!
5:jira同fisheye以及svn的整合
进入jira的管理员页面,点击源代码控制,点击fisheye configuration标签进行整合
English » | | | | | | | | |
Text-to-speech function is limited to 100 characters
微软对测试人员有提供以下training roadmap 0. Day1-2: Cross Discipline New Employee Orientation
1. 0-2 years
Test Automation
Debugging
Model Based Testing
Elective Courses
2. 2-5 years
Technical Electives: Design patterns,
SQL server, C#, C++, protocols, other skill based courses
3. 5-10 years: 才算是senior tester
目前和公司的训练课程比较,微软在0-2 years做的事情和我们差不多, 不过我们是缺少了debugging这一块, 也能是这一块并不容易处理, 或者是目前我们这方面的人才也不够, 所以排不出这样的课程.
不过到了2-5years, 微软在这方面做的就不错. 目前我们公司在这方面就没有太多着墨, 或许我们在deveopler有开这方面的课, 但是我们却没有强迫tester去参加. 如果我们没有加强这一块, tester会不容易在前期有更多帮助, 或者是在review, debugging能出更多力. 所以这应该是之后我们公司要努力的一块.
另外, 微软也要求测试人员, 除了hard skill外, 也需要具备以下的soft skill
1. 分析解决问题的能力
2. 客户为主的创新
3. 卓越技术
5. 对品质的热情
6. 战略眼光: 可以帮助我们超越竞争者和增加stakeholder的价值
7. 自信: 要有自信知道这些bug是对顾客非常重要, 因此一但发生这些问题时, 便会强力要求开发人员修改
8. 冲击和影响: 影响力是来自于自信和经验,冲击是来自知道如何让改变发生
9. 跨界合作: 创新常常来自于跨部门或组织的合作, 若是只注意自己的功能或测试这样不会成功
10. 自我意识: 会自我捡讨自我批判, 来不段
学习和改进
English » | | | | | | | | |
Text-to-speech function is limited to 100 characters
前言:
在做WEB项目时,我们写好了一个Dao和Service后,接下来就是要进行
单元测试,测试的时候还要等到Spring容器全部加载完毕后才能进行,然后通过拿到ApplicationContext对象来gerBean()方法进行测试,或者更笨点的就是写一个控制器,在浏览器敲入地址进行deBug跟踪测试,这样不仅效率低,而且收效甚微。
本章来讲解spring融合Junit4进行单元测试。
本章的测试源目录和包是紧随上一章节的源代码。点我查看上一章节
jar包支持(上一章节代码里面已给出)
测试的源代码和包结构(同上)
注意:测试类
test包路径最好位于src根目录下,编译后为calsses文件夹下,方便其他路径的书写
实例代码演示:
****************复制该类至上一章节test包下即可************注释部分我尽可能详细讲解****************
UserServiceTest package test; import javax.annotation.Resource; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.context.ApplicationContext; import org.springframework.context.support.FileSystemXmlApplicationContext; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Sort.Direction; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.transaction.TransactionConfiguration; import org.springframework.transaction.annotation.Transactional; import com.spring.jpa.user.User; import com.spring.jpa.user.UserService; /** 声明用的是Spring的测试类 **/ @RunWith(SpringJUnit4ClassRunner.class) /** 声明spring主配置文件位置,注意:以当前测试类的位置为基准,有多个配置文件以字符数组声明 **/ @ContextConfiguration(locations={"../spring-config/spring-jpa.xml"}) /** 声明使用事务,不声明spring会使用默认事务管理 **/ @Transactional /** 声明事务回滚,要不测试一个方法数据就没有了岂不很杯具,注意:插入数据时可注掉,不让事务回滚 **/ @TransactionConfiguration(transactionManager="transactionManager",defaultRollback=true) public class UserServiceTest { @Resource private UserService userService; @Test // 新增(来个20条数据) 注意新增的时候先把事务注掉,要不会回滚操作 public void testSaveUser() { for(int i=0; i<20; i++){ User user = new User(); user.setUserName("system"); user.setPassWord(i+"system"); userService.saveUser(user); } } @Test // 删除 有事务回滚,并不会真的删除 public void testDeleteUser() { userService.deleteUser(27L); } @Test // 查询所有 public void testFindAllUser() { List<User> users = userService.findAllUsers(); System.out.println(users.size()); } @Test // 查询分页对象 public void testFindAllUserByPage() { /** * 创建一个分页对象 (注意:0代表的是第一页,5代表每页的大小,后两个参数不写即为默认排序) * Direction:为一个枚举类,定义了DESC和ASC排序顺序 * id:结果集根据id来进行DESC降序排序 * 想自己实现的话,最好继承他这个类,来定义一些个性的方法 */ PageRequest request = new PageRequest(1, 4, Direction.DESC, "id"); Page<User> users = userService.findAllUserByPage(request); // 打印分页详情 System.out.println("查询结果:共"+users.getTotalElements()+"条数据,每页显示"+users.getSize()+"条,共"+users.getTotalPages()+"页,当前第"+(users.getNumber()+1)+"页!"); // 打印结果集的内容 System.out.println(users.getContent()); } // main 用于查看spring所有bean,以此可以检测spring容器是否正确初始化 public static void main(String[] args) { // 我这里使用的是绝对路径,请根据你项目的路径来配置(相对路径挖不出来-OUT了) String [] path = {"E:/moviework/springJpa/src/spring-config/spring-jpa.xml"}; ApplicationContext ac = new FileSystemXmlApplicationContext(path); String[] beans = ac.getBeanDefinitionNames(); for(String s : beans) { System.out.println(s); // 打印bean的name } } } |
测试testFindAllUserByPage()方法控制台输出sql语句和信息:
完事,就是这么简单,和普通java类的测试多的只是注解的东西。原理还是一样的,并且它支持事务的回滚,不用担心在测试的时候对数据进行破坏。只有用了你才能体会原来Spring 框架的 WEB项目测试也可以这么的简洁。
数据都是基于上一章节来的,本章节不再贴出,项目打包的下载地址也在上一章节。点我前往上一章节
总结:
平时在编写test类的时候,写在src目录下更方便阅读和代码的编写
遵守测试规范,测试类方法名为:test + 原方法名首字母大写
注意@ContextConfiguration注解路径的引用
English » | | | | | | | | |
Text-to-speech function is limited to 100 characters
本文仿照
Windows 回收站的功能,运用 Bash 脚本在
Linux 上做了实现,创建 delete 脚本代替 rm 命令对文件或目录进行删除操做。该脚本实现了以下功能:对大于 2G 的文件或目录直接删除,否则放入$HOME/trash 目录下;恢复 trash 目录中的被删除文件到原目录下;文件存放在 trash 目录中超过七天被自动删除。
概述
删除是危险系数很高的操作,一旦误删可能会造成难以估计的损失。在 Linux 系统中这种危险尤为明显,一条简单的语句:rm –rf /* 就会把整个系统全部删除,而 Linux 并不会因为这条语句的不合理而拒绝执行。 在 Windows 中,为了防止误删,系统提供了回收站功能。用户在执行删除操作后,文件并不会直接从硬盘中删除,而是被放到回收站中。在清空回收站前,如果发现有文件被误删,用户可以将回收站中的文件恢复到原来的位置。而 Linux 并没有提供类似功能,删除命令 rm 一旦确认执行,文件就会直接从系统中删除,很难恢复。
回收站构成
本文共用三个脚本实现了回收站的主要功能:Delete 脚本、logTrashDir 脚本和 restoreTrash 脚本。其中 Delete 脚本是核心脚本,其作用是重新封装 rm 命令。相对于 rm 的直接删除,该命令会先将文件或目录
移动到$home/trash 目录下。如果用户想要将文件直接删除,可以用 -f 选项,delete 脚本会直接调用 rm –f 命令将文件从硬盘上删除。logTrashDir 脚本用于将被删除文件的信息记录到 trash 目录下的一个隐藏文件中。restoreTrash 脚本用来将放入 trash 中的文件或目录重新恢复到原路径下。在 Linux 系统中,只要将这三个脚本放到/bin/目录下,并用 chmod +X filename 赋予可执行权限,即可直接使用。下面将介绍每个脚本的主要部分
Delete 脚本
创建目录
首先要创建目录来存放被删除的文件,本文在用户根目录$HOME 下建立 trash 目录来存放文件。具体代码如下:
清单 1.创建回收站目录
realrm="/bin/rm"
if [ ! -d ~/trash ]
then
mkdir -v ~/trash
chmod 777 ~/trash
fi
如上所示,先判断目录是否已建立,如未建立,即第一次运行该脚本,则创建 trash 目录。变量 realrm 存放了 Linux 的 rm 脚本位置,用于在特定条件下调用以直接删除文件或目录。
输出帮助信息
该脚本在用户仅输入脚本名而未输入参数执行时,输出简要帮助信息,代码如下:
清单 2.输出帮助信息
if [ $# -eq 0 ]
then
echo "Usage:delete file1 [file2 file3....]"
echo "If the options contain -f,then the script will exec 'rm' directly"
如代码所示,该脚本的运用格式是 delete 后跟要删除的文件或目录的路径,中间用空格隔开。
直接删除文件
有些用户确认失效并想直接删除的文件,不应放入回收站中,而应直接从硬盘中删除。Delete 脚本提供了-f 选项来执行这项操作:
清单 3.直接删除文件
while getopts "dfiPRrvW" opt do case $opt in f) exec $realrm "$@" ;; *) # do nothing ;; esac done |
如果用户在命令中加入了-f 选项,则 delete 脚本会直接调用 rm 命令将文件或目录直接删除。如代码中所示,所有的参数包括选项都会传递给 rm 命令。所以只要选项中包括选项-f 就等于调用 rm 命令,可以使用 rm 的所有功能。如:delete –rfv filename 等于 rm –rfv filename。
用户交互
需要与用户确认是否将文件放入回收站。相当于 Windows 的弹窗提示,防止用户误操作。
清单 4.用户交互
echo -ne "Are you sure you want to move the files to the trash?[Y/N]:\a"
read reply
if [ $reply = "y" -o $reply = "Y" ]
then #####
判断文件类型并直接删除大于 2G 文件
本脚本只对普通文件和目录做操作,其他类型文件不做处理。先对每个参数做循环,判断他们的类型,对于符合的类型再判断他们的大小是否超过 2G,如果是则直接从系统中删除,避免回收站占用太大的硬盘空间。
清单 5.删除大于 2G 的文件
for file in $@ do if [ -f "$file" –o –d "$file" ] then if [ -f "$file" ] && [ `ls –l $file|awk '{print $5}'` -gt 2147483648 ] then echo "$file size is larger than 2G,will be deleted directly" `rm –rf $file` elif [ -d "$file" ] && [ `du –sb $file|awk '{print $1}'` -gt 2147483648 ] then echo "The directory:$file is larger than 2G,will be deleted directly" `rm –rf $file` |
如以上代码所示,该脚本用不同的命令分别判断目录和文件的大小。鉴于目录的大小应该是包含其中的文件以及子目录的总大小,所以运用了’du -sb’命令。两种情况都使用了 awk 来获取特定输出字段的值来作比较。
移动文件到回收站并做记录
该部分是 Delete 脚本的主要部分,主要完成以下几个功能
获取参数的文件名。因为用户指定的参数中可能包含路径,所以要从中获取到文件名,用来生成 mv 操作的参数。该脚本中运用了字符串正则表达式’${file##*/}’来获取。
生成新文件名。在原文件名中加上日期时间后缀以生成新的文件名,这样用户在浏览回收站时,对于每个文件的删除日期即可一目了然。
生成被删文件的绝对路径。为了以后可能对被删文件进行的恢复操作,要从相对路径生成绝对路径并记录。用户输入的参数可能有三种情况:只包含文件名的相对路径,包含点号的相对路径以及绝对路径,脚本中用字符串处理对三种情况进行判断,并进行相应的处理。
调用 logTrashDir 脚本,将回收站中的新文件名、原文件名、删除时间、原文件绝对路径记录到隐藏文件中
将文件通过 mv 命令移动到 Trash 目录下。详细代码如下所示:
清单 6.移动文件到回收站并做记录
now=`date +%Y%m%d_%H_%M_%S` filename="${file##*/}" newfilename="${file##*/}_${now}" mark1="." mark2="/" if [ "$file" = ${file/$mark2} ] then fullpath="$(pwd)/$file" elif [ "$file" != ${file/$mark1} ] then fullpath="$(pwd)${file/$mark1}" else fullpath="$file" fi echo "the full path of this file is :$fullpath" if mv -f $file ~/trash/$newfilename then $(/logTrashDir "$newfilename $filename $now $fullpath") echo "files: $file is deleted" else echo "the operation is failed" fi |
logTrashDir 脚本
该脚本较简单,仅是一个简单的文件写入操作,之所以单独作为一个脚本,是为了以后扩展的方便,具体代码如下:
清单 7.logTrashDir 代码
if [ ! -f ~/trash/.log ]
then
touch ~/trash/.log
chmod 700~/trash/.log
fi
echo $1 $2 $3 $4>> ~/trash/.log
该脚本先建立.log 隐藏文件,然后往里添加删除文件的记录。
restoreTrash 脚本
该脚本主要完成以下功能:
从.log 文件中找到用户想要恢复的文件对应的记录。此处依然使用 awk,通过正表达式匹配找到包含被删除文件名的一行
从记录中找到记录原文件名的字段,以给用户提示
将回收站中的文件移动到原来的位置,在这里运用了 mv –b 移动文件,之所以加入-b 选项是为了防止原位置有同名文件的情况。
将.log 文件中与被恢复文件相对应的记录删除
清单 8.获取相应记录
originalPath=$(awk /$filename/'{print $4}' "$HOME/trash/.log")
清单 9.查找原文件名及现文件名字段
filenameNow=$(awk /$filename/'{print $1}' ~/trash/.log)
filenamebefore=$(awk /$filename/'{print $2}' ~/trash/.log)
echo "you are about to restore $filenameNow,original name is $filenamebefore"
echo "original path is $originalPath"
清单 10.恢复文件到原来位置并删除相应记录
echo "Are you sure to do that?[Y/N]" read reply if [ $reply = "y" ] || [ $reply = "Y" ] then $(mv -b "$HOME/trash/$filename" "$originalPath") $(sed -i /$filename/'d' "$HOME/trash/.log") else echo "no files restored" fi |
自动定期清理 trash 目录
因为 delete 操作并不是真正删除文件,而是移动操作,经过一段时间的积累,trash 目录可能会占用大量的硬盘空间,造成资源浪费,所以定期自动清理 trash 目录下的文件是必须得。本文的清理规则是:在回收站中存在 7 天以上的文件及目录将会被自动从硬盘中删除。运用的工具是 Linux 自带的 crontab。
Crontab 是 Linux 用来定期执行程序的命令。当安装完成操作系统之后,默认便会启动此任务调度命令。Crontab 命令会定期检查是否有要执行的工作,如果有要执行的工作便会自动执行该工作。而 Linux 任务调度的工作主要分为以下两类:
1、系统执行的工作:系统周期性所要执行的工作,如备份系统数据、清理缓存
2、个人执行的工作:某个用户定期要做的工作,例如每隔 10 分钟检查邮件服务器是否有新信,这些工作可由每个用户自行设置。
首先编写 crontab 执行时要调用的脚本 cleanTrashCan.如清单 10 所示,该脚本主要完成两项功能:
判断回收站中的文件存放时间是否已超过 7 天,如果超过则从回收站中删除。
将删除文件在.log 文件中相应的记录删除,保持其中数据的有效性,提高查找效率。
清单 11.删除存在回收站超过 7 天的文件并删除.log 中相应记录
arrayA=($(find ~/trash/* -mtime +7 | awk '{print $1}')) for file in ${arrayA[@]} do $(rm -rf "${file}") filename="${file##*/}" echo $filename $(sed -i /$filename/'d' "$HOME/trash/.log") done |
脚本编写完成后通过 chmod 命令赋予其执行权限,然后运过 crontab –e 命令添加一条新的任务调度:
10 18 * * * /bin/ cleanTrashCan
该语句的含义为,在每天的下午 6 点 10 分执行 cleanTrashCan 脚本
通过这条任务调度,trash 的大小会得到有效的控制,不会持续增大以致影响用户的正常操作。
实际应用
首先要将 delete 脚本,logTrashDir 脚本,restoreTrash 脚本和 cleanTrashCan 放到/bin 目录下,然后用 chmod +x delete restoreTrash logTrashDir cleanTrashCan 命令赋予这三个脚本可执行权限。
运用 delete 脚本删除文件,例如要删除在/usr 目录下的 useless 文件。根据用户目前所在的位置,可以用相对路径或绝对路径来指定参数,如:delete useless,delete ./useless 或者 delete /usr/useless。执行过程如图 1 所示:
图 1.delete 脚本执行过程
执行之后,useless 文件会从原目录中删除,被移动到$HOME/trash 下,并被重命名,如图 2.所示:
图 2.回收站目录
生成的.log 记录如图 3.所示:
图 3.log 记录
如果用户在七天之内发现该文件还有使用价值,则可以使用 restoreTrash 命令将被删除文件恢复到原路径下:restoreTrash ~/trash/useless_20140923_06_28_57。具体执行情况如图 4 所示:
图 4.restoreTrash 脚本执行情况
查看/usr 目录,可以发现 useless 文件已经被恢复至此。
图 5.useless 文件被恢复
总结
本文仿照 Windows 中回收站的功能,在 Linux 中做了实现,可以有效的防止由于误删而造成的损失。读者只需要将四个脚本拷到/bin 目录下,并配置 crontab 即可使用 Linux 版回收站。
English » | | | | | | | | |
Text-to-speech function is limited to 100 characters
1.用系统管理员登陆系统。
2.停止MySQL的服务。
3.进入命令窗口,然后进入 MySQL的安装目录,比如我的安装目录是c:\mysql,进入C:\mysql\bin
4.跳过权限检查启动MySQL,
c:\mysql\bin>mysqld-nt ––skip-grant-tables
或则:c:\mysql\bin>mysqld ––skip-grant-tables
mysqld.exe是微软Windows MySQL
server数据库服务器相关程序。mysqld-nt.exe是MySQL Daemon数据库服务相关程序。
5.[未验证]
重新打开一个窗口
进入c:\mysql\bin目录,设置root的新MySQL数据库密码
c:\mysql\bin>mysqladmin -u root flush-privileges password "newpassword"
c:\mysql\bin>mysqladmin -u root -p shutdown
将newpassword替为自己的新密码
English » | | | | | | | | |
Text-to-speech function is limited to 100 characters
一个老生常谈的话题,有人说“
产品经理就是不断让正确的事情发生”,又有人说“产品经理要为团队摸清方向,指明道路”。姑娘承认,这些说法都极为正确,但是今天想简单和大家谈谈我眼中产品经理如何才能把一件事情做出色。
站的多高 想的多远
最近接手了一个新的外部合作项目。在普通的产品经理眼中,目的是把项目在规定的期限内完成接入上线;高一层的产品经理眼中,目的是把项目在规定的期限内完成接入上线,并拥有良好的用户体验;但优秀的产品经理的眼中,目的是把项目在规定的期限内完成接入上线,并拥有良好的用户体验,并且提出接入项目在产品整体发展规划中的作用。
有点绕,不如举例说明下。每个人是一个独立的产品,现在需要和第三方去合作完成婚配问题。普通的产品经理是在规定时间内和第三方完成婚配;高一层的产品经理是在规定时间内和第三方完成婚配,并且提供双方愉悦的
心情体验;优秀的产品经理是在规定时间内和第三方完成婚配,并且提供双方愉悦的心情体验,同时为产品记录双方有价值的纪念日、地点、事情等信息。不仅能为现有用户提供更好的服务(时光纪念册、纪念日提醒等),拉长产品周期,同时,经过大量的数据累积为其它用户提供完成婚配的参考方案。
放的多低 走的多远
产品经理并没有理想中光环。敲得了代码,做得了运营,搞得了推广,写得了文案,弄得了分析。一个优秀的产品经理具有强大的责任感,所谓的责任感姑娘的理解是能把自己放的多低。给你的程序猿哥哥送杯水,给你的用户解答产品疑问,给你的产品发布会一个霸气的开场。总之,哪里需要就去哪里,哪里要打替补你就得上,任何人都可以推,但你不行,那是你“儿子”。
不断发现问题 并且解决问题
不断发现问题,不单单是产品的问题,还有团队的问题、流程的问题等等。把发现的所有问题归纳并进行解决。
以目的为导向 灵活变通
工作中发现,往往会遇到这样的情况,目的是这样这样的,我们构思的方法是这样这样的,然后发现行不通,就“哒哒哒”跑去告诉领导不行。其实目的是只有一个,但中间的方法总是可以变通的。有时候我们的思维总是很可怕,会陷入单一的模式中不能自拔。学会跳出固有思维,发散性的考虑问题会有更多的收获。
在工作中我们往往会忽略掉很多,希望通过不断的总结提醒自己,让自己养成习惯。
English » | | | | | | | | |
Text-to-speech function is limited to 100 characters
我们讨论了同步容器(Hashtable、Vector),也讨论了并发容器(ConcurrentHashMap、CopyOnWriteArrayList),这些工具都为我们编写多线程程序提供了很大的方便。今天我们来讨论另外一类容器:阻塞队列。
在前面我们接触的队列都是非阻塞队列,比如PriorityQueue、LinkedList(LinkedList是双向链表,它实现了Dequeue接口)。
使用非阻塞队列的时候有一个很大问题就是:它不会对当前线程产生阻塞,那么在面对类似消费者-生产者的模型时,就必须额外地实现同步策略以及线程间唤醒策略,这个实现起来就非常麻烦。但是有了阻塞队列就不一样了,它会对当前线程产生阻塞,比如一个线程从一个空的阻塞队列中取元素,此时线程会被阻塞直到阻塞队列中有了元素。当队列中有元素后,被阻塞的线程会自动被唤醒(不需要我们编写代码去唤醒)。这样提供了极大的方便性。
本文先讲述一下
java.util.concurrent包下提供主要的几种阻塞队列,然后分析了阻塞队列和非阻塞队列的中的各个方法,接着分析了阻塞队列的实现原理,最后给出了一个实际例子和几个使用场景。
一.几种主要的阻塞队列
自从Java 1.5之后,在java.util.concurrent包下提供了若干个阻塞队列,主要有以下几个:
ArrayBlockingQueue:基于数组实现的一个阻塞队列,在创建ArrayBlockingQueue对象时必须制定容量大小。并且可以指定公平性与非公平性,默认情况下为非公平的,即不保证等待时间最长的队列最优先能够访问队列。
LinkedBlockingQueue:基于链表实现的一个阻塞队列,在创建LinkedBlockingQueue对象时如果不指定容量大小,则默认大小为Integer.MAX_VALUE。
PriorityBlockingQueue:以上2种队列都是先进先出队列,而PriorityBlockingQueue却不是,它会按照元素的优先级对元素进行排序,按照优先级顺序出队,每次出队的元素都是优先级最高的元素。注意,此阻塞队列为无界阻塞队列,即容量没有上限(通过源码就可以知道,它没有容器满的信号标志),前面2种都是有界队列。
DelayQueue:基于PriorityQueue,一种延时阻塞队列,DelayQueue中的元素只有当其指定的延迟时间到了,才能够从队列中获取到该元素。DelayQueue也是一个无界队列,因此往队列中插入数据的操作(生产者)永远不会被阻塞,而只有获取数据的操作(消费者)才会被阻塞。
二.阻塞队列中的方法 VS 非阻塞队列中的方法
1.非阻塞队列中的几个主要方法:
add(E e):将元素e插入到队列末尾,如果插入成功,则返回true;如果插入失败(即队列已满),则会抛出异常;
remove():移除队首元素,若移除成功,则返回true;如果移除失败(队列为空),则会抛出异常;
offer(E e):将元素e插入到队列末尾,如果插入成功,则返回true;如果插入失败(即队列已满),则返回false;
poll():移除并获取队首元素,若成功,则返回队首元素;否则返回null;
peek():获取队首元素,若成功,则返回队首元素;否则返回null
对于非阻塞队列,一般情况下建议使用offer、poll和peek三个方法,不建议使用add和remove方法。因为使用offer、poll和peek三个方法可以通过返回值判断操作成功与否,而使用add和remove方法却不能达到这样的效果。注意,非阻塞队列中的方法都没有进行同步措施。
2.阻塞队列中的几个主要方法:
阻塞队列包括了非阻塞队列中的大部分方法,上面列举的5个方法在阻塞队列中都存在,但是要注意这5个方法在阻塞队列中都进行了同步措施。除此之外,阻塞队列提供了另外4个非常有用的方法:
put(E e)
take()
offer(E e,long timeout, TimeUnit unit)
poll(long timeout, TimeUnit unit)
put方法用来向队尾存入元素,如果队列满,则等待;
take方法用来从队首取元素,如果队列为空,则等待;
offer方法用来向队尾存入元素,如果队列满,则等待一定的时间,当时间期限达到时,如果还没有插入成功,则返回false;否则返回true;
poll方法用来从队首取元素,如果队列空,则等待一定的时间,当时间期限达到时,如果取到,则返回null;否则返回取得的元素;
三.阻塞队列的实现原理
前面谈到了非阻塞队列和阻塞队列中常用的方法,下面来探讨阻塞队列的实现原理,本文以ArrayBlockingQueue为例,其他阻塞队列实现原理可能和ArrayBlockingQueue有一些差别,但是大体思路应该类似,有兴趣的朋友可自行查看其他阻塞队列的实现源码。
首先看一下ArrayBlockingQueue类中的几个成员变量:
public class ArrayBlockingQueue<E> extends AbstractQueue<E> implements BlockingQueue<E>, java.io.Serializable { private static final long serialVersionUID = -817911632652898426L; /** The queued items */ private final E[] items; /** items index for next take, poll or remove */ private int takeIndex; /** items index for next put, offer, or add. */ private int putIndex; /** Number of items in the queue */ private int count; /* * Concurrency control uses the classic two-condition algorithm * found in any textbook. */ /** Main lock guarding all access */ private final ReentrantLock lock; /** Condition for waiting takes */ private final Condition notEmpty; /** Condition for waiting puts */ private final Condition notFull; } |
可以看出,ArrayBlockingQueue中用来存储元素的实际上是一个数组,takeIndex和putIndex分别表示队首元素和队尾元素的下标,count表示队列中元素的个数。
lock是一个可重入锁,notEmpty和notFull是等待条件。
下面看一下ArrayBlockingQueue的构造器,构造器有三个重载版本:
public ArrayBlockingQueue(int capacity) { } public ArrayBlockingQueue(int capacity, boolean fair) { } public ArrayBlockingQueue(int capacity, boolean fair, Collection<? extends E> c) { } |
第一个构造器只有一个参数用来指定容量,第二个构造器可以指定容量和公平性,第三个构造器可以指定容量、公平性以及用另外一个集合进行初始化。
然后看它的两个关键方法的实现:put()和take():
public void put(E e) throws InterruptedException { if (e == null) throw new NullPointerException(); final E[] items = this.items; final ReentrantLock lock = this.lock; lock.lockInterruptibly(); try { try { while (count == items.length) notFull.await(); } catch (InterruptedException ie) { notFull.signal(); // propagate to non-interrupted thread throw ie; } insert(e); } finally { lock.unlock(); } } |
从put方法的实现可以看出,它先获取了锁,并且获取的是可中断锁,然后判断当前元素个数是否等于数组的长度,如果相等,则调用notFull.await()进行等待,如果捕获到中断异常,则唤醒线程并抛出异常。
当被其他线程唤醒时,通过insert(e)方法插入元素,最后解锁。
我们看一下insert方法的实现:
private void insert(E x) { items[putIndex] = x; putIndex = inc(putIndex); ++count; notEmpty.signal(); } |
它是一个private方法,插入成功后,通过notEmpty唤醒正在等待取元素的线程。
下面是take()方法的实现:
public E take() throws InterruptedException { final ReentrantLock lock = this.lock; lock.lockInterruptibly(); try { try { while (count == 0) notEmpty.await(); } catch (InterruptedException ie) { notEmpty.signal(); // propagate to non-interrupted thread throw ie; } E x = extract(); return x; } finally { lock.unlock(); } } |
跟put方法实现很类似,只不过put方法等待的是notFull信号,而take方法等待的是notEmpty信号。在take方法中,如果可以取元素,则通过extract方法取得元素,下面是extract方法的实现:
private E extract() {
final E[] items = this.items;
E x = items[takeIndex];
items[takeIndex] = null;
takeIndex = inc(takeIndex);
--count;
notFull.signal();
return x;
}
跟insert方法也很类似。
其实从这里大家应该明白了阻塞队列的实现原理,事实它和我们用Object.wait()、Object.notify()和非阻塞队列实现生产者-消费者的思路类似,只不过它把这些工作一起集成到了阻塞队列中实现。
四.示例和使用场景
下面先使用Object.wait()和Object.notify()、非阻塞队列实现生产者-消费者模式:
public class Test { private int queueSize = 10; private PriorityQueue<Integer> queue = new PriorityQueue<Integer>(queueSize); public static void main(String[] args) { Test test = new Test(); Producer producer = test.new Producer(); Consumer consumer = test.new Consumer(); producer.start(); consumer.start(); } class Consumer extends Thread{ @Override public void run() { consume(); } private void consume() { while(true){ synchronized (queue) { while(queue.size() == 0){ try { System.out.println("队列空,等待数据"); queue.wait(); } catch (InterruptedException e) { e.printStackTrace(); queue.notify(); } } queue.poll(); //每次移走队首元素 queue.notify(); System.out.println("从队列取走一个元素,队列剩余"+queue.size()+"个元素"); } } } } class Producer extends Thread{ @Override public void run() { produce(); } private void produce() { while(true){ synchronized (queue) { while(queue.size() == queueSize){ try { System.out.println("队列满,等待有空余空间"); queue.wait(); } catch (InterruptedException e) { e.printStackTrace(); queue.notify(); } } queue.offer(1); //每次插入一个元素 queue.notify(); System.out.println("向队列取中插入一个元素,队列剩余空间:"+(queueSize-queue.size())); } } } } } |
这个是经典的生产者-消费者模式,通过阻塞队列和Object.wait()和Object.notify()实现,wait()和notify()主要用来实现线程间通信。
具体的线程间通信方式(wait和notify的使用)在后续问章中会讲述到。
下面是使用阻塞队列实现的生产者-消费者模式:
public class Test { private int queueSize = 10; private ArrayBlockingQueue<Integer> queue = new ArrayBlockingQueue<Integer>(queueSize); public static void main(String[] args) { Test test = new Test(); Producer producer = test.new Producer(); Consumer consumer = test.new Consumer(); producer.start(); consumer.start(); } class Consumer extends Thread{ @Override public void run() { consume(); } private void consume() { while(true){ try { queue.take(); System.out.println("从队列取走一个元素,队列剩余"+queue.size()+"个元素"); } catch (InterruptedException e) { e.printStackTrace(); } } } } class Producer extends Thread{ @Override public void run() { produce(); } private void produce() { while(true){ try { queue.put(1); System.out.println("向队列取中插入一个元素,队列剩余空间:"+(queueSize-queue.size())); } catch (InterruptedException e) { e.printStackTrace(); } } } } } |
有没有发现,使用阻塞队列代码要简单得多,不需要再单独考虑同步和线程间通信的问题。
在并发编程中,一般推荐使用阻塞队列,这样实现可以尽量地避免程序出现意外的错误。
阻塞队列使用最经典的场景就是socket客户端数据的读取和解析,读取数据的线程不断将数据放入队列,然后解析线程不断从队列取数据解析。还有其他类似的场景,只要符合生产者-消费者模型的都可以使用阻塞队列。
English » | | | | | | | | |
Text-to-speech function is limited to 100 characters