通过我们的客户端(终端,CRT,XShell)
ssh hadoop@hadoop000
ssh hadoop@192.168.199.102
远程服务器的用户名是hadoop,密码也是hadoop
有没有提供root权限,sudo command
hadoop000(192.168.199.102)是远程服务器的hostname
如果你想在本地通过ssh hadoop@hadoop000远程登录,
那么你本地的hosts肯定要添加ip和hostname的映射
192.168.199.102 hadoop000
将所有的软件都安装到~/app
tar -zxvf jdk-8u91-linux-x64.tar.gz -C ~/app/
建议将jdk的bin目录配置到系统环境变量中: ~/.bash_profile
export JAVA_HOME=/home/hadoop/app/jdk1.8.0_91
export PATH=$JAVA_HOME/bin:$PATH
让系统环境变量生效
source ~/.bash_profile
验证
java -version
下载ZK的安装包:http://archive.cloudera.com/cdh5/cdh/5/
解压:tar -zxvf zookeeper-3.4.5-cdh5.7.0.tar.gz -C ~/app/
建议ZK_HOME/bin添加到系统环境变量: ~/.bash_profile
export ZK_HOME=/home/hadoop/app/zookeeper-3.4.5-cdh5.7.0
export PATH=$ZK_HOME/bin:$PATH
让系统环境变量生效
source ~/.bash_profile
修改ZK的配置: $ZK_HOME/conf/zoo.cfg
dataDir=/home/hadoop/app/tmp/zookeeper
启动zk: $ZK_HOME/bin/
zkServer.sh start
验证: jps
多了一个QuorumPeerMain进程,就表示zk启动成功了
jps -m
jps -l
www.elastic.co
集中、转换和存储数据
Logstash 是开源的服务器端数据处理管道,能够同时从多个来源采集数据,转换数据,然后将数据发送到您最喜欢的 “存储库” 中。(我们的存储库当然是 Elasticsearch。)
1 | cd logstash-2.4.0 |
和消息系统类似消息中间件:生产者和消费者妈妈:生产者你:消费者馒头:数据流、消息 正常情况下: 生产一个 消费一个 其他情况: 一直生产,你吃到某一个馒头时,你卡住(机器故障), 馒头就丢失了 一直生产,做馒头速度快,你吃来不及,馒头也就丢失了 拿个碗/篮子,馒头做好以后先放到篮子里,你要吃的时候去篮子里面取出来吃篮子/框: Kafka 当篮子满了,馒头就装不下了,咋办? 多准备几个篮子 === Kafka的扩容
producer:生产者,就是生产馒头(老妈)consumer:消费者,就是吃馒头的(你)broker:篮子topic:主题,给馒头带一个标签,topica的馒头是给你吃的,topicb的馒头是给你弟弟吃
1 | $KAFKA_HOME/config/server.properties |
1 | server-1.properties |
核心接口(interface),负责将数据发送到topology中去处理
Storm会跟踪Spout发出去的tuple的DAG
ack/fail
tuple: message id
ack/fail/nextTuple是在同一个线程中执行的,所以不用考虑线程安全方面
open: 初始化操作
close: 资源释放操作
nextTuple: 发送数据 core api
ack: tuple处理成功,storm会反馈给spout一个成功消息
fail:tuple处理失败,storm会发送一个消息给spout,处理失败
1 | public abstract class BaseRichSpout extends BaseComponent implements IRichSpout { |
public interface IComponent extends Serializable
为topology中所有可能的组件提供公用的方法
void declareOutputFields(OutputFieldsDeclarer declarer);
用于声明当前Spout/Bolt发送的tuple的名称
使用OutputFieldsDeclarer配合使用
1 | public abstract class BaseComponent implements IComponent |
职责:接收tuple处理,并进行相应的处理(filter/join/….)
hold住tuple再处理
IBolt会在一个运行的机器上创建,使用Java序列化它,然后提交到主节点(nimbus)上去执行
nimbus会启动worker来反序列化,调用prepare方法,然后才开始处理tuple处理
prepare:初始化
execute:处理一个tuple数据,tuple对象中包含了元数据信息
cleanup:shutdown之前的资源清理操作
1 | public abstract class BaseRichBolt extends BaseComponent implements IRichBolt { |
需求:1 + 2 + 3 + …. = ???
实现方案:Spout发送数字作为input
使用Bolt来处理业务逻辑:求和
将结果输出到控制台拓扑设计: DataSourceSpout –> SumBolt
1 | import java.util.Map; |
需求:读取指定目录的数据,并实现单词计数功能
实现方案:Spout来读取指定目录的数据,作为后续Bolt处理的input
使用一个Bolt把input的数据,切割开,我们按照逗号进行分割
使用一个Bolt来进行最终的单词的次数统计操作
并输出拓扑设计: DataSourceSpout ==> SplitBolt ==> CountBolt
1 | import java.io.File; |
]]>1) Exception in thread “main” java.lang.IllegalArgumentException: Spout has already been declared for id DataSourceSpout
【不能bolt和spout命名一样】
2) org.apache.storm.generated.InvalidTopologyException: null
【还不能以双下划线开头命名】
3) Topology的名称不是重复: local似乎没问题, 等我们到集群测试的时候再来验证这个问题
【同时启用同一个Topology】
http://storm.apache.org/index.html
https://github.com/apache/storm
http://python-storm-tutorial.readthedocs.io/zh_CN/latest/
多尝试、多思考、遇到问题看日志
Apache Storm is a free and open source distributed realtime computation system.
免费、开源、分布式、实时计算系统
Storm makes it easy to reliably process unbounded streams of data
unbounded:无界,源源不断
bounded:Hadoop/spark SQL 离线 (input–>output)
doing for realtime processing what Hadoop did for batch processing
storm:实时流处理
Hadoop:离线批处理
Storm has many use cases:
realtime analytics:实时分析
online machine learning:在线机器学习
continuous computation:持续计算
distributed RPC,
ETL:
and more.
fast: over a million tuples processed per second per node 。一秒1亿
scalable(可添加机器)
fault-tolerant 容错
guarantees your data will be processed 保证每条数据被处理
easy to set up and operate.
storm能实现高频数据和大规模数据的实时处理
storm产生于twitter
需求:大户数的实时处理
实时系统要考虑:
1)健壮性
2)拓展性/分布式
3)数据不丢失不重复
4)高性能低延时
处理过程
Hadoop—–> map reduce
storm —–> spout bolt
storm进程不杀死不结束
Hadoop进程完成就结束
处理速度: storm 快
看:
社区的发展、活跃度
企业的需求
大数据的相关大会,如storm的数量上升
互联网公司使用度
1. Topologies 把顺序串起来的东西/ 调度中心2. Streams 流,数据流,水流3. Spouts 产生数据、水流的东西(水龙头)4. Bolts 处理数据/水流的东西 水壶/水桶5. Tuple 数据/水6. Stream groupings7. Reliability8. Tasks9. Workers
Topology:计算拓扑, 由spout和bolt组成的
Stream:消息流,抽象概念,没有边界的tup le构成
Tuple:消息/数据传递的基本 单元
Spout:消息流的源头,Topology的消息生产者
Bolt:消息处理单元,可以做过滤、聚合、查询/写数据库的操作
]]>1 |
|
1 | /** |
1 | ... |
AQS全名:AbstractQueuedSynchronizer,是并发容器J.U.C(java.lang.concurrent)下locks包内的一个类。它实现了一个FIFO(FirstIn、FisrtOut先进先出)的队列。底层实现的数据结构是一个双向列表。
Sync queue:同步队列,是一个双向列表。包括head节点和tail节点。head节点主要用作后续的调度。
Condition queue:非必须,单向列表。当程序中存在cindition的时候才会存在此列表
使用Node实现FIFO队列,可以用于构建锁或者其他同步装置的基础框架。
利用int类型标识状态。在AQS类中有一个叫做state的成员变量
1 | /** |
基于AQS有一个同步组件,叫做ReentrantLock。在这个组件里,stste表示获取锁的线程数,假如state=0,表示还没有线程获取锁,1表示有线程获取了锁。大于1表示重入锁的数量。
继承:子类通过继承并通过实现它的方法管理其状态(acquire和release方法操纵状态)。
可以同时实现排它锁和共享锁模式(独占、共享),站在一个使用者的角度,AQS的功能主要分为两类:独占和共享。它的所有子类中,要么实现并使用了它的独占功能的api,要么使用了共享锁的功能,而不会同时使用两套api,即便是最有名的子类ReentrantReadWriteLock也是通过两个内部类读锁和写锁分别实现了两套api来实现的。
AQS内部维护了一个CLH队列来管理锁。线程会首先尝试获取锁,如果失败就将当前线程及等待状态等信息包装成一个node节点加入到同步队列sync
queue里。接着会不断的循环尝试获取锁,条件是当前节点为head的直接后继才会尝试。如果失败就会阻塞自己直到自己被唤醒。而当持有锁的线程释放锁的时候,会唤醒队列中的后继线程。
通过一个计数来保证线程是否需要被阻塞。实现一个或多个线程等待其他线程执行的场景。
我们定义一个CountDownLatch,通过给定的计数器为其初始化,该计数器是原子性操作,保证同时只有一个线程去操作该计数器。调用该类await方法的线程会一直处于阻塞状态。只有其他线程调用countDown方法(每次使计数器-1),使计数器归零才能继续执行。
1 | final CountDownLatch countDownLatch = new CountDownLatch(threadCount); |
CountDownLatch的await方法还有重载形式,可以设置等待的时间,如果超过此时间,计数器还未清零,则不继续等待:
1 | countDownLatch.await(10, TimeUnit.MILLISECONDS); |
用于保证同一时间并发访问线程的数目。
信号量在操作系统中是很重要的概念,Java并发库里的Semaphore就可以很轻松的完成类似操作系统信号量的控制。Semaphore可以很容易控制系统中某个资源被同时访问的线程个数。
在数据结构中我们学过链表,链表正常是可以保存无限个节点的,而Semaphore可以实现有限大小的列表。
使用场景:仅能提供有限访问的资源。比如数据库连接。 Semaphore使用acquire方法和release方法来实现控制:
1 | /** |
也是一个同步辅助类,它允许一组线程相互等待,直到到达某个公共的屏障点(循环屏障)通过它可以完成多个线程之间相互等待,只有每个线程都准备就绪后才能继续往下执行后面的操作。
每当有一个线程执行了await方法,计数器就会执行+1操作,待计数器达到预定的值,所有的线程再同时继续执行。由于计数器释放之后可以重用(reset方法),所以称之为循环屏障。
与CountDownLatch区别:
1、计数器可重复用
2、描述一个或多个线程等待其他线程的关系/多个线程相互等待//公共线程循环调用方法
1 | private static CyclicBarrier barrier = new CyclicBarrier(5); |
Java并发容器JUC是三个单词的缩写。是JDK下面的一个包名。即Java.util.concurrency。
上一节我们介绍了ArrayList、HashMap、HashSet对应的同步容器保证其线程安全,这节我们介绍一下其对应的并发容器。
CopyOnWriteArrayList
写操作时复制,当有新元素添加到集合中时,从原有的数组中拷贝一份出来,然后在新的数组上作写操作,将原来的数组指向新的数组。整个数组的add操作都是在锁的保护下进行的,防止并发时复制多份副本。读操作是在原数组中进行,不需要加锁
缺点:
1.写操作时复制消耗内存,如果元素比较多时候,容易导致young gc 和full gc。
2.不能用于实时读的场景.由于复制和add操作等需要时间,故读取时可能读到旧值。 能做到最终一致性,但无法满足实时性的要求,更适合读多写少的场景。
如果无法知道数组有多大,或者add,set操作有多少,慎用此类,在大量的复制副本的过程中很容易出错。
设计思想:
1.读写分离
2.最终一致性
3.使用时另外开辟空间,防止并发冲突
源码分析
1 | //构造方法 |
它是线程安全的,底层实现使用的是CopyOnWriteArrayList,因此它也适用于大小很小的set集合,只读操作远大于可变操作。因为他需要copy整个数组,所以包括add、remove、set它的开销相对于大一些。
迭代器不支持可变的remove操作。使用迭代器遍历的时候速度很快,而且不会与其他线程发生冲突。
源码分析:
1 | //构造方法 |
它是JDK6新增的类,同TreeSet一样支持自然排序,并且可以在构造的时候自己定义比较器。
同其他set集合,是基于map集合的(基于ConcurrentSkipListMap),在多线程环境下,里面的contains、add、remove操作都是线程安全的。
多个线程可以安全的并发的执行插入、移除、和访问操作。但是对于批量操作addAll、removeAll、retainAll和containsAll并不能保证以原子方式执行,原因是addAll、removeAll、retainAll底层调用的还是contains、add、remove方法,只能保证每一次的执行是原子性的,代表在单一执行操纵时不会被打断,但是不能保证每一次批量操作都不会被打断。在使用批量操作时,还是需要手动加上同步操作的。
不允许使用null元素的,它无法可靠的将参数及返回值与不存在的元素区分开来。
源码分析:
1 | //构造方法 |
不允许空值,在实际的应用中除了少数的插入操作和删除操作外,绝大多数我们使用map都是读取操作。而且读操作大多数都是成功的。基于这个前提,它针对读操作做了大量的优化。因此这个类在高并发环境下有特别好的表现。
ConcurrentHashMap作为Concurrent一族,其有着高效地并发操作,相比Hashtable的笨重,ConcurrentHashMap则更胜一筹了。
在1.8版本以前,ConcurrentHashMap采用分段锁的概念,使锁更加细化.
但是1.8已经改变了这种思路,而是利用CAS+Synchronized来保证并发更新的安全,当然底层采用数组+链表+红黑树的存储结构。
底层实现采用SkipList跳表
曾经有人用ConcurrentHashMap与ConcurrentSkipListMap做性能测试,在4个线程1.6W的数据条件下,前者的数据存取速度是后者的4倍左右。但是后者有几个前者不能比拟的优点:
1、Key是有序的
2、支持更高的并发,存储时间与线程数无关 安全共享对象策略
]]>线程限制:一个被线程限制的对象,由线程独占,并且只能被占有它的线程修改
共享只读:一个共享只读的对象,在没有额外同步的情况下,可以被多个线程并发访问,但是任何线程都不能修改它
线程安全对象:一个线程安全的对象或者容器,在内部通过同步机制来保障线程安全,多以其他线程无需额外的同步就可以通过公共接口随意访问他
被守护对象:被守护对象只能通过获取特定的锁来访问。
如果一个类的对象同时可以被多个线程访问,并且你不做特殊的同步或并发处理,那么它就很容易表现出线程不安全的现象。比如抛出异常、逻辑处理错误…
下面列举一下常见的线程不安全的类及对应的线程安全类:
StringBuilder是线程不安全的,而StringBuffer是线程安全的。分析源码:StringBuffer的方法使用了synchronized关键字修饰。
1 |
|
SimpleDateFormat 类在处理时间的时候,如下写法是线程不安全的:
1 | private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyyMMdd"); |
但是我们可以变换其为线程安全的写法:在每次转换的时候使用线程封闭,新建变量
1 | private static void update() { |
另外我们也可以使用jodatime插件来转换时间:其可以保证线程安全性
Joda 类具有不可变性,因此它们的实例无法被修改。(不可变类的一个优点就是它们是线程安全的)
1 | private static DateTimeFormatter dateTimeFormatter = DateTimeFormat.forPattern("yyyyMMdd"); |
分析源码:(不可变性)
1 | public class DateTimeFormatter { |
像ArrayList,HashSet,HashMap 等Collection类均是线程不安全的,我们以ArrayList举例分析一下源码:
:
在声明时使用了transient 关键字,此关键字意为在采用Java默认的序列化机制的时候,被该关键字修饰的属性不会被序列化。而ArrayList实现了序列化接口,自己定义了序列化方法(在此不描述)。
//对象数组:ArrayList的底层数据结构
private transient Object[] elementData;
//elementData中已存放的元素的个数
private int size;
//默认数组容量
private static final int DEFAULT_CAPACITY = 10;
1 | /** |
1 | //每次添加时将数组扩容1,然后再赋值 |
总结:ArrayList每次对内容进行插入操作的时候,都会做扩容处理,这是ArrayList的优点(无容量的限制),同时也是缺点,线程不安全。(以下例子取材于鱼笑笑博客)
一个 ArrayList ,在添加一个元素的时候,它可能会有两步来完成:在 Items[Size] 的位置存放此元素; 增大 Size 的值。 在单线程运行的情况下,如果 Size =
0,添加一个元素后,此元素在位置 0,而且 Size=1; 而如果是在多线程情况下,比如有两个线程,线程 A 先将元素存放在位置
0。但是此时 CPU 调度线程A暂停,线程 B 得到运行的机会。线程B也向此 ArrayList 添加元素,因为此时 Size 仍然等于 0
(注意,我们假设的是添加一个元素是要两个步骤哦,而线程A仅仅完成了步骤1),所以线程B也将元素存放在位置0。然后线程A和线程B都继续运行,都增加
Size 的值。 那好,现在我们来看看 ArrayList 的情况,元素实际上只有一个,存放在位置 0,而 Size 却等于
2。这就是“线程不安全”了。那么如何将其处理为线程安全的?或者说对应的线程安全类有哪些呢?接下来就涉及到我们同步容器。
同步容器分两类,一种是Java提供好的类,另一类是Collections类中的相关同步方法。
Vector实现了List接口,Vector实际上就是一个数组,和ArrayList非常的类似,但是内部的方法都是使用synchronized修饰过的方法。
Stack它的方法也是使用synchronized修饰了,继承了Vector,实际上就是栈
使用举例(Vector):
1 | //定义 |
运行结果:报错java.lang.ArrayIndexOutOfBoundsException: Array index out of
range
原因分析:同时发生获取与删除的操作。当两个线程在同一时间都判断了vector的size,假设都判断为9,而下一刻线程1执行了remove操作,随后线程2才去get,所以就出现了错误。synchronized关键字可以保证同一时间只有一个线程执行该方法,但是多个线程同时分别执行remove、add、get操作的时候就无法控制了。
1 | 错误[2]:使用foreach\iterator遍历Vector的时候进行增删操作 |
解决办法:在使用iteratir进行增删操作的时候,加上Lock或者synchronized同步措施或者并发容器
使用举例:
1 | //定义 |
源码分析:
保证安全性:使用了synchronized修饰 不允许空值(在代码中特殊做了判断)
HashMap和HashTable都使用哈希表来存储键值对。在数据结构上是基本相同的,都创建了一个继承自Map.Entry的私有的内部类Entry,每一个Entry对象表示存储在哈希表中的一个键值对。
Entry对象唯一表示一个键值对,有四个属性:
-K key 键对象
-V value 值对象
-int hash 键对象的hash值
-Entry entry 指向链表中下一个Entry对象,可为null,表示当前Entry对象在链表尾部
1 | public synchronized V put(K key, V value) { |
Collections类中提供了一系列的线程安全方法用于处理ArrayList等线程不安全的Collection类
1 | 使用方法: |
使一个对象能够被当前范围之外的代码所使用。
在我们的日常开发中,我们经常要发布一些对象,比如通过类的非私有方法返回对象的引用,或者通过公有静态变量发布对象。
一种错误的发布。当一个对象还没有构造完成时,就使它被其他线程所见。
1 | package cn.northpark.concurrency.publish; |
1 | package cn.northpark.concurrency.publish; |
如何安全发布对象?共有四种方法
1、在静态初始化函数中初始化一个对象引用
2、将对象的引用保存到volatile类型域或者AtomicReference对象中
3、将对象的引用保存到某个正确构造对象的final类型域中
4、将对象的引用保存到一个由锁保护的域中
下面我们用各种单例模式来演示其中的几种方法
1 | package cn.northpark.concurrency.singleton; |
分析:
在多线程环境下,当两个线程同时访问这个方法,同时制定到instance==null的判断。都判断为null,接下来同时执行new操作。这样类的构造函数被执行了两次。一旦构造函数中涉及到某些资源的处理,那么就会发生错误。所以说最简式是线程不安全的
1 | package cn.northpark.concurrency.singleton; |
分析:
1、使用synchronized修饰静态方法后,保证了方法的线程安全性,同一时间只有一个线程访问该方法
2、有缺陷:会造成性能损耗
1 | package cn.northpark.concurrency.singleton; |
(入坑)分析:
1、我们将上面的第二个例子(懒汉式(synchronized))进行了改进,由synchronized修饰方法改为先判断后,再锁定整个类,再加上双重的检测机制,保证了最大程度上的避免耗损性能。
2、这个方法是线程不安全的,可能大家会想在多线程情况下,只要有一个线程对类进行了上锁,那么无论如何其他线程也不会执行到new的操作上。接下来我们分析一下线程不安全的原因:
这里有一个知识点:
CPU指令相关 在上述代码中,执行new操作的时候,CPU一共进行了三次指令
(1)memory = allocate() 分配对象的内存空间
(2)ctorInstance() 初始化对象
(3)instance = memory
设置instance指向刚分配的内存
在程序运行过程中,CPU为提高运算速度会做出违背代码原有顺序的优化。我们称之为乱序执行优化或者说是指令重排。
那么上面知识点中的三步指令极有可能被优化为(1)(3)(2)的顺序。当我们有两个线程A与B,A线程遵从132的顺序,经过了两此instance的空值判断后,执行了new操作,并且cpu在某一瞬间刚结束指令(3),并且还没有执行指令(2)。而在此时线程B恰巧在进行第一次的instance空值判断,由于线程A执行完(3)指令,为instance分配了内存,线程B判断instance不为空,直接执行return,返回了instance,这样就出现了错误。
(出坑)解决办法:
在对象声明时使用volatile关键字修饰,阻止CPU的指令重排。
1 | package cn.northpark.concurrency.singleton; |
1 | package cn.northpark.concurrency.singleton; |
分析:
1、饿汉模式由于单例实例是在类装载的时候进行创建,因此只会被执行一次,所以它是线程安全的。
2、该方法存在缺陷:如果构造函数中有着大量的事情操作要做,那么类的装载时间会很长,影响性能。如果只是做的类的构造,却没有引用,那么会造成资源浪费
3、饿汉模式适用场景为:(1)私有构造函数在实现的时候没有太多的处理(2)这个类在实例化后肯定会被使用
1 | package cn.northpark.concurrency.singleton; |
分析:
1、除了使用静态域直接初始化单例对象,还可以用静态块初始化单例对象。
2、值得注意的一点是,静态域与静态块的顺序一定不要反,在写静态域和静态方法的时候,一定要注意顺序,不同的静态代码块是按照顺序执行的,它跟我们正常定义的静态方法和普通方法是不一样的。
1 | package cn.northpark.concurrency.singleton; |
]]>分析
由于枚举类的特殊性,枚举类的构造函数Singleton方法只会被实例化一次,且是这个类被调用之前。这个是JVM保证的。
对比懒汉与饿汉模式,它的优势很明显。
它其实就是把对象封装到一个线程里,只有一个线程能看到这个对象,那么这个对象就算不是线程安全的,也不会出现任何线程安全方面的问题。
线程封闭技术有一个常见的应用:
数据库连接对应jdbc的Connection对象,Connection对象在实现的时候并没有对线程安全做太多的处理,jdbc的规范里也没有要求Connection对象必须是线程安全的。
实际在服务器应用程序中,线程从连接池获取了一个Connection对象,使用完再把Connection对象返回给连接池,由于大多数请求都是由单线程采用同步的方式来处理的,并且在Connection对象返回之前,连接池不会将它分配给其他线程。因此这种连接管理模式处理请求时隐含的将Connection对象封闭在线程里面,这样我们使用的connection对象虽然本身不是线程安全的,但是它通过线程封闭也做到了线程安全。
Ad-hoc线程封闭是指,维护线程封闭性的职责完全由程序实现来承担。Ad-hoc线程封闭是非常脆弱的,因为没有任何一种语言特性,例如可见性修饰符或局部变量,能将对象封闭到目标线程上。事实上,对线程封闭对象(例如,GUI应用程序中的可视化组件或数据模型等)的引用通常保存在公有变量中。
堆栈封闭其实就是方法中定义局部变量。不存在并发问题。多个线程访问一个方法的时候,方法中的局部变量都会被拷贝一份到线程的栈中(Java内存模型),所以局部变量是不会被多个线程所共享的。
它是一个特别好的封闭方法,其实ThreadLocal内部维护了一个map,map的key是每个线程的名称,而map的value就是我们要封闭的对象。ThreadLocal提供了get、set、remove方法,每个操作都是基于当前线程的,所以它是线程安全的。
1 | //ThreadLocal的get方法源码 |
说明:
1、这里不描述springboot框架的搭建过程,假定你已经有了一个可以正常运行的springboot简单项目。
2、我们这里的例子使用的是springboot框架中的filter与Interceptor来使用threadLocal,对于Springboot的filter与Interceptor不做过多的讲解。
coding:
1 | package cn.northpark.concurrency.threadLocal; |
1 | package cn.northpark.concurrency; |
1 | package cn.northpark.concurrency.controller; |
1 | package cn.northpark.concurrency; |
要继承^[WebMvcConfigurerAdapter 已废弃]WebMvcConfigurerAdapter 【WebMvcConfigurationSupport】
类。(我这里的启动类名为:ConcurrencyApplication)
1 | package cn.northpark.concurrency; |
(6)运行程序,访问 http://localhost:8080/threadLocal/test 结果如下:
页面中打印出我们当前的线程ID: 40
查看控制台:
2018-12-27 18:29:30.517 INFO 8648 — [nio-8080-exec-2]
cn.northpark.concurrency.HttpFilter : do filter, 23,
/threadLocal/test 2018-12-27 18:29:30.525 INFO 8648 —
[nio-8080-exec-2] c.northpark.concurrency.HttpInterceptor :
HttpInterceptor–>preHandle 2018-12-27 18:29:30.582 INFO 8648 —
[nio-8080-exec-2] c.northpark.concurrency.HttpInterceptor :
HttpInterceptor–>afterCompletion
从控制台的打印日志我们可以看出,首先filter过滤器先获取到我们当前的线程ID为40、我们当前的请求路径为/threadLocal/test ,紧接着进入了我们的Interceptor的preHandle方法中,打印了preHandle字样。最后进入了我们的Interceptor的afterCompletion方法,删除了我们之前存入的值,并打印了afterCompletion字样。
]]>有一种对象只要它发布了就是安全的,它就是不可变对象。一个不可变对象需要满足的条件:
对象创建一个其状态不能修改
对象所有域都是final类型
对象是正确创建的(在对象创建期间,this引用没有逸出)
(1)自己定义
这里可以采用的方式包括:
1、将类声明为final,这样它就不能被继承。
2、将所有的成员声明为私有的,这样就不允许直接访问这些成员。
3、对变量不提供set方法,将所有可变的成员声明为final,这样就只能赋值一次。通过构造器初始化所有成员进行深度拷贝。
4、在get方法中不直接返回对象的本身,而是克隆对象,返回对象的拷贝。
(2)使用Java中提供的Collection类中的各种unmodifiable开头的方法
(3)使用Guava中的Immutable开头的类
final关键字可以修饰类、修饰方法、修饰变量
修饰类:类不能被集成。
基础类型的包装类都是final类型的类。final类中的成员变量可以根据需要设置为final,但是要注意的是,final类中的所有成员方法都会被隐式的指定为final方法
修饰方法:
(1)把方法锁定,以防任何继承类修改它的含义
(2)效率:在早期的java实现版本中,会将final方法转为内嵌调用。但是如果方法过于庞大,可能看不见效果。一个private方法会被隐式的指定为final方法
修饰变量:
基本数据类型变量,在初始化之后,它的值就不能被修改了。如果是引用类型变量,在它初始化之后便不能再指向另外的对象。
1 | package cn.northpark.concurrency.immutable; |
(1)对一个被final修饰的变量(Integer a、String b)被赋值时在编译过程中就出现了错误。
(2)(map)在重新被指向一个新的map对象的时候也出现了错误。
那么对被定义为final的map进行赋值呢?我们单独运行map.put(1,3)语句,结果是可以的。被final修饰的引用类型变量,虽然不能重新指向,但是可以修改,这一点尤为要注意。
18:05:19.163 [main] INFO cn.northpark.concurrency.immutable.ImmutableExample1 - 3
(3)当final修饰方法的参数时:同样也是不允许在方法内部对其修改的。
1 | private void test(final int a) { |
使用Java的Collection类的unmodifiable相关方法,可以创建不可变对象。unmodifiable相关方法包含:Collection、List、Map、Set….
1 | package cn.northpark.concurrency.immutable; |
上面程序的执行结果为:在map.put(1,3)操作的位置抛出了异常。由此可见map对象已经成为不可变对象。
Exception in thread “main” java.lang.UnsupportedOperationException
at java.util.Collections$UnmodifiableMap.put(Unknown Source)
at cn.northpark.concurrency.immutable.ImmutableExample2.main(ImmutableExample2.java:25)
那么unmodifiable相关类的实现原理是什么呢?我们查看一下Collections.unmodifiableMap的源码:(以下源码只筛选出表达观点的部分,非全部)
1 | public static <K,V> Map<K,V> unmodifiableMap(Map<? extends K, ? extends V> m) { |
Collections.unmodifiableMap在执行时,将参数中的map对象进行了转换,转换为Collection类中的内部类 UnmodifiableMap对象。而 UnmodifiableMap对map的更新方法(比如put、remove等)进行了重写,均返回UnsupportedOperationException异常,这样就做到了map对象的不可变。
使用Guava的Immutable相关类也可以创建不可变对象。同样包含很多类型:Collection、List、Map、Set….
例如:
1 | private final static ImmutableList<Integer> list = ImmutableList.of(1, 2, 3); |
对于ImmutableList.of方法,如果传多个参数,需要这样一直写下去,以逗号分隔每个参数。其源码中是这样实现的:
1 | //单个或少于12个参数时 |
运行结果仍然为抛出UnsupportedOperationException异常。
分析源码:Immutable相关类使用了跟Java的unmodifiable相关类相似的实现方法。
1 | /** @deprecated */ |
ImmutableSet除了使用of的方法进行初始化,还可以使用copyof方法,将Collection类型、Iterator类型作为参数。
1 | private final static ImmutableSet set = ImmutableSet.copyOf(list); |
ImmutableMap有特殊的builder写法:
1 | private final static ImmutableMap<Integer, Integer> map = ImmutableMap.of(1, 2, 3, 4); |
当多个线程访问某个类时,不管运行时环境采用何种调度方式或者这些进程将如何交替执行,并且在主调代码中不需要任何额外的同步或协同,这个类都能表现出正确的行为,那么就称这个类是线程安全的。线程安全性?
主要体现在三个方面:原子性、可见性、有序性
基础代码:以下代码用于描述下方的知识点,所有代码均在此代码基础上进行修改。
1 | package cn.northpark.concurrency.atomic; |
说到原子性,一共有两个方面需要学习一下,一个是JDK中已经提供好的Atomic包,他们均使用了CAS完成线程的原子性操作,另一个是使用锁的机制来处理线程之间的原子性。锁包括:synchronized、Lock
我们从最简单的AtomicInteger类来了解什么是CAS
上边的示例代码就是通过AtomicInteger类保证了线程的原子性。
那么它是如何保证原子性的呢?我们接下来分析一下它的源码。示例中,对count变量的+1操作,采用的是incrementAndGet方法,此方法的源码中调用了一个名为==unsafe.getAndAddInt==的方法
1 | public final int incrementAndGet() { |
而getAndAddInt方法的具体实现为:
1 | public final int getAndAddInt(Object var1, long var2, int var4) { |
在此方法中,方法参数为要操作的对象Object
var1、期望底层当前的数值为var2、要修改的数值var4。定义的var5为真正从底层取出来的值。采用do..while循环的方式去获取底层数值并与期望值进行比较,比较成功才将值进行修改。而这个比较再进行修改的方法就是compareAndSwapInt就是我们所说的CAS,它是一系列的接口,比如下面罗列的几个接口。使用native修饰,是底层的方法。CAS取的是==compareAndSwap==三个单词的首字母.
另外,示例代码中的count可以理解为JMM中的==工作内存==,而这里的底层数值即为==主内存==,如果看过我上一篇文章的盆友就能把这一块的知识点串联起来了。
1 | public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5); |
LongAdder是java8为我们提供的新的类,跟AtomicLong有相同的效果。首先看一下代码实现:
AtomicLong:
1 | //变量声明 |
LongAdder:
1 | //变量声明 |
那么问题来了,为什么有了AtomicLong还要新增一个LongAdder呢?
原因是:CAS底层实现是在一个死循环中不断地尝试修改目标值,直到修改成功。如果竞争不激烈的时候,修改成功率很高,否则失败率很高。在失败的时候,这些重复的原子性操作会耗费性能。
知识点: 对于普通类型的long、double变量,JVM允许将64位的读操作或写操作拆成两个32位的操作。
1 | LongAdder类的实现核心是将热点数据分离,比如说它可以将AtomicLong内部的内部核心数据value分离成一个数组,每个线程访问时,通过hash等算法映射到其中一个数字进行计数,而最终的计数结果则为这个数组的求和累加,其中热点数据value会被分离成多个单元的cell,每个cell独自维护内部的值。当前对象的实际值由所有的cell累计合成,这样热点就进行了有效地分离,并提高了并行度。这相当于将AtomicLong的单点的更新压力分担到各个节点上。在低并发的时候通过对base的直接更新,可以保障和AtomicLong的性能基本一致。而在高并发的时候通过分散提高了性能。 |
源码:
1 | public void increment() { |
缺点:如果在统计的时候,如果有并发更新,可能会有统计数据有误差。实际使用中在处理高并发计数的时候优先使用==LongAdder[并发]==,而不是AtomicLong在线程竞争很低的时候,使用AtomicLong会简单效率更高一些。比如==序列号生成(准确性)==
这个类中值得一提的是它包含了一个名为==compareAndSet==的方法,这个方法可以做到的是控制一个boolean变量在一件事情执行之前为false,事情执行之后变为true。或者也可以理解为可以控制某一件事只让一个线程执行,并仅能执行一次。
他的源码如下:
1 | public final boolean compareAndSet(boolean expect, boolean update) { |
举例说明:
1 | package cn.northpark.concurrency.atomic; |
结果:(log只打印一次)
1 | 11:27:32.761 [pool-1-thread-1] INFO cn.northpark.concurrency.atomic.TestAtomicBoolean - execute |
这个类的核心作用是要更新一个指定的类的某一个字段的值。并且这个字段一定要用==volatile修饰==同时还==不能是static==的。
举例说明:
1 | package cn.northpark.concurrency.atomic; |
此方法输出的结果为:
11:57:31.507 [main] INFO
cn.northpark.concurrency.atomic.TestAtomicReferenceFieldUpdater -
update success ,120 11:57:31.511 [main] INFO
cn.northpark.concurrency.atomic.TestAtomicReferenceFieldUpdater -
update fail
由此可见,count的值只修改了一次。
什么是ABA问题?
CAS操作的时候,其他线程将变量的值A改成了B,但是随后又改成了A,本线程在CAS方法中使用期望值A与当前变量进行比较的时候,发现变量的值未发生改变,于是CAS就将变量的值进行了交换操作。但是实际上变量的值已经被其他的变量改变过,这与设计思想是不符合的。所以就有了AtomicStampReference。
源码:
1 | private static class Pair<T> { |
AtomicStampReference的处理思想是,每次变量更新的时候,将变量的版本号+1,之前的ABA问题中,变量经过两次操作以后,变量的版本号就会由1变成3,也就是说只要线程对变量进行过操作,变量的版本号就会发生更改。从而解决了ABA问题。
解释一下上边的源码:
类中维护了一个volatile修饰的Pair类型变量current,Pair是一个私有的静态类,current可以理解为底层数值。
compareAndSet方法的参数部分分别为期望的引用、新的引用、期望的版本号、新的版本号。
return的逻辑为判断了期望的引用和版本号是否与底层的引用和版本号相符,并且排除了新的引用和新的版本号与底层的值相同的情况(即不需要修改)的情况(return代码部分3、4行)。条件成立,执行casPair方法,调用CAS操作。
这个类实际上维护了一个Array数组,我们在对数值进行更新的时候,会多一个索引值让我们更新。
原子性,提供了互斥访问,同一时刻只能有一个线程来对它进行操作。那么在java里,保证同一时刻只有一个线程对它进行操作的,除了==Atomic包==之外,还有==锁的机制==。JDK提供锁主要分为两种:==synchronized==和==Lock==。接下来我们了解一下synchronized。
依赖于JVM去实现锁,因此在这个关键字作用对象的作用范围内,都是同一时刻只能有一个线程对其进行操作的。
synchronized是java中的一个关键字,是一种同步锁。它可以修饰的对象主要有四种:
———————————————————————–
被修饰的代码称为同步语句块,作用的范围是大括号括起来的部分。作用的对象是调用这段代码的对象。
验证:
1 | public class SynchronizedExample { |
//使用线程池方法进行测试:
1 | public static void main(String[] args) { |
结果:不同对象之间的操作互不影响
被修饰的方法称为同步方法,作用的范围是大括号括起来的部分,作用的对象是调用这段代码的对象。
验证:
public class SynchronizedExample
public synchronized void test(int j){
for (int i = 0; i < 10; i++) {
log.info(“test - {} - {}”,j,i);
}
}
//验证方法与上面相同
…
}
结果:不同对象之间的操作互不影响
==TIPS:
如果当前类是一个父类,子类调用父类的被synchronized修饰的方法,不会携带synchronized属性,因为synchronized不属于方法声明的一部分synchronized 修饰一个静态方法==
作用的范围是synchronized 大括号括起来的部分,作用的对象是这个类的所有对象。
验证:
1 | public class SynchronizedExample{ |
结果:同一时间只有一个线程可以执行
验证:
1 | public class SynchronizedExample{ |
结果:同一时间只有一个线程可以执行
]]>synchronized:不可中断锁,==适合竞争不激烈==,可读性好
Lock:可中断锁,多样化同步,==竞争激烈时能维持常态==
Atomic:==竞争激烈时能维持常态==,==比Lock性能好==,==每次只能同步一个值==
Java并发编程入门,适合没有并发编程经验的同学,本章首先从课程重点、特点、适合人群及学习收获几个方面对课程进行整体的介绍,然后会从一个实际的计数场景实现开始,给大家展示多线程并发时的线程不安全问题,让大家能够初体验到并发编程,之后会讲解并发和高并发的概念,并通过对比让大家明白到底什么是并发和…
Apache Bench(AB) :Apache附带的工具,测试网站性能
Jmeter : Apache组织开发的压力测试工具(比AB更强大)
代码测试方法 :Semaphore、CountDownLatch类
信号量,在我们测试的过程中充当监控并发数的角色。能够维持在同一时间的请求的并发量,达到并发量上线,会阻塞进程。
说明:假设计数器的值为3,线程A执行了await()方法之后,进入了awaiting等待状态。在其他线程的方法中执行了countDown()方法之后,计数器的值都会减一,直到计数器的值减为0,线程A的方法才继续执行。所以说,countDownLatch类可以阻塞线程执行,并且当满足指定条件后让线程继续执行。
1 | package cn.northpark.concurrency.annotaion; |
1 | package cn.northpark.concurrency.controller; |
并发:同时拥有两个或者多个线程,如果程序在单核处理器上运行,多个线程将交替地还如或者换出内存,这些线程是同时”存在”的,每隔线程都处于执行过程中的某个状态,如果运行在多核处理器上,此时,程序中的每个线程都将分配到一个处理器核上,因此可以同时运行。
高并发:High Concurrency
是互联网分布式系统架构设计中必须考虑的因素之一,它通常是指,通过设计保证系统能够==同时并行处理==很多请求
。
CPU的频率太快了,快到主存跟不上,这样在处理器时钟周期内,CPU常常需要等待主存,浪费资源,所以cache的出现,是为了缓解CPU和内存之间速度的不匹配问题。
CPU多级缓存配置(演变):
局部性原理:
(1) 时间局部性:如果某个数据被访问,那么在不久的将来它很可能被再次访问。
(2) 空间局部性:如果某个数据被访问,那么与他相邻的数据很快也可能被访问。
Modify:被修改,该缓存行只被缓存在该CPU的缓存中。并且是被修改过的,因此它与主存中的数据是不一致的,该缓存行中的内存需要在未来的某个时间点写回主存,这个时间点是允许其他CPU读取主存中相应的内存之前。当这里的值被写回主存之后,该缓存行的状态将变为Excluisive.
Exclusive:独享,该缓存行只被缓存在该CPU的缓存中,他是未被修改过的,是与主存中的数据一致的。他可以在任何时刻,被其他CPU读取该内存时,变成Share。当该CPU修改他的内容时,变成Modify
Share:共享,意味着该缓存行可能被多个CPU进行缓存,并且各缓存中的数据与主存数据是一致的。当有一个CPU修改该缓存行的时候,其他CPU中该缓存行变成Invalid
Invalid:无效
四种操作
本地读取 local read :读本地缓存
本地写入 local write : 写本地缓存
远端读取 remote rade : 将Memory中的数据读取过来
远端写入 remote write : 将数据写回Memory中
缓存被修改时的情况:
某一时刻缓存被CPU A 与CPU B共享,这时CPU A 要修改本地缓存的时候,会将主存的数据与CPU B在共享的数据置为无效状态。缓存由S -> I
处理器为提高运算速度而做出违背代码原有顺序的优化。
举例:初始计算需求如下
预期计算流程:
实际计算流程(乱序执行优化后):
一种规范,规范了java虚拟机与计算机内存如何协同工作的。它规定了一个线程如何和何时可以看到其他线程修改过的共享变量的值,以及在必须时如何同步地访问共享变量。
堆Heap:运行时数据区,有垃圾回收,堆的优势可以动态分配内存大小,生存期也不必事先告诉编译器,因为他是在运行时动态分配内存。缺点是由于运行时动态分配内存,所以存取速度慢一些。
栈Stack:优势存取速度快,速度仅次于计算机的寄存器。栈的数据是可以共享的,但是缺点是存在栈中数据的大小与生存期必须是确定的。主要存放基本类型变量,对象据点。要求调用栈和本地变量存放在线程栈上。
静态类型变量跟随类的定义存放在堆上。存放在堆上的对象可以被所持有对这个对象引用的线程访问。
如果两个线程同时调用了同一个对象的同一个方法,他们都会访问这个对象的成员变量。但是这两个线程都拥有的是该对象的成员变量(局部变量)的私有拷贝。—[线程封闭中的堆栈封闭]
CPU
Registers(寄存器):是CPU内存的基础,CPU在寄存器上执行操作的速度远大于在主存上执行的速度。这是因为CPU访问寄存器速度远大于主存。
CPU Cache
Memory(高速缓存):由于计算机的存储设备与处理器的运算速度之间有着几个数量级的差距,所以现代计算机系统都不得不加入一层读写速度尽可能接近处理器运算速度的高级缓存,来作为内存与处理器之间的缓冲。将运算时所使用到的数据复制到缓存中,让运算能快速的进行。当运算结束后,再从缓存同步回内存之中,这样处理器就无需等待缓慢的内存读写了。
RAM-Main Memory(主存/内存):
当一个CPU需要读取主存的时候,他会将主存中的部分读取到CPU缓存中,甚至他可能将缓存中的部分内容读到他的内部寄存器里面,然后在寄存器中执行操作。当CPU需要将结果回写到主存的时候,他会将内部寄存器中的值刷新到缓存中,然后在某个时间点从缓存中刷回主存。
Java内存模型抽象结构:每个线程都有一个私有的本地内存,本地内存他是java内存模型的一个抽象的概念。它并不是真实存在的,它涵盖了缓存、写缓冲区、寄存器以及其他的硬件和编译器的优化。本地内存中它存储了该线程以读或写共享变量拷贝的一个副本。
从更低的层次来说,主内存就是硬件的内存,是为了获取更高的运行速度,虚拟机及硬件系统可能会让工作内存优先存储于寄存器和高速缓存中,java内存模型中的线程的工作内存是CPU的寄存器和高速缓存的一个抽象的描述。而JVM的静态内存存储模型它只是对内存的一种物理划分而已。它只局限在内存,而且只局限在JVM的内存。
如果要把一个变量从主内存中复制到工作内存,就需要按顺序的执行read和load操作,如果把变量从工作内存中同步回主内存中,就要按顺序的执行store和write操作。但java内存模型只要求上述操作必须按顺序执行,而没有保证必须是连续执行
不允许read和load、store和write操作之一单独出现
不允许一个线程丢弃它的最近assign的操作,即变量在工作内存中改变了之后必须同步到主内存中
不允许一个线程无原因的(没有发生过任何assign操作)把数据从工作内存同步回主内存中
一个新的变量只能在主内存中诞生,不允许在工作内存中直接使用一个未被初始化(load或assign)的变量。即就是对一个变量实施use和store操作之前,必须先执行过了assign和load操作。
一个变量早同一时刻只允许一条线程对其进行lock操作,但lock操作可以被同一条线程重复执行多次,多次执行lock后,只有执行相同次数的unlock操作,变量才会被解锁。lock和unlock必须是成对出现。
如果对一个变量执行lock操作,将会清空工作内存中此变量的值,在执行引擎使用这个变量前需要重新执行load或assign操作初始化变量的值。
如果一个变量事先没有被lock锁定,则不允许对它执行unlock操作,也不允许unlock一个被其他线程锁定的变量
对一个变量执行unlock操作之前,必须先把此变量同步到主内存中(执行store和write操作)
风险:
安全性:多个线程共享数据时可能会产生于期望不相符的结果
活跃性:某个操作无法继续进行下去时,就会发生活跃性问题。比如死锁、饥饿问题
性能:线程过多时会使得CPU频繁切换,调度时间增多;同步机制;消耗过多内存。
优势:
速度:同时处理多个请求,响应更快;复杂的操作可以分成多个进程同时进行。
设计:程序设计在某些情况下更简单,也可以有更多选择
资源利用:CPU能够在等待IO的时候做一些其他的事情
guice servlet提供了几个比较有用的web scope,类似与传统servlet
的session,request这些提供的范围等。
guice servlet 提供的web scope 如下:
例子如下:
1 | package cn.northpark.action; |
1 | package cn.northpark.action; |
例子如下:
1 | package cn.northpark.action; |
1 | package cn.northpark.action; |
例子如下:
1 |
|
]]>guice servlet 还是比较好用,如果你选择用servlet开发的时候建议用它了。
Guice Servlet 为使用web应用程序和Servlet容器提供了一个完整的模式。. Guice’s servlet
扩展允许从你的servlet应用中完全淘汰web.xml,并且具有类型安全(type-safe)的优势。
符合Java方式的配置你的servlet和filter组件。
这不仅在于可以使用更好的API来配置你的web应用程序,而且也在于在web应用组件中加入依赖注入,意味着你的servlet和filter得益于以下几个方面:
==guice servlet简化了传统servlet的开发。==
具体如下:
1 | <filter> |
1 | package cn.northpark.listener; |
1 | import com.google.inject.AbstractModule; |
1 | package cn.northpark.action; |
guice在moudle中提供了良好的绑定方法。
它提供了普通的绑定,自定义注解绑定,按名称绑定等。
下面直接看代码:
1 | import com.google.inject.AbstractModule; |
1 | public class Dog { |
1 | import java.lang.annotation.ElementType; |
1 |
|
然后是一个接口,两个实现:
1 | public interface Dao { |
1 | public class DaoImpl2 implements Dao{ |
1 | import com.google.inject.Inject; |
测试代码:
1 | import com.google.inject.Guice; |
在 Guice 中 Providers 就像 Factories 一样创建和返回对象。在大部分情况下,客户端可以直接依赖 Guice
框架来为服务(Services)创建依赖的对象。但是少数情况下,应用程序代码需要为一个特定的类型定制对象创建流程(Object
creation process),这样可以控制对象创建的数量,提供缓存(Cache)机制等,这样的话我们就要依赖 Guice 的
Provider 类。
1 | import com.google.inject.ProvidedBy; |
1 | import com.google.inject.Provider; |
1 | public class MockConnection implements ConnectionI{ |
1 | import com.google.inject.AbstractModule; |
1 | import com.google.inject.Guice; |
Summary一下:
]]>普通绑定用得最多,
name那个方法绑定用于多个接口实现,
自定注解那种按特殊情况使用。
说明一下,name那个注解绑定,用于绑定属性常量也很方便。
guice是使用module进行绑定的,它提供了两种方式进行操作.
1 | import com.google.inject.AbstractModule; |
1 | import com.google.inject.Binder; |
例子代码如下:
1 | public class Dog { |
1 | public class DarkDog extends Dog{ |
1 | import com.google.inject.AbstractModule; |
1 | import com.google.inject.Binder; |
测试:
1 | import com.google.inject.Guice; |
==我们项目用得最多的是AbstractModule,当然如果有特殊需要,你也可以扩张Module了。==
]]>guice提供了强大的注入方式。
1 | import com.google.inject.Inject; |
1 | import com.google.inject.Inject; |
1 | import com.google.inject.Inject; |
==当然,在我们的module中需要绑定这个TestInjection这个类才可以用哦.==
1 | import com.google.inject.AbstractModule; |
测试代码如下:
1 | import com.google.inject.Guice; |
结果就是你想要的…
平时用得最多,估计属性注入最方便了。但是也有特殊情况,一切看项目需要来做了。。。
]]>guice是google一个轻量级的DI注入框架,现在比较强大了,也与目前流行的struts2、jpa等都有集成了。
先看一个例子:
1 | public interface Dao { |
1 | import com.google.inject.Singleton; |
1 | import com.google.inject.AbstractModule; |
1 | import com.google.inject.Guice; |
使用javaMail收邮件主要有两种协议,一种是pop3,一种是imap。这两种协议都可以用来收邮件,但是在其中的处理上是有区别的。pop3是不支持判断邮件是否为已读的,也就是说你不能直接从收件箱里面取到未读邮件,这需要自己进行判断,然而imap就提供了这样的功能,使用imap时可以很轻松的判断该邮件是否为已读或未读或其他。
由此看来,pop3不适合我的需求【通过每次检索全部,然后判断subject也可以实现,不过每次都需要遍历对比,比较麻烦】
那么我就用imap协议来收取了,pop3和imap只是配置email的url有点区别 其他地方是一样的
pop3和imap主要区别就是能否判断邮件状态的问题,其他的操作都差不多.
去你的126|163|qq邮箱 打开设置-pop3 如图
注意重点:
1 | package cn.northpark.utils; |
在讲解CSS布局之前,我们需要提前知道一些知识,在CSS中,html中的标签元素大体被分为三种不同的类型:块状元素、内联元素(又叫行内元素)和内联块状元素。
常用的块状元素有:
1 | <div>、<p>、<h1>...<h6>、<ol>、<ul>、<dl>、<table>、<address>、<blockquote> 、<form> |
常用的内联元素有:
1 | <a>、<span>、<br>、<i>、<em>、<strong>、<label>、<q>、<var>、<cite>、<code> |
常用的内联块状元素有:
1 | <img>、<input> |
1 | 什么是块级元素?在html中<div>、 <p>、<h1>、<form>、<ul> 和 <li>就是块级元素。设置display:block就是将元素显示为块级元素。如下代码就是将内联元素a转换为块状元素,从而使a元素具有块状元素特点。 |
1 | 在html中,<span>、<a>、<label>、 <strong> 和<em>就是典型的内联元素(行内元素)(inline)元素。当然块状元素也可以通过代码display:inline将元素设置为内联元素。如下代码就是将块状元素div转换为内联元素,从而使 div 元素具有内联元素特点。 |
内联元素特点:
1、和其他元素都在一行上;
2、元素的高度、宽度及顶部和底部边距不可设置;
3、元素的宽度就是它包含的文字或图片的宽度,不可改变。
1 | 内联块状元素(inline-block)就是同时具备内联元素、块状元素的特点,代码display:inline-block就是将元素设置为内联块状元素。(css2.1新增),<img>、<input>标签就是这种内联块状标签。 |
inline-block 元素特点:
1、和其他元素都在一行上;
2、元素的高度、宽度、行高以及顶和底边距都可设置。
盒子模型的边框就是围绕着内容及补白的线,这条线你可以设置它的粗细、样式和颜色(边框三个属性)。
如下面代码为 div 来设置边框粗细为 2px、样式为实心的、颜色为红色的边框:
1 | div{ |
注意:
1、border-style(边框样式)常见样式有: dashed(虚线)| dotted(点线)| solid(实线)。
2、border-color(边框颜色)中的颜色可设置为十六进制颜色,如: border-color:#888;//前面的井号不要忘掉。
3、border-width(边框宽度)中的宽度也可以设置为: thin | medium |
thick(但不是很常用),最常还是用象素(px)。
现在有一个问题,如果有想为 p 标签单独设置下边框,而其它三边都不设置边框样式怎么办呢?css 样式中允许只为一个方向的边框设置样式:
1 | div{border-bottom:1px solid red;} |
同样可以使用下面代码实现其它三边(上、右、左)边框的设置:
1 | border-top:1px solid red; |
盒模型宽度和高度和我们平常所说的物体的宽度和高度理解是不一样的,css内定义的宽(width)和高(height),指的是填充以里的内容范围。
因此一个元素实际宽度(盒子的宽度)=左边界+左边框+左填充+内容宽度+右填充+右边框+右边界。
元素内容与边框之间是可以设置距离的,称之为“填充”。填充也可分为上、右、下、左(顺时针)。如下代码:
1 | div{padding:20px 10px 15px 30px;} |
顺序一定不要搞混。可以分开写上面代码:
1 | div{ |
如果上、右、下、左的填充都为10px;可以这么写
1 | div{padding:10px;} |
如果上下填充一样为10px,左右一样为20px,可以这么写:
1 | div{padding:10px 20px;} |
总结一下:padding和margin的区别,padding在边框里,margin在边框外。
清楚了CSS 盒模型的基本概念、 盒模型类型, 我们就可以深入探讨网页布局的基本模型了。布局模型与盒模型一样都是 CSS 最基本、 最核心的概念。 但布局模型是建立在盒模型基础之上,又不同于我们常说的 CSS 布局样式或 CSS 布局模板。如果说布局模型是本,那么 CSS 布局模板就是末了,是外在的表现形式。
CSS包含3种基本的布局模型,用英文概括为:Flow、Layer 和 Float。
在网页中,元素有三种布局模型:
1、流动模型(Flow)
2、浮动模型 (Float)
3、层模型(Layer)
先来说一说流动模型,流动(Flow)是默认的网页布局模式。也就是说网页在默认状态下的 HTML 网页元素都是根据流动模型来分布网页内容的。
流动布局模型具有2个比较典型的特征:
第一点,块状元素都会在所处的包含元素内自上而下按顺序垂直延伸分布,因为在默认状态下,块状元素的宽度都为100%。实际上,块状元素都会以行的形式占据位置。如右侧代码编辑器中三个块状元素标签(div,h1,p)宽度显示为100%。
第二点,在流动模型下,内联元素都会在所处的包含元素内从左到右水平分布显示。(内联元素可不像块状元素这么霸道独占一行)
右侧代码编辑器中内联元素标签a、span、em、strong都是内联元素。
块状元素这么霸道都是独占一行,如果现在我们想让两个块状元素并排显示,怎么办呢?不要着急,设置元素浮动就可以实现这一愿望。
任何元素在默认情况下是不能浮动的,但可以用 CSS 定义为浮动,如 div、p、table、img 等元素都可以被定义为浮动。如下代码可以实现两个 div 元素一行显示。
1 | div{ |
什么是层布局模型?层布局模型就像是图像软件PhotoShop中非常流行的图层编辑功能一样,每个图层能够精确定位操作,但在网页设计领域,由于网页大小的活动性,层布局没能受到热捧。但是在网页上局部使用层布局还是有其方便之处的。下面我们来学习一下html中的层布局。
如何让html元素在网页中精确定位,就像图像软件PhotoShop中的图层一样可以对每个图层能够精确定位操作。CSS定义了一组定位(positioning)属性来支持层布局模型。
==层模型有三种形式:
1、绝对定位(position: absolute)
2、相对定位(position: relative)
3、固定定位(position: fixed)==
如果想为元素设置层模型中的绝对定位,需要设置position:absolute(表示绝对定位),这条语句的作用将元素从文档流中拖出来,然后使用left、right、top、bottom属性相对于其最接近的一个具有定位属性的父包含块进行绝对定位。如果不存在这样的包含块,则相对于body元素,即相对于浏览器窗口。
如下面代码可以实现div元素相对于浏览器窗口向右移动100px,向下移动50px。
1 | div{ |
效果如下:
fixed:表示固定定位,与absolute定位类型类似,但它的相对移动的坐标是视图(屏幕内的网页窗口)本身。由于视图本身是固定的,它不会随浏览器窗口的滚动条滚动而变化,除非你在屏幕中移动浏览器窗口的屏幕位置,或改变浏览器窗口的显示大小,因此固定定位的元素会始终位于浏览器窗口内视图的某个位置,不会受文档流动影响,这与background-attachment:fixed;
属性功能相同。以下代码可以实现相对于浏览器视图向右移动100px,向下移动50px。并且拖动滚动条时位置固定不变。
1 |
|
小伙伴们学习了12-6小节的绝对定位的方法:使用position:absolute可以实现被设置元素相对于浏览器(body)设置定位以后,大家有没有想过可不可以相对于其它元素进行定位呢?答案是肯定的,当然可以。使用position:relative来帮忙,但是必须遵守下面规范:
1、参照定位的元素必须是相对定位元素的前辈元素:
1 | <div id="box1"><!--参照定位的元素--> |
从上面代码可以看出box1是box2的父元素(父元素当然也是前辈元素了)。
2、参照定位的元素必须加入position:relative;
1 | #box1{ |
3、定位元素加入position:absolute,便可以使用top、bottom、left、right来进行偏移定位了。
1 | #box2{ |
这样box2就可以相对于父元素box1定位了(这里注意参照物就可以不是浏览器了,而可以自由设置了)。
关于颜色的css样式也是可以缩写的,当你设置的颜色是16进制的色彩值时,如果每两位的值相同,可以缩写一半。
1 | 例子1: |
网页中的字体css样式代码也有他自己的缩写方式,下面是给网页设置字体的代码:
1 | body{ |
这么多行的代码其实可以缩写为一句:
1 | body{ |
注意:
1、使用这一简写方式你至少要指定 font-size 和 font-family 属性,其他的属性(如 font-weight、font-style、font-variant、line-height)如未指定将自动使用默认值。
2、在缩写时 font-size 与 line-height 中间要加入“/”斜扛。
一般情况下因为对于中文网站,英文还是比较少的,所以下面缩写代码比较常用:
1 | body{ |
只是有字号、行间距、中文字体、英文字体设置。
在网页中的颜色设置是非常重要,有字体颜色(color)、背景颜色(background-color)、边框颜色(border)等,设置颜色的方法也有很多种:
前面几个小节中经常用到的就是这种设置方法:
p{color:red;}
这个与 photoshop 中的 RGB 颜色是一致的,由 R(red)、G(green)、B(blue) 三种颜色的比例来配色。
p{color:rgb(133,45,200);}
每一项的值可以是 0~255 之间的整数,也可以是 0%~100% 的百分数。如:
p{color:rgb(20%,33%,25%);}
这种颜色设置方法是现在比较普遍使用的方法,其原理其实也是 RGB 设置,但是其每一项的值由 0-255 变成了十六进制 00-ff。
p{color:#00ffff;}
配色表:
长度单位总结一下,目前比较常用到px(像素)、em、% 百分比,要注意其实这三种单位都是相对单位。
像素为什么是相对单位呢?因为像素指的是显示器上的小点(CSS规范中假设“90像素=1英寸”)。实际情况是浏览器会使用显示器的实际像素值有关,在目前大多数的设计者都倾向于使用像素(px)作为单位。
就是本元素给定字体的 font-size 值,如果元素的 font-size 为 14px ,那么 1em = 14px;如果 font-size 为 18px,那么 1em = 18px。如下代码:
1 | p{font-size:12px;text-indent:2em;} |
上面代码就是可以实现段落首行缩进 24px(也就是两个字体大小的距离)。
下面注意一个特殊情况:
但当给 font-size 设置单位为 em 时,此时计算的标准以 p 的父元素的 font-size 为基础。如下代码:
1 | html: |
结果 span 中的字体“例子”字体大小就为 11.2px(14 * 0.8 = 11.2px)。
1 | p{font-size:12px;line-height:130%} |
设置行高(行间距)为字体的130%(12 * 1.3 = 15.6px)。
我们在实际工作中常会遇到需要设置水平居中的场景,比如为了美观,文章的标题一般都是水平居中显示的。
这里我们又得分两种情况:行内元素 还是 块状元素 ,块状元素里面又分为定宽块状元素,以及不定宽块状元素。今天我们先来了解一下行内元素怎么进行水平居中?
如果被设置元素为文本、图片等行内元素时,水平居中是通过给父元素设置 text-align:center 来实现的。(父元素和子元素:如下面的html代码中,div是“我想要在父容器中水平居中显示”这个文本的父元素。反之这个文本是div的子元素 )如下代码:
1 | html代码: |
当被设置元素为 块状元素 时用 text-align:center 就不起作用了,这时也分两种情况:定宽块状元素和不定宽块状元素。
这一小节我们先来讲一讲定宽块状元素。(定宽块状元素:块状元素的宽度width为固定值。)
满足定宽和块状两个条件的元素是可以通过设置“左右margin”值为“auto”来实现居中的。我们来看个例子就是设置 div 这个块状元素水平居中:
1 | html代码: |
在实际工作中我们会遇到需要为“不定宽度的块状元素”设置居中,比如网页上的分页导航,因为分页的数量是不确定的,所以我们不能通过设置宽度来限制它的弹性。(不定宽块状元素:块状元素的宽度width不固定。)
不定宽度的块状元素有三种方法居中(这三种方法目前使用的都很多):
1. 加入 table 标签2. 设置 display: inline 方法:与第一种类似,显示类型设为 行内元素,进行不定宽元素的属性设置3. 设置 position:relative 和 left:50%:利用 相对定位 的方式,将元素向左偏移 50% ,即达到居中的目的
这一小节我们来讲一下第一种方法:
为什么选择方法一加入table标签? 是利用table标签的长度自适应性—即不定义其长度也不默认父元素body的长度(table其长度根据其内文本长度决定),因此可以看做一个定宽度块元素,然后再利用定宽度块状居中的margin的方法,使其水平居中。
第一步:为需要设置的居中的元素外面加入一个 table 标签 ( 包括
1 | html代码: |
任务
我来试试:补充代码实现右侧中的 class 为 wrap 的 div 水平居中显示(要注意是这个 div元素 居中,而不是里面的文本居中啊)。(记得点击右上角的全屏按钮查看效果哦!)
备注:这一小节没有正确性验证,请查看结果窗口,从而判断输入代码是否正确。
除了上一节讲到的插入table标签,可以使不定宽块状元素水平居中之外,本节介绍第2种实现这种效果的方法,改变元素的display类型为行内元素,利用其属性直接设置。
第二种方法:改变块级元素的 display 为 inline 类型(设置为 行内元素 显示),然后使用 text-align:center 来实现居中效果。如下例子:
1 | html代码: |
这种方法相比第一种方法的优势是不用增加无语义标签,但也存在着一些问题:它将块状元素的 display 类型改为 inline,变成了行内元素,所以==少了一些功能,比如设定长度值。==
除了前两节讲到的插入table标签,以及改变元素的display类型,可以使不定宽块状元素水平居中之外,本节介绍第3种实现这种效果的方法,设置浮动和相对定位来实现。
方法三:通过给父元素设置 float,然后给父元素设置 position:relative 和 left:50%,子元素设置 position:relative 和 left: -50% 来实现水平居中。
我们可以这样理解:假想ul层的父层(即下面例子中的div层)中间有条平分线将ul层的父层(div层)平均分为两份,ul层的css代码是将ul层的最左端与ul层的父层(div层)的平分线对齐;而li层的css代码则是将li层的平分线与ul层的最左端(也是div层的平分线)对齐,从而实现li层的居中。
代码如下:
1 | <body> |
这三种方法使用得都非常广泛,各有优缺点,具体选用哪种方法,可以视具体情况而定。
任务
我来试试:添加代码为任务区中的 class 为 wrap-center 的div设置水平居中。(记得点击右上角的全屏按钮查看效果哦!)
我们在实际工作中也会遇到需要设置垂直居中的场景,比如好多报纸的文章标题在左右一侧时,常常会设置为垂直居中,为了用户体验性好。
这里我们又得分两种情况:父元素高度确定的单行文本,以及父元素高度确定的多行文本。
本节我们先来看第一种父元素高度确定的单行文本, 怎么设置它为垂直居中呢?
父元素高度确定的单行文本的竖直居中的方法是通过设置父元素的 height 和 line-height 高度一致来实现的。(height: 该元素的高度,line-height: 顾名思义,行高(行间距),指在文本中,行与行之间的 基线间的距离 )。
line-height 与 font-size 的计算值之差,在 CSS 中成为“行间距”。分为两半,分别加到一个文本行内容的顶部和底部。
这种文字行高与块高一致带来了一个弊端:==当文字内容的长度大于块的宽时,就有内容脱离了块。==
如下代码:
1 | <div class="container"> |
任务
我来试试:补充右侧代码使 h2 中的文本垂直方向居中。(记得点击右上角的全屏按钮查看效果哦!)
父元素高度确定的多行文本、图片等的竖直居中的方法有两种:
方法一:使用插入 table (包括tbody、tr、td)标签,同时设置 vertical-align:middle。
css 中有一个用于竖直居中的属性 vertical-align,在父元素设置此样式时,会对inline-block类型的子元素都有用。下面看一下例子:
html代码:
1 | <body> |
css代码:
1 | table td{height:500px;background:#ccc} |
因为 td 标签默认情况下就默认设置了 vertical-align 为 middle,所以我们不需要显式地设置了。
任务
我来试试:把右侧的小女生图片设置 为相对于浏览器窗口垂直居中。(记得点击右上角的全屏按钮查看效果哦!)
除了上一节讲到的插入table标签,可以使父元素高度确定的多行文本垂直居中之外,本节介绍另外一种实现这种效果的方法。但这种方法兼容性比较差,只是提供大家学习参考。
在 chrome、firefox 及 IE8 以上的浏览器下可以设置块级元素的 display 为 table-cell(设置为表格单元显示),激活 vertical-align 属性,但注意 IE6、7 并不支持这个样式, 兼容性比较差。
html代码:
1 | <div class="container"> |
这种方法的好处是不用添加多余的==无意义的标签,但缺点也很明显,它的兼容性不是很好,不兼容 IE6、7==而且这样修改display的block变成了table-cell,破坏了原有的块状元素的性质。
任务
我来试试:如果你使用的是 chrome、firefox 及 IE8 以上的浏览器,你可以使用本小节的方法把右小女生的图片设置为垂直居中。(记得点击右上角的全屏按钮查看效果哦!)
有一个有趣的现象就是当为元素(不论之前是什么类型元素,display:none 除外)设置以下 2 个句之一:
1 | <div class="container"> |
想不起 display:inline-block 是做什么的小伙伴们,单击“元素分类–内联块状元素”可返回到前面小节进行复习。
任务
我来试试:下面我们来试试 float 有没有这种功能。(记得点击右上角的全屏按钮查看效果哦!)
把右侧代码编辑器中的第 8 行改为 float:left ;看是否 width:200px 还起作用。
]]>position
把元素放置到一个静态的、相对的、绝对的、或固定的位置中。
top
定义了一个定位元素的上外边距边界与其包含块上边界之间的偏移。
right
定义了定位元素右外边距边界与其包含块右边界之间的偏移。
bottom
定义了定位元素下外边距边界与其包含块下边界之间的偏移。
left
定义了定位元素左外边距边界与其包含块左边界之间的偏移。
overflow
设置当元素的内容溢出其区域时发生的事情。
clip
设置元素的形状。元素被剪入这个形状之中,然后显示出来。
vertical-align
设置元素的垂直对齐方式。
z-index
设置元素的堆叠顺序
css 样式由选择符和声明组成,而声明又由属性和值组成,如下图所示:
CSS样式可以写在哪些地方呢?从CSS 样式代码插入的形式来看基本可以分为以下3种:内联式、嵌入式和外部式三种。这一小节先来讲解内联式。
<p style="color:red">这里文字是红色。</p>
嵌入式css样式,就是可以把css样式代码写在<style type="text/css"></style>
标签之间。
如下面代码实现把三个<span>标签中的文字设置为红色: <style type="text/css"> span{ color:red; } </style>
外部式css样式(也可称为外联式)就是把css代码写一个单独的外部文件中,这个css样式 文件以“.css”为扩展名,在<head>内(不是在<style>标签内)使用<link>标签将css样式文件链接到HTML文件内,如下面代码: <link href="base.css" rel="stylesheet" type="text/css" />
三种方法的优先级
有的小伙伴问了,如果有一种情况:对于同一个元素我们同时用了三种方法设置css样式,那么哪种方法真正有效呢?在右边编辑器就出现了这种情况
1、使用内联式CSS设置“超酷的互联网”文字为粉色。
2、然后使用嵌入式CSS来设置文字为红色。
3、最后又使用外部式设置文字为蓝色(style.css文件中设置)。
但最终你可以观察到“超酷的互联网”这个短词的文本被设置为了粉色。因为这三种样式是有优先级的,记住他们的优先级:
内联式 > 嵌入式 > 外部式
p img body
类选择器在css样式编码中是最常用到的,如右侧代码编辑器中的代码:可以实现为“胆小如鼠”、“勇气”字体设置为红色。
语法:
.类选器名称{css样式代码;}
注意:
1、英文圆点开头
2、其中类选器名称可以任意起名(但不要起中文噢)
使用方法:
第一步:使用合适的标签把要修饰的内容标记起来,如下:
胆小如鼠
第二步:使用class=”类选择器名称”为标签设置一个类,如下:
胆小如鼠
第三步:设置类选器css样式,如下:
.stress{color:red;}/类前面要加入一个英文圆点/
在很多方面,ID选择器都类似于类选择符,但也有一些重要的区别:
1、为标签设置id=”ID名称”,而不是class=”类名称”。
2、ID选择符的前面是井号(#)号,而不是英文圆点(.)。
右侧代码编辑器中就是一个ID选择符的完整实例。
子选择器
还有一个比较有用的选择器子选择器,即大于符号(>),用于选择指定标签元素的第一代子元素。如右侧代码编辑器中的代码:
.food>li{border:1px solid red;}
这行代码会使class名为food下的子元素li(水果、蔬菜)加入红色实线边框。
这个“选择指定标签元素的第一代子元素”就是让样式只作用于它的孩子,不作用与他的孙子。
包含(后代)选择器
包含选择器,即加入空格,用于选择指定标签元素下的后辈元素。如右侧代码编辑器中的代码:
.first span{color:red;}
总结:>作用于元素的第一代后代,空格作用于元素的所有后代。
通用选择器是功能最强大的选择器,它使用一个(*)号指定,它的作用是匹配html中所有标签元素,如下使用下面代码使用html中任意标签元素字体颜色全部设置为红色:
当你想为html中多个标签元素设置同一个样式时,可以使用分组选择符(,),如下代码为右侧代码编辑器中的h1、span标签同时设置字体颜色为红色:
h1,span{color:red;}
有一些css样式是不具有继承性的,有一些具有继承性的,例如字体系列属性
标签的权值为1,类选择符的权值为10,ID选择符的权值最高为100
-继承也有权值但很低,有的文献提出它只有0.1,所以可以理解为继承的权值最低。
我们来思考一个问题:如果在html文件中对于同一个元素可以有多个css样式存在并且这多个css样式具有相同权重值怎么办?好,这一小节中的层叠帮你解决这个问题。
层叠就是在html文件中对于同一个元素可以有多个css样式存在,当有相同权重的样式存在时,会根据这些css样式的前后顺序来决定,处于最后面的css样式会被应用。
如下面代码:
1 | p{color:red;} |
最后 p 中的文本会设置为green,这个层叠很好理解,理解为后面的样式会覆盖前面的样式。
所以前面的css样式优先级就不难理解了:
内联样式表(标签内部)> 嵌入样式表(当前文件中)> 外部样式表(外部文件中)
我们在做网页代码的时,有些特殊的情况需要为某些样式设置具有最高权值,怎么办?这时候我们可以使用!important来解决。
如下代码:
1 | p{color:red!important;} |
这时 p 段落中的文本会显示的red红色。
注意:!important要写在分号的前面
我们可以使用css样式为网页中的文字设置字体、字号、颜色等样式属性。下面我们来看一个例子,下面代码实现:为网页中的文字设置字体为宋体。
1 | body{font-family:"宋体";} |
上图中的原价上的删除线使用下面代码就可以实现:
1 | .oldPrice{text-decoration:line-through;} |
中文文字中的段前习惯空两个文字的空白,这个特殊的样式可以用下面代码来实现:
1 | p{text-indent:2em;} |
注意:2em的意思就是文字的2倍大小。
如果想在网页排版中设置文字间隔或者字母间隔就可以使用 letter-spacing 来实现,如下面代码:
1 | h1{ |
注意:这个样式使用在英文单词时,是设置字母与字母之间的间距。
如果我想设置英文单词之间的间距呢?可以使用 word-spacing 来实现。如下代码:
1 | h1{ |
想为块状元素中的文本、图片设置居中样式吗?可以使用text-align样式代码,如下代码可实现文本居中显示。
1 | h1{ |
下面我们来了解一下
标签的作用。文档的头部描述了文档的各种属性和信息,包括文档的标题等。绝大多数文档头部包含的数据都不会真正作为内容显示给读者。1 | <head> |
<title>
标签:在<title>
和</title>
标签之间的文字内容是网页的标题信息,它会出现在浏览器的标题栏中。网页的title标签用于告诉用户和搜索引擎这个网页的主要内容是什么,搜索引擎可以通过网页标题,迅速的判断出网页的主题。每个网页的内容都是不同的,每个网页都应该有一个独一无二的title。
<em>
的内容在浏览中显示为斜体,显示为加粗。如果不喜欢这种样式,没有关系,以后可以使用css样式去改变它。
<q>
标签,==短文本引用==
想在你的html中加一段引用吗?比如在你的网页的文章里想引用某个作家的一句诗,这样会使你的文章更加出彩,那么标签是你所需要的。
语法:<q>
引用文本</q>
不需要双引号
`<blockquote>`的作用也是引用别人的文本。但它是对长文本的引用,如在文章中引入大段某知名作家的文字,这时需要这个标签。 等等,上一节`<q>`标签不是也是对文本的引用吗?不要忘记`<q>`标签是对简短文本的引用,比如说引用一句话就用到`<q>`标签。 如想在我的文章中引用李白《关山月》中的诗句,因为引用文本比较长,所以使用`<blockquote>`。
语法:
<blockquote>
引用文本</blockquote>
认识<hr>标签,添加水平横线<address>标签,为网页加入地址信息
想加入一行代码吗?使用<code>标签
单行<code></code>;多行代码,<pre>
注意:<pre> 标签不只是为显示计算机的源代码时用的,在你需要在网页中预显示格> 式时都可以使用它,只是<pre>标签的一个常见应用就是用来展示计算机的源代码。
ul标签是圆点ol是数字 可以使用<ol>标签来制作有序列表来展示。
<table>(表格标签)<tbody>(不加tbody表格将加载完再显示,加tbody表格将逐行显示,还有另外两个thead><tfooter>)<tr>(行标签)<th></th>(表头标签)<td></td>(单元格标签)</tr></tbody></table>caption标签,为表格添加标题和摘要表格还是需要添加一些标签进行优化,可以添加标题和摘要。代码如下:摘要摘要的内容是不会在浏览器中显示出来的。它的作用是增加表格的可读性(语义化),使搜索引擎更好的读懂表格内容,还可以使屏幕阅读器更好的帮助特殊用户读取表格内容。 语法:<table summary="表格简介文本">标题用以描述表格内容,标题的显示位置:表格上方。 语法:<table><caption>标题文本</caption><tr><td>…</td><td>…</td>…</tr>…</table>使用mailto在网页中链接Email地址在网页的制作中为使网页炫丽美观,肯定是缺少不了图片,可以使用<img>标签来插入图片。语法:<img src="图片地址" alt="下载失败时的替换文本" title = "提示文本">举例:<img src = "myimage.gif" alt = "My Image" title = "My Image" />文本域,支持多行文本输入当用户需要在表单中输入大段文字时,需要用到文本输入域。语法:<textarea rows="行数" cols="列数">文本</textarea>使用下拉列表框进行多选下拉列表也可以进行多选操作,在<select>标签中设置multiple="multiple"属性,就可以实现多选功能,在 windows 操作系统下,进行多选时按下Ctrl键同时进行单击(在 Mac下使用 Command +单击),可以选择多个选项。
]]>最近有一个需求是: 实现一个java
standalone应用,定时处理一些数据,并且记录日志,假如定时任务停掉,就会报警
先实现各定时任务在java项目main运行,后边的再慢慢研究
- 创建我的job类
- 创建test用于写测试调用job的代码
1 | package cn.northpark.test.jobtask; |
1 | package cn.northpark.test.jobtask; |
myjob为定义的job类
Job(接口)
JobDetail: 真正的任务内容,任务本身是集成Job接口的,但是真正的任务是JobBuilder通过反射的方式实例化的,
CronTrigger用于处理quartz表达式任务 比如每天的几点执行
SimpleTrigger 主要用于处理格时间重复调度
最近有一个需求是: 实现一个java
standalone应用,开辟静态HashMap(1500),创建500个线程,序号从1到500,每个线程都会建立3次连接访问数据库test,
调用数据库函数cms.sp_get_fundasset,传入线程序号作为参数。数据库函数返回的账户净值按序号写入对应的静态HashMap。统计500个线程全部结束后消耗的时间总长;
- 创建调用传统创建线程的类
- 创建db用于jdbc连接
- 创建test用于写测试代码
1 | package cn.northpark.test.multhread; |
1 | package cn.northpark.test.multhread; |
1 | package cn.northpark.test.multhread; |
主要介绍了MySQL安全配置向导mysql_secure_installation各项配置的含义,并依据经验给予一了一些建议
安装完mysql-server 会提示可以运行mysql_secure_installation。运行mysql_secure_installation会执行几个设置:
- 为root用户设置密码
- 删除匿名账号
- 取消root用户远程登录
- 删除test库和对test库的访问权限
- 刷新授权表使修改生效
通过这几项的设置能够提高mysql库的安全。建议生产环境中mysql安装这完成后一定要运行一次mysql_secure_installation,详细步骤请参看下面的命令:
1 | [root@server1 ~] |
之前尝试过3次centos安装mysql服务,都失败了,严重的时候,系统都挂了,所有服务重新撘~
我尝试了各种安装方式,例如:下载别人提供好的tag.gz,解压缩,按照人家的步骤一步步来,结果安装完后启动失败~
这样指令安装:yum install -y mysql-server mysql mysql-devel,安装完后启动失败,fuck!!!希望有同样遭遇的小伙伴,可以按照我的方式来装一次,版本号相同的话,肯定是可以的,所有的雷我都替你们趟过了!!
找到需要下载的资源: http://dev.mysql.com/downloads/repo/yum/
MySQL Yum 下载页面
复制了下载链接,wget获取资源库,确保没有错误,用md5校验,确保和官网文件是一致没错误的
1 | wget http://dev.mysql.com/get/mysql57-community-release-el6-7.noarch.rpm |
1 | md5sum mysql57-community-release-el6-7.noarch.rpm |
1 | rpm -ivh mysql57-community-release-el6-7.noarch.rpm |
以上我们安装了mysql5.7的yum资源库,可以开始安装mysql服务端和客户端依赖包了
1 | yum install -y mysql-community-client mysql-community-server |
Start MySQL 5.7 service
打开mysql服务
1 | service mysqld start |
And find initial mysql 5.7 root password from log file
找到初始化mysql的管理员密码,初始化密码在log文件中
1 | grep -i temporary /var/log/mysqld.log |
Login into MySQL 5.7 using password you got from temporary password
that you searched from above
利用初始化密码登录到mysql
1 | mysql -uroot -p |
Only command MySQL 5.7 is going to let you run once you login with
your default password is password command
设置你的mysql管理员的新密码
1 | SET PASSWORD FOR 'root'@'localhost' = PASSWORD('Yourpassword1!'); |
这样就成功安装了MySQL 5.7 server
下面进行一些必要的配置
1 | mysql -uroot -p |
允许任何主机使用“myuser”账号和“mypwd”密码连接到 MySQL 服务器。
1 | mysql> |
1 | FLUSH PRIVILEGES; |
1 | iptables -I INPUT -p tcp -m state --state NEW -m tcp --dport 3306 -j ACCEPT |
1 | /etc/init.d/iptables save |
1 | chkconfig mysqld on |
1 | mysql_secure_installation |
了解更多增强MySQL安全设置来Northpark博客
]]>12月份mac系统升级,黑屏了,Genius Bar 客服告诉我只能格式化,重装新系统。我(。_°☆╲(- – )…..((/- -)/
我的博客代码也没备份,一直懒惰,这几个月也没更新博文了。 终于,今天勤奋一会,把博客代码重写重搭了~
- 再有一周就要去陕西赏春了,快按耐不住躁动的心情了~撒欢
- 内心憧憬的华山是“三峰森翠倚云棱,凝睇烟萝最上层。” 亦是“云含幽兮月添冷,月凝晖兮江漾影。”华山!等着我们
- 哥们刚刚从陕西那边玩回来,和我们的行程比较匹配,下面引用一下他的攻略
文 波波整理 jeyy
- 文笔不好,咱们直入主题!
> # 准备阶段
- 西安,其实要真是想游个遍,讲真还是有一定难度的!毕竟西安的文化底蕴还是不了低估的!单是那些古建筑就满大街都是,什么大雁塔,古城墙,华清池,兵马俑在地图上你随便一搜就够你转几天的。不过我这个人怎么说的,反正就是不大喜欢那些什么大院,什么寺庙,什么宫的……完全不感冒!而且也感觉千篇一律。但是我比较喜欢大自然的那些山山水水的,还有就是我是一名铁杆吃货,西安的美食也是我来西安的一项重要任务!
- 其实不管去哪里游玩,有一个问题必须先解决掉——住宿!
- 俗话说
“兵马未动,粮草先行”
也是这么个理,后方必须有保障!- 住宿一般要根据自己的旅行计划来,一般我的方案是走到哪儿住到哪儿;
- 还有一种省心的方案就是只要在一个城市,找个地理位置居中交通便利的地段一直住下!
- 其实,我还是喜欢前者,因为不同地段的美食文化什么的,只有住下了才能深入了解。但是还有个弊端就是需要带着行李搬来搬去(这也是旅行尽量轻装出行)。
- 一般找旅馆就是在地图或者蚂蜂窝上景点附近酒店推荐就可以对比判断(多看看评价以及酒店周边环境),然后千万要打电话确认有没有空房以及房费问题与网络报价是否一致!
- 但是,我并不推荐提前预定,因为地图有的时候定位不准就会各种找不到,所以最好是找到店以后再定或者直接店付(西安一般150左右就能找到不错的大床房)。
> # 吃货攻略
- 对于西安景点推荐旅游攻略什么的我还真说不上,因为去了西安我只有两点主题——吃,登华山!但是吃的地方我倒是有几句话要说,很多人去了西安吃的地方就想到了回民街,不错那儿的东西的确多,但是物价也稍微有点儿贵,一般比较适合时间不充足的游客去体验一把西安美食文化,听西安同学说要想吃地地道道的没事儿,就应该去袁家村(咸阳的)因为那里实惠,而且种类繁多(但是因为我没找到去的方法转念一想来回路费都能补差价了就直接在回民街体验了)!如果想去的小伙伴可以提前做做功课!至于去了以后吃什么特色,蚂蜂窝上一搜,各种推荐以及酌情选择就可以!
]]>
> # 华山攻略
- 在西安住了一晚第二天早上又简单转了转我就转战华山了,从西安去华山我是坐的高铁(西安北—华山北)30分钟,班次也很多!下了车站找出租就行(切记20元即可去玉泉院),一般来说登山都需要从玉泉院那儿往上爬,玉泉院是不收费的寺院听说陈抟老祖就是在那儿清修,个人感觉还可以。
- 登华山看日出一般有两种,在山下留宿然后夜爬和白天爬在山上留宿第二天下来,山下宾馆便宜100左右都是,山上就比较贵了,像点样子的就得300+估计。
- 夜爬一般需要23点左右就要开始了,这样中间也有个休息缓劲儿的时间(我们那天连休息带爬一共近7个小时)!
- 爬山之前一定要把登山装备准备好,一般来说
吃的
,喝的(一般每人1L左右)
,手套(山下超市两三元一副)
,手电
,充电宝
,保暖衣物
,登山包
,雨衣
。- 还有一条就是登山一定要准备好
登山鞋
或者防滑运动鞋
!- 登华山,切记不要心急,节奏很重要,很多人(包括我)可能检票进去后走了一段觉得路宽又平,一点都不险么,其实,那两三千米你还没有真正开始登……后面有你受的!
- 还有那些登山拐杖,年轻人别买,登山那就是个累赘!
- 登华山也简单,只要路没封,跟着路标有就行!有人可能会选择坐汽车跟缆车,首先提示晚上没有,而且我也觉得登山坐那些东西反而丧失了登山的乐趣!
- 我们是晚上登的,周边景色没看到所以下山也纯走(如果爬山过程中欣赏了抑或觉得累了什么的下山坐缆车什么的也未尝不可,毕竟上山容易下山难)!
- 看日出,就是去东峰,完事儿可以再做行程安排(我爬完东峰就下来了,体力感觉已经不行了)。还有两件事要提醒大家如果想登山看日出要提前关注天气变化噢,还有就是登山
一定要注意安全,一定要注意安全,一定要注意安全!
作为一名苦逼的移动互联网创业者,经常会听到外行的朋友们问这样的问题:“做一个网站需要多少钱?”或者“做一个APP需要多少钱?”
3年前,天真的我认为做一个APP顶多5000块钱,网站2000块以内就能搞定。高于这个价格就是唬小孩。后来我确实花过1000块钱买了个模板网页,5000块钱买个模板APP,干了这些傻事。最后的结果就像淘宝上的买家秀和卖家秀的天差地别,钱打了水漂。
本着信天信地不信邪的原则,我们开始组建自己的技术团队,从UI,到后台,前端,安卓,苹果各类程序员和产品经理,组成了一个最基本的APP开发团队。到现在已经做过五个APP,其中三个都获得了融资,另外两个是政府扶持项目。给团队发了将近一年工资,对于一个APP从无到有的开发到底要花多少钱,实在是再清楚不过。
首先,如果你也是创业者,而且满腔热血,那么产品就必须自己开发。因为创业是很牛逼的事,所有的环节都要最好的。把产品技术开发交给外包难免不放心,当时我们也是这么想的。自己懂技术,是相关专业出身还好。但据我了解,一般的创业者难免都不懂。所以,如果你要做一个APP,以此为切入点,首先需要知道至少需要哪些人员配置。
包含研发一个新产品过程中一些常见的工作,团队的结构以架构师兼后台开发1名+安卓2名+iOS开发1名+产品经理1名+前端开发1名+设计师1名,你可能也发现了这些人员配备都是单点的,一旦有人生病或请假,某个职位就会缺失,项目进度就会拖后,创业公司员工任职不稳定可是很正常的现象,所以我们大约配备2个浮动名额,也就是,初始团队大概有9名。估计一般的创业者都想越快越好的让产品开发上线,假设你想的的产品上线期3~6个月。
北上广一线深城市还好,只要资金充裕人才不愁,简历丰富挨个筛选。但是在常州这样的二三线城市,一个WEB前端足足招了7个月才有人投简历上门面试。更为可怕的是我们同时付费在“拉勾”“前程无忧”“智联招聘”甚至“赶集”这些非专业平台以及朋友圈,各种无限寻找的情况下。我们从第一个人入职,到团队勉强能够开工一共花了5个月招聘时间。
这里有一个比较好的经验,先把所有的力气去寻找一个志同道合的技术总监,高薪是必须的。但是薪资高了他可以在开发上帮你少走弯路省回来。
所以第一步的花费大概是–招聘平台网站会员费:平均1000元/家*3=3000元。然后是假设你3个月就招聘齐活儿了。
这三个月平均要发4个人工资,试用期内薪资80%算,而且还不用五险一金。常州此类人才一般需要招聘至少有过一年以上开发经验的,要不然开发质量难以保障。
这样人才平均薪资大概在8K左右。所以这三个月你要发(8000元80%试用期4个人*3个月=76800)。
8人团队可以选择中低端的办公场所,这类的办公场所在常州大约是70元/㎡,大约需要100平米,并不是很好找。
创业孵化器是很不错的选择。在现在的大背景下,孵化器现在很多很容易找。孵化器的租金会比较便宜甚至免费,在接纳创业团队之前一般会签署协议,需要以场地作为股权置换条件,或者是优先投资权。至于孵化器是否划算,得创业团队自己掂量。算上水电、物业,一个月10000左右算比较节省。
普通的人体工程学椅子价格在150元左右,如果不买工程学椅子用普通的椅子代替,加上桌子平摊成本,桌椅需要200左右。这些一次性付出,6000元可以搞定吧,拍脑袋也得付。
加上设计师和程序猿电脑,配置在4000一台,一共七台包括一台IOS机。开发移动APP需要提供样机,二手就行,安卓系统4部,2000元一部,苹果系统也要4部,3500一部。
好,算一算6000元+4000元7+2000元4+3500*4=56000元
APP开发之前,你知道自己想做个什么样的APP软件,但一般不知道自己具体要做什么东西,需求是什么,产品定位是什么。这时候产品经理就需要出马,跟老板各种沟通、理清需求、找产品定位。
这阶段大概需要花费两周,好说歹说仔细分析,才会把产品的定位、一期的需求想清楚。期间架构师可以开始构思产品的技术架构,还未能正式开工。产品定位以及早期的需求确定出来之后,需要构思产品名称、需求调研、竞品调研分析、注册域名、购置服务器以及周边基础能力(如CDN存储、短信服务、消息推送等)、商标(未注册商标、很多团队就是吃了这个亏)、抢注域名、设计logo。这阶段如果不中途改变计划、不变更产品需求和定位的前提下,最起码花费一周时间。这时候产品的原型、技术架构的雏形也呈现,基本可以进入真刀真枪的干活。
这一步假设域名不需要花大价钱购买,全部费用算起来拍脑袋15000元/年。
其中短信2000,服务器1000一个月。域名等商标注册2000一个,商标,软著,LOGO一次性投入有10000左右。
看似一切都准备就绪之后,产品开始整理需求,按照需求优先级规划版本。架构师按照产品的发展方向构想,开始着手进入开发环境、生产环境部署、基础代码研发阶段。
很有必要提一下的是,做APP比较常见的两种功能实现方式有native原生和webview
方式,这两种方式的优劣不在本文的讨论范围,一般为了保证体验,以原生的方式为主,webview的方式为辅。以原生的方式做开发,版本是尤为重要的。其一,APP开发完成之后,需要把上架到APP store(假设我们只上iOS和安卓),iOS 的APP Store
有冗长的应用审核周期,你必须提前把往后几个中小版本的功能规划好了才不至于在这个环节自乱阵脚;其二,你的APP安装到用户的手机上时你肯定希望他大部分时候运行的都是最新的版本,如果你的版本更新过于频繁,用户也会嫌烦。
看起来过程似乎非常顺利,产品经理把产品的思路理清之后,开始准备产品V1.0 的规划。
(1)产品官网,产品官网是产品、公司、团队的脸面,让用户进入之后能以最快的速度了解你的定位以及产品特性,也就是什么样的人在为解决什么样的问题,做着什么样的事情。
(2)用户协议。是的,如果用户要注册、使用你的产品,你得起草你的用户协议,跟律师或法务不断沟通调整。
(3)种子用户。你要思考你的第一批种子用户从哪里来,如何组织,怎么管理,他们是检验你产品的第一群人,对于验证你的产品定位、发现产品问题、提升产品体验来说至关重要。
(4)基础数据上报规划。你要知道产品一旦发布,在对用户行为一无所知的情况下,需要收集什么数据来优化、调整你的产品设计,思考你的产品方向。
(5)UI设计与交互标准制定。不能保证产品的UI以及交互一上线就有让用户觉得惊艳的效果,但是必须保证UI/交互在一致性上没有问题。
(6)后台管理系统。如论是查看数据还是内容管理,后台管理系统是你工作的有效辅助,也不能少。
(7)APP。这是你的产品的重中之重,密切观察跟踪。
如果一切顺利,那么在3-6个月之后,你的APP第一版应该有了第一个雏形。
在这个阶段,你要跟设计、研发、种子用户之间保持密切沟通,不断收集问题、发现问题、优化、解决问题,期间可能需要发布1-3个beta子版本。同时,你要想办法保持种子用户的活跃与配合的激情,这是一项艰巨的工作,谁做谁知道。
]]>发布的时候,不算渠道宣传费用,不打广告,上线iOS APP Store
需要注册开发者账号,购买证书,也需要花费一定的费用,不多,也就99美元/年。如果你做得多一些,找安全团队帮你做ac安全评估,这里也要花上一些钱,这里就不算了。至于负载均衡、加速技术什么的,你的APP刚上线,应该用不上。
6个月的时间,一个APP从无到有已经十分迅速。那么这一整个步骤里面。可能人员成本最大,8个人平均薪资是8K。一般当时急着招人都会有其它福利的。
五险一金,公司最低要为每个人补贴1100元左右。十三薪,折合每个月,相当于每个月多发1000元。团队刚招过来没有磨合时间,所以需要尽快出产品,肯定需要加班,加班是要有加班工资的,周末加一天班算200一天。
那么平均每人加班工资是500.将近7个月,肯定有节假日,还需要有节日福利吧,平常肯定有团队建设费用吧。平均每人每月摊头上多100元。
好的,那么一个人一个月平均费用为(8000+1100+1000+200+500+100=10900)
我这里假设招聘完全后,开发只用了4个月上线(这还是算快的)。就是(109004个月8个人=348800元)。这还不算什么,你要想,现在做互联网的哪个是一开始能赚钱的,这批功臣,每个月固定人员开销是87200元。
那么最后,如果你要创业自己组建技术团队的话,从招人到产品制作完成。一共算是招全人3个月,制作4个月。一共7个月计算的总花费是:
人员费用:76800元(招人期间)+ 348800(正式阶段)=425600 房租水电:10000/月*7个月=70000
办公设备:56000元 商标等注册服务器:20000元 一共是:425600+70000+56000+10000=571600元这里还没有算你的市场人员,财务人员,营销推广。而且只算了4个月的正式开发期,每个月你的固定技术人员开销是87200。
所以当有人问自己组建团队做一个APP需要多少钱时,你可以回答一个不太复杂的APP,在像常州这样的2.5线城市,从无到有半年第一个版本出来,至少需要60万。如果在一线城市,至少也是100万。
如果找别的成熟团队或公司做,4个月上架的话,他们的成本大概是87200*4=348800元。不过一般可以接外包的公司,很多类型都已经做过,做一些死板的开发不需要太高的成本。
所以各位苦逼的创业者们。如果自己有信心,口袋里至少有个百来万,可以考虑自己组建技术团队。
但记住最多9个月,9个月后要么融资要么盈利。如果自己只有小几十万,产品盈利性是在后期,那么建议还是还是找个靠谱点的外包公司做,,一点一点从细节完善合同。
由于我的网站之前用的c3p0数据连接池配置,总是引发一些莫名其妙的错误,几次内存泄漏都和这个有关系,google之发现好多人都发现了这些bug.
于是了解了一下常见的数据源的配置,并改成了dbcp的配置方案。
Spring中提供了4种不同形式的数据源配置方式:
1、Spring自带的数据源(DriverMangerDataSource);
2、DBCP数据源;
3、C3P0数据源;
4、JNDI数据源。
以上数据源配置需要用的Jar包在http://www.java2s.com/Code/Jar/c/Catalogc.htm中都可以下载到
下面详细介绍这四种数据源配置方式:
使用org.springframework.jdbc.datasource.DriverManagerDataSource
说明:DriverManagerDataSource建立连接是只要有连接就新建一个connection,根本没有连接池的作用。
XML代码
1 | <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> |
使用org.apache.commons.dbcp.BasicDataSource
说明:这是一种推荐说明的数据源配置方式,它真正使用了连接池技术DBCP的配置依赖于2个jar包commons-dbcp.jar,commons-pool.jar。
XML代码:
1 | <!-- 使用org.apache.commons.dbcp.BasicDataSource |
上面代码的解释:
BasicDataSource提供了close()方法关闭数据源,所以必须设定destroy-method=”close”属性,
以便Spring容器关闭时,数据源能够正常关闭。除以上必须的数据源属性外,还有一些常用的属性:defaultAutoCommit:设置从数据源中返回的连接是否采用自动提交机制,默认值为 true;
defaultReadOnly:设置数据源是否仅能执行只读操作, 默认值为 false;
maxActive:最大连接数据库连接数,设置为0时,表示没有限制; maxIdle:最大等待连接中的数量,设置为0时,表示没有限制;
maxWait:最大等待秒数,单位为毫秒, 超过时间会报出错误信息;
validationQuery:用于验证连接是否成功的查询SQL语句,SQL语句必须至少要返回一行数据,
如你可以简单地设置为:“select count(*) from user”; removeAbandoned:是否自我中断,默认是
false ;
removeAbandonedTimeout:几秒后数据连接会自动断开,在removeAbandoned为true,提供该值;
logAbandoned:是否记录中断事件, 默认为 false;
C3P0是一个开放源代码的JDBC数据源实现项目,C3P0依赖于jar包c3p0.jar。
XML代码:
1 | <!-- 配置数据源 c3p0 --> |
使用org.springframework.jndi.JndiObjectFactoryBean
说明:JndiObjectFactoryBean 能够通过JNDI获取DataSource
如果应用配置在高性能的应用服务器(如WebLogic或Websphere,tomcat等)上,我们可能更希望使用应用服务器本身提供的数据源。应用服务器的数据源
使用JNDI开放调用者使用,Spring为此专门提供引用JNDI资源的JndiObjectFactoryBean类。
xml 代码:
1 | <bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean"> |
目前网上搭建个人博客的方案很多,虽然使用诸如 Wordpress ( PHP )、Hexo ( Node.js )
等可以方便快速地搭建一款功能齐全的高性能个人博客,但是本文将尝试一种更为小众化的方案 —— 一款基于 django-blog-zinnia
( Python ) 的个人博客应用。 django-blog-zinnia
虽然小巧,但是具备了个人博客应用的全部基础功能,且具有很高的拓展性,并且开箱即用。以下是官方列出的一些特性:
评论
站点地图(用于搜索引擎优化)
注:本博客在写作时每一个步骤均在实际环境下测试了一遍,基本确保没有问题。但是由于个人写作时的疏忽或者计算机环境的差异,也可能会有一些错误导致你卡在某个地方无法继续进行下去。如果是这样请给我留言,我和你一起排查问题,如果发现是博客写作时的错误也好使我尽快更正。
因为在安装 django-blog-zinnia 的过程中会安装很多其他第三方依赖包,因此强烈建议使用虚拟环境安装,以免把系统环境弄乱。
假设你的 python 版本是 3.4 或更高(建议使用 3.4 以上版本,当然 django-blog-zinnia 本身是兼容 python2.7 及以上版本的),且已经安装了虚拟环境管理工具 virtualenv,如果没有的话通过pip install virtualenv
安装。打开命令行,进入到你想建立虚拟环境的目录,通过命令 virtualenv zinnia_demo_env
创建一个名为 zinnia_demo_env 的虚拟环境,当然 zinnia_demo_env 这个目录名你可以任意指定。进入到创建的虚拟环境的 Scripts 目录下,输入 activate 命令激活虚拟环境,此时命令行前多了一个 ( zinnia_demo_env ) 说明已经激活,例如我的是:
1 | (zinnia_demo_env) D:\Users\zmrenwu\Envs\zinnia_demo_env\Scripts> |
通过 pip install django==1.9.6 安装 django,建议使用 1.9.6 版本,当然 >=1.9 的版本都是兼容的,但注意目前不兼容 django1.10。
进入你喜欢的目录(确保依然在虚拟环境中,如果没有则按照上面的方法重新开启,且下边的操作默认都在虚拟环境中运行,因此不要退出),通过命令python django-admin.py startproject zinnia_demo
创建一个 django 工程。这里 zinnia_demo 是项目名,可以取任何你喜欢的名字。此时你会发现多了一个名为 zinnia_demo 的目录,这样 django 工程就建立好了。进入到这个目录,会看到有一个 manage.py 文件,尝试运行命令 python manage.py runserver
,不报错的话,在浏览器输入 127.0.0.1:8000,看到如下字样说明 django 工程已经可以正确运行。
It worked! Congratulations on your first Django-powered page. Of
course, you haven’t actually done any work yet. Next, start your first
app by running python manage.py startapp app_label.You’re seeing this message because you have DEBUG = True in your
Django settings file and you haven’t configured any URLs. Get to work!
按 Ctrl + c 退出服务器。
在虚拟环境中输入 pip install django-blog-zinnia
安装 django-blog-zinnia,必要的依赖其会自动帮我们安装,但是一些拓展依赖需要我们手动安装,包括:
pip install markdown
安装 markdown,以便使博客文章支持 markdown 格式的文本。
pip install pygments
安装 pygments,以便支持代码语法高亮。
进入到 zinnia_demo/zinnia_demo (当然你可能设置了其他项目名,但我相信你能找到),打开 settings.py 文件(用文本编辑器或者 python IDE 打开,不要直接运行),在 INSTALL_APPS 列表里添加以下的 APP,这些 APP 都是 django-blog-zinnia 依赖运行的 APP :
1 | zinnia_demo/zinnia_demo/settings.py |
在 TEMPLATES 列表的如下位置加入 zinnia.context_processors.version
,当然这一步是可选的,其作用只是在博客页面的底部显示一个django-blog-zinnia 的版本号:
1 | zinnia_demo/zinnia_demo/settings.py |
在 ALLOWED_HOSTS = 的下面添加 SITE_ID = 1
1 | zinnia_demo/zinnia_demo/settings.py |
并修改语言和时区,获得更友善的语言和时间显示,注意 + 号表示添加的行,- 号表示删去的行:
1 | zinnia_demo/zinnia_demo/settings.py |
打开相同目录下的 urls.py 文件,做如下修改,注意 + 号表示添加的行,- 号表示删去的行:
1 | zinnia_demo/zinnia_demo/urls.py |
在 manage.py 文件所在目录下输入 python manage.py migrate
建立相应的数据库表结构。此时会看到目录下多了一个 db.sqlite 文件,这是存储博客数据的数据库文件,默认使用的 sqlite3。输入命令 python manage.py createsuperuser
创建后台管理员账户,命令行会提示你输入用户名、邮箱、密码。注意密码输入时不会有任何显示,只管输下去就行。
再次运行python manage.py runserver
开启开发服务器,在浏览器输入 127.0.0.1:8000/weblog
将看到博客的首页面。输入 127.0.0.1:8000/admin 会进入后台登录页面,输入刚才创建的管理员账户用户名和密码就可以登录到后台管理界面。在日志后面点击增加按钮尝试着添加一篇博客看看!再次进入 127.0.0.1:8000/weblog 就可以看到刚才发表的博客了。
至此基本的博客已经搭建完毕,接下来是一些可选功能拓展,包括 markdown 语法支持,代码高亮,bootstrap 主题的安装。
再次打开 settings.py 文件,在文件的最后添加:
1 | zinnia_demo/zinnia_demo/settings.py |
bingo!!
ZINNIA_MARKUP_LANGUAGE 指明了使用 markdown 语法标记,ZINNIA_MARKDOWN_EXTENSIONS 做了两个拓展,其中 markdown.extensions.codehilite 表示支持语法高亮,markdown.extensions.extra 包含的特性请参见 markdown 相关文档。
如果你不喜欢原生的主题的话,django-blog-zinnia 为我们提供了一套 bootstrap 主题,相对来说更加好看一点。虽然说实在话内置的主题感觉都已经过时了,因此我重新为它设置了一套全新的主题,稍后会有介绍。
中断服务器的运行,进入到虚拟环境(如果你已经退出了的话),首先输入命令pip install django-app-namespace-template-loader
安装 django-app-namespace-template-loader,这是替换主题的一个必要组件。再输入 pip install zinnia-theme-bootstrap
安装主题 APP ,打开 settings.py 文件,在 INSTALLED_APPS 中注册主题 APP ,注意主题 zinnia_bootstrap 一定要在 zinnia 之前:
1 | zinnia_demo/zinnia_demo/settings.py |
再将 TEMPLATES 列表做如下修改:
1 | zinnia_demo/zinnia_demo/settings.py |
此时再次开启服务器,进入主页 127.0.0.1:8000/weblog
就可以看到主题变成了 bootstrap 样式了。
注意:这一步必须在安装完 bootstrap 主题之后。
pygments 已经帮我做好了一切语法高亮的准备,其原理就是把 html 中的代码文本分成很多块,用适当的 html 标签包裹,并且添加相应的 css 类,我们只需引入一个相应的 css 样式文件即可。
为了方便起见,我们新建一个 APP 来存放我的需要引入的 css 样式文件,在 zinnia_demo/ 目录下(与 manage.py 同级)下输入 python manage.py startapp theme
,这样我们就创建了一个名为 theme 的 app,可以看到 zinnia_demo/ 多了一个 theme 的文件夹。
在 zinnia_demo/ 目录下(与 manage.py 同级)建立如下的目录结构和文件:
zinnia_demo/templates/zinnia/skeleton.html,把这里面的内容:skeleton 模板代码 ,复制到 skeleton.html中,并且在 skeleton.html 的 \ 标签里添加一行:
1 | zinnia_demo/templates/zinnia/skeleton.html |
再在 zinnia_demo/ 下建立如下的目录结构和文件:
zinnia_demo/theme/static/theme/css/github.css,把这里面的内容:=github.css 样式代码 ,复制到 github.css 文件中。
打开 settings.py 文件,做如下修改:
1 | zinnia_demo/zinnia_demo/settings.py |
将 theme app 注册到 INSTALLED_APPS 列表中:
1 | zinnia_demo/zinnia_demo/settings.py |
打开开发服务器,进入相应页面就可以看到代码高亮效果了。记得事先填充一些代码到博客文章中。
PS:
自带的评论功能当有人回复你发表的博客文章后会发送一封 email 给你的后台管理员账户邮箱(创建后台管理员账户填写的)。不过需要设置好发送邮件的邮箱,参考配置如下,在 settings.py 中:
1 | zinnia_demo/zinnia_demo/settings.py |
不过要确保你的邮箱开启了 SMTP,如果没有的话请参考邮箱服务商的相关设置进行开启。
]]>进去之后最左侧的第一个,第二个,第三个,都非常的好玩,建议进去先玩,然后大波浪有时间限制,看好时间再去玩。有几个项目12点以后才开,看好时间早点去排队把。
我总结一下经验! 因为住回龙观所以一早七点多就出发了! 怕路上堵车,幸运的是没有堵车!八点半不到就到了!
首先说明一下正门北边的停车场是员工停车场!因为去的早可以停在这里!
不要停车费!八点四十左右有保安管理所以不要在车上呆着可以在安检通道大棚下等候!不然就叫你去停车场里了!哪里收费5元一小时!还要注意的是一定在安检通道前等待!早进去也没有用,倒时候还要把你哄出来安检!
如果多人去的话九点半安检可以先叫一个人不拿东西去售票窗口换票,6、7、8是网络换票口!其他人拿东西过安检到大门口排队,因为十点才能进大门所以这样最省时间!我们一直排第一名!
还有水只能带矿泉水!别的有色饮料都不行!吃的可以带些火腿肠和巧克力!九点五十多门口有开园欢迎仪式!进入园区后先找一个人去换储物柜!存里面200到300之间就行!如果不在园区吃饭的话存100就够了!
园区吃饭普遍比外面要贵一倍多!量也少!盖饭在40左右,面也要25元!我们四个人吃了快两百!
剩下的人去换衣服,换完储物柜的人在去找换衣服的人这样最省时间!
储物柜两个人用一个就可以了!没有想象的那么小!还要注意的是买手机防水袋的还是先把手机放在储物柜里!因为你进去先玩大型戏水项目是什么都不让带的!我们就是又回去放了次手机!
左手边的两个项目非常刺激!一进去可以多玩几次!
因为早上排队的比较少可以连着玩!到了下午就要排好半天了!玩完大型项目就可以回去拿手机了!去沙滩等待冲浪最早一次是11点半!
不会游泳的要带好游泳圈,里面租35不限时间!我们自己带了游泳圈但是打气的不给力,去买游泳圈那充的气一个要15元,觉得有点被黑的感觉!
冲浪相当刺激!玩了就不想玩别的了!
还有注意的是防晒还有拖鞋的事情,最好带个看不到里面东西的布袋!大家把拖鞋放一起这样不容易丢!因为到下午冲浪时人非常多!我就看到一个人穿了个久拖鞋换了个别人的新的!
一直玩到晚上八点我们才走!非常愉快的一次游玩!
感谢驴妈妈旅游网价格合理公道!门口到票的也没办法在便宜!只能说他们的票可以一直玩到晚上叫我们退票买他的!我们没有相信,后来在门口知道白天的票本来就可以玩到晚上!就写到这里吧!希望可以对大家有帮助!
最好最好四个人结伴前往,前提是四个人都是“敢玩”的,因为大的游乐设施都是2或4人一组,多了少了都不让上去。
能不周末就不周末去,今天是周六,园内的工作人员说,预计两万的客流,实际达到了四万。那人,,,,煮饺子!!
带泳衣就可以了,想游两下的话带上泳镜,重要的是要穿拖鞋,里边不提供而且地非常烫,游泳圈特别没必要,尤其在门口买的小摊上的更是亏,园内提供泳圈和救生衣,泳帽是根本不用滴。
非常晒预备好防晒霜和浴巾还有伞,其实太阳那么大个人认为防晒霜已经浮云了,浴巾非常有必要,当你排队两三个小时的时候就会发现有个浴巾遮下太阳非常有用。
拿水有必要,但是里面也有卖的,矿泉水4块一瓶这个价位,门口也有小摊,这个倒是没什么太纠结的。
基本上一去就是一天,做好准备吧。早去晚归。
不要以为团购并不普及,全是团购去的!那兑票的队,长!打架的骂人的插队的应有尽有,所以最好最好九点就到把票换了,晚了九点半到,十点以后的话,用一个愤青的话“排两个小时了没动窝”。
可能人太多了管理特别差劲,身份证根本没用了。。。但也最好预备着。
有传言说丰台的身份证半价,但是仍需验证。
进门直奔造浪池,看好几点有造浪表演,这个可是水魔方不可错过的一项,确定参加哪一场后开始安排玩其他设施的时间,以保证及时冲浪冲浪池最深的地方1.4m越来越浅直到沙滩,1.4m那里很猛哒我姐夫在1.4m那里,有个女的泳裤被冲掉了就被他们抛啊抛的哈哈还听别人说有的女的,上半身全被冲掉啦==
以周末为例,最好多人分头排队,最高最火的那个滑梯,同时也是水魔方精彩的项目,我们排了足足三个小时,工作人员还跟游客骂起来了啊各种骂啊最高的滑梯一定不要错过!!不然你就白来水魔方了!!爽啊
龙卷风我们没排上因为马上要关门了,也是必去项目之一,四人一起的,每个出来的人第一句话都是“我*!”惊险可想而知。
魔幻漩涡。别小看这个看起来并不怎么样的实施,必去之一!!不必最高的那个滑梯次。如果跟我们一样倒着出来就悲剧咯
精彩的肯定还有,剩下的没开or没排上,总之还是挺好玩的。休闲的也有漂流池和SPA池,休闲好去处
看到N多人为了抢不到双人游泳圈苦苦寻觅,其实去漂流池旁边等最有效的,好多人在那里都抛弃了自己的泳圈,随便拿一个就走人啦,用不着在设施底下排队等也用不到等人家慢慢充气啦。
本来他们设计的是园内手牌消费,不知人太多了还是没有完善系统,今天是现金,所以不要把钱也锁到储物柜啦,没法买东西啦。
关于饭,价钱和吉野家差不多,一个纸筒一份饭,个人觉得味道和吉野家也差不多,但是我姐不这么认为。鸡块12块四小块,烤肠5块一根差不多就这个价位,饮料6或8,咖啡奶茶啥的普遍12+。
零食也是有必要的,因为我们排漫长队的时候看到别人吃零食真是羡慕嫉妒恨啊。
关于看队伍目测时间,楼梯上可以排队的设施基本上到楼梯口就需要30min了,这里指的是比较大的设施,高滑梯那种是10个10个往上放人,插队的具多,排到楼梯口也还需要多则一小时少则半小时。
三个高滑梯的身体要求,最高的那个有身高要求是1.4M+,三个共有的是40kg以上90kg以下,不符合的不要费劲排队了,轻了重了都有可能滑不下来。今天看到不少孩子排了好几个小时最后也上不去。
任何首饰手表卡子啥的都别带进去了,玩设施前都会让你摘下,也有就不听劝的,个人认为一个首饰就别带进去了,而且确实有个设施以因为一个首饰碰到了不该碰的按钮停运了10分钟的场面,何必。
除了造浪池最深1.4m,其他的基本0.9m,还是有一定安全保障的。
用springmvc的handlerinterceptor的来实现。
HandlerInterceptor是Spring MVC为我们提供的拦截器接口,来让我们实现自己的处理逻辑,HandlerInterceptor 的内容如下:
1 | public interface HandlerInterceptor { |
可以看到接口有3个方法,其含义如下:
preHandle:在执行action里面的处理逻辑之前执行,它返回的是boolean,这里如果我们返回true在接着执行postHandle和afterCompletion,如果我们返回false则中断执行。
postHandle:在执行action里面的逻辑后返回视图之前执行。
afterCompletion:在action返回视图后执行。
HandlerInterceptorAdapter适配器是Spring
MVC为了方便我们使用HandlerInterceptor而对HandlerInterceptor
的默认实现,里面的3个方法没有做任何处理,在preHandle方法直接返回true,这样我们继承HandlerInterceptorAdapter后只需要实现3个方法中我们需要的方法即可,而不像继承HandlerInterceptor一样不管是否需要3个方法都要实现。当然借助于HandlerInterceptor我们可以实现很多其它功能,比如日志记录、请求处理时间分析等,权限验证只是其中之一。
首先添加一个账户的Controller和登录的Action及视图来模拟在没有权限时跳转到登陆页面,内容分别如下:
1 | package com.bruce.interceptor; |
com.bruce.interceptor包中的LoginInterceptor.java 内容如下:
1 | package com.bruce.interceptor; |
1 | <!-- 定义拦截器 --> |
1 | //添加次注解,未登录的自动跳转到登录页 |
可以看到正确执行了权限判断逻辑,这样我们只需要在我们在需要权限验证的action上加上这个注解就可以实现权限控制功能了。
注解式权限验证的内容到此结束。
]]>有一个大侠很孤独,孤独得不知道做什么好,于是决定去追杀一个恶霸。恶霸被追得满世界跑,跑着跑着,亲戚朋友都跑没了,于是恶霸也变得很孤独。为了方便,我们不妨称这两人为孤独大侠和孤独恶霸。
孤独大侠追着孤独恶霸,从早到晚,从春到冬,从左到右,从北到南。追着追着,他们感到实在太他妈的孤独了,于是约定好每天午觉的时候聊上一聊。为了保证彼此的安全,他们睡在两座相邻的山峰上,隔空用传音聊天。有时孤独大侠生病了,拉下了距离,两个人就在隔得很远的山峰上用“远得不能再远的传音”喊话。两个人叽里呱啦聊得火热,果然一点儿都不孤独了。高兴起来的孤独大侠觉得追杀孤独恶霸好像也没有什么特别的意义,就经常故意放慢步伐,好让他多歇一会儿,不要跑得那么累。偶尔聊得特别高兴,孤独大侠还会给孤独恶霸放个假,让他回家探探亲,看看老婆抱抱孩子。但没放几天,孤独大侠又会去追孤独恶霸,因为他又感到无比的孤独了。
他们聊得越来越投契,越来越觉得对方理应是自己这辈子最好的朋友,从来没有任何人能像对方那样了解自己。有一回,他们甚至就隔着块大石头聊了一晚上,直到清晨的露水打湿干裂的嘴唇,才勉强挪动僵硬的双腿开始例行的追和逃。还有一回,他们隔着一条河交换了食物,孤独大侠吃了孤独恶霸的烤兔,觉得好吃,竖了下大拇指。孤独恶霸吃了孤独大侠的烤鱼,更觉得好吃,抱了一下拳。那时天已擦黑,孤独大侠忽然想起今天还没有追过孤独恶霸,貌似没有办法对自己交差,连忙说声对不住,“嗖”地一声,朝孤独恶霸头顶两尺高处打过去一颗铁丸。孤独恶霸摸着头嘿嘿一笑,牙齿很白。
某天,排名比孤独大侠还要高出许多的另一位大侠(据说就是头号大侠,我们不妨称之为未知大侠)路过碰到了正在休息的孤独恶霸。本来未知大侠也不屑于搭理这种小角色,可看到孤独恶霸笑得实在太开心,不由想起自己已经孤独很久,也没个人可以说话解闷。于是他就微笑上前,想交个朋友。不料对方正沉浸在和十几座山外的孤独大侠的聊天当中,居然没理他。未知大侠三番两次得不到回应,一怒之下运出“打谁谁死神功”轰了过去。孤独恶霸这次本来已经死翘翘了,但是闻讯赶来的孤独大侠舍不得他死,费尽九牛二虎之力,求了不少人,终于把他救活。
开始追逃的前几年,每到春天最宜人的日子,管督察的大侠都会不远不近地跟着孤独大侠进行督查,这时两人就只好以命相搏,飞刀,铁丸满天飞。当然,每当督察大侠责问孤独大侠为什么不杀了孤独恶霸时,孤独大侠都会借口距离太远不方便操作,始终没有使出绝技“重得接不住的掌”来痛下杀手。后来,督察大侠也就懒得陪他们满世界乱跑,侠界也就彻底失去了他们的消息。
不过,孤独大侠仍然在追杀着孤独恶霸,其实对方早已提议——既然大家交情都这么好了,为什么不停止追逃而化敌为友呢?孤独大侠想了很久说,不行,因为除了这个,我实在不知道做什么好。又过了一阵子,在一次兴高采烈的聊天之后,孤独大侠终于同意了对方的请求,决定试上一试,便和孤独恶霸一起回了家。孤独大侠在孤独恶霸家做客好几个月,开始也觉得确实十分快活,绝无孤独感。可惜时间一久,浑身瘙痒,痒得呆不住,只好趁孤独恶霸一家熟睡之际逃走了。孤独恶霸晨起发现对方跑掉了,本以为自己会大笑三声说危险解除了,我高枕无忧啦,没想到却被一种突然而至的孤独击中。他虽然很爱自己的妻子和孩子,很想多陪陪他们,但最后还是出门追孤独大侠去了。
这一追,就是整整一年,因为孤独大侠实在逃得太快,孤独恶霸往往只能看到他的残影。在这年的最后一天,气喘吁吁的孤独恶霸终于追上了孤独大侠,孤独恶霸扶着腰正想问对方为什么一直不开“远得不能再远的传音”,孤独大侠却忽然变了脸色,运起“重得接不住的掌”打了过来。因为过了整整一年,孤独大侠已经变得十分极其太过孤独了,他已经升级为“终极孤独大侠”,终极孤独的他,必然要对孤独恶霸实行终极追杀。孤独恶霸心想这回必然歇菜了,挡也是白费。没想到,由于孤独大侠已经升级为“终极孤独大侠”,他的“重得接不住的掌”也自动升级为了“终极重得接不住的掌”,还没等发出多远,这个掌就因为自身太重直接砸到了地上,砸出了一个深坑,爆出漫天尘土,差点砸到了自己的脚尖。等烟尘散尽,灰头土脸的孤独大侠看了看同样灰头土脸的孤独恶霸,终于忍不住哈哈地笑了,孤独恶霸抓了抓脑袋,也嘿嘿地笑了起来。
从此两人继续追来逃去,聊东聊西的生活,他们仍然感到对方是这辈子最值得珍惜的朋友。一直追啊追啊,逃啊逃啊,直到孤独大侠都退休了,直到孤独恶霸也退休了,直到孤独恶霸的孩子都生第三个孩子了,直到孤独恶霸的妻子老死了,直到他们都死掉了,他们还是好朋友。可是,他们到底为什么那么投缘啊?
永远没有人知道,这成了侠界和恶霸界永远的谜。
小仓鼠是动物园里蛇先生的晚餐。
“蛇先生!你打算吃我吗!”
“……”
“蛇先生!你确定不先洗洗吗!”
“……”
“蛇先生!!你吃东西不拔毛吗!!”
“……”
“蛇先生!不拔毛的话……”
“闭嘴!再吵我就真的吃了你!”
小仓鼠却变成了蛇先生的储备粮。
“蛇先生!今天你饿了吗!”
“……”
“蛇先生!!那你现在饿吗!”
“……”
“蛇先生!你吃不吃玉米粒啊!”
“……”
“蛇先生!你……”
蛇先生张开蛇嘴,吐出蛇信子,露出凶煞的表情。
“……蛇先生你终于肯吃我了吗?”
“……”
“那你拔毛吗!”
“滚。”
蛇先生一个扫尾把小仓鼠咕噜咕噜滚走了。
小仓鼠成了动物园最著名的仓鼠。
蛇先生用尾尖点点仓鼠鼓鼓的腮帮子。
“这个是什么?”
“这似窝的粮似!!”
蛇先生用力戳了戳。
“很疼的!!!!!”
蛇先生张开嘴,“你还敢吼我。”
小仓鼠不舍的捂住腮帮子。
“…那你可以等我吃完再吃我吗?”
蛇先生收回蛇信子,溜去一边儿睡觉去了。
“好吧。”
小仓鼠是动物园中长肉最快的仓鼠。
“你怎么这么胖了?”
“…因为没有跑轮!”
蛇先生将小仓鼠扫到自己身上。
“跑吧。”
“蛇先生你身上好滑!!!!”
“闭嘴,再不跑我吃了你。”
“蛇先生你身上好凉!!”
“闭嘴,再啰嗦我就吃了你。”
“蛇先生你身上还有花纹!!!”
“闭嘴!再多嘴我就……”
“蛇先生你身上一点儿毛都没有!!”
蛇先生很挫败。
为什么这招不管用了呢?
小仓鼠是动物园中最奄奄一息的仓鼠。
“好热好热好热好热!!!”
“……”
“真的好热!!!”
“……”
“以前仓鼠窝还有纳凉板呢!!!”
“……”
“为什么蛇先生就没有呢!!”
“……”
“蛇先生你不热吗!!”
“你想说什么?”
“……我可以躺在你身上睡一觉吗?”
“……”
“蛇先生身上特别凉快的!!!”
“……”
“还滑滑的!!”
“……”
“还……”
“闭嘴,上来,吵醒我你就死定了。”
于是小仓鼠欢快的跑到蛇先生身上睡觉去了。
饲养员送食时,看到盘着蛇身休息的蛇先生,身上还有个四只爪子平爬的小东西。
“……那是什么?”
“是仓鼠。”
“它怎么不吃了?”
“……大概是……恋爱了。”
小仓鼠是动物园中最幸福的仓鼠。
饲养员惊奇。
游客也很惊奇。
“哇!!你看!那条蛇不吃仓鼠!”
“原来真的不吃!”
“叽叽喳喳喳喳叽叽……”
小仓鼠抬头问问蛇先生。
“蛇先生蛇先生他们在看什么!!”
“看你,和我。”
“啊!为什么!!”
“因为我们相处的很愉快。”
小仓鼠噗嗤噗嗤的爬上蛇先生身上,抱着蛇先生后脑狠狠亲了一口。
“当然要愉快!!!”
“…给我滚下去。”
“哇!妈妈!你看那条蛇不仅不吃仓鼠!还会脸红啊!!!”
小仓鼠到现在都不懂为什么蛇先生一直不吃自己。
“蛇先生!!蛇先生!!”
“……”
“你为什么不吃我啊!!”
“嫌脏。”
“我每天都有口水浴的!!!”
“嫌腻。”
“我每天都有在你身上锻炼的!!!”
“嫌小。”
“你看你看我都胖了一圈儿了!!!”
“……”
蛇先生的窝里,每天小仓鼠都会这么问它。
然而,蛇先生依旧不会吃它。
真是个美好的动物园。
真是个美好的蛇先生,和小仓鼠。
]]>1 | public static void main(String[] args) { |
1 | /** * 读取文件返回string |
1 | /** * 实现统计逻辑 |
1 | /** * 根据column判断字段的取值 |
1 | //遍历Map并且返回排版好的String字符串 |
1 | public class trackVO { |
1 | public class EmailUtils { |
一个应用开发到一定阶段,普遍会遇到一个问题。当功能越来越多,代码量越来越大,bug修复越来越频繁,开发人员一波一波的交替,…..应该用会向着越来越不可控发展。我们不能再准确估计新功能的开发时间,也不知道一个bug修复后是否会引发另一个bug出现。所有的程序开发,都会面临着这样的问题。
C/C++程序通过makefile管理编译测试打包的过程,Java程序通过Maven,Ant实现项目构建管理功能,Python有pip,Ruby有gem。在Nodejs的领域,我们同样需要一个项目构建工具,这就是Grunt。Grunt可以执行像压缩, 编译, 单元测试, 代码检查以及打包发布的任务。
Grunt是一个自动化的项目构建工具. 如果你需要重复的执行像压缩, 编译, 单元测试, 代码检查以及打包发布的任务. 那么你可以使用Grunt来处理这些任务, 你所需要做的只是配置好Grunt, 这样能很大程度的简化你的工作.
如果在团队中使用Grunt, 你只需要与其他人员约定好使用Grunt应该规避的问题, 就能够很方便的自动化的处理大部分的常见工作任务, 你所付出的努力几乎为0.
Grunt和Grunt插件都是通过npm, Node.js包管理器安装和管理的.
我的系统环境
win7 64bit
Nodejs:v0.10.5
Npm:1.2.19
~ D:\workspace\javascript>node -v
v0.10.5
~ D:\workspace\javascript>npm -v1.2.19
在系统中,我们已经安装好了Nodejs和npm。win7安装nodejs请参考文章:Nodejs开发框架Express3.0开发手记–从零开始
安装grunt-cli
grunt-cli并不grunt,grunt-cli的作用是管理本地各版本的grunt,让命令行可以直接执行grunt命令。
下面全局安装grunt-cli(-g)
~ D:\workspace\javascript>npm install -g grunt-cliD:\toolkit\nodejs\grunt -> D:\toolkit\nodejs\node_modules\grunt-cli\bin\gruntgrunt-cli@0.1.9 D:\toolkit\nodejs\node_modules\grunt-cli├── resolve@0.3.1├── nopt@1.0.10 (abbrev@1.0.4)└── findup-sync@0.1.2 (lodash@1.0.1, glob@3.1.21)
我们看到grunt-cli似乎做了一个软件链接,把grunt脚本复制到nodejs安装根目录里。
接下来全局安装grunt
~ D:\workspace\javascript>npm install -g grunt~ D:\workspace\javascript>gruntgrunt-cli: The grunt command line interface. (v0.1.9)Fatal error: Unable to find local grunt.If you're seeing this message, either a Gruntfile wasn't found or grunthasn't been installed locally to your project. For more information aboutinstalling and configuring grunt, please see the Getting Started guide:http://gruntjs.com/getting-started
执行grunt命令,我们发现系统报错了,提示不能加载本地库。因为,grunt命令执行,是需要当前目录中包括package.json和Gruntfile.js两个文件。
package.json,是npm项目配置文件
Gruntfile.js,是专门用来配置grunt的配置文件
接下来,我们创建一个express3的项目。
D:\workspace\javascript>express -e nodejs-grunt
D:\workspace\javascript>cd nodejs-grunt && npm install
D:\workspace\javascript\nodejs-grunt>npm install grunt –save-dev
安装-save-dev,就可以,直接把grunt作为devDependencies写入的package.json中。
~ vi package.json{ "name": "nodejs-grunt", "version": "0.0.1", "private": true, "scripts": {"start": "node app.js" }, "dependencies": {"express": "3.2.2","ejs": "*" }, "devDependencies": {"grunt": "~0.4.1", }}
然后,我们再执行grunt,系统提示缺少Gruntfile文件
~ D:\workspace\javascript\nodejs-grunt>gruntA valid Gruntfile could not be found. Please see the getting started guide formore information on how to configure grunt: http://gruntjs.com/getting-startedFatal error: Unable to find Gruntfile.
创建Gruntfile文件
~ vi Gruntfile.jsmodule.exports = function(grunt) { // Project configuration. grunt.initConfig({pkg: grunt.file.readJSON('package.json'),uglify: { options: {banner: '/*! <%= pkg.name %> <%= grunt.template.today("yyyy-mm-dd") %> */\n' }, build: {src: 'src/<%= pkg.name %>.js',dest: 'build/<%= pkg.name %>.min.js' }} }); // Load the plugin that provides the "uglify" task. grunt.loadNpmTasks('grunt-contrib-uglify'); // Default task(s). grunt.registerTask('default', ['uglify']);};
再次运行grunt,这时提示是grunt-contrib-uglify包找不到,是Gruntfile.js配置文件中的错误了。
~ D:\workspace\javascript\nodejs-grunt>grunt
Local Npm module "grunt-contrib-uglify" not found. Is it installed?Warning: Task "uglify" not found. Use --force to continue.
我们编辑package.js, 在devDependencies中增加grunt-contrib-uglify的依赖库
~ vi package.js{ "name": "application-name", "version": "0.0.1", "private": true, "scripts": {"start": "node app.js" }, "dependencies": {"express": "3.2.2","ejs": "*" }, "devDependencies": {"grunt": "~0.4.1""grunt-contrib-uglify": "~0.1.1" }}~ D:\workspace\javascript\nodejs-grunt>npm install
我们创建两个目录src和build,和nodejs-grunt.js的文件
~ D:\workspace\javascript\nodejs-grunt>mkdir src~ D:\workspace\javascript\nodejs-grunt>mkdir build~ D:\workspace\javascript\nodejs-grunt>vi src/nodejs-grunt.jsvar sayHello = function(name){return "Hello " + name;}
我们再执行grunt
~ D:\workspace\javascript\nodejs-grunt>gruntRunning "uglify:build" (uglify) taskFile "build/nodejs-grunt.min.js" created.Uncompressed size: 59 bytes.Compressed size: 40 bytes gzipped (43 bytes minified).Done, without errors.
grunt运行正常,并且执行了uglify:build的任务。打开build/nodejs-grunt.min.js文件
~ D:\workspace\javascript\nodejs-grunt>vi build/nodejs-grunt.min.js/*! nodejs-grunt 2013-08-17 */var sayHello=function(l){return"Hello "+l};
我们可以看到一个新生成的压缩文件nodejs-grunt.min.js。
上面的例子,是一个js文件压缩的例子。
我们可以通过help帮助,看一下grunt怎么用。
~ D:\workspace\javascript\nodejs-grunt>grunt --helpGrunt: The JavaScript Task Runner (v0.4.1)Usage grunt [options] [task [task ...]]Options--help, -h Display this help text.--base Specify an alternate base path. By default, all file paths arerelative to the Gruntfile. (grunt.file.setBase) *--no-color Disable colored output. --gruntfile Specify an alternate Gruntfile. By default, grunt looks in thecurrent or parent directories for the nearest Gruntfile.js orGruntfile.coffee file. --debug, -d Enable debugging mode for tasks that support it. --stack Print a stack trace when exiting with a warning or fatal error. --force, -f A way to force your way past warnings. Want a suggestion? Don'tuse this option, fix your code. --tasks Additional directory paths to scan for task and "extra" files.(grunt.loadTasks) * --npm Npm-installed grunt plugins to scan for task and "extra" files.(grunt.loadNpmTasks) *--no-write Disable writing files (dry run). --verbose, -v Verbose mode. A lot more information output. --version, -V Print the grunt version. Combine with --verbose for more info. --completion Output shell auto-completion rules. See the grunt-clidocumentation for more information.Options marked with * have methods exposed via the grunt API and should insteadbe specified inside the Gruntfile wherever possible.Available tasksuglify Minify files with UglifyJS. * default Alias for "uglify" task.Tasks run in the order specified. Arguments may be passed to tasks that acceptthem by using colons, like "lint:files". Tasks marked with * are "multi tasks"and will iterate over all sub-targets if no argument is specified.The list of available tasks may change based on tasks directories or gruntplugins specified in the Gruntfile or via command-line options.For more information, see http://gruntjs.com/
有两方面是我们需要注意的:
Available tasks: 当目录可执行的任务
grunt-contrib-uglify:压缩js代码
插件安装及更新到配置
D:\workspace\javascript\nodejs-grunt>npm install grunt-contrib-concat –save-dev
修改Gruntfile.js文件
grunt.initConfig({pkg: grunt.file.readJSON('package.json'),concat:{ options: {//定义一个字符串插入没个文件之间用于连接输出separator: ';' }, dist: { src: ['src/*.js'], dest: 'build/<%= pkg.name %>.cat.js' }}, });grunt.loadNpmTasks('grunt-contrib-qunit');grunt.registerTask('default', ['uglify','concat']);
在src目录,新增加文件src/sayBye.js
~ vi src/sayBye.jsvar sayBye = function(name){return "Bye " + name;}
执行concat任务
~ D:\workspace\javascript\nodejs-grunt>grunt concatRunning "concat:dist" (concat) taskFile "build/nodejs-grunt.cat.js" created.Done, without errors.
查看生成的文件build/nodejs-grunt.cat.js
~ vi build/nodejs-grunt.cat.jsvar sayHello = function(name){return "Hello " + name;};var sayBye = function(name){return "Bye " + name;}
两个文件完全的合并。
插件安装及更新到配置
D:\workspace\javascript\nodejs-grunt>npm install grunt-contrib-qunit –save-dev
修改Gruntfile.js文件
grunt.initConfig({pkg: grunt.file.readJSON('package.json'),qunit: { files: ['test/*.html']} });grunt.loadNpmTasks('grunt-contrib-qunit');grunt.registerTask('default', ['uglify','concat','qunit']);
创建一个test目录,并编写用于测试的qunit.html文件
~ mkdir test~ vi test/qunit.html<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"><html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><link rel="stylesheet" href="http://github.com/jquery/qunit/raw/master/qunit/qunit.css" type="text/css" media="screen" /><script type="text/javascript" src="http://github.com/jquery/qunit/raw/master/qunit/qunit.js"></script><script>test("hello", function() {ok(true, "world");});</script></head><body><h1 id="qunit-header">QUnit example</h1><h2 id="qunit-banner"></h2><h2 id="qunit-userAgent"></h2><ol id="qunit-tests"></ol></body></html>
执行qunit命令
~ D:\workspace\javascript\nodejs-grunt>grunt qunitRunning "qunit:files" (qunit) taskTesting test/qunit.html .OK
1 assertions passed (67ms)
Done, without errors.
完成单元测试!!
插件安装及更新到配置
~ D:\workspace\javascript\nodejs-grunt>npm install grunt-contrib-jshint --save-dev
修改Gruntfile.js文件
grunt.initConfig({pkg: grunt.file.readJSON('package.json'),jshint: {files: ['gruntfile.js', 'src/*.js', 'build/*.js'],options: {globals: {exports: true}}} });grunt.loadNpmTasks('grunt-contrib-jshint');grunt.registerTask('default', ['uglify','concat','qunit','jshint']);
执行jshint代码检查
~ D:\workspace\javascript\nodejs-grunt>grunt jshintRunning "jshint:files" (jshint) taskLinting src/nodejs-grunt.js ...ERROR[L3:C2] W033: Missing semicolon.}Linting build/nodejs-grunt.cat.js ...ERROR[L5:C2] W033: Missing semicolon.}Linting build/nodejs-grunt.min.js ...ERROR[L2:C42] W033: Missing semicolon.var sayHello=function(l){return"Hello "+l};Warning: Task "jshint:files" failed. Use --force to continue.Aborted due to warnings.
好多的错误啊,细看一下,都是”丢失分号”的错误。
~ vi src/sayBye.jsvar sayBye = function(name){return "Bye " + name;};
增加最后一行的分号,解决上面的错误。
我感觉这个插入,就点类似于supervisor的功能。
插件安装及更新到配置
~ D:\workspace\javascript\nodejs-grunt>npm install grunt-contrib-watch --save-dev
修改Gruntfile.js文件
grunt.initConfig({pkg: grunt.file.readJSON('package.json'),watch: {files: ['<%= jshint.files %>'],tasks: ['jshint', 'qunit']} });grunt.loadNpmTasks('grunt-contrib-watch');grunt.registerTask('default', ['uglify','concat','qunit','jshint']);
执行watch任务
~ D:\workspace\javascript\nodejs-grunt>grunt watchRunning "watch" taskWaiting...OK
手动修改src/sayBye.js文件,下面watch的任务被触发
File "src\sayBye.js" changed.
Running "jshint:files" (jshint) taskLinting src/sayBye.js ...ERROR[L3:C2] W033: Missing semicolon.}Linting build/nodejs-grunt.cat.js ...ERROR[L3:C3] W032: Unnecessary semicolon.};;var sayBye = function(name){Linting build/nodejs-grunt.min.js ...ERROR[L2:C42] W033: Missing semicolon.var sayHello=function(l){return"Hello "+l};Warning: Task "jshint:files" failed. Use --force to continue.Aborted due to warnings.Completed in 0.770s at Sat Aug 17 2013 20:49:15 GMT+0800 (中国标准时间) - Waiting...
上面介绍的5个任务,可能是我们比较常用配置的任务,大家也可以按照需要指定自己的任务。
下面贴上所最终的package.json和Gruntfile.js文件代码
package.json{ "name": "nodejs-grunt", "version": "0.0.1", "private": true, "scripts": {"start": "node app.js" }, "dependencies": {"express": "3.2.2","ejs": "*" }, "devDependencies": {"grunt": "~0.4.1","grunt-contrib-uglify": "~0.1.1","grunt-contrib-concat": "~0.3.0","grunt-contrib-qunit": "~0.2.2","grunt-contrib-jshint": "~0.6.3","grunt-contrib-watch": "~0.5.2" }}Gruntfile.jsmodule.exports = function(grunt) { // Project configuration. grunt.initConfig({pkg: grunt.file.readJSON('package.json'),uglify: { options: {banner: '/*! <%= pkg.name %> <%= grunt.template.today("yyyy-mm-dd") %> */\n' }, build: {src: 'src/<%= pkg.name %>.js',dest: 'build/<%= pkg.name %>.min.js' }},concat:{ options: {//定义一个字符串插入没个文件之间用于连接输出separator: ';' }, dist: { src: ['src/*.js'], dest: 'build/<%= pkg.name %>.cat.js' }},qunit: { files: ['test/*.html']},jshint: {files: ['gruntfile.js', 'src/*.js', 'build/*.js'],options: {globals: {exports: true}}},watch: {files: ['<%= jshint.files %>'],tasks: ['jshint', 'qunit']} }); grunt.loadNpmTasks('grunt-contrib-uglify'); grunt.loadNpmTasks('grunt-contrib-concat'); grunt.loadNpmTasks('grunt-contrib-qunit'); grunt.loadNpmTasks('grunt-contrib-jshint'); grunt.loadNpmTasks('grunt-contrib-watch'); // Default task(s). grunt.registerTask('default', ['uglify','concat','qunit','jshint']);};
]]>1,安装JDK,并配置JAVA\_HOME
环境变量。因为Gradle是用Groovy编写的,而Groovy基于JAVA。另外,Java版本要不小于1.5.
2,下载。地址是:http://www.gradle.org/downloads
。在这里下载你要的版本。
3,解压。如果你下载的是gradle-xx-all.zip的完整包,它会有以下内容:
GRADLE\_HOME
到你的gradle根目录当中,然后把%GRADLE\_HOME%/bin
(linux或mac的是$GRADLE\_HOME/bin
)加到PATH的环境变量。配置完成之后,运行gradle -v
,检查一下是否安装无误。如果安装正确,它会打印出Gradle的版本信息,包括它的构建信息,Groovy, Ant, Ivy, 当前JVM和当前系统的版本信息。
另外,可以通过GRADLE\_OPTS
或JAVA\_OPTS
来配置Gradle运行时的JVM参数。不过,JAVA_OPTS设置的参数也会影响到其他的JAVA应用程序。
“你知道方块鲸吗?一条鲸鱼卡在方块里了。”吃晚饭时,她不经意地说。
“下午办公室里还在聊这个呢。”丈夫疲倦的脸上,闪出兴奋的光,对神秘的事情,他一向热衷,“说是恶棍快车进入终点站时,会发出一种鲸鱼的叫声,就像一头鲸鱼卡在方块里,慢慢地,慢慢地窒息……”丈夫夸张地做了一个掉脑袋的动作,随后总结:即使是机器人,面对死亡,也是会被痛苦折磨的,所以,他们最好别干坏事。
胃不太舒服,她不想继续这个话题了。
晚上,他们早早地躺下。她睡了一小会儿,眼皮又睁开了,陷入了所谓的对自己完全无能为力的失眠的长夜。思绪拉扯得很开,散漫,像血瘀女子脸上的红血丝。肚子里不知发生了什么事情,不断有咕噜噜的叫声,焦躁与烦闷一阵阵地涌上来,脸颊像傍晚的天边,堆满了绚烂的火烧云。后来,月光透过窗帘缝洒到被子上,她安静了下来,一遍遍地想到海,如果那辆列车真的在终点站掉下去的话,会不会径直掉到大海里,如果是在海水里,鲸鱼就不会被卡住了吧。头一回,她对身边人的呼噜声没有反感,聆听着,像在海边住着的人,呼噜声只是海浪,她从未去过海边——现在,她对海可以一遍又一遍地猜想。
海是浓烈的,可能裹住人就不松动,紧紧地缠绕着;海是稀松的,像晒干了的棉花,轻的在被面下一抖,都能看到棉丝之间的空隙;海是僵硬的,像被烧得通红的铁块突然面对一盆冷水,发出咝咝声,接着便缩小了自己的热度,老老实实地固化了……
嘴角有一个小泡泡嘟出来,她舔了一下,满不在乎地起床去厨房取上明信片,收拾了一个挎包,掂了掂房间钥匙,甩进包里,关上门,就向地铁走去。搭乘驶向郊区的地铁,一小时后就能转站到恶棍快车。她为什么会知道这条路线?光线昏暗的车厢里,她挖出了一件少女时代的往事。高考那年,父母专门租了一个家教机器人来家里,辅导她的功课,除了晚上陪她做作业到深夜,其它时候他就跟一个暂住她家的远方亲戚没什么两样。她经常偷偷看他,他去洗手间时,她也从门缝里瞄看,他用热水洗脸,刮幼嫩的胡子,还喜欢喷佛手柑味的古龙香水;为了让自己看上去显得老成一点,他总是故意地耷拉下嘴角,拉出两条本来没有的法令纹。本来一切都会顺利,租期一到,她会准备一样小礼物送给他做纪念,在他所属的AI公司接他回去之后,她还要给他写电子邮件,保持联系。然而接近考期的某一天,他带了一个另一家AI公司的女机器人回来,旁若无人地与她在家里天台上呆了一个晚上,她所服务的家庭因为她的失踪报了警,AI管理局派了警察过来,带走了他,罪名是诱拐与破坏服务。那个女机器人也被带走了,据说她是被动犯罪的一方,返厂维修就可以了。他也去维修了吗?她问过父母,他们安慰她说应该是的,但她心里隐约有另一个答案,当时电视新闻里播放过AI管理局道德伦理委员会的声明:即将制定AI惩戒细则,将一些干坏事的机器人直接送往监狱。
夜浓得化不开,从黎明到破晓之间的距离特别漫长,她坐在候车室里,不停地看手表,数着秒针的滴答,直到她车票上显示的那个时间。
根据明信片上的介绍,作为参观者,她应该去最后一节车厢,在快到终点站时,这节车厢会与前面的车厢脱节,停留在铁轨上,车厢里的参观者能透过车窗看到其他车厢一节节地掉入深渊状的地下监狱。这种设置能让AI使用者们放心,清楚地看到那些“彻底坏掉了”的机器人会得到什么样的下场。可是,一些使用者并不喜欢车厢下落过程中,机器人的惨叫声,毕竟,如今的他们长得跟真实人类没什么差别,谁是机器人谁是真实人类,平时就很容易搞错,更别提参观时了。而更多的人还是乐意去看一看,多少像古代抱着极大热情观看绞刑实施的人,对死亡与厄运有着奇怪的痴迷。上车时,她先上了最后一节车厢,又迅速溜下来,去到前面一节。
是卧铺车厢,她找到一个空铺,躺上去。火车开动的瞬间,她睡着了。醒来时,黎明的光线照亮了一切,她看到对面床上的一个男人睡姿不雅,呈大字型地躺着,窄小的床容不下他的一条胳膊与腿,它们铃铛一般晃荡在床沿。阳光从窗户外伸入大手,紧紧握住他的身体,他像一株旷野上的藤蔓,强悍而骄傲地炫耀着不被干扰的优越感。这时,她反而被阳光中的阴影部分打动,鼻子抽吸起来,对即将发生的各种想象使她无法平静下来。
他醒了。他把手掌摊在她面前,他拿眼角余光看她,似乎掌握了她的秘密,他把她当成一个专攻星座、塔罗、梅花心易的占卜机器人了。
算个命吧。他说。
他的手掌大而松软,她的手指划过那几条纹路时,一股青草混杂着牛马的粪便味升腾,她没有捂住鼻子,反而深深地吸了口气,胸腔与鼻翼舒爽地抽动了一下。“你放过牛?骑过马?你有一个农场?”她试探性地问。他哈哈大笑,她认为那是嘲笑。但这想法阻止不了他把她的手指卷起来,卷得完整,仿佛烟丝被卷在纸中。在她的手心里,是他刚放入的一瓶淡绿色药水,上面的标签写着“LSD通用型”。“喝下去。”他命令她。她没有拒绝。随后,他们并肩坐在床上,等药力开始作用,“感觉到了没有?”他问。“什么?”她的脑袋已经开始变得庞大,耳膜上是大自然的各种声音,夜深人静时听到的天籁的放大声,一张薄雾拉丝的面罩徐缓地从额头顶端下落,一层层地,不断地下落,面罩却没有因此变得更厚,依旧朦胧与透明,映照出一幕幕她熟悉与陌生的画面,她不由自主地大笑起来,她听到他也在笑,“好好享受吧,”他说,“以后想尝试就再也没有了。”双手贴住她的太阳穴,他将她的脑袋向自己拉近,抵到自己的额头上,充满红血丝的眼睛盯着她的眼睛,“那下面,什么也没有,但你一下子摔不死,没有供给,没有检修,你的身体会慢慢生锈、断裂、成为瘸子、瞎子与聋子,最后,你再也爬不动了,一动也不能动,大脑死机了。死了。”他叨叨着,抱住她,开始亲她。
接下来,她经历的似乎是:火车呜咽一声停下,他带她去了一个陌生城市的一间陌生的屋子;又或者是,他们所在的这节车厢脱离了其他车厢,从铁轨上滚下山坡。地点不确定,她清晰地记得,在一个封闭的空间里,他和她在一起,睡觉,做爱,说话,喝水,吃一点点东西,在一种难以分辨是快乐还是痛苦的情绪作用下,她哭了很长时间。而他的脸呈狮子状,张口的样子像是随时准备咆哮。她无法不屈从。
“你害怕什么?”他问。
“害怕你害怕的。”她说。
“骗子,你害怕暴露身份,你压根就不是我的同类。”他试探她。
“我是。”
“你不是。”
争辩很无谓。他们用身体解决。冷感的乳头被抚摩坚硬后,他便进入了,不等她略微潮湿。但是,那突如其来的冲击激发了她喉咙里的怒火,她后来居上地成了主控者。这点让他很惬意,并发出享受的哼哼唧唧。
“你果然是我们中的一个。”他说,“但你像在逃避什么?”
“逃避本身。”她说。
她很不自信地说出“逃避本身”四字。任何词语都有一个身体。逃避本身是一条没有尽头的巷子,巷子里躲藏着无数被驱逐的千奇百怪的人,有他的同类,也包括她的同类。
“逃避本身与现实中的你关联在哪里?”
“关联就是我真的不是你们的同类。”她哆嗦着,终于喊了出来。
“骗子!”他不信任地看着她,摇晃她,“如果你真的不是我的同类,你就不会在这节车厢里。”
她不知该怎么向他道歉,好在,浑身的晕眩被新升涌的血流击退,她已有力气驱赶幻觉。甩开他,她跑向紧急隔离门,用明信片上的二维码扫过电子眼,进入火车的最后一节车厢。药力仍有残余,到终点站时,她如一只收紧的海葵蜷缩在角落里。前面的车厢开始下坠,发出惊心动魄的巨响,她听到身旁几个站立的参观者发出啧啧声,一个人说:“真壮观啊!”另一个人说:“安全了,那些恶棍再也不会来打扰我们了。”她勉强站起来,去看他们看着的景象,一个巨大的深渊暗黑无边,一节节车厢像断裂的巨人肢体落下去,她分明被一种“整个人类历史的阴影”穿过,影子向地心陷落,她分明听到一种鲸鱼的哀鸣。
]]>文丨烟波人长安
我有一个朋友,单身好些年,最近看了几场爱情电影,少女心勃发,哭着喊着要谈恋爱。我说这个简单啊,满世界是男人,总有一个你看上眼的吧?难不成你看上的是吴彦祖?
朋友说不是啊,你不觉得谈恋爱很难吗?
哪里难了?我反问,喜欢就表白,成功了就在一起,不成功找下一个,不难啊。
你说得轻松。朋友说,第一眼看上了,要找机会说话吧?万一聊不到一块儿怎么办?两个人都不说话,给谁默哀呢?有了共同话题,就要考虑将来一起生活,是不是又一堆问题?到我这年纪,恋爱多数都是奔着结婚去的,我总不能等结婚了,发现不合适,再离婚吧?
哦,她今年27,家里催婚,已经催了两年。
真的,都快不会谈恋爱了。朋友说。
这种时候,一般都应该我出马,告诉她爱情很美好恋爱很温馨喜欢就要上一类的话,灌一碗鸡汤,打一打鸡血,安慰加鼓励。
但我仔细想想她那一连串问句,发现我没有办法说些这样的话。
再仔细想想,周围单身的朋友一大把,年龄相仿境遇相似,每年的愿望都是脱单,但他娘的,一直没有实现过。也不是没有“好像还挺合适的”那个人,但都没有走到一起。
什么时候,谈恋爱变得这么难了?
想想我们上学的时候,好像一切都很容易。
小学对一个人有好感,可能就因为他是班长、她辫子长,初中对一个人有好感,可能就因为他个子高、她喜欢笑,高中对一个人有好感,可能就因为他学习好、她爱看书。那会儿世界很单纯,不着急表白,不指望回报,看他一眼都觉得开心,天天幻想上学路上会不会偶遇、食堂排队他会不会就在前头,上午出操偶尔打个照面,都面红耳热、心跳能停两分钟。
那会儿的恋爱也很容易,偷偷传个纸条、课间在楼梯拐角说说话、躲着家长把那些小心思写在有锁的日记里,一起出去买个头绳,能做到敢拉个手的,我操,那都是英雄。
到了大学,“恋爱”这件事就开始复杂了,而且随着学龄见长,还在递增。大一,他够不够体贴、能不能全宿舍组队下副本的时候接你电话;大二,他情人节送你什么礼物、用不用心;大三,他暑假会不会陪你、每天的晚安短信有没有忘;大四,他对未来有没有规划、出国考研还是工作、需不需要异地、异地多久……
转眼到了工作,问题又发生了质变,他工资多少、花销多大、我们在哪里开始将来的生活、什么时候结婚、能不能买得起房、他家长怎么看我、要不要孩子、要几个……
这就好像学游泳,一开始只要不淹死就行,后来开始讲究姿势,姿势对了,还要力度,力度对了,就要追求速度(对我说的是游泳,你们不要想歪),发展到最后,标准泳池里不能一口气一个来回,你好意思说你会游泳?
甚至“喜欢”这个词,都会发生变化。年少时候喜欢一个人是“找不同”,越标新立异的,越招人喜欢;越是和自己不一样的,就越吸引你的注意。你活泼,他安静,他坐在那里,身上就有一道光。他抱着个篮球拍拍打打,你在教室一待就是一天,你一拢头发,他就看你一眼。
而一旦到了适婚的年纪,喜欢一个人就变成了“找相同”,兴趣、爱好、性格、家庭背景,有一点相悖,就多一道坎儿。你抱着电脑看韩剧,他说你俗不可耐;他一回家就打游戏,你说他没有上进心。说多了必然争吵,吵多了,一拍两散。
所以歌里唱的“我不想我不想不想长大”,不是唬人的。年龄越大,谈恋爱就越难,因为已经不再是你喜欢我我喜欢你就可以在一起的情况,他背后有他的父母,你背后有你的诉求,两个人站在一块儿,拿着笔画未来,结果一不小心,画出了两条不相交的平行线。
听别人说过一个故事。真假不知道,但很耐琢磨。
一个朋友的朋友,女孩,刚过二十五岁,一场旷日持久的恋爱惨淡收场,从此叫嚣一定要找个有钱的,多老都行。后来真的找到了一个。男方大她十岁,不过她不介意,欢天喜地地,搬进了别墅。
不到半年,女孩自己提出分手,又搬了出来。
不是男的不好,据她说,也是温柔体贴善解人意那种,但就有一个问题,两个人的生活方式相差太远。
男的大学没毕业就自己闯荡,拼到三十五岁,有两家公司,平时忙进忙出,很少有休息时间,休闲娱乐以深夜看球为主。女孩美国读的硕士,原则性不加班,工作绝对不带回家,电影话剧如数家珍。两个人一天说不了几句话,一说话就闹矛盾。她不明白他为什么没有时间陪她哪怕逛一次街,他不明白为什么她要看一些不搞笑、不刺激,全程都没几句台词的电影。
不是不理解,是没有办法理解。
女孩以为,为了别墅,她可以无条件忍让,后来发现如鲠在喉,吐不出来,咽不下去。
朋友们都羡慕她找了个有钱的,下半辈子吃穿不愁。她羡慕她们的男朋友虽然工薪一族,但每天都可以坐在一起,吃一顿晚饭。
一提到谈恋爱,我们每个人都说自己要求不高,有钱就可以、有胸就可以、一米八就可以、软妹子就可以,可真的要一起走下去,这些都变成了附加题,答对了加分,但能不能通过考试,看的反而不是这些。
有时候一想前路艰难,我们就退缩了。反正一个人好像也挺舒服,犯不着拿两个人生活的繁琐和磨合来折腾自己。
于是很长时间都是一个人。于是每一年结束,“脱单”后头都画着一个叉。
于是情人节闺密爽约去陪她临时取消加班的男朋友,你带着刚画好的妆缩在沙发里刷手机,外卖叫了一份单人餐。于是酒局未能成行好哥们儿在家哄老婆,你重新打开电脑鼠标停在熟悉的游戏上,犹豫是不是再打一盘。
于是你又翻开了朋友圈、微博,看看那个人在干什么。
看到Ta也是一个人过,你长出了一口气。
“情人节快乐!”你留言。
]]>最近想把UUID32位的主键策略改为6位的INT自增的主键,来改善个人网站的优雅和简洁性。于是找了些资料整理在这。由于我的网站用的注解,来减少xml配置文件带来的烦躁,所以主键策略一定要是基于注解的。
hibernate在JPA的基础上进行了扩展,可以用一下方式引入hibernate独有的主键生成策略,就是通过@GenericGenerator加入的。**
比如说,JPA标准用法**1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Id
@GeneratedValue(GenerationType.AUTO)
就可以用hibernate特有以下用法来实现
@GeneratedValue(generator = "paymentableGenerator")
@GenericGenerator(name = "paymentableGenerator", strategy = "assigned")
@GenericGenerator的定义:
@Target({PACKAGE, TYPE, METHOD, FIELD})
@Retention(RUNTIME)
public @interface GenericGenerator {
String name();
String strategy();
Parameter[] parameters() default {};
}
对于这些hibernate主键生成策略和各自的具体生成器之间的关系,在org.hibernate.id.IdentifierGeneratorFactory中指定了,
1 |
|
1 | @GeneratedValue(generator = "paymentableGenerator") |
1 | @GeneratedValue(generator = "paymentableGenerator") |
1 | @GeneratedValue(generator = "paymentableGenerator") |
1 | @GeneratedValue(generator = "paymentableGenerator") |
1 | @GeneratedValue(generator = "paymentableGenerator") |
1 | @GeneratedValue(generator = "paymentableGenerator") |
1 | @GeneratedValue(generator = "paymentableGenerator") |
1 | @GeneratedValue(generator = "paymentableGenerator") |
1 | @GeneratedValue(generator = "paymentableGenerator") |
1 | @GeneratedValue(generator = "idGenerator") |
@GeneratedValue(generator = “paymentableGenerator”)
@GenericGenerator(name = “paymentableGenerator”, strategy = “guid”)1
## 12、uuid.hex
@GeneratedValue(generator = “paymentableGenerator”)
@GenericGenerator(name = “paymentableGenerator”, strategy = “uuid.hex”)1
2
## 13、sequence-identity
@GeneratedValue(generator = “paymentableGenerator”)
@GenericGenerator(name = “paymentableGenerator”, strategy = “sequence-identity”,
parameters = { @Parameter(name = “sequence”, value = “seq_payablemoney”) })
1 | # 三、通过@GenericGenerator自定义主键生成策略 |
public class AssignedSequenceGenerator extends SequenceGenerator implements
PersistentIdentifierGenerator, Configurable {
private String entityName;
public void configure(Type type, Properties params, Dialect dialect) throws MappingException {
entityName = params.getProperty(ENTITY_NAME);
if (entityName==null) {
throw new MappingException(“no entity name”);
}
super.configure(type, params, dialect);
}
public Serializable generate(SessionImplementor session, Object obj)
throws HibernateException {
Serializable id = session.getEntityPersister( entityName, obj )
.getIdentifier( obj, session.getEntityMode() );
if (id==null) {
id = super.generate(session, obj);
}
return id;
}
}1
实际应用中,定义同sequence。
@GeneratedValue(generator = “paymentableGenerator”)
@GenericGenerator(name = “paymentableGenerator”, strategy = “AssignedSequenceGenerator”,
parameters = { @Parameter(name = “sequence”, value = “seq_payablemoney”) })
1 | 值得注意的是,定义的这种策略,就像打开了潘多拉魔盒,非常不可控。正常情况下,不建议这么做。 |
设置自增长主键的初始值
1 | alter table test AUTO_INCREMENT = 500000; |
撑着油纸伞,独自
彷徨在悠长、悠长
又寂寥的雨巷
我希望逢着
一个丁香一样地
结着愁怨的姑娘
她是有
丁香一样的颜色
丁香一样的芬芳
丁香一样的忧愁
在雨中哀怨
哀怨又彷徨
她彷徨在这寂寥的雨巷
撑着油纸伞
像我一样
像我一样地
默默行着
寒漠、凄清,又惆怅
她默默地走近
走近,又投出
太息一般的眼光
她飘过
像梦一般地
像梦一般地凄婉迷茫
像梦中飘过
一枝丁香地
我身旁飘过这女郎
她静默地远了、远了
到了颓圮的篱墙
走尽这雨巷
在雨的哀曲里
消了她的颜色
散了她的芬芳
消散了,甚至她的
太息般的眼光
丁香般的惆怅
撑着油纸伞,独自
彷徨在悠长、悠长
又寂寥的雨巷
我希望飘过
一个丁香一样地
结着愁怨的姑娘
• 茫茫的人海中,你很难第一时间发现我。
• 有时候我希望我是一个胖子,这样我可以多占点地方,有时候我希望我能赶紧秃顶,这样还会显得与众不同,可是我就是一个过着安安静静,一成不变生活的男人。
• 所以我期待改变,如果我注定改变不了自己,我希望我能改变下你的生活,带给你点依靠,让我有点责任。带给你些帮助,让我有点动力。带给你些温暖,让我有点充实。
• 我可能会喜欢你的任性,矫情,蛮不讲理,也可能会喜欢你的无赖,泼皮,漫不经心。 亦或者,我会喜欢你的唠唠叨叨,黏黏糊糊。
• 请原谅的我笨拙,因为我实在描绘不出你的样子,但是我知道,一定有生气时撅起的嘴,一定要开心时放肆的笑,如果一定要在我们的画布上填上一点元素,可不可以有害羞的捶胸,埋首的娇嗔,不舍得凝望和对于上天赐予彼此的感恩.如果可以选择不要,可不可以让我偷偷的把眼泪,失望,怨恨,分离丢弃到垃圾桶里,让城市化妆师带他们去离我们遥远的深山老林,要么焚烧,要么深埋。
• 我期待,暂时也只是期待,因为我不知道我能不能令人满意,我唯一确定的就是我会努力,无论结果怎么样,我期待我能成为你清晨的一缕阳光,我期待我能成为你寒冬的一袭暖风,我期待我是你干涸时刻的一阵清露,我期待我是你孤独无助之际的一怀拥抱。
• 新的一年沉淀我的孤独,等待你的出现,因为我又积攒够了勇气再次出发。
]]>第一次使用Mac OS X下的时光机器(Time Machine),就被苹果如此完美贴心的备份恢复方案打动。数据无价,电脑里留下了岁月的痕迹和凝结了我们的心血,数据备份的重要性是怎么强调都不为过的。Time Machine是对这些数据最好的保护。只要你已经使用Time Machine备份过,设想一下:
如果你熟悉git,那Time Machine简直就是一个操作系统级别的版本控制!
当我安装好一个新的Mac OS X系统以后,第一件事情,就是设置Time Machine。
现在云存储已经普及,容量也足够大,但是Time Machine还是有不可替代的优势:
简单地说,云存储就是一个简单的Windows操作系统级别的拷贝备份,和Time Machine细颗粒度,多版本,包括数据、配置、状态的完整备份恢复方案相比,弱爆了。
基于上述理由,郑重提醒,在设置Time Machine的exclude目录的时候,千万不要将云存储映射的目录排除在外。否则这些你认为已经被云存储保护过的资料,就不能享受Time Machine的保护,而这些资料往往是你最重要的资料。
Time Machine可以使用下面一些方式进行,我还是选择简单、可靠、廉价的第一种方案:
基于速度考虑,建议使用USB 3或者雷电高速接口,硬盘的大小可以选择1T或者2T,这个硬盘将伴随着你,保护你一生的数据资料。移动硬盘的分区方案,参见[安装]部分说明(https://github.com/liuhouer/macdev/blob/master/install.md)
无线方案,使用方便。缺点是Time Capsule发热量大,速度相对于有线方案也较慢。
看上去是一个理想的方案,但是设置好备份以后,不知道由于什么原因出现过几次备份无效,导致需要完全重新备份,后来丢弃了这个方案。
Time Machine这么强大的功能,设置其实很简单,苹果官方网站有详细的指南,我在这里就不再重复。
需要注意的是,Mac OS X会在运行中产生一些临时数据或者中间数据,这些数据往往占用空间较大,而没有保存价值,可以设置成exclude,常见的有:
/Library/Application Support//Library/Caches//private/var/log//private/var/vm//private/var/tmp/~/Downloads/~/Library/Caches~/Library/Application Support/MobileSync/~/.Trash/~/Documents/Virtual\ Machines.localized/~/Library/Parallels
备份完毕以后,移动硬盘需要在Finder下umount才能拔出,否则有可能数据丢失。
]]>
def square_of_sum(L): sum = 0 for i in L: sum += i * i return sumprint square_of_sum([1, 2, 3, 4, 5])print square_of_sum([-5, 0, 5, 15, 25])
1 | 我们对柱子编号为a, b, c,将所有圆盘从a移到c可以描述为: |
def move(n, a, b, c): if n==1: print a, "-->", c return move(n-1, a, c, b) print a,"-->",c move(n-1, b , a, c)
def greet(b='world'): print 'hello, '+b+'.'greet()greet('Bart')
def fn(*args):print args
假设我们要计算任意个数的平均值,就可以定义一个可变参数:
def average(*args):
sum = 0.0if len(args)==0: return sumelse: for i in args: sum+=ireturn sum / len(args)
print average()print average(1, 2)print average(1, 2, 2, 3, 4)
range(1, 101)[1, 2, 3, ..., 100]
对应上面的问题,取前3个元素,用一行代码就可以完成切片:
L[0:3]
如果第一个索引是0,还可以省略:
L[:3]
也可以从索引1开始,取出2个元素出来:
L[1:3]
只用一个 : ,表示从头到尾:
L[:]
切片操作还可以指定第三个参数:
L[::2]
[‘Adam’, ‘Bart’]第三个参数表示每N个取一个,上面的 L[::2] 会每两个元素取出一个来,也就是隔一个取一个。{第三个参数每隔N个数取第一个数}
字符串有个方法 upper() 可以把字符变成大写字母:
‘abc’.upper()
提示:利用切片操作简化字符串操作。def firstCharUpper(s): return s[:1].upper()+s[1:]print firstCharUpper('hello')print firstCharUpper('sunday')print firstCharUpper('september')
集合是指包含一组元素的数据结构,我们已经介绍的包括:1. 有序集合:list,tuple,str和unicode;2. 无序集合:set3. 无序集合并且具有 key-value 对:dict
1 | //第一种实现 |
1 | //第三种实现 ,range有步长功能,不需要切片 |
zip([10, 20, 30], [‘A’, ‘B’, ‘C’])
[(10, ‘A’), (20, ‘B’), (30, ‘C’)]在迭代 [‘Adam’, ‘Lisa’, ‘Bart’, ‘Paul’] 时,如果我们想打印出名次 - 名字(名次从1开始),请考虑如何在迭代中打印出来。
1 | L = ['Adam', 'Lisa', 'Bart', 'Paul'] |
1 | ① d.values |
d = { 'Adam': 95, 'Lisa': 85, 'Bart': 59, 'Paul': 74 }打印出 name : score,最后再打印出平均分 average : score。d = { 'Adam': 95, 'Lisa': 85, 'Bart': 59, 'Paul': 74 }sum = 0.0for k, v in d.items(): sum = sum + v print k,":",vprint 'average', ':', sum/len(d)
任务请利用列表生成式生成列表 [1x2, 3x4, 5x6, 7x8, ..., 99x100]提示:range(1, 100, 2) 可以生成list [1, 3, 5, 7, 9,...]
1 | print [x *(x+1) for x in range(1,101,2)] |
-提示:红色可以用
1 | d = { 'Adam': 95, 'Lisa': 85, 'Bart': 59 } |
列表生成式的 for 循环后面还可以加上 if 判断。例如:>>> [x * x for x in range(1, 11)][1, 4, 9, 16, 25, 36, 49, 64, 81, 100]如果我们只想要偶数的平方,不改动 range()的情况下,可以加上 if 来筛选:>>> [x * x for x in range(1, 11) if x % 2 == 0][4, 16, 36, 64, 100]有了 if 条件,只有 if 判断为 True 的时候,才把循环的当前元素添加到列表中。
提示:1. isinstance(x, str) 可以判断变量 x 是否是字符串;2. 字符串的 upper() 方法可以返回大写的字母。def toUppers(L): return [s.upper() for s in L if isinstance(s, str)]print toUppers(['Hello', 'world', 101])
for循环可以嵌套,因此,在列表生成式中,也可以用多层 for 循环来生成列表。
对于字符串 ‘ABC’ 和 ‘123’,可以使用两层循环,生成全排列:
[m + n for m in ‘ABC’ for n in ‘123’]
[‘A1’, ‘A2’, ‘A3’, ‘B1’, ‘B2’, ‘B3’, ‘C1’, ‘C2’, ‘C3’]
翻译成循环代码就像下面这样:
L = []
for m in ‘ABC’:
for n in '123': L.append(m + n)
print [a*100+b*10+c for a in range(1,10) for b in range(0,10) for c in range(0,10) if a==c]
]]>raw字符串表示不需要转译里面的特殊字符
屏蔽转译r'i'm a "nice" student' = 'i\'m a \"nice\" student'
'''内容 内容2 内容3 内容4''' 会直接输出这4行内容
print u '中文' ## 表示输出unicode编码的各种文字【maxosx10自带的python2.7以上已经默认支持unicode】
append追加到list队尾
insert(index_,'content')插入到index_的后边
pop()删除并且返回list的最后一个元素
pop(index_)删除并且返回list的第index_个元素
tuple元素创建以后,只能访问不能修改。
if else语句后面条件分别有: ,执行逻辑前面有4个空格
1 | score = 55 |
1 | score = 85 |
L = [75, 92, 59, 68]sum = 0.0for s in L: sum += sprint sum / 4
sum = 0x = 1while x<100: sum+= x x+=2print sum
sum = 0x = 1n = 1while True: if n>20: break sum+=x x = x * 2 n+=1print sum
sum = 0x = 0while True: x = x + 1 if x > 100: break if x % 2 == 0: continue sum += xprint sum
for x in [ 1,2,3,4,5,6,7,8,9]: for y in [ 0,1,2,3,4,5,6,7,8,9 ]: if x<y: print (x*10+y)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
d = {
'Adam': 95,
'Lisa': 85,
'Bart': 59
}
d = {
'Adam': 95,
'Lisa': 85,
'Bart': 59
}
print 'Adam:',d.get('Adam')
print 'Lisa:',d.get('Lisa')
print 'Bart:',d.get('Bart')
虽然Mac OS X 自带了Python,但是我们可以选择使用Homebrew安装Python 2.7.x。因为:
安装python 2.7.x:
brew install python --with-brewed-openssl
安装python 3.x:
brew install python3 --with-brewed-openssl
我们使用pip来管理软件包:
sudo easy_install pip
由于pip从pypi下载软件包,境外的pypi站点非常慢,导致安装缓慢或者下载失败。所幸清华大学和豆瓣都提供了pypi镜像,可以通过下面的配置文件来配置:
$ vim ~/.pip/pip.conf; http://www.pypi-mirrors.org/[global]use-mirrors=truemirrors=http://pypi.douban.comindex-url=http://pypi.douban.com/simple
安装软件包:
sudo pip install virtualenv virtualenvwrapper
创建virtualenv目录:
mkdir ~/virtualenvs
在.zshrc或者.bash_profile里配置virtualenv:
# virtualenvwrapperexport VIRTUALENVWRAPPER_PYTHON=/usr/bin/pythonexport WORKON_HOME=~/virtualenvs[ -f /usr/local/bin/virtualenvwrapper.sh ] && source /usr/local/bin/virtualenvwrapper.sh[ -f /etc/bash_completion.d/virtualenvwrapper ] && source /etc/bash_completion.d/virtualenvwrapperexport PIP_VIRTUALENV_BASE=$WORKON_HOMEexport PIP_RESPECT_VIRTUALENV=true
创建第一个virtualenv:
mkvirtualenv --no-site-packages test
PyCharm是python最好的集成开发工具,目前有专业版和社区版可供选择。
]]>git给我带来的好处:
##- 本地的版本管理
不需要远程或架设服务器就能做到本地版本管理.
不污染子目录的track文件
svn每个子目录都要扔一个.svn.这个实在是.. .(我想很多人都碰到过svn lock folder的情况.实在让人气急败坏.实际上.svn文件就是罪魁祸首.各种clean up无果. delelte后svn up异常.真是.. 摔!.去你妹的svn.)
##- 强大的branch
超轻量级的branch建立.(实际只是建立文件指针).推荐根据的git workflow的开发流程.将workspace分成几区.master dev feature hotfix区等.开发层次就出来了.
##- merge工具的强力
git根据commit ticket依次再进行一次merge.提高了merge成功率.避免svn merge中的难堪.即使merge失败.也不会生成乱七八糟的版本文件.简单修改后.commit就是.
##- 神奇的git gc
由于git本身不保存文件之前的差异文件.只保存每个文件的快照.所以在频繁修改大文件的情况下会造成git目录变得肥大不堪.git早就有了解决方案.git gc后,会在.git目录下生成一个packfile与idx文件.只保存文件差异.满塞!.
##- 清爽实用的cli
嗯,我在离开”乌龟”之后是彻底就不会用svn了.看来应该还是内心在抵触.没有学习.
计算机世界所有的问题都可以通过添加一个间接层来实现
git确实增加了一层间接层,实现了去中心化scm工具.当然增加了一点学习的成本.初次接触可能不知道push跟pull的作用.set origin的意义.当然只在本地做管理的话,是基本没有所谓成本的.
##- github
github作为新一代的程序员靠实力,凭作品交流的sns+code host平台.将geek精神贯彻整站.cool!.相对而言google code则正在走下坡路.
可能还有一些好处或者弊端.没看到一个产品的弊端说明你没真正理解它?我可能也只是单纯的崇拜.这篇还有待继续编辑.待我有了更多的经验.或许这篇文章会变成”git为什么这么烂?”也说不定.
svn 是中央集权,没了服务器就都没了。git是分布式,没了服务器,自己本地也能查看历史记录,分支操作什么的。
cvs, git, svn都用过,以下完全是主观感受:
Talk is cheap,show me your code ~
/** * @desc 随机取出一个数【例如size 为 10 ,取得类似0-9的区间数】 * @return */ public static Integer getRandomOne(List<?> list){ Random ramdom = new Random(); int number = -1; int max = list.size(); //例如size 为 10 ,取得类似0-9的区间数 number = Math.abs(ramdom.nextInt() % max ); return number; } //初始化一个list public static final List<String> COLOR_LIST = new LinkedList<String>(); static{ COLOR_LIST.add("#9013FE");//紫色 COLOR_LIST.add("#E73D52");//红色 COLOR_LIST.add("#4A90E2");//蓝色 COLOR_LIST.add("#50E3C2");//绿色 COLOR_LIST.add("#FFA101");//黄色 };//随机取出颜色集合的一个public static void main(String[] args) { String randomcolor = COLOR_LIST.get(getRandomOne(COLOR_LIST)); System.out.println(randomcolor); }
]]>public class Lookup{ public static void main(String[] args){ String s = "the instruction set of the Java virtual machine distinguishes its operand types using instructions intended to operate on values of specific types"; String[] word = s.split(" "); Map<String,Integer> m = new HashMap<String,Integer>(); //用word初使化m,m中包含了所有不重复的单词 for(int j=0;j<word.length;j++){ m.put(word[j],0); } Set<String> set = m.keySet(); //用word中的每个单词与m中的单词比较,发现相同的就统计一次 for(int i=0;i<word.length;i++){ Iterator<String> it = set.iterator(); while(it.hasNext()){ String k = it.next(); if(word[i].equals(k)){ int c = m.get(k); c++; m.put(word[i],c); } } } System.out.println(m); }}
]]>1.登陆到SVN服务器
1 | 帐号: ssh test@192.168.1.100 |
2.新建SVN项目
a、 进入svn创建项目的目录
1 | cd /etc/apache2/mods-enabled/ |
b、 编辑文件1
sudo vi dav_svn.conf
密码: 123456(即账户test的登录密码,下同)
c、 添加新项目(testsvn为例),在文件末尾添加以下代码1
2
3
4
5
6
7
8
9
<Location /testsvn>
DAV svn
SVNPath /home/fruits/svn/projects/code/testsvn
AuthType Basic
AuthName "Subversion repository"
AuthUserFile /etc/svn-auth-file
Require valid-user
</Location>
1 | sudo svnadmin create /home/fruits/svn/projects/code/testsvn |
注:现在可以通过TortoiseSVN下载svn资源了,但还不能上传(因为用户没有写权限)
1 | sudo chmod 777 -R /home/fruits/svn/projects/code/testsvn |
注: -R 递归设置testsvn文件夹下的所有权限为读+写+执行
1 | http://192.168.1.100/testsvn/ |
或 命令下载svn资源如下1
svn co http://192.168.1.100/testsvn/ testsvn
b. 上传本地文件到svn上1
2
3
4> a、 新建文件 aaa.txt, 输入:doodlemobile
> b、 右键——》TortoiseSVN ——》Add...
> c、 右键——》 SVN Commit... ——》 输入更改记录,如:add aaa.txt
> d、 打开浏览器,输入:http://192.168.1.100/testsvn/,查看是否上传成功!
1 | 解决: 这是因为还没有创建项目资源库(testsvn),因此无法访问到此文件,解决方法请见上述步骤3 |
2) Permission denied 错误1
解决: 这是用户没有写权限(无法上传文件),解决方法请见上述步骤4
DELETE FROM posts WHERE id IN ( SELECT * FROM ( SELECT id FROM posts GROUP BY id HAVING ( COUNT(id) > 1 ) ) AS p)
]]>在我四岁那年,父亲送了我一台Xbox。你们了解的,如果我没记错的话那是2001年的款式,一个黑色硬梆梆的盒子。我和父亲一起玩了很多游戏,非常开心,直到两年后,我的父亲去世了。
之后的十年时光里,我再也没有碰过这台游戏机。
]]>然而当我再度启动它时,我发现了一些事情……
我和父亲曾经一起玩过一款赛车游戏叫《越野挑战赛》,在当时,这真的是款很好玩的游戏。
就在我重新启动这款游戏时,我发现了一个真正的幽灵!
这款游戏有个奇妙的设定,上一轮比赛中最快的选手的影子将会出现在接下来的比赛中,与选手一起参赛,就是所谓的“幽灵驾驶者”。我想你一定猜到了。没错,当年我父亲的幽灵至今仍然在赛道上奔驰着。
于是我一遍又一遍的玩着,试图打败这个幽灵,慢慢的,我终于接近了它的速度,甚至直到有一天我超过了它,然后……
我在终点线前停了下来,这样爸爸的幽灵就不会消失了。
Talk is cheap,show me your code ~
①
/** * list去重-通过Iterator 的remove方法; * @return */ public static List<String> listRM1(List<String> list) { List<String> listTemp= new ArrayList<String>(); Iterator<String> it=list.iterator(); while(it.hasNext()){ String a=it.next(); if(listTemp.contains(a)){ it.remove(); } else{ listTemp.add(a); } } return listTemp; }
②
/** * list去重-直接将结果赋值给另一个List; * @return */ public static List<String> listRM(List<String> list) { List<String> tempList= new ArrayList<String>(); for(String i:list){ if(!tempList.contains(i)){ tempList.add(i); } } return tempList; }
③
/** * list去重-用set去重 * @return */ public static List<String> listRM3(List<String> list) { List<String> listTemp= null; HashSet<String> set = new HashSet<String>(); set.addAll(list); listTemp = new ArrayList<String>(set); return listTemp; }
]]>Talk is cheap,show me your code ~
/** * 图片压缩处理 * @author bruce */ public class ImgCompress { public Image img; public int width; public int height; /** * 构造函数 */ public ImgCompress(String fileName) throws IOException { File file = new File(fileName);// 读入文件 img = ImageIO.read(file); // 构造Image对象 width = img.getWidth(null); // 得到源图宽 height = img.getHeight(null); // 得到源图长 } /** * 按照宽度还是高度进行压缩 * @param w int 最大宽度 * @param h int 最大高度 */ public void resizeFix(int w, int h) throws IOException { if (width / height > w / h) { resizeByWidth(w); } else { resizeByHeight(h); } } /** * 以宽度为基准,等比例放缩图片 * @param w int 新宽度 */ public void resizeByWidth(int w) throws IOException { int h = (int) (height * w / width); resize(w, h); } /** * 以高度为基准,等比例缩放图片 * @param h int 新高度 */ public void resizeByHeight(int h) throws IOException { int w = (int) (width * h / height); resize(w, h); } /** * 强制压缩/放大图片到固定的大小 * @param w int 新宽度 * @param h int 新高度 * @return */ public void resize(int w, int h) throws IOException { // SCALE_SMOOTH 的缩略算法 生成缩略图片的平滑度的 优先级比速度高 生成的图片质量比较好 但速度慢 BufferedImage image = new BufferedImage(w, h,BufferedImage.TYPE_INT_RGB ); image.getGraphics().drawImage(img, 0, 0, w, h, null); // 绘制缩小后的图 File destFile = new File("//Users//zhangyang//Downloads//222.jpg"); FileOutputStream out = new FileOutputStream(destFile); // 输出到文件流 // 可以正常实现bmp、png、gif转jpg JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(out); encoder.encode(image); // JPEG编码 out.close(); } /** * 强制压缩/放大图片到固定的大小 * @param w int 新宽度 * @param h int 新高度 * @return */ public static byte[] resizeByte(MultipartFile file , String type) throws IOException { CommonsMultipartFile cf= (CommonsMultipartFile)file; DiskFileItem fi = (DiskFileItem)cf.getFileItem(); File f = fi.getStoreLocation(); Image img = ImageIO.read(f); // 构造Image对象 int w = img.getWidth(null); // 得到源图宽 int h = img.getHeight(null); // 得到源图长 byte[] b = null; // SCALE_SMOOTH 的缩略算法 生成缩略图片的平滑度的 优先级比速度高 生成的图片质量比较好 但速度慢 BufferedImage image = new BufferedImage(w, h,BufferedImage.TYPE_INT_RGB ); image.getGraphics().drawImage(img, 0, 0, w, h, null); // 绘制缩小后的图 ByteArrayOutputStream out = new ByteArrayOutputStream(); try { ImageIO.write(image, type , out); b = out.toByteArray(); } catch (IOException e) { e.printStackTrace(); }finally{ out.close(); } return b;} @SuppressWarnings("deprecation") public static void main(String[] args) throws Exception { System.out.println("开始:" + new Date().toLocaleString()); ImgCompress imgCom = new ImgCompress("//Users//zhangyang//Downloads//22.jpg"); imgCom.resizeFix(1600, 2300); System.out.println("结束:" + new Date().toLocaleString()); }
}
]]>Talk is cheap,show me your code ~
<title>图片上传预览</title><input type="file" id="fileElem" multiple accept="image/*" onchange="handleFiles(this)"><div id="fileList" style="width:200px;height:200px;"></div><script> window.URL = window.URL || window.webkitURL; var fileElem = document.getElementById("fileElem"), fileList = document.getElementById("fileList"); function handleFiles(obj) { var files = obj.files; for(var _i=0;_i<files.length;_i++){ var img = new Image(200); if(window.URL){ img.src = window.URL.createObjectURL(files[_i]); //img.width = 200; img.onload = function(e) { window.URL.revokeObjectURL(this.src); } fileList.appendChild(img); }else if(window.FileReader){ //opera不支持createObjectURL/revokeObjectURL方法。我们用FileReader对象来处理 var reader = new FileReader(); reader.readAsDataURL(files[_i]); reader.onload = function(e){ img.src = this.result; //img.width = 200; fileList.appendChild(img); } }else{ //ie obj.select(); obj.blur(); var nfile = document.selection.createRange().text; document.selection.empty(); img.src = nfile; //img.width = 200; fileList.appendChild(img); } } }</script>
]]>倒不是说她的情感是假的,那个时代确实有很多人对毛主席有感情。她属于入戏不深的,入戏深的,跟着自杀都有可能。问题在于,这些情感被混入了一些奇怪的东西:对于什么样的情感是正当和合法的定义。
刻奇并不只是跟政治因素有关。前段时间,有个很红的音乐选秀节目,上来一个90后的小伙子,唱完了一首歌,讲你有什么梦想的时候,他一把鼻涕一把眼泪地讲我们90后多么多么不容易,现在社会对我们90后有很多偏见,我自己做音乐,有很多人不赞成,觉得不务正业,但是我一定要努力做给大家看,证明我们90后也是有梦想的一代。最后他停下眼泪,振臂高呼,请评委老师给我一个机会。
我心说,小伙子,你刻奇了。
刻奇的问题不在于这些情感是什么——要说自我感动,我们也经常把自我感动哭——而在于在表达这些情感的时候,一直有一双眼睛,小心翼翼地透过指缝瞄别人,以确定自己的情感是否正常,应该扩大它还是应该缩小它。当我们这么做的时候,情感不再只是自然流露,它成为了一个工具。参与社会、扩大传播、价值变现的工具。
无论表面的情感多么热烈和丰富,刻奇的背后,都有一个冷冰冰的强大的秩序,和一个虚弱的小我。
强大的秩序定义了什么情感是正当的、高尚的、合法的,而什么情感是不正当的、低劣的、不被允许的。强大的秩序有时候是政治或权力,有时候是我们想加入的社会团体,有时候是商业运行法则,有时候甚至就是那些我们身边那些我们在乎的、渴望被他们认同和接纳的父母、老师、同事朋友。
虚弱的小我,则在不停地东张西望,以确定自己想表达的情感是被接纳的、合乎时宜的。当情感的合法性来自外界,情感表达就变成了一个工具,一种矫揉造作的表演,一种讨好和谄媚。这并非说这种感动一定就不真实,但就像报界常爱引用的一句话:“若批评不自由,则赞美无意义”,如果感动的背面,愤怒、伤害、委屈是不允许被表达的,那感动也失去了它的真实性。我们的真实情感开始被忽略,社会公众领域开始入侵私人情感领域,我们开始找不到自己。
哪些情感能成为“刻奇”的情感是有时代性的。一般来说,那些肤浅的自我感动似的情感更容易成为刻奇的情感,因为它人畜无害,而且更容易被社会群体理解和复制,进而反过来成为一种政治正确的群体压力。但也不一定,80年代,刚刚从文革中解脱出来的年轻人都爱读诗、谈论尼采、弗洛伊德、昆德拉,不说这些都不好意思跟人打招呼,那昆德拉自己就成为了刻奇的一部分了。
无论我们如何倡导感动、崇高、积极幸福、小清新,不喜欢焦虑抑郁、愤怒、委屈和牢骚,情感仅仅因为真实而合法,它的合法性不依赖于其它价值判断。所以题主你也别问我怎么不刻奇了,因为如果你参考我的建议,那不是又刻奇了吗?无论别人怎么说三道四,你的情感值得被自己尊重,所以你爱感动啥感动啥,爱谁谁好了。
]]>• 从前有一个住在森林边缘的小孩子,他是一个很礼貌的孩子。他认识了来自森林里的一头熊,他和这个熊很快成为了朋友。但是有一个问题,就是这个熊实在是嘴太臭了。而且这个熊总是喜欢走的很近和他说话,这个孩子非常头疼。但是由于他真的很喜欢这头熊,于是他就忍住了这股臭味,每次都笑而不语。但是这股臭味实在是太大了,这个孩子终于被熏得头晕眼花,甚至当熊不在的时候,他看到有毛的动物都会想吐,仿佛那个气味就不停地在身边徘徊着。孩子很愤怒,他很生气为什么这个熊从来都看不到他在接近自己时候,自己的龇牙咧嘴,他恼怒为什么这个熊总是忽略,他略微远离熊嘴的小动作。孩子越想越生气。
• 于是有一天他终于忍不住了,他气冲冲的和熊说“我再也不要和你做朋友了,你这个又脏又臭的笨熊!”熊听了以后愣了一小会,轻轻地从孩子的房间里拿出了一把餐刀,和孩子说“来给我一刀”孩子听了以后大惑不解,但还是照着做了,餐刀在熊的身上划出了一个非常巨大的伤口,鲜血淋漓。但是熊并没有说什么,他告诉孩子“我现在要离开这里,一年以后我会回来找你”说完熊就离开了,果然他走了一年的时间。• 在这一年里,孩子开始怀念熊的温暖,怀念熊的憨厚,怀念熊在的一切一切,他开始记不起来熊的口臭,甚至都记不起来熊把一嘴大黄牙凑过来的样子。孩子很想念熊,他开始在森林的边缘等熊。一年到了,熊回来了,孩子迫不及待的跑过去抱着熊,说“我错了,我不该嫌弃你的口臭。”熊轻轻地拍打着孩子的后背,说“不,你说的对,你应该嫌弃我的口臭。”说完,熊轻轻地推开了孩子,把自己的伤口展现给孩子,“你真的应该嫌弃我的口臭,因为他真的臭。这个问题就像是你在我身上砍的这一刀一样,你看现在这已经好了。”孩子懊悔的抚摸着熊的伤口,果然已经愈合了。熊接着说道“但是你知道,你当时那句又脏又臭的笨熊,在我心里留下的伤口,直到现在还滴着血,它从未愈合过。”说完熊转身离开,慢慢的走回了森林里,再也没有回来过。
]]>brew –help
brew install git
brew uninstall git
brew search git
brew list
brew update
brew upgrade git
brew [info | home] [FORMULA...]
1 | brew cleanup git |
brew outdated
1 | brew list —列出已安装的软件 |
1 | curl http://www.centos.org |
-o:将文件保存为命令行中指定的文件名的文件中
-O:使用URL中默认的文件名保存文件到本地
将文件下载到本地并命名为mygettext.html
1 | curl -o mygettext.html http://www.gnu.org/software/gettext/manual/gettext.html |
将文件保存到本地并命名为gettext.html
1 | curl -O http://www.gnu.org/software/gettext/manual/gettext.html |
同样可以使用转向字符”>”对输出进行转向输出
1 | curl -O URL1 -O URL2 |
若同时从同一站点下载多个文件时,curl会尝试重用链接(connection)。
通过-L选项进行重定向
默认情况下CURL不会发送HTTP Location headers(重定向).当一个被请求页面移动到另一个站点时,会发送一个HTTP Loaction header作为请求,然后将请求重定向到新的地址上。
例如:访问google.com时,会自动将地址重定向到google.com.hk上。1
curl http://www.google.com
上述输出说明所请求的档案被转移到了http://www.google.com.hk。
这是可以通过使用-L选项进行强制重定向
1 # 让curl使用地址重定向,此时会查询google.com.hk站点
2curl -L http://www.google.com
- 断点续传
通过使用-C选项可对大文件使用断点续传功能,如:
1 # 当文件在下载完成之前结束该进程
2 $curl -O http://www.gnu.org/software/gettext/manual/gettext.html
3 ############## 20.1%
4
5 # 通过添加-C选项继续对该文件进行下载,已经下载过的文件不会被重新下载
6curl -C - -O http://www.gnu.org/software/gettext/manual/gettext.html
7 ############### 21.1%
通过–limit-rate选项对CURL的最大网络使用进行限制
1 # 下载速度最大不会超过1000B/second
2
3curl --limit-rate 1000B -O http://www.gnu.org/software/gettext/manual/gettext.html
下载指定时间内修改过的文件
当下载一个文件时,可对该文件的最后修改日期进行判断,如果该文件在指定日期内修改过,就进行下载,否则不下载。
该功能可通过使用-z选项来实现:
1 # 若yy.html文件在2011/12/21之后有过更新才会进行下载
2curl -z 21-Dec-11 http://www.example.com/yy.html
在访问需要授权的页面时,可通过-u选项提供用户名和密码进行授权
1curl -u username:password URL
2
3 # 通常的做法是在命令行只输入用户名,之后会提示输入密码,这样可以保证在查看历史记录时不会将密码泄露
4curl -u username URL
- 从FTP服务器下载文件
CURL同样支持FTP下载,若在url中指定的是某个文件路径而非具体的某个要下载的文件名,CURL则会列出该目录下的所有文件名而并非下载该目录下的所有文件
1 # 列出public_html下的所有文件夹和文件
2curl -u ftpuser:ftppass -O ftp://ftp_server/public_html/
3
4 # 下载xss.php文件
5curl -u ftpuser:ftppass -O ftp://ftp_server/public_html/xss.php
- 上传文件到FTP服务器
通过 -T 选项可将指定的本地文件上传到FTP服务器上
将myfile.txt文件上传到服务器
curl -u ftpuser:ftppass -T myfile.txt ftp://ftp.testserver.com
同时上传多个文件curl -u ftpuser:ftppass -T "{file1,file2}" ftp://ftp.testserver.com
从标准输入获取内容保存到服务器指定的文件中curl -u ftpuser:ftppass -T - ftp://ftp.testserver.com/myfile_1.txt
- 获取更多信息
通过使用 -v 和 -trace获取更多的链接信息
通过字典查询单词1 # 查询bash单词的含义
2curl dict://dict.org/d:bash
3
4 # 列出所有可用词典
5curl dict://dict.org/show:db
6
7 # 在foldoc词典中查询bash单词的含义
8curl dict://dict.org/d:bash:foldoc
-x 选项可以为CURL添加代理功能
1 # 指定代理主机和端口
2curl -x proxysever.test.com:3128 http://google.co.in
其他网站整理
保存与使用网站cookie信息
1 # 将网站的cookies信息保存到sugarcookies文件中
2curl -D sugarcookies http://localhost/sugarcrm/index.php
3
4 # 使用上次保存的cookie信息
5curl -b sugarcookies http://localhost/sugarcrm/index.php
传递请求数据
默认curl使用GET方式请求数据,这种方式下直接通过URL传递数据
可以通过 –data/-d 方式指定使用POST方式传递数据
]]>1 # GET
2 curl -u username https://api.github.com/user?access_token=XXXXXXXXXX
3
4 # POST
5 curl -u username –data “param1=value1¶m2=value” https://api.github.com
6
7 # 也可以指定一个文件,将该文件中的内容当作数据传递给服务器端
8 curl –data @filename https://github.api.com/authorizations
注:默认情况下,通过POST方式传递过去的数据中若有特殊字符,首先需要将特殊字符转义在传递给服务器端,如value值中包含有空格,则需要先将空格转换成%20,如:
1 curl -d “value%201” http://hostname.com
在新版本的CURL中,提供了新的选项 –data-urlencode,通过该选项提供的参数会自动转义特殊字符。
1 curl –data-urlencode “value 1” http://hostname.com
除了使用GET和POST协议外,还可以通过 -X 选项指定其它协议,如:
1 curl -I -X DELETE https://api.github.cim
上传文件
1 curl –form “fileupload=@filename.txt“ http://hostname/resource
1 | •JVM中类的装载是由ClassLoader和它的子类来实现的,Java ClassLoader 是一个重要的Java运行时系统组件。 |
1 |
|
数据的锁定分为两种方法,第一种叫做悲观锁,第二种叫做乐观锁。
1 | •什么叫悲观锁呢,悲观锁顾名思义,就是对数据的冲突采取一种悲观的态度, |
1 | •而乐观锁就是认为数据一般情况下不会造成冲突,所以在数据进行提交更新的时候, |
1 | <bean id="starTask" class="com.mai.task.StarTask"></bean> |
Action:StarTask.java
public class StarTask {
@Autowiredprivate LevelRuleService levelService;public void runTask(){ System.out.println("定时任务开始"+TimeUtils.getNowTime()); levelService.updateAllStar(); System.out.println("定时任务结束"+TimeUtils.getNowTime());}
}
service:LevelRuleServiceImpl.java
@Override
/**
* 批量更新所有社团的星级*/public void updateAllStar() { // TODO Auto-generated method stub try { List<LevelRule> list = levelruleDao.findAll(); List<Society> sl = societyDao.getSocietyListByZTOver0(); if(list!=null&&list.size()>0){ for(LevelRule m: list){ int zan = m.getPraiseNum(); int fan = m.getFollowNum(); String level = m.getLevel(); if(sl!=null&&sl.size()>0){ for(Society s:sl){ int myzan = s.getPraiseNum()==null?0:s.getPraiseNum(); int myfan = s.getFollowNum()==null?0:s.getFollowNum(); if(myzan>=zan && myfan>=fan){ s.setLevel(Integer.parseInt(level)); if(societyDao.updateSociety(s)>0){ Society society = societyService.getSocietyByID(s.getSocietyID()); society.setLevel(Integer.parseInt(level)); societyRedisDao.updateSociety(society); } } } } } } } catch (BaseException e) { // TODO Auto-generated catch block e.printStackTrace(); }}
cron表达式
///////////////////////////////cron表达式详解、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、
Cron表达式是一个字符串,字符串以5或6个空格隔开,分为6或7个域,每一个域代表一个含义,Cron有如下两种语法格式: Seconds Minutes Hours DayofMonth Month DayofWeek Year或 Seconds Minutes Hours DayofMonth Month DayofWeek每一个域可出现的字符如下: Seconds:可出现", - * /"四个字符,有效范围为0-59的整数 Minutes:可出现", - * /"四个字符,有效范围为0-59的整数 Hours:可出现", - * /"四个字符,有效范围为0-23的整数 DayofMonth:可出现", - * / ? L W C"八个字符,有效范围为0-31的整数 Month:可出现", - * /"四个字符,有效范围为1-12的整数或JAN-DEc DayofWeek:可出现", - * / ? L C #"四个字符,有效范围为1-7的整数或SUN-SAT两个范围。1表示星期天,2表示星期一, 依次类推 Year:可出现", - * /"四个字符,有效范围为1970-2099年每一个域都使用数字,但还可以出现如下特殊字符,它们的含义是: (1)*:表示匹配该域的任意值,假如在Minutes域使用*, 即表示每分钟都会触发事件。(2)?:只能用在DayofMonth和DayofWeek两个域。它也匹配域的任意值,但实际不会。因为DayofMonth和DayofWeek会相互影响。例如想在每月的20日触发调度,不管20日到底是星期几,则只能使用如下写法: 13 13 15 20 * ?, 其中最后一位只能用?,而不能使用*,如果使用*表示不管星期几都会触发,实际上并不是这样。 (3)-:表示范围,例如在Minutes域使用5-20,表示从5分到20分钟每分钟触发一次 (4)/:表示起始时间开始触发,然后每隔固定时间触发一次,例如在Minutes域使用5/20,则意味着5分钟触发一次,而25,45等分别触发一次. (5),:表示列出枚举值值。例如:在Minutes域使用5,20,则意味着在5和20分每分钟触发一次。 (6)L:表示最后,只能出现在DayofWeek和DayofMonth域,如果在DayofWeek域使用5L,意味着在最后的一个星期四触发。 (7)W:表示有效工作日(周一到周五),只能出现在DayofMonth域,系统将在离指定日期的最近的有效工作日触发事件。例如:在 DayofMonth使用5W,如果5日是星期六,则将在最近的工作日:星期五,即4日触发。如果5日是星期天,则在6日(周一)触发;如果5日在星期一到星期五中的一天,则就在5日触发。另外一点,W的最近寻找不会跨过月份 (8)LW:这两个字符可以连用,表示在某个月最后一个工作日,即最后一个星期五。 (9)#:用于确定每个月第几个星期几,只能出现在DayofMonth域。例如在4#2,表示某月的第二个星期三。举几个例子: 0 0 2 1 * ? * 表示在每月的1日的凌晨2点调度任务 0 15 10 ? * MON-FRI 表示周一到周五每天上午10:15执行作业 0 15 10 ? 6L 2002-2006 表示2002-2006年的每个月的最后一个星期五上午10:15执行作一个cron表达式有至少6个(也可能7个)有空格分隔的时间元素。 按顺序依次为 秒(0~59) 分钟(0~59) 小时(0~23) 天(月)(0~31,但是你需要考虑你月的天数) 月(0~11) 天(星期)(1~7 1=SUN 或 SUN,MON,TUE,WED,THU,FRI,SAT) 年份(1970-2099)其中每个元素可以是一个值(如6),一个连续区间(9-12),一个间隔时间(8-18/4)(/表示每隔4小时),一个列表(1,3,5),通配符。由于"月份中的日期"和"星期中的日期"这两个元素互斥的,必须要对其中一个设置?0 0 10,14,16 * * ? 每天上午10点,下午2点,4点 0 0/30 9-17 * * ? 朝九晚五工作时间内每半小时 0 0 12 ? * WED 表示每个星期三中午12点 "0 0 12 * * ?" 每天中午12点触发 "0 15 10 ? * *" 每天上午10:15触发 "0 15 10 * * ?" 每天上午10:15触发 "0 15 10 * * ? *" 每天上午10:15触发 "0 15 10 * * ? 2005" 2005年的每天上午10:15触发 "0 * 14 * * ?" 在每天下午2点到下午2:59期间的每1分钟触发 "0 0/5 14 * * ?" 在每天下午2点到下午2:55期间的每5分钟触发 "0 0/5 14,18 * * ?" 在每天下午2点到2:55期间和下午6点到6:55期间的每5分钟触发 "0 0-5 14 * * ?" 在每天下午2点到下午2:05期间的每1分钟触发 "0 10,44 14 ? 3 WED" 每年三月的星期三的下午2:10和2:44触发 "0 15 10 ? * MON-FRI" 周一至周五的上午10:15触发 "0 15 10 15 * ?" 每月15日上午10:15触发 "0 15 10 L * ?" 每月最后一日的上午10:15触发 "0 15 10 ? * 6L" 每月的最后一个星期五上午10:15触发 "0 15 10 ? * 6L 2002-2005" 2002年至2005年的每月的最后一个星期五上午10:15触发 "0 15 10 ? * 6#3" 每月的第三个星期五上午10:15触发有些子表达式能包含一些范围或列表例如:子表达式(天(星期))可以为 “MON-FRI”,“MON,WED,FRI”,“MON-WED,SAT”“*”字符代表所有可能的值因此,“*”在子表达式(月)里表示每个月的含义,“*”在子表达式(天(星期))表示星期的每一天“/”字符用来指定数值的增量 例如:在子表达式(分钟)里的“0/15”表示从第0分钟开始,每15分钟 在子表达式(分钟)里的“3/20”表示从第3分钟开始,每20分钟(它和“3,23,43”)的含义一样“?”字符仅被用于天(月)和天(星期)两个子表达式,表示不指定值 当2个子表达式其中之一被指定了值以后,为了避免冲突,需要将另一个子表达式的值设为“?”“L” 字符仅被用于天(月)和天(星期)两个子表达式,它是单词“last”的缩写 但是它在两个子表达式里的含义是不同的。 在天(月)子表达式中,“L”表示一个月的最后一天 在天(星期)自表达式中,“L”表示一个星期的最后一天,也就是SAT如果在“L”前有具体的内容,它就具有其他的含义了例如:“6L”表示这个月的倒数第6天,“FRIL”表示这个月的最一个星期五 注意:在使用“L”参数时,不要指定列表或范围,因为这会导致问题字段 允许值 允许的特殊字符 秒 0-59 , - * / 分 0-59 , - * / 小时 0-23 , - * / 日期 1-31 , - * ? / L W C 月份 1-12 或者 JAN-DEC , - * / 星期 1-7 或者 SUN-SAT , - * ? / L C # 年(可选) 留空, 1970-2099 , - * /
the end .
]]>cd 例:想到驱动目录下溜达一圈 cd /System/Library/Extensions
3、建立新目录
mkdir 目录名 例:在驱动目录下建一个备份目录 backup mkdir /System/Library/Extensions/backup
在桌面上建一个备份目录 backup mkdir /User/用户名/Desktop/backup
4、拷贝文件
cp 参数 源文件 目标文件 例:想把桌面的Natit.kext 拷贝到驱动目录中 cp -R /User/用户名/Desktop/Natit.kext /System/Library/Extensions
参数R表示对目录进行递归操作,kext在图形界面下看起来是个文件,实际上是个文件夹。
把驱动目录下的所有文件备份到桌面backup
cp -R /System/Library/Extensions/* /User/用户名/Desktop/backup
5、删除文件
rm 参数 文件 例:想删除驱动的缓存 rm -rf /System/Library/Extensions.kextcache rm -rf /System/Library/Extensions.mkext
参数-rf 表示递归和强制,千万要小心使用,如果执行了 rm -rf / 你的系统就全没了
6、移动文件
mv 文件 例:想把AppleHDA.Kext 移到桌面 mv /System/Library/Extensions/AppleHDA.kext /User/用户名/Desktop
想把AppleHDA.Kext 移到备份目录中 mv /System/Library/Extensions/AppleHDA.kext /System/Library/Extensions/backup
7、文本编辑
nano 文件名 例:编辑natit Info.plist nano /System/Library/Extensions/Natit.kext/Info.plist
目录操作
命令名 功能描述 使用举例
mkdir 创建一个目录 mkdir dirname
rmdir 删除一个目录 rmdir dirname
mvdir 移动或重命名一个目录 mvdir dir1 dir2
cd 改变当前目录 cd dirname
pwd 显示当前目录的路径名 pwd
ls 显示当前目录的内容 ls -la
文件操作
命令名 功能描述 使用举例
cat 显示或连接文件 cat filename
od 显示非文本文件的内容 od -c filename
cp 复制文件或目录 cp file1 file2
rm 删除文件或目录 rm filename
mv 改变文件名或所在目录 mv file1 file2
find 使用匹配表达式查找文件 find . -name "*.c" -print
file 显示文件类型 file filename
选择操作
命令名 功能描述 使用举例
head 显示文件的最初几行 head -20 filename
tail 显示文件的最后几行 tail -15 filename
cut 显示文件每行中的某些域 cut -f1,7 -d: /etc/passwd
colrm 从标准输入中删除若干列 colrm 8 20 file2
diff 比较并显示两个文件的差异 diff file1 file2
sort 排序或归并文件 sort -d -f -u file1
uniq 去掉文件中的重复行 uniq file1 file2
comm 显示两有序文件的公共和非公共行 comm file1 file2
wc 统计文件的字符数、词数和行数 wc filename
nl 给文件加上行号 nl file1 >file2
进程操作
命令名 功能描述 使用举例
ps 显示进程当前状态 ps u
kill 终止进程 kill -9 30142
时间操作
命令名 功能描述 使用举例
date 显示系统的当前日期和时间 date
cal 显示日历 cal 8 1996
time 统计程序的执行时间 time a.out
网络与通信操作
命令名 功能描述 使用举例
telnet 远程登录 telnet hpc.sp.net.edu.cn
rlogin 远程登录 rlogin hostname -l username
rsh 在远程主机执行指定命令 rsh f01n03 date
ftp 在本地主机与远程主机之间传输文件 ftpftp.sp.net.edu.cn
rcp 在本地主机与远程主机 之间复制文件 rcp file1 host1:file2
ping 给一个网络主机发送 回应请求 ping hpc.sp.net.edu.cn
mail 阅读和发送电子邮件 mail
write 给另一用户发送报文 write username pts/1
mesg 允许或拒绝接收报文 mesg n
Korn Shell 命令
命令名 功能描述 使用举例
history 列出最近执行过的 几条命令及编号 history
r 重复执行最近执行过的 某条命令 r -2
alias 给某个命令定义别名 alias del=rm -i
unalias 取消对某个别名的定义 unalias del
其它命令
命令名 功能描述 使用举例
uname 显示操作系统的有关信息 uname -a
clear 清除屏幕或窗口内容 clear
env 显示当前所有设置过的环境变量 env
who 列出当前登录的所有用户 who
whoami 显示当前正进行操作的用户名 whoami
tty 显示终端或伪终端的名称 tty
stty 显示或重置控制键定义 stty -a
du 查询磁盘使用情况 du -k subdirdf /tmp 显示文件系统的总空间和可用空间
w 显示当前系统活动的总信息
Mac OS X 终端命令开启功能
1.Lion下显示资源库
方法一:
显示
在“终端”中输入下面的命令:
chflags nohidden ~/Library/
隐藏
在“终端”中输入下面的命令:
chflags hidden ~/Library/
方法二:
打开Finder,菜单中选择前往按住option键就会显示资源库项(每次打开都需要重复操作一次)。
2.Finder显示隐藏文件
显示隐藏文件
在“终端”中输入下面的命令:
defaults write com.apple.finder AppleShowAllFiles -bool true
killall Finder
恢复隐藏文件
在“终端”中输入下面的命令:
defaults write com.apple.finder AppleShowAllFiles -bool false
killall Finder
3.Xcode卸载
在“终端”中输入下面的命令:
sudo /Library/uninstall-devtools –mode=all
为实际安装的目录,默认情况下Xcode安装在/Developer目录下,即可执行
sudo /Developer/Library/uninstall-devtools –mode=all
4.在Finder标题栏显示完整路径
在“终端”中输入下面的命令:
defaults write com.apple.finder _FXShowPosixPathInTitle -bool YES
killall Finder
5.去掉窗口截屏的阴影
对窗口进行截屏的时候(Command-Shift-4, 空格),得到的图片周围会自动被加上一圈阴影。
如果你不喜欢这个阴影的效果,可以把它关掉。
在“终端”中输入下面的命令:
defaults write com.apple.screencapture disable-shadow -bool true
killall SystemUIServer
6.强制Safari在新标签中打开网页
Safari是默认支持标签浏览的。但是,我们在页面上点击链接或者在其他应用程序中点击链接的时候,
Safari往往是打开了一个新的窗口,导致页面上的Safari窗口过多,不好管理。通过下面这个小窍门,
我们可以让Safari默认是在一个新标签中打开网页。
在“终端”中输入下面的命令:
defaults write com.apple.Safari TargetedClicksCreateTabs -bool true
7.改变截屏图片的保存位置
Mac OS提供了非常方便的截屏快捷键,可以让我们非常快速的对整个屏幕、部分屏幕或者应用程序窗口进行截屏。不过,这个截屏功能有一个不足之处,就是只能将截 屏图片保存到桌面。如果我们截取的图片特别多,就会让桌面显得特别凌乱。那有没有办法来修改截屏图片的默认保存位置呢?有。方法非常简单,只要在“终端” 中输入下面的命令就可以了。
defaults write com.apple.screencapture location 存放位置
killall SystemUIServer
在输入命令的时候,将“存放位置”替换成真正的文件夹就可以了。例如,你希望存放到自己用户目录的Screenshots文件夹下,就输入
defaults write com.apple.screencapture location ~/Screenshots
Mac锁屏设置快捷键
]]>如果用户要离开电脑一段时间,可以选择直接把笔记本直接合上。但是这样原先在跑 的进程就会挂起或者结束,如果正在下载,那么下载就被暂停(有时还不能恢复),如果正在提供网络服务,那么因为网络断了,别人也连不上你的笔记本。锁屏可 以解决这个问题,在Windows下用Win+L快捷键就锁屏了,但Mac OS X下一直没有默认的快捷键。
对于像我一样的新手,不要说设置锁屏快捷键,即便要使用其他锁屏的方法我也要到网上才能找到。其中一种方法是,首先在Finder找到“钥匙串访 问”这个实用工具(具体位置是/Applications/Utilities/Keychain Access.app),然后在“偏好设置”里选择“在菜单栏中显示钥匙串状态”。这时我们发现系统菜单多了一个像锁一样的小图标,只要点击它,就能找到 锁屏。
上述的方法实现了锁屏,但还没能达到快捷键控制锁屏的目标。虽然如此,但它的效果给我们一个锁屏思路。首先,我们要求屏幕保护程序在恢复时必须输入密码,然后锁屏问题就变成启动屏幕保护程序的问题了。Lock the screen via a keyboard shortcut这篇文章叙述了具体的实施方法。
第一步,找到“系统偏好设置”下的“安全性与隐私”,在“通用”页里勾上“进入睡眠或开始屏幕保护程序后立即要求输入密码”。
第二步,要用快捷键启动屏幕保护程序,相对复杂一点。在“应用程序”里找到“Automator”。新建一个服务,在“操作”下的“实用工具”里找 到“启动屏幕保护程序”,并把此操作拖动到右边,并且选择“没有输入”和位于“任何应用程序”,如下图所示。然后把服务保存为自己定义的名字。
hexo出自台湾大学生tommy351之手,是一个基于Node.js的静态博客程序,其编译上百篇文字只需要几秒。hexo生成的静态网页可以直接放到GitHub Pages,BAE,SAE等平台上。先看看tommy是如何吐槽Octopress的 →_→ Hexo颯爽登場。
如果你对默认配置满意,只需几个命令便可秒搭一个hexo。
如果你跟我一样喜欢折腾下,30分钟也足够个性化。
如果你过于喜欢折腾,可以折腾个把星期,尽情的玩。
搭建过程你或许觉得有那么点小繁琐,但一旦搭建完成,写文章是极简单,极舒服的。
只需要几个简单命令,你就可以完成一切。
1 | hexo n 写文章 |
下面逐步介绍,进入正题。
环境准备
• 安装Node
到Node.js官网下载相应平台的最新版本,一路安装即可。我用的是node-v0.10.22-x86.msi
• 安装Git
Git的客户端很多,我用的是msysgit,喜欢用绿色版本Portable application for official Git for Windows 1.8.4,下载下来设置一下环境变量即可,Git_HOME,%Git_HOME%\bin之类的,不多说。
• 安装Sublime(可选)
Sublime Text 2在这里仅仅作为一个文本编辑器使用,支持各种编程语言和文件格式,当然也支持Markdown语法,实在是个不可多得的练码奇才。喜欢追鲜的也可以尝试处于beta版本的Sublime Text 3。我用绿色版本Portable Sublime Text 2.0.2.zip。
• GitHub
GitHub账号和GitHub Pages 一般都应该有吧,已有的请自动无视这一部分。
首先注册一个『GitHub』帐号,已有的默认默认请忽略
建立与你用户名对应的仓库,仓库名必须为『your_user_name.github.com』
添加SSH公钥到『Account settings -> SSH Keys -> Add SSH Key』
my GitHub Pages
前两步忽略,只说第三步,添加SSH-Key。
首先设置你的用户名密码:
1 | git config --global user.email "zhangyang.z@icloud.com@qq.com" |
生成密钥:
1 | ssh-keygen -t rsa -C "zhangyang.z@icloud.com@qq.com" |
输入文件路径:
1 | H:\hexo\blog>ssh-keygen -t rsa -C "zhangyang.z@icloud.com@qq.com" |
Generating public/private rsa key pair.
Enter file in which to save the key (//.ssh/id_rsa): H:\git\myssh\ssh
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in H:\git\myssh\ssh.
Your public key has been saved in H:\git\myssh\ssh.pub.
The key fingerprint is:
b0:0c:2e:67:33:ab:c1:50:10:40:0a:ba:c1:80:59:22 zhangyang.z@icloud.com@qq.com
有个bug,文件路径中的盘符H必须大写,否则会报错。
上述命令若执行成功,会在H:\git\myssh目录下生成两个文件id_rsa和id_rsa.pub,最后两步:
用文本编辑器打开ssh.pub文件,拷贝其中的内容,将其添加到Add SSH Key
将id_rsa和id_rsa.pub拷贝至你Git安装目录下的.ssh目录,如H:\PortableGit-1.8.4.ssh
Add SSH Keys
最后可以验证一下:
1 | ssh -T git@github.com |
若有问题,请重新设置。常见错误请参考:1
2GitHub Help - Generating SSH Keys
GitHub Help - Error Permission denied (publickey)
• 安装
Node和Git都安装好后,可执行如下命令安装hexo:
1 | npm install -g hexo |
初始化
然后,执行init命令初始化hexo到你指定的目录:
1 | hexo init <folder> |
也可以cd到目标目录,执行hexo init。
好啦,至此,全部安装工作已经完成!
• 生成静态页面
cd 到你的init目录,执行如下命令,生成静态页面至hexo\public\目录。
1 | hexo generate |
命令必须在init目录下执行,否则不成功,但是也不报错。
当你修改文章Tag或内容,不能正确重新生成内容,可以删除hexo\db.json后重试,还不行就到public目录删除对应的文件,重新生成。
• 部署到github
安装git deploy插件
1 | npm install hexo-deployer-git --save |
站点配置文件1
2
3
4
5
6# Deployment
## Docs: https://hexo.io/docs/deployment.html
deploy:
type: git
repository: git@github.com:liuhouer/liuhouer.github.io.git
branch: master
hexo d
部署上去
• 本地启动
执行如下命令,启动本地服务,进行文章预览调试。
1 | ``` |
1 | 浏览器输入http://localhost:4000就可以看到效果。 |
hexo new [layout] “postName” 新建文章1
其中layout是可选参数,默认值为post。有哪些layout呢,请到scaffolds目录下查看,这些文件名称就是layout名称。当然你可以添加自己的layout,方法就是添加一个文件即可,同时你也可以编辑现有的layout,比如post的layout默认是hexo\scaffolds\post.md
title: { { title } }
date: { { date } }
tags:1
2
3
4---
请注意,大括号与大括号之间我多加了个空格,否则会被转义,不能正常显示。
我想添加categories,以免每次手工输入,只需要修改这个文件添加一行,如下:
title: { { title } }
date: { { date } }
categories:
tags:
1 | postName是md文件的名字,同时也出现在你文章的URL中,postName如果包含空格,必须用”将其包围,postName可以为中文。 |
hexo new photo “photoPostName” 新建照片文章
description
markdown文件头中也可以添加description,以覆盖全局配置文件中的description内容,请参考下文_config.yml的介绍。
title: hexo你的博客
date: 2013-11-22 17:11:54
categories: default
tags: [hexo]
description: 你对本页的描述
1 | hexo默认会处理全部markdown和html文件,如果不想让hexo处理你的文件,可以在文件头中加入layout: false。 |
以上是摘要
以下是余下全文1
2
3
4
5
6
7
8
9
10more以上内容即是文章摘要,在主页显示,more以下内容点击『> Read More』链接打开全文才显示。
hexo中所有文件的编码格式均是UTF-8。
•主题安装
> 萝卜白菜各有所爱,玩博客换主题是必不可少的,hexo的主题列表Hexo Themes。
> 我比较喜欢pacman,modernist、ishgo,raytaylorism。Pacman最为优秀,简洁大方小清新,同时移动版本支持的也很好,但作者并没有把很多参数分离出来给出可配置项,我最终选择了modernist。
安装主题的方法就是一句git命令:
git clone https://github.com/heroicyang/hexo-theme-modernist.git themes/modernist1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30目录是否是modernist无所谓,只要与_config.yml文件一致即可。
安装完成后,打开hexo\_config.yml,修改主题为modernist
> > theme: modernist
> > 打开hexo\themes\modernist目录,编辑主题配置文件_config.yml:
> > menu: 配置页头显示哪些菜单
> > Home: /
> > Archives: /archives
> > Reading: /reading
> > About: /about
> > Guestbook: /about
> > excerpt_link: Read More 摘要链接文字
> > archive_yearly: false 按年存档
> > widgets: 配置页脚显示哪些小挂件
> > - category
> > - tag
> > - tagcloud
> > - recent_posts
> > - blogroll
> > blogrolls: 友情链接
> > - bruce sha's duapp wordpress: http://ibruce.duapp.com
> > - bruce sha's javaeye: http://buru.iteye.com
> > - bruce sha's oschina blog: http://my.oschina.net/buru
> > - bruce sha's baidu space: http://hi.baidu.com/iburu
> > fancybox: true 是否开启fancybox效果
> > duoshuo_shortname: buru 多说账号
> > google_analytics:
> > rss:
更新主题
cd themes/modernist
git pull1
2
3
4
5
6
7
8
9
10
11
12•评论框
> 静态博客要使用第三方评论系统,hexo默认集成的是Disqus,因为你懂的,所以国内的话还是建议用多说。
> 直接用你的微博/豆瓣/人人/百度/开心网帐号登录多说,做一下基本设置。如果使用modernist主题,在modernist_config.yml中配置duoshuo_shortname为多说的基本设置->域名中的shortname即可。你也可以在多说后台自定义一下多说评论框的格式,比如评论框的位置,对于css设置,可以参考这里,我是在HeroicYang的基础上修改的。
> > 如果你是有的其他第三方评论系统,将通用代码粘贴到hexo\themes\modernist\layout\_partial\comment.ejs里面,如下:
> > <% if (config.disqus_shortname && page.comments){ %>
> > <section id="comment">
> > 你的通用代码
> > <% } %>
•自定义页面
执行new page命令
hexo new page “about”1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33> 在hexo\source\下会生成about目录,里面有个index.md,直接编辑就可以了,然后在主题的_config.yml中将其配置显示出来。
> 上述步骤,也可以手工生成,在hexo\source\下手工新建about和index.md也是完全等价的。
> 因为markdown对table的支持不好,我是在about中直接建立index.html,里面书写页面内容,hexo会帮你加上头和尾。
•404页面
> GitHub Pages 自定义404页面非常容易,直接在根目录下创建自己的404.html就可以。但是自定义404页面仅对绑定顶级域名的项目才起作用,GitHub默认分配的二级域名是不起作用的,使用hexo server在本机调试也是不起作用的。
> 其实,404页面可以做更多有意义的事,来做个404公益项目吧。现在,看下我的404页面一个ibruce.info上不存在的页面,做点有意义的事情,也对得起这个域名。
> 目前有如下几个公益404接入地址,我选择了腾讯的。404页面,每个人可以做的更多。
> 腾讯公益404
> 404公益_益云(公益互联网)社会创新中心
> 失蹤兒童少年資料管理中心404
> 图床
> 考虑到博客的速度,同时也为了便于博客的迁移,图床是必须的。我墙裂推荐七牛,访问速度极快,支持日志、防盗链和水印。
> 免费用户有每月10GB流量+总空间10GB+PUT/DELETE 10万次请求+GET 100万次请求,这对个人博客来说足够,不够的话点这个活动页面,也可通过邀请好友获得奖励,我也求一下七牛邀请。有一点要说明的是,七牛没有目录的概念,但是文件名可以包含/,比如2013/11/27/reading/photos-0.jpg,参考这里关于key-value存储系统。
> 七牛除了作为图床还可以作为其他静态文件存储空间,比如我的个人站点首页有个字库文件和JS文件下载较慢,有时间会把它弄到七牛上去,以提高首页打开速度。请看这篇Linux中国采用七牛云存储支撑图片访问。
> 如果非要说不足的话,就是文件管理界面不是很友好,不支持CNAME到分配的永久链接,也不能绑定未备案的自有域名,必须备案才可以。
> 如果你对七牛web版的文件管理界面不满意,可以用官方的七牛云存储工具。
> 您还可以使用如下图床服务 FarBox,Dropbox,又拍云。
•申请域名(可选)
> GitHubPages默认为每个用户分配了一个二级域名『your_user_name.github.com』或『your_user_name.github.io』。
> 如果你对上述域名不满意,可以到狗爹上申请一个自己的域名,然后绑定到GitHub Pages。绑定方法很简单,在repo根目录下建立一个CNAME文件,里面写上域名即可。
> GoDaddy
> 买域名首选狗爹,国内的服务商大家都懂的。
> 目前.info域名只要¥18.99,但据说续费比较贵,我是先玩下,一年后再换,至于搜索引擎重新索引之类的,无所谓。.me和.com域名稍微贵点,大约¥60-100,网上有很多优惠码可用,可惜有的优惠码有限制。比如有个.com域名优惠码只要$1.99,但只能用国外信用卡购买。更多优惠码可以自行谷歌或到独特优惠码找。不着急的同学可以将中意的域名加入购物车先不付款,过几天,狗爹就会发优惠信息给你。狗爹不定期也会有活动,可以多关注。
> 付款后,需要稍微等一会你才会拿到域名,特别是支付宝付款的,要等大约半小时左右。此外域名要一年年的买,这样比较划算。
> 建议大家申请.com或.me域名。据说.info因垃圾网站太多,被搜索引擎惩罚,而且续费较贵。
> DNSPod
> GoDaddy的NameServers有时会被墙,因此墙裂推荐国内的DNSPod解析域名,免费服务真心不错。支持微信/邮件提醒,监控与报警,访问统计,健康诊断,搜索引擎推送,速度哇哇的,对于我来说足够。
> 两步设置就可以搞定,怎么操作参考Godaddy注册商域名修改DNS地址。
命令
常用命令:
hexo new “postName” 新建文章
hexo new page “pageName” 新建页面
hexo generate 生成静态页面至public目录
hexo server 开启预览访问端口(默认端口4000,’ctrl + c’关闭server)
hexo deploy 将.deploy目录部署到GitHub
常用复合命令:
hexo deploy -g
hexo server -g
简写:
hexo n == hexo new
hexo g == hexo generate
hexo s == hexo server
hexo d == hexo deploy
至此,基本操作介绍完毕,以下内容普通用户无需了解。`
• 目录介绍
默认目录结构:
.
├── .deploy
├── public
├── scaffolds
├── scripts
├── source
| ├── _drafts
| └── _posts
├── themes
├── _config.yml
└── package.json
.deploy:执行hexo deploy命令部署到GitHub上的内容目录
public:执行hexo generate命令,输出的静态网页内容目录
scaffolds:layout模板文件目录,其中的md文件可以添加编辑
scripts:扩展脚本目录,这里可以自定义一些javascript脚本
source:文章源码目录,该目录下的markdown和html文件均会被hexo处理。该页面对应repo的根目录,404文件、favicon.ico文件,CNAME文件等都应该放这里,该目录下可新建页面目录。
_drafts:草稿文章
_posts:发布文章
themes:主题文件目录
_config.yml:全局配置文件,大多数的设置都在这里
package.json:应用程序数据,指明hexo的版本等信息,类似于一般软件中的关于按钮
接下来是重头戏_config.yml,做个简单说明:
Hexo Configuration
Docs: http://zespia.tw/hexo/docs/configure.html
Source: https://github.com/tommy351/hexo/
• Site 整站的基本信息
title: 不如 网站标题
subtitle: 码农,程序猿,未来的昏析师 网站副标题
description: bruce sha’s blog | java | scala | bi 网站描述,给搜索引擎用的,在生成html中的head->meta中可看到
author: bruce 网站作者,在下方显示
email: zhangyang.z@icloud.com@qq.com 联系邮箱
language: zh-CN 语言
• URL 域名和文件结构
If your site is put in a subdirectory, set url as ‘http://yoursite.com/child' and root as ‘/child/‘
url: http://ibruce.info 你的域名
root: /
permalink: :year/:month/:day/:title/
tag_dir: tags
archive_dir: archives
category_dir: categories
code_dir: downloads/code
• Writing 写文章选项
new_post_name: :title.md File name of new posts
default_layout: post 默认layout方式
auto_spacing: false Add spaces between asian characters and western characters
titlecase: false Transform title into titlecase
external_link: true Open external links in new tab
max_open_file: 100
multi_thread: true
filename_case: 0
render_drafts: false
highlight: 代码高亮
enable: true 是否启用
line_number: false 是否显示行号
tab_replace:
• Category & Tag 分类与标签
default_category: uncategorized default
category_map:
tag_map:
• Archives 存档,这里的说明好像不对。全部选择1,这个选项与主题中的选项有时候会有冲突
2: Enable pagination
1: Disable pagination
0: Fully Disable
archive: 1
category: 1
tag: 1
• Server 本地服务参数
Hexo uses Connect as a server
You can customize the logger format as defined in
http://www.senchalabs.org/connect/logger.html
port: 4000
logger: true
logger_format:
• Date / Time format 日期显示格式
Hexo uses Moment.js to parse and display date
You can customize the date format as defined in
http://momentjs.com/docs//displaying/format/
date_format: MMM D YYYY
time_format: H:mm:ss
• Pagination 分页设置
Set per_page to 0 to disable pagination
per_page: 10 每页10篇文章
pagination_dir: page
• Disqus 社会化评论disqus,我使用多说,在主题中配置
disqus_shortname:
• Extensions 插件,暂时未安装插件
Plugins: https://github.com/tommy351/hexo/wiki/Plugins
Themes: https://github.com/tommy351/hexo/wiki/Themes
• 主题
theme: modernist raytaylorism pacman modernist light
exclude_generator:
• Deployment 部署
Docs: http://zespia.tw/hexo/docs/deploy.html
deploy:
type: github
repository: git@github.com:bruce-sha/bruce-sha.github.com.git 你的GitHub Pages仓库
修改局部页面
页面展现的全部逻辑都在每个主题中控制,源代码在hexo\themes\你使用的主题\中,以modernist主题为例:
.
├── languages 多语言
| ├── default.yml 默认语言
| └── zh-CN.yml 中文语言
├── layout 布局,根目录下的.ejs文件是对主页,分页,存档等的控制
| ├── _partial 局部的布局,此目录下的.ejs是对头尾等局部的控制
| └── _widget 小挂件的布局,页面下方小挂件的控制
├── source 源码
| ├── css css源码
| | ├── _base .styl基础css
| | ├── _partial .styl局部css
| | ├── fonts 字体
| | ├── images 图片
| | └── style.styl *.styl引入需要的css源码
| ├── fancybox fancybox效果源码
| └── js javascript源代码
├── _config.yml 主题配置文件
└── README.md 用GitHub的都知道
如果你需要修改头部,直接修改hexo\themes\modernist\layout_partial\header.ejs,比如头上加个搜索框:
再如,你要修改页脚版权信息,直接编辑hexo\themes\modernist\layout_partial\footer.ejs。同理,你需要修改css,直接去修改对应位置的styl文件。
统计
因Google Analytics偶尔被墙,故用百度统计,以modernist主题为例,介绍如何添加。
编辑文件hexo\themes\modernist_config.yml,增加配置选项:
baidu_tongji: true
新建文件hexo\themes\modernist\layout_partial\baidu_tongji.ejs,内容如下:
<% if (theme.baidu_tongji){ %>
<% } %>
注册并登录百度统计获取你的统计代码。
编辑文件hexo\themes\modernist\layout_partial\head.ejs,在『/head』之前增加:
<%- partial(‘baidu_tongji’) %>
重新生成并部署你的站点。
不出意外的话,在你的站点的每个页面的左上角都会看到一个恶心的百度LOGO。你只能在『百度统计首页->网站列表->获取代码->系统管理设置->统计图标设置->显示图标』,把那个勾去掉。百度真是恶心,我准备还是用Google Analytics。
分享
我没有添加分享,觉得这个不是很必要,导致页面看起来啰嗦。以加网为例介绍如何添加:
在hexo\themes\modernist\layout_partial\post下新建jiathis.ejs文件。
注册加网获得你的分享代码,写入jiathis.ejs。
在hexo\themes\modernist\layout_partial\article.ejs中,添加<%-partial(‘post/jiathis’)%>。
分享服务还可以使用如下企业提供的技术加网,bShare,百度分享。
网站图标
看一下hexo\themes\modernist\layout_partial\head.ejs,找到这句:
你懂的,将你的favicon.ico放到工程根目录下即可,也就是hexo\source目录。可以在Faviconer制作你的ico图标,国内有比特虫。
• 自定义挂件
除了默认已提供的挂件外,你还可以自定义自己的小挂件,在hexo\themes\modernist\layout_widget\下,新建自己的ejs文件,如myWidget.ejs,然后在配置文件hexo\themes\modernist_config.yml中配置。
widgets:
生成自己的微博组件。
添加hexo\themes\modernist\layout_widget\weibo.ejs文件。
配置hexo\themes\modernist_config.yml。
插件
• 安装插件:
npm install
启用插件:在*hexo_config.yml文件添加:
plugins:
npm update
卸载插件:
npm uninstall
RSS插件
将上述命令中的『plugin-name』,替换为hexo-generator-feed。一旦安装完成,你可以在配置显示你站点的RSS,文件路径\atom.xml。
你可以用rss作为迁移工具,用如下命令读取其他位置的rss:
hexo migrate rss
• Sitemap插件
将上述命令中的『plugin-name』,替换为hexo-generator-sitemap。你可以将你站点地图提交给搜索引擎,文件路径\sitemap.xml。
更多插件的安装方法,请参考官方Wiki。
如果你按照上述步骤做,但插件不起作用,没有生成atom.xml和sitemap.xml,也没有报错,那么你应该cd到你的hexo初始化目录,在该目录下重新安装插件,重试。
• 迁移
hexo支持从其他类型站点迁移,如通用RSS,Jekyll,Octopress,WordPress等,这一部分我没试过。请参考官方文档Hexo Migration。
• 搜索引擎
你可以到屈站长提交你的站点给搜索引擎。其他内容如添加站点或页面的description,提交Sitemap,添加百度统计,Google Analytics等等,参考本文其他章节的内容,不再一一阐述。
• 更新
更新hexo:
npm update -g hexo
更新主题:
cd themes/你的主题
git pull
更新插件:
npm update
]]>如果你是真的想要玩清楚 Linux 的来龙去脉, 那么 shell script 就不可不知,为什么呢?因为:
#自动化管理的重要依据:
不用鸟哥说你也知道,管理一部主机真不是件简单的事情,每天要进行的任务就有: 查询登录档、追踪流量、监控使用者使用主机状态、主机各项硬件设备状态、 主机软件升级查询、更不要说得应付其他使用者的突然要求了。而这些工作的进行可以分为: (1)自行手动处理,或是 (2)写个简单的程序来帮你每日自动处理分析这两种方式,你觉得哪种方式比较好? 当然是让系统自动工作比较好,对吧!呵呵~这就得要良好的 shell script 来帮忙的啦!
#追踪与管理系统的重要工作:
虽然我们还没有提到服务启动的方法,不过,这里可以先提一下,我们 Linux 系统的服务 (services) 启动的介面是在 /etc/init.d/ 这个目录下,目录下的所有文件都是 scripts ; 另外,包括启动 (booting) 过程也都是利用 shell script 来帮忙搜寻系统的相关配置数据, 然后再代入各个服务的配置参数啊!举例来说,如果我们想要重新启动系统登录档, 可以使用:『/etc/init.d/syslogd restart』,那个 syslogd 文件就是 script 啦!
另外,鸟哥曾经在某一代的 Fedora 上面发现,启动 MySQL 这个数据库服务时,确实是可以启动的, 但是萤幕上却老是出现『failure』!后来才发现,原来是启动 MySQL 那个 script 会主动的以『空的密码』去尝试登陆 MySQL ,但为了安全性鸟哥修改过 MySQL 的密码罗~当然就登陆失败~ 后来改了改 script ,就略去这个问题啦!如此说来, script 确实是需要学习的啊!
#简单入侵侦测功能:
当我们的系统有异状时,大多会将这些异状记录在系统记录器,也就是我们常提到的『系统登录档』, 那么我们可以在固定的几分钟内主动的去分析系统登录档,若察觉有问题,就立刻通报管理员, 或者是立刻加强防火墙的配置守则,如此一来,你的主机可就能够达到『自我保护』的聪明学习功能啦~ 举例来说,我们可以通过 shell script 去分析『当该封包尝试几次还是连线失败之后,就予以抵挡住该 IP』之类的举动!
#连续命令单一化:
其实,对於新手而言, script 最简单的功能就是:『汇整一些在 command line 下达的连续命令,将他写入 scripts 当中,而由直接运行 scripts 来启动一连串的 command line 命令输入!』例如: 防火墙连续守则 (iptables),启动加载程序的项目 (就是在 /etc/rc.d/rc.local 里头的数据) ,等等都是相似的功能啦! 其实,说穿了,如果不考虑 program 的部分,那么 scripts 也可以想成『仅是帮我们把一大串的命令汇整在一个文件里面, 而直接运行该文件就可以运行那一串又臭又长的命令段!』就是这么简单啦!
#简易的数据处理:
你可以发现, awk 可以用来处理简单的数据数据呢!例如薪资单的处理啊等等的。 shell script 的功能更强大,例如鸟哥曾经用 shell script 直接处理数据数据的比对啊, 文字数据的处理啊等等的,撰写方便,速度又快(因为在 Linux 效能较佳),真的是很不错用的啦!
#跨平台支持与学习历程较短:
几乎所有的 Unix Like 上面都可以跑 shell script ,连 MS Windows 系列也有相关的 script 模拟器可以用, 此外, shell script 的语法是相当亲和的,看都看的懂得文字 (虽然是英文),而不是机器码, 很容易学习~这些都是你可以加以考量的学习点啊!
上面这些都是你考虑学习 shell script 的特点~此外, shell script 还可以简单的以 vim 来直接编写,实在是很方便的好东西!所以,还是建议你学习一下啦。
不过,虽然 shell script 号称是程序 (program) ,但实际上, shell script 处理数据的速度上是不太够的。因为 shell script 用的是外部的命令与 bash shell 的一些默认工具,所以,他常常会去呼叫外部的函式库,因此,运算速度上面当然比不上传统的程序语言。 所以罗, shell script 用在系统管理上面是很好的一项工具,但是用在处理大量数值运算上, 就不够好了,因为 Shell scripts 的速度较慢,且使用的 CPU 资源较多,造成主机资源的分配不良。还好, 我们通常利用 shell script 来处理服务器的侦测,倒是没有进行大量运算的需求啊!所以不必担心的啦!
2.撰写 shell script 的良好习惯创建script 的功能;script 的版本资讯;script 的作者与联络方式;script 的版权宣告方式;script 的 History (历史纪录);script 内较特殊的命令,使用『绝对路径』的方式来下达;script 运行时需要的环境变量预先宣告与配置。
我的练习程序
@1#!/bin/bash# Program:# This program shows "Hello World!" in your screen and "duang!" de voice!# History:# 2015/04/17 bruce First release# 使用 exit 0 ,这代表离开 script 并且回传一个 0 给系统, 所以我运行完这个 script 后,若接著下达 echo $? 则可得到 0 的值喔!# echo 必须要加上 -e 的选项才行 会看到萤幕是这样,而且应该还会听到『咚』的一声
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/binexport PATHecho -e "Hello World! \a \n"exit 0
@2 #!/bin/bash# Programe# 功能描述:请你以 read 命令的用途,撰写一个 script ,他可以让使用者输入:1. first name 与 2. last name, 最后并且在萤幕上显示:『Your full name is: 』的内容# History:# 时间:2015-4-17 作者:bruce 版本:version 1PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/binexport PATHread -p "please input your first name within 30 s: " -t 30 firstread -p "please input your last name within 30 s: " -t 30 lastecho -e "\n your full name is:$first $last"exit 0
@3:#!/bin/bash# Program:# 功能描述:假设我想要创建三个空的文件 (透过 touch) ,档名最开头由使用者输入决定,假设使用者输入 filename 好了,那今天的日期是 2009/02/14 , 我想要以前天、昨天、今天的日期来创建>这些文件,亦即 filename_20090212, filename_20090213, filename_20090214 ,该如何是好?# History:# 时间 作者:bruce 版本:version 1PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/binexport PATH#第一步:让输入者输入文件名read -p " please write your filename: " fileuser#第二步:判断用户的是否随便输入enter键利用变量功能分析档名是否有配置?filename=${fileuser:-"filename"}#第三步:利用date取得时间date1=$(date --date='2 days ago' +%Y%m%d)date2=$(date --date='1 days ago' +%Y%m%d)date3=$(date +%Y%m%d)file1=${filename}${date1}file2=${filename}${date2}file3=${filename}${date3}#@4 :创建文档touch "$file1"touch "$file2"touch "$file3"echo -e "已经创建了3个文档"exit 0
sh 运行shell脚本和source运行shell脚本的区别sh 运行的脚本的变量是局部变量,source运行的脚本的变量是全局变量。sh运行完以后,在bash最父级echo调用不到子bash的定义变量source运行完以后,在bash最父级echo可以调用bash的定义变量利用 source 来运行脚本:在父程序中运行如果你使用 source 来运行命令那就不一样了!同样的脚本我们来运行看看:[root@www scripts]# source sh02.shPlease input your first name: VBirdPlease input your last name: TsaiYour full name is: VBird Tsai[root@www scripts]# echo $firstname $lastnameVBird Tsai <==嘿嘿!有数据产生喔!
]]>命令与文件补全功能: ([tab] 按键的好处)还记得[tab] 这个按键吗?这个按键的功能就是在 bash 里头才有的啦!常常在 bash 环境中使用 [tab] 是个很棒的习惯喔!因为至少可以让你 1)少打很多字; 2)确定输入的数据是正确的! 使用 [tab] 按键的时机依据 [tab] 接在命令后或参数后而有所不同。我们再复习一次:[Tab] 接在一串命令的第一个字的后面,则为命令补全;[Tab] 接在一串命令的第二个字以后时,则为『文件补齐』!所以说,如果我想要知道我的环境中,所有可以运行的命令有几个? 就直接在 bash 的提示字符后面连续按两次 [tab] 按键就能够显示所有的可运行命令了。 那如果想要知道系统当中所有以 c 为开头的命令呢?就按下『 c[tab][tab] 』就好啦! ^_^是的!真的是很方便的功能,所以,有事没事,在 bash shell 底下,多按几次 [tab] 是一个不错的习惯啦!
命令别名配置功能: (alias)假如我需要知道这个目录底下的所有文件 (包含隐藏档) 及所有的文件属性,那么我就必须要下达『 ls -al 』这样的命令串,唉!真麻烦,有没有更快的取代方式?呵呵!就使用命令别名呀!例如鸟哥最喜欢直接以 lm 这个自定义的命令来取代上面的命令,也就是说, lm 会等于 ls -al 这样的一个功能,嘿!那么要如何作呢?就使用 alias 即可!你可以在命令列输入 alias 就可以知道目前的命令别名有哪些了!也可以直接下达命令来配置别名呦:alias lm='ls -al'通配符: (Wildcard)除了完整的字符串之外, bash 还支持许多的通配符来帮助用户查询与命令下达。 举例来说,想要知道 /usr/bin 底下有多少以 X 为开头的文件吗?使用:『 ls -l /usr/bin/X* 』就能够知道啰~此外,还有其他可供利用的通配符, 这些都能够加快使用者的操作呢!变量的取用与配置:echo, 变量配置守则, unset说的口沫横飞的,也不知道『变量』与『变量代表的内容』有啥关系? 那我们就将『变量』的『内容』拿出来给您瞧瞧好了。你可以利用 echo 这个命令来取用变量, 但是,变量在被取用时,前面必须要加上钱字号『 $ 』才行,举例来说,要知道 PATH 的内容,该如何是好?例题:请在屏幕上面显示出您癿环境发量 HOME与 MAIL:答:echo $HOME 或者是 echo ${HOME}echo $MAIL 或者是 echo ${MAIL}--------------------------------------------------------------------------------------------[root@www ~]# echo $myname <==这里并没有任何数据~因为这个变量尚未被配置!是空的![root@www ~]# myname=VBird[root@www ~]# echo $mynameVBird <==出现了!因为这个变量已经被配置了!
双引号内癿特殊字符如 $ 等,可以保有原本癿特性,如下所示:『var="lang is $LANG"』则『echo $var』可得『lang is en_US』o 单引号内癿特殊字符则仅为一般字符 (纯文本),如下所示:『var='lang is $LANG'』则『echo $var』可得『lang is $LANG』如何讥我刚刚讴定癿 name=VBird 可以用在下个 shell 癿程序?[root@www ~]# name=VBird[root@www ~]# bash <==迚入刡所谓癿子程序[root@www ~]# echo $name <==子程序:再次癿 echo 一下;<==嘿嘿!幵没有刚刚讴定癿内容喔![root@www ~]# exit <==子程序:离开这个子程序[root@www ~]# export name[root@www ~]# bash <==迚入刡所谓癿子程序[root@www ~]# echo $name <==子程序:在此执行!VBird <==看吧!出现讴定值了![root@www ~]# exit <==子程序:离开这个子程序esc下面那个/tab键上面那个````键在挃令下达癿过程中,反单引号( ` )这个符号代表癿意义为何?答:在一串挃令中,在 ` 乊内癿挃令将会被先执行,而其执行出杢癿结果将做为外部癿输入信息!例如 uname -r 会显示出目前癿核心版本,而我们癿核心版本在 /lib/modules 里面,因此,你可以先执行 uname -r 找出核心版本,然后再以『 cd 目录』刡该目录下,当然也可以执行如同上面范例六癿执行内容啰。另外再丼个例子,我们也知道, locate 挃令可以列出所有癿相关档案档名,但是,如果我想要知道各个档案癿权限呢?丼例杢说,我想要知道每个 crontab 相关档名癿权限:[root@www ~]# ls -l `locate crontab`如此一杢,先以 locate 将文件名数据都列出杢,再以 ls 挃令杢处理癿意思啦!瞭了吗?^_^
查看环境变量 环境变量可以帮我们达到很多功能~包括家目录的变换啊、提示字符的显示啊、运行文件搜寻的路径啊等等的, 还有很多很多啦!那么,既然环境变量有那么多的功能,问一下,目前我的 shell 环境中, 有多少默认的环境变量啊?我们可以利用两个命令来查阅,分别是 env 与 export 呢! -------------------------------------------------------------------------------------------------------------------------------------- HOME 代表用户的家目录。还记得我们可以使用 cd ~ 去到自己的家目录吗?或者利用 cd 就可以直接回到用户家目录了。那就是取用这个变量啦~ 有很多程序都可能会取用到这个变量的值! SHELL 告知我们,目前这个环境使用的 SHELL 是哪支程序? Linux 默认使用 /bin/bash 的啦! HISTSIZE 这个与『历史命令』有关,亦即是, 我们曾经下达过的命令可以被系统记录下来,而记录的『笔数』则是由这个值来配置的。 MAIL 当我们使用 mail 这个命令在收信时,系统会去读取的邮件信箱文件 (mailbox)。 PATH 就是运行文件搜寻的路径啦~目录与目录中间以冒号(:)分隔, 由于文件的搜寻是依序由 PATH 的变量内的目录来查询,所以,目录的顺序也是重要的喔。 LANG 这个重要!就是语系数据啰~很多信息都会用到他, 举例来说,当我们在启动某些 perl 的程序语言文件时,他会主动的去分析语系数据文件, 如果发现有他无法解析的编码语系,可能会产生错误喔!一般来说,我们中文编码通常是 zh_TW.Big5 或者是 zh_TW.UTF-8,这两个编码偏偏不容易被解译出来,所以,有的时候,可能需要修订一下语系数据。 这部分我们会在下个小节做介绍的! RANDOM 这个玩意儿就是『随机随机数』的变量啦!目前大多数的 distributions 都会有随机数生成器,那就是 /dev/random 这个文件。 我们可以透过这个随机数文件相关的变量 ($RANDOM) 来随机取得随机数值喔。在 BASH 的环境下,这个 RANDOM 变量的内容,介于 0~32767 之间,所以,你只要 echo $RANDOM 时,系统就会主动的随机取出一个介于 0~32767 的数值。万一我想要使用 0~9 之间的数值呢?呵呵~利用 declare 宣告数值类型, 然后这样做就可以了:
?:(关于上个运行命令的回传值) 什么?问号也是一个特殊的变量?没错!在 bash 里面这个变量可重要的很! 这个变量是:『上一个运行的命令所回传的值』, 上面这句话的重点是『上一个命令』与『回传值』两个地方。当我们运行某些命令时, 这些命令都会回传一个运行后的代码。一般来说,如果成功的运行该命令, 则会回传一个 0 值,如果运行过程发生错误,就会回传『错误代码』才对!一般就是以非为 0 的数值来取代。 我们以底下的例子来看看: [root@www ~]# echo $SHELL /bin/bash <==可顺利显示!没有错误! [root@www ~]# echo $? 0 <==因为没问题,所以回传值为 0 [root@www ~]# 12name=VBird -bash: 12name=VBird: command not found <==发生错误了!bash回报有问题 [root@www ~]# echo $? 127 <==因为有问题,回传错误代码(非为0) # 错误代码回传值依据软件而有不同,我们可以利用这个代码来搜寻错误的原因喔! [root@www ~]# echo $? 0 # 咦!怎么又变成正确了?这是因为 "?" 只与『上一个运行命令』有关, # 所以,我们上一个命令是运行『 echo $? 』,当然没有错误,所以是 0 没错!
read要读取来自键盘输入的变量,就是用 read 这个命令了。这个命令最常被用在 shell script 的撰写当中, 想要跟使用者对谈?用这个命令就对了。关于 script 的写法,底下先来瞧一瞧 read 的相关语法吧! [root@www ~]# read [-pt] variable 选项与参数: -p :后面可以接提示字符! -t :后面可以接等待的『秒数!』这个比较有趣~不会一直等待使用者啦! 范例一:让用户由键盘输入一内容,将该内容变成名为 atest 的变量 [root@www ~]# read atest This is a test <==此时光标会等待你输入!请输入左侧文字看看 [root@www ~]# echo $atest This is a test <==你刚刚输入的数据已经变成一个变量内容! 范例二:提示使用者 30 秒内输入自己的大名,将该输入字符串作为名为 named 的变量内容 [root@www ~]# read -p "Please keyin your name: " -t 30 named Please keyin your name: VBird Tsai <==注意看,会有提示字符喔! [root@www ~]# echo $named VBird Tsai <==输入的数据又变成一个变量的内容了! read 之后不加任何参数,直接加上变量名称,那么底下就会主动出现一个空白行等待你的输入(如范例一)。 如果加上 -t 后面接秒数,例如上面的范例二,那么 30 秒之内没有任何动作时, 该命令就会自动略过了~如果是加上 -p ,嘿嘿!在输入的光标前就会有比较多可以用的提示字符给我们参考! 在命令的下达里面,比较美观啦! ^_^
declare / typesetdeclare 或 typeset 是一样的功能,就是在『宣告变量的类型』。如果使用 declare 后面并没有接任何参数,那么 bash 就会主动的将所有的变量名称与内容通通叫出来,就好像使用 set 一样啦! 那么 declare 还有什么语法呢?看看先:[root@www ~]# declare [-aixr] variable选项与参数:-a :将后面名为 variable 的变量定义成为数组 (array) 类型-i :将后面名为 variable 的变量定义成为整数数字 (integer) 类型-x :用法与 export 一样,就是将后面的 variable 变成环境变量;-r :将变量配置成为 readonly 类型,该变量不可被更改内容,也不能 unset范例一:让变量 sum 进行 100+300+50 的加总结果[root@www ~]# sum=100+300+50[root@www ~]# echo $sum100+300+50 <==咦!怎么没有帮我计算加总?因为这是文字型态的变量属性啊![root@www ~]# declare -i sum=100+300+50[root@www ~]# echo $sum450 <==瞭乎??由于在默认的情况底下, bash 对于变量有几个基本的定义:变量类型默认为『字符串』,所以若不指定变量类型,则 1+2 为一个『字符串』而不是『计算式』。 所以上述第一个运行的结果才会出现那个情况的; bash 环境中的数值运算,默认最多仅能到达整数形态,所以 1/3 结果是 0; 现在你晓得为啥你需要进行变量宣告了吧?如果需要非字符串类型的变量,那就得要进行变量的宣告才行啦! 底下继续来玩些其他的 declare 功能。范例二:将 sum 变成环境变量[root@www ~]# declare -x sum[root@www ~]# export | grep sumdeclare -ix sum="450" <==果然出现了!包括有 i 与 x 的宣告!范例三:让 sum 变成只读属性,不可更动![root@www ~]# declare -r sum[root@www ~]# sum=tesgting-bash: sum: readonly variable <==老天爷~不能改这个变量了!范例四:让 sum 变成非环境变量的自定义变量吧![root@www ~]# declare +x sum <== 将 - 变成 + 可以进行『取消』动作[root@www ~]# declare -p sum <== -p 可以单独列出变量的类型declare -ir sum="450" <== 看吧!只剩下 i, r 的类型,不具有 x 啰!declare 也是个很有用的功能~尤其是当我们需要使用到底下的数组功能时, 他也可以帮我们宣告数组的属性喔!不过,老话一句,数组也是在 shell script 比较常用的啦! 比较有趣的是,如果你不小心将变量配置为『只读』,通常得要注销再登陆才能复原该变量的类型了! @_@
]]><property name=”age” update=”false”></property><BR> 在Annotation中 在属性GET方法上加上@Column(updatable=false)
@Column(updatable=false) public int getAge() { return age; }我们在执行 Update方法会发现,age 属性 不会被更改Hibernate: UPDATE Teacher SET birthday=?, name=?, title=? WHERE id=?缺点:不灵活····这个很少有人选择这种处理方法吧
2.第2种方法:使用XML中的 dynamic-update=”true”
<class name="com.sccin.entity.Student" table="student" dynamic-update="true"/><BR> OK,这样就不需要在字段上设置了。// ····这个很少有人选择这种处理方法吧,因为现在是注解的天下啊但这样的方法在Annotation中没有
3.第2种方式:使用HQL语句(灵活,方便)
使用HQL语句修改数据
public void update(){ Session session = HibernateUitl.getSessionFactory().getCurrentSession();session.beginTransaction();Query query = session.createQuery("update Teacher t set t.name = 'yangtianb' where id = 3"); query.executeUpdate(); session.getTransaction().commit(); Hibernate 执行的SQL语句:Hibernate: update Teacher set name='yangtianb' where id=3 // ····这个很少有人选择这种处理方法吧,用了hibernate还写基础的sql,不靠谱。
4.第4种方法:
将需要更新的对象加载上来后,利用BeanUtils类的copyProperties方法,将需要更新的值copy到对象中,最后再调用update方法。注意:这里推荐使用的方法并非org.apache.comm*****.beanutils包中的方法,而是org.springframework.beans.BeanUtils中的copyProperties方法。原因是Spring工具类提供了copyProperties(source, target, ignoreProperties)方法,它能在复制对象值的时候忽略指定属性值,保护某些值不被恶意修改,从而更安全的进行对象的更新。此外,根据一些测试结果spring中的copyProperties方法效率要高于apache的方法(这点未作进一步验证)。参考代码:Admin persistent = adminService.load(id);// 加载对象BeanUtils.copyProperties(admin, persistent, new String[]{"id", "username"});// 复制对象属性值时忽略id、username属性,可避免username被恶意修改adminService.update(persistent);// 更新对象// ····推荐这个,用起来还是很方便的 啊
5.第5种方法:这也是挺方便的一种方式吧
不用工具的话 只能先查询出来,再部分setPropertity//参数 String nickname。。。。。例如:User m1 = userservice.getModel(userid);m1.setNickName(nickname);m1.setXXX(XXX);m1.update();
]]>Redis不仅仅支持简单的key/value类型的数据,同时还提供list,set,zset,hash等数据结构的存储。
Redis支持数据的备份,即master-slave模式的数据备份。
Redis支持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使用+数据同步
这两年 Redis火得可以,Redis也常常被当作 Memcached的挑战者被提到桌面上来。关于Redis与Memcached的比较更是比比皆是。然而,Redis真的在功能、性能以及内存使用效率上都超越了Memcached吗?
没有必要过于关注性能,因为二者的性能都已经足够高了。由于Redis只使用单核,而Memcached可以使用多核,所以二者比较起来,平均每一个核上,Redis在存储小数据时比Memcached性能更高。而在100k以上的数据中,Memcached性能要高于Redis。虽然Redis最近也在存储大数据的性能上进行优化,但是比起Memcached,还是稍有逊色。说了这么多,结论是,无论你使用哪一个,每秒处理请求的次数都不会成为瓶颈。
在内存使用效率上,如果使用简单的key-value存储,Memcached的内存利用率更高。而如果Redis采用hash结构来做key-value存储,由于其组合式的压缩,其内存利用率会高于Memcached。当然,这和你的应用场景和数据特性有关。
如果你对数据持久化和数据同步有所要求,那么推荐你选择Redis。因为这两个特性Memcached都不具备。即使你只是希望在升级或者重启系统后缓存数据不会丢失,选择Redis也是明智的。
当然,最后还得说到你的具体应用需求。Redis相比Memcached来说,拥有更多的数据结构,并支持更丰富的数据操作。通常在Memcached里,你需要将数据拿到客户端来进行类似的修改再set回去。这大大增加了网络IO的次数和数据体积。在Redis中,这些复杂的操作通常和一般的GET/SET一样高效。所以,如果你需要缓存能够支持更复杂的结构和操作,那么Redis会是不错的选择。
1 | $ sudo apt-get install subversion |
2.添加svn管理用户及subversion组1
2
3$ sudo adduser svnuser zhangyang226
$ sudo addgroup subversion
$ sudo addgroup svnuser subversion
3.创建项目目录1
2
3
4
5$ sudo mkdir /home/svn
$ cd /home/svn
$ sudo mkdir robots
$ sudo chown -R root:subversion robots
$ sudo chmod -R g+rws robots
4.创建SVN文件仓库1
$ sudo svnadmin create /home/svn/robots
5.访问方式及项目导入:1
2
3$ svn co file:///home/svn/robots
或者
$ svn co file://localhost/home/svn/robots
注意:
如果您并不确定主机的名称,您必须使用三个斜杠(///),而如果您指定了主机的名称,则您必须使用两个斜杠(//).
//–
下面的命令用于将项目导入到SVN 文件仓库:
$ svn import -m “New import” /home/svn/robots file:///home/svnuser/src/robots
一定要注明导入信息
//————————–//
6.访问权限设置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32 修改 /home/svn/robots目录下:
svnserve.conf 、passwd 、authz三个文件,行最前端不允许有空格
//--
编辑svnserve.conf文件,把如下两行取消注释
password-db = password
authz-db = authz
//补充说明
# [general]
anon-access = read
auth-access = write
password-db = passwd
其中 anon-access 和 auth-access 分别为匿名和有权限用户的权限,默认给匿名用户只读的权限,但如果想拒绝匿
名用户的访问,只需把 read 改成 none 就能达到目的。
//--
编辑/home/svnuser/etc/passwd 如下:
[users]
mirze = 123456
test1 = 123456
test2 = 123456
//--
编辑/home/svnuser/etc/authz如下
[groups]
admin = mirze,test1
test = test2
[/]
@admin=rw
*=r
这里设置了三个用户mirze,test1,test2密码都是123456
其中mirze和test1属于admin组,有读和写的权限,test2属于test组只有读的权限
7.启动SVN服务1
2
3
4
5
6
7
8
9svnserve -d -r /home/svn
描述说明:
-d 表示svnserver以“守护”进程模式运行
-r 指定文件系统的根位置(版本库的根目录),这样客户端不用输入全路径,就可以访问版本库
如: svn://192.168.12.118/robots
这时SVN安装就完成了.
局域网访问方式:
例如:svn checkout svn://192.168.12.118/robots --username mirze --password 123456 /var/www/robots
• 二、HTTP:// [apache]
1.安装包 [已安装subversion]1
$ sudo apt-get install libapache2-svn
创建版本仓库:1
sudo svnadmin create /目录地址
1 | 目录地址必须存在,这个就是保存版本仓库的地方,不同的版本仓库创建不同的文件夹即可,比如: |
加入如下内容:1
2
3
4
5
6
7
8
9
10<Location /project>
DAV svn
SVNPath /home/svn/project
AuthType Basic
AuthName “myproject subversion repository”
AuthUserFile /home/svn/project/conf/passwd
#<LimitExcept GET PROPFIND OPTIONS REPORT>
Require valid-user
#</LimitExcept>
</Location>
location说的是访问地址,比如上述地址,访问的时候就是1
2
3
4
5
6
7
8
9
10
11http://127.0.0.1/project
其中有两行被注释掉了,以保证每次都需要用户名密码。
最后一步就是创建访问用户了,建议将用户名密码文件存放在当前版本仓库下conf文件夹下,这样版本仓库多的时候无至于太乱。
因为conf文件夹下已经存在passwd文件了,所以直接添加用户:
sudo htpasswd -c /home/svn/project/conf/passwd test
然后输入两遍密码,laoyang这个用户就创建好了。
打开/home/svn/project/conf/passwd这个文件,会开到形如如下形式的文本:
test:WEd.83H.gealA //后面是加密后的密码。
创建以后,再次需要往别的版本仓库添加这个用户,直接把这一行复制过去就可以了。
重启apache就可以了。
sudo /etc/init.d/apache2 restart
• 三、同步更新 [勾子]
同步程序思路:用户提交程序到SVN,SVN触发hooks,按不同的hooks进行处理,这里用到的是post-commit,利用post-commit到代码检出到SVN服务器的本地硬盘目录,再通过rsync同步到远程的WEB服务器上。
知识点:
1、SVN的hooks1
2
3
4
5
6# start-commit 提交前触发事务
# pre-commit 提交完成前触发事务
# post-commit 提交完成时触发事务
# pre-revprop-change 版本属性修改前触发事务
# post-revprop-change 版本属性修改后触发事务
通过上面这些名称编写的脚本就就可以实现多种功能了,相当强大。
2、同步命令rsync的具体参数使用
3、具有基个语言的编程能力bash python perl都可以实现
1 | post-commit具体实现细节 |
以上是具体的post-commit程序
注意事项:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
211、一定要定义变量,主要是用过的命令的路径。因为SVN的考虑的安全问题,没有调用系统变量,如果手动执行是没有问题,但SVN自动执行就会无法执行了。
2、SVN update 之前一定要先手动checkout一份出来,还有这里一定要添加用户和密码如果只是手动一样会更新,但自动一样的不行。
3、加上了对前一个命令的判断,如果update的时候出了问题,程序没有退出的话还会继续同步代码到WEB服务器上,这样会造成代码有问题
4、记得要设置所属用户,因为rsync可以同步文件属性,而且我们的WEB服务器一般都不是root用户,用户不正确会造成WEB程序无法正常工作。
5、建议最好记录日志,出错的时候可以很快的排错
6、最后最关键的数据同步,rsync的相关参数一定要清楚,这个就不说了。注意几个场景:
这里的环境是SVN服务器与WEB服务器是开的
把SVN服务器定义为源服务器 WEB服务器为目的服务器
场景一、如果目的WEB服务器为综合的混杂的,像只有一个WEB静态资源,用户提交的,自动生成的都在WEB的一个目录下,建议不要用–delete这个参数
上面这个程序就是这样,实现的是源服务器到目的服务器的更新和添加,而没有删除操作,WEB服务器的内容会多于源SVN的服务器的
场景二、实现镜像,即目的WEB服务器与源SVN服务器一样的数据,SVN上任何变化WEB上一样的变化,就需要–delete参数
场景三、不需要同步某些子目录,可能有些目录是缓存的临时垃圾目录,或者是专用的图片目录(而不是样式或者排版的)要用exclude这个参数
注意:这个参数的使用不用写绝对路径,只要目录名称就行 aa代表文件 aa/ 代表目录 ,缺点就是如果有多个子目录都是一样的名称那么这些名称就都不会被同步
建议用–exclude-from=/home/svn/exclude.list 用文件的形式可以方便的添加和删除
exclude.list
.svn/
.DS_Store
images/
利用SVN的钩子还可以写出很多的程序来控制SVN 如代码提交前查看是否有写日志,是否有tab,有将换成空格,是否有不允许上传的文件,是否有超过限制大小的文件等等。
下面具体介绍Git服务器的搭建,机器环境:ubuntu12.04,Git服务器软件采用gitosis(https://github.com/res0nat0r/gitosis )。
一、软件安装
1.1 安装ssh的服务端和客户端:sudo apt-get install openssh-server openssh-client1.2 安装git-core软件,这个是git服务的基础:sudo apt-get install git-core1.3 安装 Gitosis,这个是git服务器软件
1 | sudo apt-get install python-setuptools |
1 | cd /tmp |
1 | git clone https://github.com/res0nat0r/gitosis.git |
1 | cd gitosis |
1 | sudo python setup.py install |
ok,软件的安装都此结束。
二、创建Git专用账号git
sudo useradd -m -s /bin/bash git //创建git账号,用户家目录默认为/home/git,shell为/bin/bash
sudo passwd git //设置git用户的密码
三、初始化Git仓库
1、初始化Git仓库需要一个管理员账号,如上图所示,管理员也是一个客户端用户,所以需要在客户端主机(我的客户端机器是Win7系统)生成一个用户,并且生成ssh-key。具体操作如下:
在客户端安装ssh服务,包括客户端和服务端。如果你的客户端系统是: (1)Windows机器,建议直接安装git bash软件,其中包括了ssh这个服务; (2)Linux系统,可以按照先前的命令apt-get install openssh-server openssh-client安装ssh服务。 在客户端机器我的账号是huixiao200068,在用户家目录中生成ssh-key: (1)Windows机器,打开git bash软件,输入命令 cd ~ ssh-keygen -t rsa (2)Linux系统,也执行以上相同的命令,即可以生成当前用户的ssh-key。 执行完成之后,输入命令:ls -al,如果出现了.ssh目录,则表示ssh-key成功创建。2、把ssh的公钥上传到服务器,我这里假设上传到服务器的临时目录/tmp。 scp ~/.ssh/id_rsa.pub git@server:/tmp //命令中的server改成你自己服务器的IP到此为止,仅仅只是做好了初始化仓库的准备工作,上面的2步操作都是在客户端操作的。下面是关键的第三步——初始化,该步骤在服务器端执行。3、初始化 sudo -H -u git gitosis-init < /tmp/id_rsa.pub //将git仓库目录初始化到了git用户家目录下 此时,git用户的home目录中将出现repositories目录,该目录为git的仓库。 修改目录权限:sudo chmod 755 /home/git/repositories/gitosis-admin.git/hooks/post-update,该步骤尚不清楚其具体的作用,修改了这个目录的权限有啥用呢?望各位指教。
恭喜你,到此为止,你已经创建了一个git仓库,而且账号huixiao200068是git仓库的管理员啦。下面要做的就是仓库配置啦,管理项目和用户。
四、下载仓库配置项目gitosis-admin到本地客户端
因为git仓库的配置文件都是以git方式来管理的,所以你需要先下载一份到客户端本地 。
在你的用户目录下面创建一个临时目录work,
然后 进入到该目录:cd work,
然后执行命令:
git clone git@server:gitosis-admin.git // 命令中的server改成你自己服务器的IP
执行完成之后,work目录下会生成gitosis-admin目录,目录下面有一个gitosis.conf文件和一个keydir目录,它们将是下面配置任务的主要操作对象,请牢记它们的位置。
五、新建项目
1、修改配置文件gitosis.conf,增加如下内容。
[group first-pro] //用户组名
members = huixiao200068 //成员名,多个成员可以用空格隔开
writable = first-pro //项目名及其用户对于此项目的权限,目前是可写
2、创建项目目录 mkdir first-pro 初始化该目录 cd first-pro; git init 添加远程仓库 git remote add origin git@SERVER:first-pro.git 创建工程文件 touch 1.txt 添加工程文件到本地仓库中 git add ./1.txt 提交整个项目到本地仓库 git commit -m "comment" 提交整个项目到远程仓库 git push origin master //只有这样子,团队其他开发人员才能看到你修改的代码 查看提交日志 git log 查看本地库当前的状态,比如是否有文件需要提交。 git status新的项目仓库已经生成,其他开发人员可以git clone 命令从服务器上下载一份工程到自己的本地机器上,协同开发啦!
六、新建用户
关于上一节最后提到的内容,“其他开发人员”,他们是需要管理员来增加和配置的,这一节主要讲怎么添加用户。
(1)客户端操作:
首先要生成ssh-key,方法和上述说明的一样。
cd ~
ssh-keygen -t rsa
然后一直回车,就OK。然后将生成的id_rsa.pub文件传给GIT服务器管理员
(2)服务器端操作:
管理员将客户上传的id_rsa.pub文件移到gitosis-admin/keydir目录中,并且改名为CLIENT_NAME.pub。注意:如果客户端如下图所示
,则CLIENT_NAME为sean@bogon,否则后续操作会出现“Repository read access denied”的错误。
给项目first-pro增加新的开发者,编辑gitosis.conf文件,vi gitosis.conf。 [group first-pro] //用户组名 members = huixiao200068 sean@bogon //成员名,多个成员可以用空格隔开 writable = first-pro //项目名及其用户对于此项目的权限,目前是可写提交修改的管理文件: git commit -a -m "add user sean@bogon" git push origin master
完成上述2步之后,即可以使用该账号共同开发项目first-pro啦!
cd ~
git clone git@SERVER:first-pro.git //克隆项目到本地
…… //do anything you want to do
commit -am “comment”
commit push origin master
初始化配置
#配置使用git仓库的人员姓名
git config –global user.name “Your Name Comes Here”
#配置使用git仓库的人员email
git config –global user.email you@yourdomain.example.com
#配置到缓存 默认15分钟
git config –global credential.helper cache
#修改缓存时间
git config –global credential.helper ‘cache –timeout=3600’
git config –global color.ui true
git config –global alias.co checkout
git config –global alias.ci commit
git config –global alias.st status
git config –global alias.br branch
git config –global core.editor “mate -w” # 设置Editor使用textmate
git config -1 #列举所有配置
#用户的git配置文件~/.gitconfig
查看、添加、提交、删除、找回,重置修改文件
git help
git show # 显示某次提交的内容
git show $id
git co –
git co . # 抛弃工作区修改
git add
git add . # 将所有修改过的工作文件提交暂存区
git rm
git rm
git reset
git reset – . # 从暂存区恢复到工作文件
git reset –hard # 恢复最近一次提交过的状态,即放弃上次提交后的所有本次修改
git ci
git ci .
git ci -a # 将git add, git rm和git ci等操作都合并在一起做
git ci -am “some comments”
git ci –amend # 修改最后一次提交记录
git revert <$id> # 恢复某次提交的状态,恢复动作本身也创建了一次提交对象
git revert HEAD # 恢复最后一次提交的状态
查看文件diff
git diff
git diff
git diff <$id1> <$id2> # 比较两次提交之间的差异
git diff
git diff –staged # 比较暂存区和版本库差异
git diff –cached # 比较暂存区和版本库差异
git diff –stat # 仅仅比较统计信息
查看提交记录
git log
git log
git log -p
git log -p -2 # 查看最近两次详细修改内容的diff
git log –stat #查看提交统计信息
tig
Mac上可以使用tig代替diff和log,brew install tig
取得Git仓库
#初始化一个版本仓库
git init
#Clone远程版本库
git clone git@xbc.me:wordpress.git
#添加远程版本库origin,语法为 git remote add [shortname] [url]
git remote add origin git@xbc.me:wordpress.git
#查看远程仓库
git remote -v
提交你的修改
#添加当前修改的文件到暂存区
git add .
#如果你自动追踪文件,包括你已经手动删除的,状态为Deleted的文件
git add -u
#提交你的修改
git commit –m “你的注释”
#推送你的更新到远程服务器,语法为 git push [远程名] [本地分支]:[远程分支]
git push origin master
#查看文件状态
git status
#跟踪新文件
git add readme.txt
#从当前跟踪列表移除文件,并完全删除
git rm readme.txt
#仅在暂存区删除,保留文件在当前目录,不再跟踪
git rm –cached readme.txt
#重命名文件
git mv reademe.txt readme
#查看提交的历史记录
git log
#修改最后一次提交注释的,利用–amend参数
git commit –amend
#忘记提交某些修改,下面的三条命令只会得到一个提交。
git commit –m "add readme.txt"
git add readme_forgotten
git commit –amend
#假设你已经使用git add .,将修改过的文件a、b加到暂存区
#现在你只想提交a文件,不想提交b文件,应该这样
git reset HEAD b
#取消对文件的修改
git checkout –- readme.txt
查看、切换、创建和删除分支
git br -r # 查看远程分支
git br <new_branch> # 创建新的分支
git br -v # 查看各个分支最后提交信息
git br –merged # 查看已经被合并到当前分支的分支
git br –no-merged # 查看尚未被合并到当前分支的分支
git co
git co -b <new_branch> # 创建新的分支,并且切换过去
git co -b <new_branch>
git co $id # 把某次历史提交记录checkout出来,但无分支信息,切换到其他分支会自动删除
git co $id -b <new_branch> # 把某次历史提交记录checkout出来,创建成一个分支
git br -d
git br -D
分支合并和rebase
git merge
git merge origin/master –no-ff # 不要Fast-Foward合并,这样可以生成merge提交
git rebase master
git co
Git补丁管理(方便在多台机器上开发同步时用)
git diff > ../sync.patch # 生成补丁
git apply ../sync.patch # 打补丁
git apply –check ../sync.patch #测试补丁能否成功
Git暂存管理
git stash # 暂存
git stash list # 列所有stash
git stash apply # 恢复暂存的内容
git stash drop # 删除暂存区
Git远程分支管理
git pull # 抓取远程仓库所有分支更新并合并到本地
git pull –no-ff # 抓取远程仓库所有分支更新并合并到本地,不要快进合并
git fetch origin # 抓取远程仓库更新
git merge origin/master # 将远程主分支合并到本地当前分支
git co –track origin/branch # 跟踪某个远程分支创建相应的本地分支
git co -b <local_branch> origin/<remote_branch> # 基于远程分支创建本地分支,功能同上
git push # push所有分支
git push origin master # 将本地主分支推到远程主分支
git push -u origin master # 将本地主分支推到远程(如无远程主分支则创建,用于初始化远程仓库)
git push origin <local_branch> # 创建远程分支, origin是远程仓库名
git push origin <local_branch>:<remote_branch> # 创建远程分支
git push origin :<remote_branch> #先删除本地分支(git br -d
基本的分支管理
#创建一个分支
git branch iss53
#切换工作目录到iss53
git chekcout iss53
#将上面的命令合在一起,创建iss53分支并切换到iss53
git chekcout –b iss53
#合并iss53分支,当前工作目录为master
git merge iss53
#合并完成后,没有出现冲突,删除iss53分支
git branch –d iss53
#拉去远程仓库的数据,语法为 git fetch [remote-name]
git fetch
#fetch 会拉去最新的远程仓库数据,但不会自动到当前目录下,要自动合并
git pull
#查看远程仓库的信息
git remote show origin
#建立本地的dev分支追踪远程仓库的develop分支
git checkout –b dev origin/develop
Git远程仓库管理
git remote -v # 查看远程服务器地址和仓库名称
git remote show origin # 查看远程服务器仓库状态
git remote add origin git@ github:robbin/robbin_site.git # 添加远程仓库地址
git remote set-url origin git@ github.com:robbin/robbin_site.git # 设置远程仓库地址(用于修改远程仓库地址)
git remote rm
创建远程仓库
git clone –bare robbin_site robbin_site.git # 用带版本的项目创建纯版本仓库
scp -r my_project.git git@ git.csdn.net:~ # 将纯仓库上传到服务器上
mkdir robbin_site.git && cd robbin_site.git && git –bare init # 在服务器创建纯仓库
git remote add origin git@ github.com:robbin/robbin_site.git # 设置远程仓库地址
git push -u origin master # 客户端首次提交
git push -u origin develop # 首次将本地develop分支提交到远程develop分支,并且track
git remote set-head origin master # 设置远程仓库的HEAD指向master分支
也可以命令设置跟踪远程库和本地库
git branch –set-upstream master origin/master
git branch –set-upstream develop origin/develop
public List query(String hql, Object... args) { Query query = getMasterSession().createQuery(hql); for (int i = 0; i < args.length; i++) query.setParameter(i, args[i]); return query.list();}
///////////////////////////////////////////////////////////dao
package com.yunlu.dao;
import java.io.Serializable;
import java.util.LinkedHashMap;
import java.util.List;
import com.yl.util.QueryResult;
@SuppressWarnings(“hiding”)
public interface HibernateDao<T, PK extends Serializable> {
public <T extends Serializable> PK save(T entity);public <T extends Serializable> void update(T entity);public <T extends Serializable> void update(LinkedHashMap<String, T> update);public <T extends Serializable> void update( LinkedHashMap<String, T> update, String where, Object[] parameter);public <T extends Serializable> void delete(T entity);public void delete(PK... primaryKeyId);public void delete(String where, Object[] parameter);public T find(PK primarKeyId);public List<T> findAll();public <T extends Serializable> QueryResult<T> findByCondition( int firstResult, int maxResults, String where, LinkedHashMap<String, String> orderBy);public <T extends Serializable> QueryResult<T> findByCondition(String where);
}
//////////////////////////////////////////////////////////////////////////////////////////////impl
package com.yunlu.dao.impl;
import java.io.Serializable;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import javax.annotation.Resource;
import org.apache.commons.lang.StringUtils;
import org.hibernate.Query;
import org.hibernate.SessionFactory;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import com.yl.util.QueryResult;
import com.yl.util.ReflectManager;
import com.yunlu.dao.HibernateDao;
@SuppressWarnings({ “unchecked”, “hiding” })
public abstract class HibernateDaoImpl<T, PK extends Serializable> implements
HibernateDao<T, PK> {
private LinkedHashMap<String, Object> temp = new LinkedHashMap<String, Object>();@Transactional(propagation = Propagation.REQUIRED)public <T extends Serializable> PK save(T entity) { PK pk = null; pk = (PK) sessionFactory.getCurrentSession().save(entity); sessionFactory.getCurrentSession().flush(); return pk;}@Transactional(propagation = Propagation.REQUIRED)public <T extends Serializable> void update(T entity) { // sessionFactory.getCurrentSession().update(entity); sessionFactory.getCurrentSession().saveOrUpdate(entity); sessionFactory.getCurrentSession().flush();}private <T extends Serializable> String getUpdateStr( LinkedHashMap<String, T> update) { StringBuilder updateStr = new StringBuilder(); int i = 1; for (Map.Entry<String, T> entry : update.entrySet()) { updateStr.append(entry.getKey()).append("=").append(":u").append(i) .append(","); this.temp.put("u" + i, entry.getValue()); i++; } updateStr.deleteCharAt(updateStr.length() - 1); return updateStr.toString();}private void setUpdateParameter(Query query) { for (Map.Entry<String, Object> entry : this.temp.entrySet()) { query.setParameter(entry.getKey(), entry.getValue()); }}@Transactional(propagation = Propagation.REQUIRED)public <T extends Serializable> void update(LinkedHashMap<String, T> update) { this.update(update, null, null);}@Transactional(propagation = Propagation.REQUIRED)public <T extends Serializable> void update( LinkedHashMap<String, T> update, String where, Object[] parameter) { String entityName = t.getName(); // this.sessionFactory.getCurrentSession().createQuery("update "+entityName+" o o.name=:name").setParameter("name","xiaoxiao").executeUpdate(); StringBuilder hql = new StringBuilder(); hql.append("update " + entityName + " as o set " + this.getUpdateStr(update)); if (where != null && parameter != null) { hql.append(" ").append(where); } System.out.println(hql.toString()); Query query = sessionFactory.getCurrentSession().createQuery( hql.toString()); if (parameter != null) { this.setQueryParameter(query, parameter); } if (update != null) { this.setUpdateParameter(query); } query.executeUpdate();}@Transactional(propagation = Propagation.REQUIRED)public <T extends Serializable> void delete(T entity) { sessionFactory.getCurrentSession().delete(entity); sessionFactory.getCurrentSession().flush();}@Transactional(propagation = Propagation.REQUIRED)public void delete(PK... primaryKeyId) { for (PK pkId : primaryKeyId) { Object entity = sessionFactory.getCurrentSession().get(t, pkId); sessionFactory.getCurrentSession().delete(entity); sessionFactory.getCurrentSession().flush(); }}@Transactional(propagation = Propagation.REQUIRED)public void delete(String where, Object[] parameter) { String entityName = t.getName(); StringBuilder hql = new StringBuilder(); hql.append("delete " + entityName + " as o"); if (where != null && parameter != null) { hql.append(" ").append(where); } Query query = sessionFactory.getCurrentSession().createQuery( hql.toString()); if (parameter != null) { this.setQueryParameter(query, parameter); } query.executeUpdate();}@Transactional(propagation = Propagation.REQUIRED)public T find(PK primarKeyId) { T findObj = null; findObj = (T) sessionFactory.getCurrentSession().get(t, primarKeyId); return findObj;}@Transactional(propagation = Propagation.REQUIRED)public List<T> findAll() { String entityName = t.getName(); String hql = "from " + entityName; Query query = sessionFactory.getCurrentSession().createQuery(hql); return query.list();}private void setQueryParameter(Query query, Object[] parameter) { if (parameter != null) { for (int i = 0; i < parameter.length; i++) { query.setParameter("p" + (i + 1), parameter[i]); } }}protected String getOrderStr(LinkedHashMap<String, String> orderBy) { StringBuilder orderStr = new StringBuilder(); for (String key : orderBy.keySet()) { orderStr.append(key).append(" ").append(orderBy.get(key)) .append(","); } orderStr.deleteCharAt(orderStr.length() - 1); return orderStr.toString();}@Transactional(propagation = Propagation.REQUIRED)public <T extends Serializable> QueryResult<T> findByCondition( int firstResult, int maxResults, String where, LinkedHashMap<String, String> orderBy) { String entityName = t.getName(); StringBuilder hql = new StringBuilder(); hql.append("select o from " + entityName + " as o"); if (StringUtils.isNotEmpty(where)) { hql.append(where); } if (orderBy != null) { hql.append(" order by ").append(this.getOrderStr(orderBy)); } Query query = null; if (firstResult != -1 && maxResults != -1) { query = sessionFactory.getCurrentSession() .createQuery(hql.toString()).setFirstResult(firstResult) .setMaxResults(maxResults); } else { query = sessionFactory.getCurrentSession().createQuery( hql.toString()); } QueryResult<T> queryResult = new QueryResult<T>(); queryResult.setResultlist(query.list()); queryResult.setTotalrecord((long) query.list().size()); return queryResult;}@Transactional(propagation = Propagation.REQUIRED)public <T extends Serializable> QueryResult<T> findByCondition( String where) { String entityName = t.getName(); StringBuilder hql = new StringBuilder(); hql.append("select o from " + entityName + " as o"); if (StringUtils.isNotEmpty(where)) { hql.append(where); } Query query = null; query = sessionFactory.getCurrentSession().createQuery( hql.toString()); QueryResult<T> queryResult = new QueryResult<T>(); queryResult.setResultlist(query.list()); queryResult.setTotalrecord((long) query.list().size()); return queryResult;}protected SessionFactory sessionFactory;public SessionFactory getSessionFactory() { return sessionFactory;}@Resource(name = "HibernateSessionFactory")public void setSessionFactory(SessionFactory sessionFactory) { this.sessionFactory = sessionFactory;}private Class<?> t;public HibernateDaoImpl() { t = ReflectManager.getFirstGenericTypeSuperclass(this.getClass());}
}
]]>//—————————————————————————————-
解析后:
[00:01.10]阳光下的泡沫 是彩色的
[00:01.72]阳光下的泡沫 是彩色的
[00:08.45]就像被骗的我 是幸福的
[00:15.58]追究什么对错 你的谎言 基于你还爱我
[00:27.21]美丽的泡沫 虽然一刹花火
[00:28.74]美丽的泡沫 虽然一刹花火
[00:35.89]你所有承诺 虽然都太脆弱
[00:42.84]但爱像泡沫 如果能够看破 有什么难过
[00:50.10]早该知道泡沫 一触就破
[00:57.95]早该知道泡沫 一触就破
procedure TForm1.Button1Click(Sender: TObject);var str: WideString;FTextList :TStrings;i:integer;k:integer;timestr:String;content:WideString;lastIndex:integer;timeNum:integer;n:integer;beginFTextList:=TStringList.create;if(op1.Execute) then begin FTextList.loadFromfile(op1.FileName); for i:=0 to FTextList.count -1 do begin str := FTextList[i]; lastIndex:=LastDelimiter(']',str); //showmessage(inttostr(lastIndex)); //计算时间标签的个数 timeNum:=lastIndex div 10; showmessage(inttostr(timeNum)); content :=copy(str,lastIndex+1,length(str)-lastIndex) ; //m1.Lines.Add(content); if timeNum=0 then m1.Lines.Add(str) else for n:=0 to timeNum-1 do begin timestr:=copy(str,n*10+1,10) ; m1.Lines.Add(timestr+content); end; end; end;end;
]]>PS:如果想要在虚拟机中安装Ubuntu12.04,然后再使用Vagrant方式搭建开发环境,请确保这个虚拟机可以使用2GB的内存,否则容易报一个IOError。上一篇EdX搭建教程结尾便是使用的虚拟机,感兴趣的可以看下里面的出错:
(http://hujiaweiyinger.diandian.com/post/2013-07-16/edx_build_edx_platform)
参考网址:
(1)在Ubuntu中安装RPM格式软件包的方法:
http://my.oschina.net/renyuansoft/blog/28187
(2)在ubuntu中开启root用户并实现root登录问题的解决方法:
http://os.51cto.com/art/200709/56719.htm
(3)在ubuntu 12.04中设置root登录的办法:
http://blog.chinaunix.net/uid-26517277-id-3217839.html
(4)Linux中uname命令的使用详解:
http://baike.baidu.com/view/1374878.htm
(1)precise32.box (提前下载好虚拟机)
网址:http://files.vagrantup.com/precise32.box
(2)vagrant 1.2.2 (虽然最新的是1.2.5了,但是还是推荐使用1.2.2)
网址:http://downloads.vagrantup.com/tags/v1.2.2
我选择的是 vagrant_1.2.2_i686.deb
(3)virtualbox 4.2.12 (千万不要使用最新的4.2.16,注意版本!)
网址:http://download.virtualbox.org/virtualbox/4.2.12/
或者是 https://www.virtualbox.org/wiki/Download_Old_Builds_4_2
需要两个:
① 对应系统的virtualbox安装包
virtualbox-4.2_4.2.12-84980~Ubuntu~precise_i386.deb
② 扩展包 (虽然不知道是否有影响,但是建议安装)
Oracle_VM_VirtualBox_Extension_Pack-4.2.12-84980.vbox-extpack
(1)以普通用户的身份登录Ubuntu
最好不要使用 root 账户登录
(2)安装git,运行 “ sudo apt-get install git ”
(3)安装nfs,运行 “ sudo apt-get install nfs-common nfs-kernel-server”
安装NFS,避免后面提示系统不支持NFS,然后才安装NFS,提前安装好
(4)新建目录 edx,将下载的所有安装包放在 edx 目录下
(5)安装Vagrant和VirtualBox及其扩展,“ cd edx”-> “ dpkg -i vagrant_xxx.deb” -> “ dpkg -i virtualbox-xxx.deb” -> 双击运行 extpack 文件安装扩展包
(6)clone repository,运行 “git clone git://github.com/edx/edx-platform.git “
千万不要只是将别的电脑中的源代码或者通过其他用户登录下载得到的源代码拿过来使用,因为这样的话当前用户执行vagrant up很容易出现权限不够的问题!
(7)完了之后进入edx-platform目录,修改以下三个地方:
① 找到 requirements目录下的 base.txt 中的 “polib = 1.0.3” ,把这一行注释掉或者删除;
② 找到 requirements目录下的 github.txt 中的 Third-party 项目,将 前面的 “git” 改成 “git+https”
例如:原来是 ”-e git://github.com/edx/django-staticfiles.git@6d2504e5c8#egg=django-staticfiles“
改成 ”-e git+https://github.com/edx/django-staticfiles.git@6d2504e5c8#egg=django-staticfiles“
③ 找到scripts目录下的create_dev_env.sh文件,找到
”pip install -r $BASE/edx-platform/requirements/edx/pre.txt“,在后面添加一行
”pip install http://bitbucket.org/izi/polib/get/1.0.3.tar.gz“
原因:polib依赖项经过pypi的解析得到的下载地址是:http://bitbucket.org/izi/polib/downloads/polib-1.0.3.tar.gz,但是在天朝,这个地址上访问不上的,然而前面的downloads是可以的,polib是必须的依赖项,所以只能是使用变相的方式将其安装上去。详细内容可见我的这个问题(https://github.com/edx/edx-platform/issues/427)
(8)添加box,运行 “cd edx-platform” -> “vagrant box add precise32 ../precise32.box”
之所以直接下载下来然后添加是为了防止网络不稳定出现故障而导致下载失败
(9)开始运行”vagrant up”,一定要保证过程中网络稳定!
如果需要输出更加详细的内容可以使用 “VAGRANT_LOG=DEBUG vagrant up”
(10)你要是有咖啡泡,建议去泡一杯,或者和朋友打盘三国杀,放心,过程中只有开始的时候要输入一次账号密码,其他时候都是不停地打印中,等着吧……
最后出现的画面就是下面的 “ Success ”了!哈哈哈
]]>一、冒泡排序(BubbleSort)
java代码实现:
/**
* 冒泡排序:执行完一次内for循环后,最小的一个数放到了数组的最前面(跟那一个排序算法* 不一样)。相邻位置之间交换 */ public class BubbleSort { /** * 排序算法的实现,对数组中指定的元素进行排序 * @param array 待排序的数组 * @param from 从哪里开始排序 * @param end 排到哪里 * @param c 比较器 */ public void bubble(Integer[] array, int from, int end) { //需array.length - 1轮比较 for (int k = 1; k < end - from + 1; k++) { //每轮循环中从最后一个元素开始向前起泡,直到i=k止,即i等于轮次止 for (int i = end - from; i >= k; i--) { //按照一种规则(后面元素不能小于前面元素)排序 if ((array[i].compareTo(array[i - 1])) < 0) { //如果后面元素小于了(当然是大于还是小于要看比较器实现了)前面的元素,则前后交换 swap(array, i, i - 1); } } } } /** * 交换数组中的两个元素的位置 * @param array 待交换的数组 * @param i 第一个元素 * @param j 第二个元素 */ public void swap(Integer[] array, int i, int j) { if (i != j) {//只有不是同一位置时才需交换 Integer tmp = array[i]; array[i] = array[j]; array[j] = tmp; } } /** * 测试 * @param args */ public static void main(String[] args) { Integer[] intgArr = { 7, 2, 4, 3, 12, 1, 9, 6, 8, 5, 11, 10 }; BubbleSort bubblesort = new BubbleSort(); bubblesort.bubble(intgArr,0,intgArr.length-1); for(Integer intObj:intgArr){ System.out.print(intObj + " "); } } }
另外一种实现方式:
/*
冒泡排序:执行完一次内for循环后,最大的一个数放到了数组的最后面。相邻位置之间交换 /
public class BubbleSort2{
public static void main(String[] args){
int[] a = {3,5,9,4,7,8,6,1,2};
BubbleSort2 bubble = new BubbleSort2();
bubble.bubble(a);
for(int num:a){
System.out.print(num + “ “);
}
}
public void bubble(int[] a){ for(int i=a.length-1;i>0;i--){ for(int j=0;j<i;j++){ if(new Integer(a[j]).compareTo(new Integer(a[j+1]))>0){ swap(a,j,j+1); } } } } public void swap(int[] a,int x,int y){ int temp; temp=a[x]; a[x]=a[y]; a[y]=temp; } }
二、选择排序
java代码实现:
/**
* 简单选择排序:执行完一次内for循环后最小的一个数放在了数组的最前面。 */ public class SelectSort { /** * 排序算法的实现,对数组中指定的元素进行排序 * @param array 待排序的数组 * @param from 从哪里开始排序 * @param end 排到哪里 * @param c 比较器 */ public void select(Integer[] array) { int minIndex;//最小索引 /* * 循环整个数组(其实这里的上界为 array.length - 1 即可,因为当 i= array.length-1 * 时,最后一个元素就已是最大的了,如果为array.length时,内层循环将不再循环),每轮假设 * 第一个元素为最小元素,如果从第一元素后能选出比第一个元素更小元素,则让让最小元素与第一 * 个元素交换 */ for (int i=0; i<array.length; i++) { minIndex = i;//假设每轮第一个元素为最小元素 //从假设的最小元素的下一元素开始循环 for (int j=i+1;j<array.length; j++) { //如果发现有比当前array[smallIndex]更小元素,则记下该元素的索引于smallIndex中 if ((array[j].compareTo(array[minIndex])) < 0) { minIndex = j; } } //先前只是记录最小元素索引,当最小元素索引确定后,再与每轮的第一个元素交换 swap(array, i, minIndex); } } public static void swap(Integer[] intgArr,int x,int y){ //Integer temp; //这个也行 int temp; temp=intgArr[x]; intgArr[x]=intgArr[y]; intgArr[y]=temp; } /** * 测试 * @param args */ public static void main(String[] args) { Integer[] intgArr = { 5, 9, 1, 4, 2, 6, 3, 8, 0, 7 }; SelectSort insertSort = new SelectSort(); insertSort.select(intgArr); for(Integer intObj:intgArr){ System.out.print(intObj + " "); } } }
三、插入排序(Insertion Sort)
java代码实现:
/**
* 直接插入排序:* 注意所有排序都是从小到大排。*/ public class InsertSort { /** * 排序算法的实现,对数组中指定的元素进行排序 * @param array 待排序的数组 * @param from 从哪里开始排序 * @param end 排到哪里 * @param c 比较器 */ public void insert(Integer[] array,int from, int end) { /* * 第一层循环:对待插入(排序)的元素进行循环 * 从待排序数组断的第二个元素开始循环,到最后一个元素(包括)止 */ for (int i=from+1; i<=end; i++) { /* * 第二层循环:对有序数组进行循环,且从有序数组最第一个元素开始向后循环 * 找到第一个大于待插入的元素 * 有序数组初始元素只有一个,且为源数组的第一个元素,一个元素数组总是有序的 */ for (int j =0; j < i; j++) { Integer insertedElem = array[i];//待插入到有序数组的元素 //从有序数组中最一个元素开始查找第一个大于待插入的元素 if ((array[j].compareTo(insertedElem)) >0) { //找到插入点后,从插入点开始向后所有元素后移一位 move(array, j, i - 1); //将待排序元素插入到有序数组中 array[j] = insertedElem; break; } } } } /** * 数组元素后移 * @param array 待移动的数组 * @param startIndex 从哪个开始移 * @param endIndex 到哪个元素止 */ public void move(Integer[] array,int startIndex, int endIndex) { for (int i = endIndex; i >= startIndex; i--) { array[i+1] = array[i]; } } //=======以下是java.util.Arrays的插入排序算法的实现 /* * 该算法看起来比较简洁一j点,有点像冒泡算法。 * 将数组逻辑上分成前后两个集合,前面的集合是已经排序好序的元素,而后面集合为待排序的 * 集合,每次内层循从后面集合中拿出一个元素,通过冒泡的形式,从前面集合最后一个元素开 * 始往前比较,如果发现前面元素大于后面元素,则交换,否则循环退出 * * 总感觉这种算术有点怪怪,既然是插入排序,应该是先找到插入点,而后再将待排序的元素插 * 入到的插入点上,那么其他元素就必然向后移,感觉算法与排序名称不匹,但返过来与上面实 * 现比,其实是一样的,只是上面先找插入点,待找到后一次性将大的元素向后移,而该算法却 * 是走一步看一步,一步一步将待排序元素往前移 */ /* for (int i = from; i <= end; i++) { for (int j = i; j > from && c.compare(array[j - 1], array[j]) > 0; j--) { swap(array, j, j - 1); } } */ /** * 测试 * @param args */ public static void main(String[] args) { Integer[] intgArr = { 5, 9, 1, 4, 2, 6, 3, 8, 0, 7 }; InsertSort insertSort = new InsertSort(); insertSort.insert(intgArr,0,intgArr.length-1); for(Integer intObj:intgArr){ System.out.print(intObj + " "); } } }
四、稀尔排序
java代码实现:
public class ShellSort {
/** * 排序算法的实现,对数组中指定的元素进行排序 * @param array 待排序的数组 * @param from 从哪里开始排序 * @param end 排到哪里 * @param c 比较器 */ public void sort(Integer[] array, int from, int end) { //初始步长,实质为每轮的分组数 int step = initialStep(end - from + 1); //第一层循环是对排序轮次进行循环。(step + 1) / 2 - 1 为下一轮步长值 for (; step >= 1; step = (step + 1) / 2 - 1) { //对每轮里的每个分组进行循环 for (int groupIndex = 0; groupIndex < step; groupIndex++) { //对每组进行直接插入排序 insertSort(array, groupIndex, step, end); } } } /** * 直接插入排序实现 * @param array 待排序数组 * @param groupIndex 对每轮的哪一组进行排序 * @param step 步长 * @param end 整个数组要排哪个元素止 * @param c 比较器 */ public void insertSort(Integer[] array, int groupIndex, int step, int end) { int startIndex = groupIndex;//从哪里开始排序 int endIndex = startIndex;//排到哪里 /* * 排到哪里需要计算得到,从开始排序元素开始,以step步长,可求得下元素是否在数组范围内, * 如果在数组范围内,则继续循环,直到索引超现数组范围 */ while ((endIndex + step) <= end) { endIndex += step; } // i为每小组里的第二个元素开始 for (int i = groupIndex + step; i <= end; i += step) { for (int j = groupIndex; j < i; j += step) { Integer insertedElem = array[i]; //从有序数组中最一个元素开始查找第一个大于待插入的元素 if ((array[j].compareTo(insertedElem)) >= 0) { //找到插入点后,从插入点开始向后所有元素后移一位 move(array, j, i - step, step); array[j] = insertedElem; break; } } } } /** * 根据数组长度求初始步长 * * 我们选择步长的公式为:2^k-1,2^(k-1)-1,...,15,7,3,1 ,其中2^k 减一即为该步长序列,k * 为排序轮次 * * 初始步长:step = 2^k-1 * 初始步长约束条件:step < len - 1 初始步长的值要小于数组长度还要减一的值(因 * 为第一轮分组时尽量不要分为一组,除非数组本身的长度就小于等于4) * * 由上面两个关系试可以得知:2^k - 1 < len - 1 关系式,其中k为轮次,如果把 2^k 表 达式 * 转换成 step 表达式,则 2^k-1 可使用 (step + 1)*2-1 替换(因为 step+1 相当于第k-1 * 轮的步长,所以在 step+1 基础上乘以 2 就相当于 2^k 了),即步长与数组长度的关系不等式为 * (step + 1)*2 - 1 < len -1 * * @param len 数组长度 * @return */ public static int initialStep(int len) { /* * 初始值设置为步长公式中的最小步长,从最小步长推导出最长初始步长值,即按照以下公式来推: * 1,3,7,15,...,2^(k-1)-1,2^k-1 * 如果数组长度小于等于4时,步长为1,即长度小于等于4的数组不用分组,此时直接退化为直接插入排序 */ int step = 1; //试探下一个步长是否满足条件,如果满足条件,则步长置为下一步长 while ((step + 1) * 2 - 1 < len - 1) { step = (step + 1) * 2 - 1; } System.out.println("初始步长 : " + step); return step; } /** * 以指定的步长将数组元素后移,步长指定每个元素间的间隔 * @param array 待排序数组 * @param startIndex 从哪里开始移 * @param endIndex 到哪个元素止 * @param step 步长 */ protected final void move(Integer[] array, int startIndex, int endIndex, int step) { for (int i = endIndex; i >= startIndex; i -= step) { array[i + step] = array[i]; } } /** * 测试 * @param args */ public static void main(String[] args) { Integer[] intgArr = { 5, 9, 1, 4, 8, 2, 6, 3, 7, 0 }; ShellSort shellSort = new ShellSort(); shellSort.sort(intgArr,0,intgArr.length-1); for(Integer intObj:intgArr){ System.out.print(intObj + " "); } } }
五、快速排序(Quick Sort)
java代码实现:
/**
* 快速排序: */ public class QuickSort { /** * 排序算法的实现,对数组中指定的元素进行排序 * @param array 待排序的数组 * @param from 从哪里开始排序 * @param end 排到哪里 * @param c 比较器 */ //定义了一个这样的公有方法sort,在这个方法体里面执行私有方法quckSort(这种方式值得借鉴)。 public void sort(Integer[] array, int from, int end) { quickSort(array, from, end); } /** * 递归快速排序实现 * @param array 待排序数组 * @param low 低指针 * @param high 高指针 * @param c 比较器 */ private void quickSort(Integer[] array, int low, int high) { /* * 如果分区中的低指针小于高指针时循环;如果low=higth说明数组只有一个元素,无需再处理; * 如果low > higth,则说明上次枢纽元素的位置pivot就是low或者是higth,此种情况 * 下分区不存,也不需处理 */ if (low < high) { //对分区进行排序整理 //int pivot = partition1(array, low, high); int pivot = partition2(array, low, high); //int pivot = partition3(array, low, high); /* * 以pivot为边界,把数组分成三部分[low, pivot - 1]、[pivot]、[pivot + 1, high] * 其中[pivot]为枢纽元素,不需处理,再对[low, pivot - 1]与[pivot + 1, high] * 各自进行分区排序整理与进一步分区 */ quickSort(array, low, pivot - 1); quickSort(array, pivot + 1, high); } } /** * 实现一 * * @param array 待排序数组 * @param low 低指针 * @param high 高指针 * @param c 比较器 * @return int 调整后中枢位置 */ private int partition1(Integer[] array, int low, int high) { Integer pivotElem = array[low];//以第一个元素为中枢元素 //从前向后依次指向比中枢元素小的元素,刚开始时指向中枢,也是小于与大小中枢的元素的分界点 int border = low; /* * 在中枢元素后面的元素中查找小于中枢元素的所有元素,并依次从第二个位置从前往后存放 * 注,这里最好使用i来移动,如果直接移动low的话,最后不知道数组的边界了,但后面需要 * 知道数组的边界 */ for (int i = low + 1; i <= high; i++) { //如果找到一个比中枢元素小的元素 if ((array[i].compareTo(pivotElem)) < 0) { swap(array, ++border, i);//border前移,表示有小于中枢元素的元素 } } /* * 如果border没有移动时说明说明后面的元素都比中枢元素要大,border与low相等,此时是 * 同一位置交换,是否交换都没关系;当border移到了high时说明所有元素都小于中枢元素,此 * 时将中枢元素与最后一个元素交换即可,即low与high进行交换,大的中枢元素移到了 序列最 * 后;如果 low <minIndex< high,表 明中枢后面的元素前部分小于中枢元素,而后部分大于 * 中枢元素,此时中枢元素与前部分数组中最后一个小于它的元素交换位置,使得中枢元素放置在 * 正确的位置 */ swap(array, border, low); return border; } /** * 实现二 * * @param array 待排序数组 * @param low 待排序区低指针 * @param high 待排序区高指针 * @param c 比较器 * @return int 调整后中枢位置 */ private int partition2(Integer[] array, int low, int high) { int pivot = low;//中枢元素位置,我们以第一个元素为中枢元素 //退出条件这里只可能是 low = high while (true) { if (pivot != high) {//如果中枢元素在低指针位置时,我们移动高指针 //如果高指针元素小于中枢元素时,则与中枢元素交换 if ((array[high].compareTo(array[pivot])) < 0) { swap(array, high, pivot); //交换后中枢元素在高指针位置了 pivot = high; } else {//如果未找到小于中枢元素,则高指针前移继续找 high--; } } else {//否则中枢元素在高指针位置 //如果低指针元素大于中枢元素时,则与中枢元素交换 if ((array[low].compareTo(array[pivot])) > 0) { swap(array, low, pivot); //交换后中枢元素在低指针位置了 pivot = low; } else {//如果未找到大于中枢元素,则低指针后移继续找 low++; } } if (low == high) { break; } } //返回中枢元素所在位置,以便下次分区 return pivot; } /** * 实现三 * * @param array 待排序数组 * @param low 待排序区低指针 * @param high 待排序区高指针 * @param c 比较器 * @return int 调整后中枢位置 */ private int partition3(Integer[] array, int low, int high) { int pivot = low;//中枢元素位置,我们以第一个元素为中枢元素 low++; //----调整高低指针所指向的元素顺序,把小于中枢元素的移到前部分,大于中枢元素的移到后面部分 //退出条件这里只可能是 low = high while (true) { //如果高指针未超出低指针 while (low < high) { //如果低指针指向的元素大于或等于中枢元素时表示找到了,退出,注:等于时也要后移 if ((array[low].compareTo(array[pivot])) >= 0) { break; } else {//如果低指针指向的元素小于中枢元素时继续找 low++; } } while (high > low) { //如果高指针指向的元素小于中枢元素时表示找到,退出 if ((array[high].compareTo(array[pivot])) < 0) { break; } else {//如果高指针指向的元素大于中枢元素时继续找 high--; } } //退出上面循环时 low = high if (low == high) { break; } swap(array, low, high); } //----高低指针所指向的元素排序完成后,还得要把中枢元素放到适当的位置 if ((array[pivot].compareTo(array[low])) > 0) { //如果退出循环时中枢元素大于了低指针或高指针元素时,中枢元素需与low元素交换 swap(array, low, pivot); pivot = low; } else if ((array[pivot].compareTo(array[low])) <= 0) { swap(array, low - 1, pivot); pivot = low - 1; } //返回中枢元素所在位置,以便下次分区 return pivot; } /** * 交换数组中的两个元素的位置 * @param array 待交换的数组 * @param i 第一个元素 * @param j 第二个元素 */ public void swap(Integer[] array, int i, int j) { if (i != j) {//只有不是同一位置时才需交换 Integer tmp = array[i]; array[i] = array[j]; array[j] = tmp; } } /** * 测试 * @param args */ public static void main(String[] args) { Integer[] intgArr = { 5, 9, 1, 4, 2, 6, 3, 8, 0, 7 }; QuickSort quicksort = new QuickSort(); quicksort.sort(intgArr,0,intgArr.length-1); for(Integer intObj:intgArr){ System.out.print(intObj + " "); } } }
六、归并排序
java代码实现:view plai
/*
归并排序:里面是一个递归程序,深刻理解之。 /
public class MergeSort{
/** * 递归划分数组 * @param arr * @param from * @param end * @param c void */ public void partition(Integer[] arr, int from, int end) { //划分到数组只有一个元素时才不进行再划分 if (from < end) { //从中间划分成两个数组 int mid = (from + end) / 2; partition(arr, from, mid); partition(arr, mid + 1, end); //合并划分后的两个数组 merge(arr, from, end, mid); } } /** * 数组合并,合并过程中对两部分数组进行排序 * 前后两部分数组里是有序的 * @param arr * @param from * @param end * @param mid * @param c void */ public void merge(Integer[] arr, int from, int end, int mid) { Integer[] tmpArr = new Integer[10]; int tmpArrIndex = 0;//指向临时数组 int part1ArrIndex = from;//指向第一部分数组 int part2ArrIndex = mid + 1;//指向第二部分数组 //由于两部分数组里是有序的,所以每部分可以从第一个元素依次取到最后一个元素,再对两部分 //取出的元素进行比较。只要某部分数组元素取完后,退出循环 while ((part1ArrIndex <= mid) && (part2ArrIndex <= end)) { //从两部分数组里各取一个进行比较,取最小一个并放入临时数组中 if (arr[part1ArrIndex] - arr[part2ArrIndex] < 0) { //如果第一部分数组元素小,则将第一部分数组元素放入临时数组中,并且临时数组指针 //tmpArrIndex下移一个以做好下次存储位置准备,前部分数组指针part1ArrIndex //也要下移一个以便下次取出下一个元素与后部分数组元素比较 tmpArr[tmpArrIndex++] = arr[part1ArrIndex++]; } else { //如果第二部分数组元素小,则将第二部分数组元素放入临时数组中 tmpArr[tmpArrIndex++] = arr[part2ArrIndex++]; } } //由于退出循环后,两部分数组中可能有一个数组元素还未处理完,所以需要额外的处理,当然不可 //能两部分数组都有未处理完的元素,所以下面两个循环最多只有一个会执行,并且都是大于已放入 //临时数组中的元素 while (part1ArrIndex <= mid) { tmpArr[tmpArrIndex++] = arr[part1ArrIndex++]; } while (part2ArrIndex <= end) { tmpArr[tmpArrIndex++] = arr[part2ArrIndex++]; } //最后把临时数组拷贝到源数组相同的位置 System.arraycopy(tmpArr, 0, arr, from, end - from + 1); } /** * 测试 * @param args */ public static void main(String[] args) { Integer[] intgArr = {5,9,1,4,2,6,3,8,0,7}; MergeSort insertSort = new MergeSort(); insertSort.partition(intgArr,0,intgArr.length-1); for(Integer a:intgArr){ System.out.print(a + " "); } } }
copy
七、堆排序(Heap Sort)
堆的定义: N个元素的序列K1,K2,K3,…,Kn.称为堆,当且仅当该序列满足特性:
Ki≤K2i Ki ≤K2i+1(1≤ I≤ [N/2])
堆实质上是满足如下性质的完全二叉树:树中任一非叶子结点的关键字均大于等于其孩子结点的关键字。例如序列10,15,56,25,30,70就是一个堆,它对应的完全二叉树如上图所示。这种堆中根结点(称为堆顶)的关键字最小,我们把它称为小根堆。反之,若完全二叉树中任一非叶子结点的关键字均大于等于其孩子的关键字,则称之为大根堆。
排序过程:
堆排序正是利用小根堆(或大根堆)来选取当前无序区中关键字小(或最大)的记录实现排序的。我们不妨利用大根堆来排序。每一趟排序的基本操作是:将当前无序区调整为一个大根堆,选取关键字最大的堆顶记录,将它和无序区中的最后一个记录交换。这样,正好和直接选择排序相反,有序区是在原记录区的尾部形成并逐步向前扩大到整个记录区。
【示例】:对关键字序列42,13,91,23,24,16,05,88建堆
java代码实现:vi
/**
选择排序之堆排序:
*/
public class HeapSort {
/**
@param c 比较器
*/
public void sort(Integer[] array, int from, int end) {
//创建初始堆
initialHeap(array, from, end);
/*
}
/**
@param c 比较器
*/
private void initialHeap(Integer[] arr, int from, int end) {
int lastBranchIndex = (end - from + 1) / 2;//最后一个非叶子节点
//对所有的非叶子节点进行循环 ,且从最一个非叶子节点开始
for (int i = lastBranchIndex; i >= 1; i–) {
adjustNote(arr, i, end - from + 1);
}
}
/**
@param c 比较器
/
private void adjustNote(Integer[] arr, int parentNodeIndex, int len) {
int minNodeIndex = parentNodeIndex;
//如果有左子树,i 2为左子节点索引
if (parentNodeIndex * 2 <= len) {
//如果父节点小于左子树时 if ((arr[parentNodeIndex - 1].compareTo(arr[parentNodeIndex * 2 - 1])) < 0) { minNodeIndex = parentNodeIndex * 2;//记录最大索引为左子节点索引 } // 只有在有或子树的前提下才可能有右子树,再进一步断判是否有右子树 if (parentNodeIndex * 2 + 1 <= len) { //如果右子树比最大节点更大 if ((arr[minNodeIndex - 1].compareTo(arr[(parentNodeIndex * 2 + 1) - 1])) < 0) { minNodeIndex = parentNodeIndex * 2 + 1;//记录最大索引为右子节点索引 } }
}
//如果在父节点、左、右子节点三都中,最大节点不是父节点时需交换,把最大的与父节点交换,创建大顶堆
if (minNodeIndex != parentNodeIndex) {
swap(arr, parentNodeIndex - 1, minNodeIndex - 1); //交换后可能需要重建堆,原父节点可能需要继续下沉 if (minNodeIndex * 2 <= len) {//是否有子节点,注,只需判断是否有左子树即可知道 adjustNote(arr, minNodeIndex, len); }
}
}
/**
@param j 第二个元素
*/
public void swap(Integer[] array, int i, int j) {
if (i != j) {//只有不是同一位置时才需交换
Integer tmp = array[i]; array[i] = array[j]; array[j] = tmp;
}
}
/**
}
八、桶式排序
java代码实现:
/**
* 桶式排序: * 桶式排序不再是基于比较的了,它和基数排序同属于分配类的排序, * 这类排序的特点是事先要知道待排 序列的一些特征。 * 桶式排序事先要知道待排 序列在一个范围内,而且这个范围应该不是很大的。 * 比如知道待排序列在[0,M)内,那么可以分配M个桶,第I个桶记录I的出现情况, * 最后根据每个桶收到的位置信息把数据输出成有序的形式。 * 这里我们用两个临时性数组,一个用于记录位置信息,一个用于方便输出数据成有序方式, * 另外我们假设数据落在0到MAX,如果所给数据不是从0开始,你可以把每个数减去最小的数。 */ public class BucketSort { public void sort(int[] keys,int from,int len,int max) { int[] temp=new int[len]; int[] count=new int[max]; for(int i=0;i<len;i++) { count[keys[from+i]]++; } //calculate position info for(int i=1;i<max;i++) { count[i]=count[i]+count[i-1];//这意味着有多少数目小于或等于i,因此它也是position+ 1 } System.arraycopy(keys, from, temp, 0, len); for(int k=len-1;k>=0;k--)//从最末到开头保持稳定性 { keys[--count[temp[k]]]=temp[k];// position +1 =count } } /** * @param args */ public static void main(String[] args) { int[] a={1,4,8,3,2,9,5,0,7,6,9,10,9,13,14,15,11,12,17,16}; BucketSort bucketSort=new BucketSort(); bucketSort.sort(a,0,a.length,20);//actually is 18, but 20 will also work for(int i=0;i<a.length;i++) { System.out.print(a[i]+","); } } }
九、基数排序
java代码实现:
/**
* 基数排序: */ public class RadixSort { /** * 取数x上的第d位数字 * @param x 数 * @param d 第几位,从低位到高位 * @return */ public int digit(long x, long d) { long pow = 1; while (--d > 0) { pow *= 10; } return (int) (x / pow % 10); } /** * 基数排序实现,以升序排序(下面程序中的位记录器count中,从第0个元素到第9个元素依次用来 * 记录当前比较位是0的有多少个..是9的有多少个数,而降序时则从第0个元素到第9个元素依次用来 * 记录当前比较位是9的有多少个..是0的有多少个数) * @param arr 待排序数组 * @param digit 数组中最大数的位数 * @return */ public long[] radixSortAsc(long[] arr) { //从低位往高位循环 for (int d = 1; d <= getMax(arr); d++) { //临时数组,用来存放排序过程中的数据 long[] tmpArray = new long[arr.length]; //位记数器,从第0个元素到第9个元素依次用来记录当前比较位是0的有多少个..是9的有多少个数 int[] count = new int[10]; //开始统计0有多少个,并存储在第0位,再统计1有多少个,并存储在第1位..依次统计到9有多少个 for (int i = 0; i < arr.length; i++) { count[digit(arr[i], d)] += 1; } /* * 比如某次经过上面统计后结果为:[0, 2, 3, 3, 0, 0, 0, 0, 0, 0]则经过下面计算后 结果为: * [0, 2, 5, 8, 8, 8, 8, 8, 8, 8]但实质上只有如下[0, 2, 5, 8, 0, 0, 0, 0, 0, 0]中 * 非零数才用到,因为其他位不存在,它们分别表示如下:2表示比较位为1的元素可以存放在索引为1、0的 * 位置,5表示比较位为2的元素可以存放在4、3、2三个(5-2=3)位置,8表示比较位为3的元素可以存放在 * 7、6、5三个(8-5=3)位置 */ for (int i = 1; i < 10; i++) { count[i] += count[i - 1]; } /* * 注,这里只能从数组后往前循环,因为排序时还需保持以前的已排序好的 顺序,不应该打 * 乱原来已排好的序,如果从前往后处理,则会把原来在前面会摆到后面去,因为在处理某个 * 元素的位置时,位记数器是从大到到小(count[digit(arr[i], d)]--)的方式来处 * 理的,即先存放索引大的元素,再存放索引小的元素,所以需从最后一个元素开始处理。 * 如有这样的一个序列[212,213,312],如果按照从第一个元素开始循环的话,经过第一轮 * 后(个位)排序后,得到这样一个序列[312,212,213],第一次好像没什么问题,但问题会 * 从第二轮开始出现,第二轮排序后,会得到[213,212,312],这样个位为3的元素本应该 * 放在最后,但经过第二轮后却排在了前面了,所以出现了问题 */ for (int i = arr.length - 1; i >= 0; i--) {//只能从最后一个元素往前处理 //for (int i = 0; i < arr.length; i++) {//不能从第一个元素开始循环 tmpArray[count[digit(arr[i], d)] - 1] = arr[i]; count[digit(arr[i], d)]--; } System.arraycopy(tmpArray, 0, arr, 0, tmpArray.length); } return arr; } /** * 基数排序实现,以降序排序(下面程序中的位记录器count中,从第0个元素到第9个元素依次用来 * 记录当前比较位是0的有多少个..是9的有多少个数,而降序时则从第0个元素到第9个元素依次用来 * 记录当前比较位是9的有多少个..是0的有多少个数) * @param arr 待排序数组 * @return */ public long[] radixSortDesc(long[] arr) { for (int d = 1; d <= getMax(arr); d++) { long[] tmpArray = new long[arr.length]; //位记数器,从第0个元素到第9个元素依次用来记录当前比较位是9的有多少个..是0的有多少个数 int[] count = new int[10]; //开始统计0有多少个,并存储在第9位,再统计1有多少个,并存储在第8位..依次统计 //到9有多少个,并存储在第0位 for (int i = 0; i < arr.length; i++) { count[9 - digit(arr[i], d)] += 1; } for (int i = 1; i < 10; i++) { count[i] += count[i - 1]; } for (int i = arr.length - 1; i >= 0; i--) { tmpArray[count[9 - digit(arr[i], d)] - 1] = arr[i]; count[9 - digit(arr[i], d)]--; } System.arraycopy(tmpArray, 0, arr, 0, tmpArray.length); } return arr; } private int getMax(long[] array) { int maxlIndex = 0; for (int j = 1; j < array.length; j++) { if (array[j] > array[maxlIndex]) { maxlIndex = j; } } return String.valueOf(array[maxlIndex]).length(); } public static void main(String[] args) { long[] ary = new long[] { 123, 321, 132, 212, 213, 312, 21, 223 }; RadixSort rs = new RadixSort(); System.out.println("升 - " + Arrays.toString(rs.radixSortAsc(ary))); ary = new long[] { 123, 321, 132, 212, 213, 312, 21, 223 }; System.out.println("降 - " + Arrays.toString(rs.radixSortDesc(ary))); } }
十、几种排序算法的比较和选择
1、插入排序(直接插入排序、折半插入排序、希尔排序);
2、交换排序(起泡排序、快速排序);
3、选择排序(直接选择排序、堆排序);
4、归并排序;
5、基数排序;
学习重点
1、掌握排序的基本概念和各种排序方法的特点,并能加以灵活应用;
2、掌握插入排序(直接插入排序、折半插入排序、希尔排序)、交换排序(起泡排序、快速排序)、选择排序(直接选择排序、堆排序)、二路归并排序的方法及其性能分析方法;
3、了解基数排序方法及其性能分析方法。
排序(sort)或分类
所谓排序,就是要整理文件中的记录,使之按关键字递增(或递减)次序排列起来。其确切定义如下:
输入:n个记录R1,R2,…,Rn,其相应的关键字分别为K1,K2,…,Kn。
输出:Ril,Ri2,…,Rin,使得Ki1≤Ki2≤…≤Kin。(或Ki1≥Ki2≥…≥Kin)。
1.被排序对象–文件
被排序的对象–文件由一组记录组成。
记录则由若干个数据项(或域)组成。其中有一项可用来标识一个记录,称为关键字项。该数据项的值称为关键字(Key)。
注意:
在不易产生混淆时,将关键字项简称为关键字。
2.排序运算的依据–关键字
用来作排序运算依据的关键字,可以是数字类型,也可以是字符类型。
关键字的选取应根据问题的要求而定。
【例】在高考成绩统计中将每个考生作为一个记录。每条记录包含准考证号、姓名、各科的分数和总分数等项内容。若要惟一地标识一个考生的记录,则必须用”准考证号”作为关键字。若要按照考生的总分数排名次,则需用”总分数”作为关键字。
排序的稳定性
当待排序记录的关键字均不相同时,排序结果是惟一的,否则排序结果不唯一。
在待排序的文件中,若存在多个关键字相同的记录,经过排序后这些具有相同关键字的记录之间的相对次序保持不变,该排序方法是稳定的;若具有相同关键字的记录之间的相对次序发生变化,则称这种排序方法是不稳定的。
注意:
排序算法的稳定性是针对所有输入实例而言的。即在所有可能的输入实例中,只要有一个实例使得算法不满足稳定性要求,则该排序算法就是不稳定的。
排序方法的分类
1.按是否涉及数据的内、外存交换分
在排序过程中,若整个文件都是放在内存中处理,排序时不涉及数据的内、外存交换,则称之为内部排序(简称内排序);反之,若排序过程中要进行数据的内、外存交换,则称之为外部排序。
注意:
① 内排序适用于记录个数不很多的小文件
② 外排序则适用于记录个数太多,不能一次将其全部记录放人内存的大文件。
2.按策略划分内部排序方法
可以分为五类:插入排序、选择排序、交换排序、归并排序和分配排序。
排序算法分析
1.排序算法的基本操作
大多数排序算法都有两个基本的操作:
(1) 比较两个关键字的大小;
(2) 改变指向记录的指针或移动记录本身。
注意:
第(2)种基本操作的实现依赖于待排序记录的存储方式。
2.待排文件的常用存储方式
(1) 以顺序表(或直接用向量)作为存储结构
排序过程:对记录本身进行物理重排(即通过关键字之间的比较判定,将记录移到合适的位置)
(2) 以链表作为存储结构
排序过程:无须移动记录,仅需修改指针。通常将这类排序称为链表(或链式)排序;
(3) 用顺序的方式存储待排序的记录,但同时建立一个辅助表(如包括关键字和指向记录位置的指针组成的索引表)
排序过程:只需对辅助表的表目进行物理重排(即只移动辅助表的表目,而不移动记录本身)。适用于难于在链表上实现,仍需避免排序过程中移动记录的排序方法。
3.排序算法性能评价
(1) 评价排序算法好坏的标准
评价排序算法好坏的标准主要有两条:
① 执行时间和所需的辅助空间
② 算法本身的复杂程度
(2) 排序算法的空间复杂度
若排序算法所需的辅助空间并不依赖于问题的规模n,即辅助空间是O(1),则称之为就地排序(In-PlaceSou)。
非就地排序一般要求的辅助空间为O(n)。
(3) 排序算法的时间开销
大多数排序算法的时间开销主要是关键字之间的比较和记录的移动。有的排序算法其执行时间不仅依赖于问题的规模,还取决于输入实例中数据的状态。
文件的顺序存储结构表示
define n l00 //假设的文件长度,即待排序的记录数目
typedef int KeyType; //假设的关键字类型
typedef struct{ //记录类型
KeyType key; //关键字项
InfoType otherinfo;//其它数据项,类型InfoType依赖于具体应用而定义
}RecType;
typedef RecType SeqList[n+1];//SeqList为顺序表类型,表中第0个单元一般用作哨兵
注意:
若关键字类型没有比较算符,则可事先定义宏或函数来表示比较运算。
【例】关键字为字符串时,可定义宏”#define LT(a,b)(Stromp((a),(b))<0)”。那么算法中”a<b”可用”LT(a,b)”取代。若使用C++,则定义重载的算符”<”更为方便。
按平均时间将排序分为四类:
(1)平方阶(O(n2))排序
一般称为简单排序,例如直接插入、直接选择和冒泡排序;
(2)线性对数阶(O(nlgn))排序
如快速、堆和归并排序;
(3)O(n1+£)阶排序
£是介于0和1之间的常数,即0<£<1,如希尔排序;
(4)线性阶(O(n))排序
如桶、箱和基数排序。
各种排序方法比较
简单排序中直接插入最好,快速排序最快,当文件为正序时,直接插入和冒泡均最佳。
影响排序效果的因素
因为不同的排序方法适应不同的应用环境和要求,所以选择合适的排序方法应综合考虑下列因素:
①待排序的记录数目n;
②记录的大小(规模);
③关键字的结构及其初始状态;
④对稳定性的要求;
⑤语言工具的条件;
⑥存储结构;
⑦时间和辅助空间复杂度等。
不同条件下,排序方法的选择
(1)若n较小(如n≤50),可采用直接插入或直接选择排序。
当记录规模较小时,直接插入排序较好;否则因为直接选择移动的记录数少于直接插人,应选直接选择排序为宜。
(2)若文件初始状态基本有序(指正序),则应选用直接插人、冒泡或随机的快速排序为宜;
(3)若n较大,则应采用时间复杂度为O(nlgn)的排序方法:快速排序、堆排序或归并排序。
快速排序是目前基于比较的内部排序中被认为是最好的方法,当待排序的关键字是随机分布时,快速排序的平均时间最短;
堆排序所需的辅助空间少于快速排序,并且不会出现快速排序可能出现的最坏情况。这两种排序都是不稳定的。
若要求排序稳定,则可选用归并排序。但本章介绍的从单个记录起进行两两归并的 排序算法并不值得提倡,通常可以将它和直接插入排序结合在一起使用。先利用直接插入排序求得较长的有序子文件,然后再两两归并之。因为直接插入排序是稳定的,所以改进后的归并排序仍是稳定的。
4)在基于比较的排序方法中,每次比较两个关键字的大小之后,仅仅出现两种可能的转移,因此可以用一棵二叉树来描述比较判定过程。
当文件的n个关键字随机分布时,任何借助于”比较”的排序算法,至少需要O(nlgn)的时间。
箱排序和基数排序只需一步就会引起m种可能的转移,即把一个记录装入m个箱子之一,因此在一般情况下,箱排序和基数排序可能在O(n)时间内完成对n个记录的排序。但是,箱排序和基数排序只适用于像字符串和整数这类有明显结构特征的关键字,而当关键字的取值范围属于某个无穷集合(例如实数型关键字)时,无法使用箱排序和基数排序,这时只有借助于”比较”的方法来排序。
若n很大,记录的关键字位数较少且可以分解时,采用基数排序较好。虽然桶排序对关键字的结构无要求,但它也只有在关键字是随机分布时才能使平均时间达到线性阶,否则为平方阶。同时要注意,箱、桶、基数这三种分配排序均假定了关键字若为数字时,则其值均是非负的,否则将其映射到箱(桶)号时,又要增加相应的时间。
(5)有的语言(如Fortran,Cobol或Basic等)没有提供指针及递归,导致实现归并、快速(它们用递归实现较简单)和基数(使用了指针)等排序算法变得复杂。此时可考虑用其它排序。
(6)本章给出的排序算法,输人数据均是存储在一个向量中。当记录的规模较大时,为避免耗费大量的时间去移动记录,可以用链表作为存储结构。譬如插入排序、归并排序、基数排序都易于在链表上实现,使之减少记录的移动次数。但有的排序方法,如快速排序和堆排序,在链表上却难于实现,在这种情况下,可以提取关键字建立索引表,然后对索引表进行排序。然而更为简单的方法是:引人一个整型向量t作为辅助表,排序前令t[i]=i(0≤i<n),若排序算法中要求交换R[i]和R[j],则只需交换t[i]和t[j]即可;排序结束后,向量t就指示了记录之间的顺序关系:
R[t[0]].key≤R[t[1]].key≤…≤R[t[n-1]].key
若要求最终结果是:
R[0].key≤R[1].key≤…≤R[n-1].key
则可以在排序结束后,再按辅助表所规定的次序重排各记录,完成这种重排的时间是O(n)。
早读时,布布想起昨天的梦境,依然奕奕不能平静下来,也不可相信。可这手臂上的蝴蝶印,却怎么解释?他瞄了一眼那蝴蝶,然后紫色印记像是有感应的闪烁了一下。他又想到了昨天听到的咒语“我以我梦界之神圣女神起誓!一切神圣的,高尚的,勇敢的力量将不遗余力的得到梦界之守护......”嗯,应该是这样翻译的。他不自觉的低吟,那印记受到了召唤而闪烁起来。布布赶紧将手臂遮了起来,因为他感觉有人在盯着自己,是右前方的方向!布布和前面的妞妞“點點”目光相交了,點點脸上立刻显现出一片红晕,缓缓板正了身体。“奇怪!为啥别人都没注意到,就她注意到了呢?”想着就拍了拍大建的肩膀:“你看看我的手臂。”“咋了啊?哎!你什么时候偷偷纹了个蝴蝶啊?我咋没注意到呢!虽然挺好看的,不过这似乎有些娘娘腔唉...”这货叨叨起来没完了,完全忽略了布布现在口中念念有词.在布布的眼里现在蝴蝶受到呼唤正熠熠生辉。他也就猜测,除非梦界有缘之人是看不到这闪光的印记的。难道近在咫尺的點點竟然是梦界有缘之人吗?“算了,先不想这些了,学习一会该下课了。漫漫的语文课,到时候有的是时间去冥想。”总算盼来了那个灰常有气质的,文艺女青年-魏芳。开了语文课,布布迫不及待的按照月神所教授的口诀,心中默默修炼起来。不多时,就能感觉到周围的意念圈了。但见大多数同学头顶都是一片灰色光圈,點點的头顶却闪着淡淡的紫色。布布用意念去探测,发出了一丝意识:“你能感觉到对不对?”點點似乎點了點頭,那你跟着我用意识默念“芝麻芝麻麻芝麻芝麻~,我带你 去梦界看看!”
这是一片绿色的大树林,少年少女两个并肩而行。 “點點,昨天晚上我做了一个梦,梦到了.....” "你是说你现在是梦界的传人?" “嗯,而你! 或许应该是梦界的有缘人!你愿意加入梦界吗?作为传人,我有寻找有缘人壮我梦界声威的责任。” “就怕我做的不好。”點點羞涩的低下了头。 “我这有一套口诀,极其深奥,里面还包含着两套武技.一套是适合男性修炼的,可以强身健体,武力值得到极大提升,当然寿命会提高许久!而另一套适合女士,可以极限的提升身体柔韧性,皮肤当然越来越好,修炼的境界愈高,甚至可以达到脱胎换骨的地步!” “嗯!我很乐意!”點點很小声的答道、 “给,这是一个储物戒,月神送了我许多魔法师的道具。我把口诀里隐含的那套武技图映到这张图上,你存到你的储物戒里。每天意念修炼一个时辰,看你的缘分来看,修炼应该蛮快!我信你!” “这件事情是应该挺隐秘的吧?放心,不会有别人知道的!” 两人边走边聊,“这里是梦界的一处随机的景象吗?” “当然了。修炼一段以后,咱们就可以来这里探险了!” ...... 一天,女生宿舍。“啊!點點你最近用的什么化妆品啊?怎么皮肤这么好了?!”看到點點刚从卫生间出来,點點的舍友大声惊呼!“而且,你变漂亮了许多啊!”“也没用啥啊!还不是常用的洗面奶而已!你们怎么这么大惊小怪的?”點點又羞红了脸。。出门前,仔细照了照镜子,发现自己确实比以前皮肤好了许多!而且从自己的身体也能感应出来,体内的有害物质似乎都被净化掉了。點點内心暗暗高兴,原来竟然是真的!不过为了不引起怀疑,还是将自己打理了一番,看起来和平时的自己没啥区别了,这才低调的出门了. ...... 很快几个月过去了,不过最近校园又开始谣传体育场附近的一间盛放闲杂物品的小屋附近,晚上经常会有恐怖的声音!甚至有人称看到了鬼!这事其实在校园里早该见怪不怪了才对!哪个校园没有鬼神的传说才是怪事了!不过布布自从修炼了这套咒语中的魔法以后,魔法境界也提升了很多!自己估计下应该达到了6级魔法师的资格。从每天早上跑操可以感觉出来,那间小屋确实有些阴气。 高中的校园,晚课都是自习课,没有老师讲课。第二节晚课下课后,布布传出了一丝意识“走,去操场那间杂物小屋瞧瞧去。點點你害怕嗎?”“不怕,不是还有你吗?”點點內心此时及其害羞。 两人低调行事,先后从教室走了出去。快到操场时,布布追上點點,闻到點點身上的清香,看到點點那修炼后愈发白净的脸庞,心中一荡,有种亲吻她的冲动,毕竟是青春懵懂的少年。一想到怎么自己这么不正经,布布马上神情一正,拉着她走到了那小屋门前。 “點點,今天就当做咱们探险的第一课吧。”说罢,布布从储物戒拿出一件万状匙,向空中一抛,意识一动,那器具融入了小屋的门锁里,向右一转。布布拉着點點的小手就走了进去。 感受到點點的小手有些发抖,布布拉的更紧了一些。安慰道:“没事儿,你这几个月的修炼,已经达到了8级武技!一般人早不是你的对手了,放心吧。再说还有我呢。” 由于最近这些事传的比较厉害,大家晚上都离着这里远远地。进入这小屋以后,布布立刻把门关上了。“果然有些蹊跷啊!”几个幽魂立刻朝着他俩聚了过来。點點毕竟是个单纯善良胆小的姑娘,吓得立刻抱住了布布。“哼哼,几个小幽魂也敢出来送死?”布布随后一侧身,伸出手指,指尖之上冒出一团火,然后这团火以一个一个的火球飞了出去!所到之处,将那几个魂魄烧散。然后左臂搂着點點,右手指闪着火光当做灯笼,接着向前面走去。走了几步,发现了一个家伙,全身黑色,正在吸几个残留的魂魄。“怪不得最近总是传言这里闹鬼,原来是死灵法师不知在哪抓来的魂魄囤积在了这个阴暗之地。在晚上夕阳下去之后进来吸食!”想到这些,布布右手一甩,几道火球立刻把这个黑子正在吸食的魂魄烧毁了。 “哪里来的不知死活的小子,呀!还有个细皮嫩肉的小妞呢!赶来打搅我的好事。让你们见识下死灵魔法!”说罢几道阴寒之气包围了他们两个,那股恐惧的力量让他们不自觉退了两步。 此刻根本由不得布布多想,一道道的顺发火球术冲着这个黑法师施展开来!但是这火球似乎中看不中用啊!到了那股恐惧的阴气面前一一熄灭了! 打架就得不要脸!布布此刻也不想低调了,冲着點點吼道:“快上啊!咱俩一块上!打死这狗日的!”就这样2个八级武士以极快的速度对这个家伙进行了群殴!但见一个蓝色的影子越变越大,像极了一个壮男的身形,在用拳头和手指上戴的尖刺不断冲刺着!还有一个粉色的,体态婀娜,扭动的腰肢已经超过人类的极限了吧?以极快的速度甩动着手中的匕首。一会时间,那个黑法师被扎成了蜂窝煤。他胸口有一枚纽扣形状的徽章闪烁了几下,然后鸟鸟熄灭! “学校里怎么有种法师?他吸食鬼魂,有什么效果呢?” “月神和我讲过这些东西,黑心的死灵法师专门吸食鬼魂的魂魄,来促进自己的邪恶力量,靠着吸食魂魄可以大大缩短修炼时间!对他们来说,已经没有良心可言。为了提升自己,再邪恶的事情都能做得出来!别说仅仅不让这些魂魄轮回了!” 顿了下,接着说道:“嘿嘿!不是有某大神说过吗:‘力量越大,责任越大!’这种邪恶的死灵法师是所有法师 的公敌,人人有义务诛杀之!你这武技练得很不错呢!这几个月下来身材越来越好了!”说罢,突然觉得自己不该这样。不应该! "你!...."顿时黑暗之中羞红了一个大苹果。朝外面跑去... “...”布布甩出一个火球毁掉了痕迹,也走了出来,不经意的上了锁,几个瞬移到了教室。點點回头瞪了布布一眼,带着一股责怪和羞涩。 这时,操场上一个白衣之物显现出来,浮在半空。“有趣的人类啊!竟然又摧毁我的一个棋子,到了教室还在那谈笑,不给你们点颜色,看来是不把我鬼界书生放在眼里了!”。 “一课无话。。” 下了晚自习,布布发出一丝意识“今晚上逃出去探险?”“才不去呢!哼哼!”“额,我...!”“一会意识沟通吧!”“OK!” “你啥时候偷着闻了个蝴蝶啊?”大建这货突然想起这茬了。 “昨天.” “别扯了,昨天你哪有时间啊?” “....” “熄灯熄灯!睡觉睡觉!”查寝的又来催了。 躺下没多久,布布正在用意识修炼他的那本武技秘籍,到了八级就是一个瓶颈,好久也突破不了,心情愈加烦闷。这时收到了點點的意识: “我怎么到了现在也突破不了呢?前面我突破的挺快的呀!”“你别着急!我也正烦闷呢!到了一个瓶颈突破不了了。慢慢思考肯定没问题的!”“嗯!我就是有些着急了。呵呵,啊! 窗外有个东西,那是什么啊?他对我召唤,让我出去接招呢。”“什么?你先应付,我马上就到。”點點看了一眼舍友都睡著了,从窗外直接追了出去,“你是什么人?”“跟我来吧,人类!毁了我的棋子,今天饶不过你们!” 很快出了学校,到了一片空地,點點以一个极其优雅的姿态落到了眼前这个白衣怪物的面前。但见这个人,暂时称他为人,耳朵很尖,美得不像话,也看不出是男是女,要是男的,长的也太美了点吧?要说是女的,呃,那么胸部是A? “别打量了,愚蠢的人类!”这家伙被點點看的及其不耐烦了,“杀了我的一个仆人,该怎么补偿我?!拿出你的实力来!上来领死!” 點點毕竟有了八级的实力,出手也带着一股凛冽的意思了。粉色的倩影就像天女散花一样,加上婀娜多姿的形态,变态到极点身体柔韧性,洒出来就像下了一阵飞刀雨,好漂亮的飞雨阵!布布赶到时看到了也不禁感到赞叹!这秘籍果然是女性修炼的唯一价值啊! 不过这并没有对鬼界书生造成伤害,这书生白色的身影就像一道道幕布将这些飞刀全接了下来!布布也毫不留情,先是一个瞬移到了书生面前,先招呼了一阵火球,想用这些火球引燃那些幕布(书生的衣服,把它烤成烧乳猪),另一方面震慑一下这个高傲狂妄的家伙! 没给书生反应的时间,近身冲上去施展武技想把他打的亲娘都不认了! “还是太弱了啊!就这点本事?”,明显的这书生都没有动手的意思,那些火球沾到了幕布立刻熄灭只是化成了一片蒸汽缓缓下流。而布布打了一刻钟显然都没沾到对方的身! 这书生身体一纵,浮在半空,动如脱兔,几个跳跃,分别在布布、點點的脖子点了一下。接着发了几个魔法,两人都飞了出去,點點也保持不了那漂亮完美的身法了,肋骨直接被打断了好几根!布布作为一个男生,平时经常锻炼身体,可以说是皮草肉厚一些稍微。"你两个现在是我的俘虏了!" 显然境界差距太大,根本打不过啊!點點已经被晕过去了! 布布显然不能坐以待毙啊,想到了月神教授的咒语,口中低吟,唤醒了蝴蝶印记,那蝴蝶闪烁着紫光伴随着咒语产生了一段音乐符,顿时解了他们俩的“禁身咒”。再看那个书生听到了这段音乐仿佛进入了无尽的回忆之中,痴痴地不动了。 布布拉起點點就飞了起来,赶忙从储物戒里取出一个帐篷似的布,想隐身。 “哼!就凭这段咒语就想迷惑住我?”瞬间把两人拦了下来。 “完了,这次真的没招了!真的要被他俘虏?我倒是没事,點點這個妞可是被我连累了!要不是我把她拉进这个世界,怎么会这样呢?”布布后悔不迭。 “我倒是对你越来越好奇了,你怎么得到了这么一段梦界诱咒?还不全啊。我倒是没了杀你的心思,少了你,我在人界将失去很多乐趣吧以后”, 说到这里,书生语气一变:“只不过,你杀了我的仆人,可还没有得到我这个主人的允许呢!你说,你该怎么赔偿我呢?” 布布眼神里露出一丝警惕:“你想要什么?” 书生眼神落在了點點的身上,伸手一指:“我要这个女人!” “…………” 听了这个条件,就连布布都呆住了。 點點?书生什么时候对點點有了兴趣了? 不过,布布在愣了一下之后,随后就放声大笑起来。他笑得很是欢快地样子,仿佛就听到了这个世界上最好笑的事情! 书生的脸上露出了一丝不满,在见到布布之后,书生的脸上,第一次出现了一丝隐隐地生气的样子了:“请问我说地哪里不对吗?你们的命换成一个俘虏,难道不值得吗?” 布布看着怀里昏迷地點點,然后昂然大声道:“换?门儿都没有!!别说一个死灵法师了!我地點點,就算你把整个人界的死灵法师力量都给我,我也……” 布布话还没说完,书生却居然立刻就道:“好!就整个人界死灵法师!只要你肯把这个女人交给我!我就把整个人界死灵法师的力量都给你!!” 这下轮到布布发楞了。 整个死灵法师?这个书生好大的口气,好大的手笔啊! 难道书生之前认得點點? 布布的发楞,让书生产生了误会,认为布布或许是动摇了。 别误会,布布此刻的发楞,完全不是什么考虑书生的开价。纯粹是被书生提出的条件震惊了!他心里唯一的念头,就是仔细的琢磨:为什么在书生的眼里,點點居然有如此价值? 布布的眼神充满了疑惑,看了看怀里的點點,又看了看书生。 书生的脸庞上带着温和的笑容,眼看布布“动摇”了,他悠悠笑道:“怎么样?布布阁下。整整一支死灵法师!虽然大部分死灵法师被我征服之后,因为一些需要,我已经下令处死了几十个。不过现在应该还剩下大约一百个左右。只要你答应我的要求,我以鬼界之王的名誉向你承诺,我将剩下的这一百个死灵法师力量收集起来全部送给你,作为你的境界提升之用!” 布布此刻略微定了定神,笑看着书生,缓缓开口了:“哦?书生阁下……那可是一百多个死灵法师群体啊……难道你已经破除了‘遗忘冰原’上的魔法阵了?” 书生板着脸,淡淡道:“这是我的问题,只要你答应我的条件,我可以在未来的一年之内将它们送来给你。而且,我向你承诺。你可以得到鬼界一族地好感和友谊,即使未来发生了冲突。鬼界一族也会因为这份友谊而对你手下留情的。” 书生地声音里带着强大地自信。他自信。这样的条件诱惑,世界上恐怕没有人能抵抗。 “的确很诱人。”布布叹了口气。 他脸上地表情有些古怪,低头看了看在自己怀里沉睡地點點。叹息道:“其实。这个女人没什么好地……她虽然长得还算不错。可是对于你们鬼界一族来说。应该不缺美女吧。而且。不瞒你说,她这个女人没有女人味。老实说,很多时候。我简直一见到她就头疼呢。”布布虚伪的表演着,我自己欺负欺负可以,别人不可能! 听完布布地话,书生的脸上露出一丝期待:“这么说。你是答应了?” 布布依然在笑。然后叹了口气,看着怀里的點點。眼神里居然露出了一丝难得地温和来:“可是。虽然我见到她就头疼。可是呢……偏偏我已经习惯了有她这么一个女人在我身边陪着,有的时候,如果她长时间不来找我麻烦。我反而会有些不习惯,甚至有些想她呢……所以呢……” 布布抬起头来。正视着书生,一字一字道:“想要用一百多个死灵法师的力量就换走她,那是不可能地!就算你把所有鬼界力量给我也一样。我的答案也是一样:不可能!再说要是不我把她拉进来,她现在也不会成为这样子.....” 说完这话之后,布布立刻心中催动了魔力,身体周围的紫色光明之罩立刻自动旋转起来,布布警惕的看着书生。以防被自己拒绝之后。这个实力恐怖的书生就要翻脸抢人了。 没想到,被布布拒绝之后。书生却丝毫没有暴怒地样子,他脸上露出了明显的失望之色。但是却依然保持着一定地风度。只是深深惋惜地叹了口气:“是这样啊……布布,难道你怀里的女子。是你地爱人吗?” “不,她不是我地爱人。" 书生捕捉到了布布脸上闪过的那一丝奇异的表情。这个书生再次叹息,他仔细的想了会儿。开口道:“好吧,布布,虽然你拒绝了,但是这个女子,是我一定要得到地,所以……” 布布绝然道:“如果你要准备抢人地话,那么你不妨就动手吧!我虽然实力不如你,但是好歹我也是一个男人!而且在我的地盘上,如果被你单枪匹马,把我怀里地女人都抢走了,那么以后我也别混下去了!”布布打算拼命了,召唤出梦中所有的咒语力量,即使超过自身的负荷,大不了暴血而死吧! 听完布布的话,书生反而笑了。 看着布布身边转动地光明之罩,看着布布警惕地眼神,如临大敌一样的姿态。书生轻轻一笑。书生优雅地躬身往后退了几步,悠然的将双手负在身后,脸上挂着轻松地微笑,摇头道:“抢人?不不,你一定是误会了。我身为鬼界一族之王,怎么能做出这种抢人的卑劣举动?我可不是那些粗鄙野蛮的兽人!鬼界一族是有荣誉和骄傲的高贵民族,那些卑劣的事情,不是我书生能做得出来的。只不过……我们可以来打一个赌,你看如何呢?” 说完,书生忽然从背后伸出一只手来,对着布布屈指轻轻一弹…… 咻! 一道淡紫色地光芒划破天空,飞快的射到了布布地面前!这一道紫色地光芒带着些许妖异的色彩,让布布惊骇地是,尽管自己面前的光明之罩旋转如常,可是这号称“梦界最强防御罩”的光明之罩,却丝毫没有能阻挡这紫光半分! 布布几乎是眼睁睁的看着这一缕紫光穿透了光明之罩,直接落在了自己的胸口,波的一声,紫色的光芒之下一朵火花绚烂生出。这并不是真正的火焰,而是魔法的光芒,布布丝毫感受不到火花地灼热。反而觉得胸口被紫光点中的部位,生出一股冰冷来。 随后。他地胸前衣服被紫光点中地那地方。布料飘然脱落,露出了他的胸膛之上,多了一个紫色的印记。那印记仿佛是已经溶入了他地肌肤一样!“这是我们鬼界一族地魔法印记。”书生含笑道:“为了表示对主人地尊重。我可以给你一个机会。我曾经听说。人类有好多有趣的赌约。我听说了这些故事之后,非常有兴趣。所以,我们不妨再来赌一场!”书生说着。语气变得严肃起来:“原本我并不想为难你,但是既然你拒绝了我的提议。那么……布布阁下。从现在开始,我会和你玩一个‘追逃’游戏。我会留在这里,等上一天时间!在这一天里,你尽可以带着你怀里地这位女士一起逃跑或者躲藏到任何地方。之后,我会想办法去找你们……如果你们被我找到了,那么我会再给你两次机会!也就是说。我可以允许你被我抓住三次!抓住你之后,我会再次放了你,并且同样给你先跑一天!但是只有三次机会哦,三次的优待时间一过,如果你再被我抓住地话,那么。我虽然之前说过,我并不想杀你,而且我也不会食言,但是……我却可以把你们两人一起捉回去,带着你们和我一起回到北方!然后,在我们鬼兽联军征服你们人类大陆之前,你和这位美丽的小姐,恐怕就要一直留在北方做客了!”他的这一番话。说出来的声音虽然轻轻柔柔,仿佛毫无威胁力。但是落在布布的耳朵里。偏偏是书生这么随意的口吻,却反而让布布心中一寒!他明白。这绝对不是开玩笑!到底點點身上有什么是值得书生如此在意地?!布布盯着书生,沉声道:“书生,你就这么有把握?要知道,这里可是人类的领地!你实力再强,可毕竟只是孤身一人!”“你说的没错。”书生毫不掩饰道:“我个人再强,也只是一个人而已。你还可以随时象其他强者求援……如果你们一起来的话,我自然是远远不敌地。不过……布布阁下,如果你那样做的话,那么就算是你先破坏了游戏规则!那么就怪不得我书生违背承诺了!你应该明白,以我的实力,我不认为你们人类大陆上能有单独和我抗衡的强者!就算你找来再多的人,我最多不敌退走罢了,你们想捉住我,或者是杀了我,那是绝无可能!一旦你先违背的游戏规则,那么我就可以不用遵守我的承诺!布布阁下,那个时候,我会找一个机会,悄悄的取走你地头颅,同时,这位美丽的人类小姐,我一样可以悄悄地把她捉回去。” 说到最后,书生悠悠道:“我说了,我是高贵地鬼界之王,不喜欢把事情做得如此卑劣。所以,这个游戏,是我给你的一个机会!我保证,一个月之内,我给你三次被我追上地机会!如果你能坚持一个月之内,不被我第四次捉到,那么我就会离开这里,这件事情就此作罢。这是我唯一的让步,如果你不同意的话,我只能采取一些我不喜欢的做法了。” 布布听得心中不禁冒火! 高贵个毛线啊!你做婊子还要立个牌坊啊!布布心中立刻盘算了一下,最后却不得不承认,书生说的没错,如果自己拒绝的话,一旦惹怒了这个家伙,他只要悄悄的,无论是杀自己也好,还是抓走點點也好,都是可以做到的!实力不如人,实在是让布布心里憋火。其实他也已经不差了,不满十八岁的年纪,修炼魔法的时间满打满算才几个月,就从零变成了七级魔法师,已经是神速。而且还短期之内修炼武技达到了八级!不过遇到书生这种超乎想象的强者,还是没办法的。沉吟了好久,在书生冷冷的眼神逼迫之下,布布深深吸了口气:“我相信你鬼界一族的名誉……所以,我和你赌了!!”“很好。”书生居然人在空中。盘膝坐了下来,就这么坐在空中,悠悠一笑:“现在。布布阁下,你可以走了。一天之后,我会去追你……哦,差点忘记告诉你了,我留在你身上的那个紫色地魔法印记,在二百里之内,我就可以感应到。所以,为了你的安全,你必须想办法保持在距离我二百里之外躲藏才行哦。所以,你最好不要想躲藏在学校里了。我就在这里坐上一天。如果期间你违背了游戏规则,那么,结果如何,你是知道地!”布布脸色虽然平静,但是心中一股被侮辱之后的怒火。却熊熊燃烧。耻辱!这绝对是耻辱!深深的看了书生一眼——这最后的一个眼神里,饱含得意义,就连书生看了。心中都微微一突。再也没有半句话,布布立刻毫不犹豫的抱着怀里的點點掉头离开!人在空中,展开驭风法术离开了。书生!今天地这个赌约,对我布布的羞辱,我一定会牢牢记在心里的!书生盘膝坐在空中,目送着布布远去,他忽然轻轻的叹了口气。抬头看着天空,悠悠自言自语道:“嗯。有才。有志,有魄……唉。书生啊书生,这么一个人类的俊杰。在大规模战争之前,你真应该立刻杀了他才对啊!为了自己的骄傲,留下这么一个人才地存在,是否是一个错误呢……” 说着,书生仿佛自嘲的笑了笑。耳边的风声呼呼,尽管华北偏冷的强风吹在脸上,却无法吹散布布心中的火焰。居然被敌人从自己的家里如此“驱赶”出来,布布虽然心中愤怒,但是h还得去控制自己的怒火。在飞离了学校之后,布布立刻取出了一副飞天幕布,點點依然没有醒来,他不得不把點點绑在了自己的身后,然后骑在幕布之上,利用幕布快速的飞行速度,尽可能的飞远!而且,这种飞行,还可以节约布布的体力和魔力,比利用魔法飞行要省力气得多。布布仔细得思考过了如何对付这个书生,他原本想向东走。如果能顺利的逃到海里地话,或许可以利用船只……所以,布布想了一会儿之后,立刻做出了决定,继续往北!他地目标是丘比特城。听月神说最北方的丘比特城有梦界最古老的一支力量存在,到了那里,以自己在梦节的接班人身份,应该有权限召唤这支力量吧!摸了摸自己胸口的那个紫色的魔法印记。布布心中更是郁闷。他已经尝试了一些办法,试图解除这个魔法印记,只要没有了印记的感应,他书生毕竟又不是神!只要自己在茫茫人海里一躲,他难道还真的能找到自己?可以说,这个印记是关键!但是书生的魔法果然厉害,布布抓破了头皮。想了不少办法。可却无法驱除这个魔法印记。这一个印记之中,仿佛隐隐地带着一丝书生地精神力。紧紧地纠缠在了自己的精神意识空间里,以布布地本事,是无法排除了。用幕布飞行了大约一个多时辰。布布计算了一下,大约已经飞出了距离学校有两百里左右了。换了飞行术飞行了一段时间,布布忽然就想到了一个关键地问题……自己看来是高估了自己的体力!高空飞行。并不是一件容易地事情。强风吹在身上,需要分出很多体力和魔力来维持方向,而且……布布怀里还要抱着一个人!他精心的计算着,确定了自己已经飞出了安全的二百里左右地距离,布布决定立刻休息一下!否则地话。等自己体力耗尽了。书生追上来的话。那么自己连反抗的机会都没有!落在了地面之上,布布选择了一片树林旁。他从储存戒里取出了水和食物。仓促的吃喝了一些,虽然连续长途奔波,身子酸痛不已,布布却坚持着站了起来,在荒野之上。将武技地基础动作,和那套体术。都做了两遍。虽然疲惫依然,但是精神却旺盛了很多。看着被自己放着躺在树干下地點點,布布叹了口气。點點还没有醒来。看来她伤得着实不轻,就算是用了梦界恢复咒语没有能让她立刻恢复。不过再次检查了點點的伤势之后,她的呼吸还算平稳。脉搏也只是略微有些快,不过也算是沉稳有力。原本肋骨几处断骨的部位。布布也趁机用伤药给她治疗了一下。只是断骨的部位,毕竟生长起来。还需要一些时间。检查完了四肢,又想起了點點受伤……只怕胸肋的部位也有骨折,不过看着闭着眼睛的點點,布布却迟疑了。毕竟是女孩子,检查胸肋,自然就要不免碰到对方的胸膛……叹了口气,布布笑了笑。都这种时候了,尽快地把这个女人的伤治好。跑起来也快一点啊。他咬了咬牙齿。伸手就去解點點地胸甲,手指刚刚接触到了點點地左侧胸膛的下放部位。大概是激动至于,不免用力有些过,不小心在點點的胸膛之下戳了一下,就听见點點忽然发出了轻轻的一声“嗯”,似乎眉宇之间露出一丝痛楚地表情来,然后,點點的两条手臂居然立刻就抓住了布布地衣襟。布布赶紧缩回了手,抬头看去,就看见點點已经睁开了眼睛,一张俏丽的脸庞之上满是红晕,正用一种复杂地眼神,静静地盯着布布。“呃……你,醒了?”布布仿佛有些“心虚”地样子,忽然察觉到自己的手还戳在女孩子家地胸部下面,赶紧咳嗽了一声,把手缩了回去。 “我只是想给你治伤……”布布支吾了一句,还没有说完,却听见點點低声道:“你……不用解释,我知道。” “呃?”布布愣了一下。 點點的脸色却更红了,垂下头去,仿佛不敢看布布的眼睛,声音更是低微:“我,我其实早就醒了。可是你给我念得那个咒语很奇怪,我明明意识已经清醒了,可是却无法睁眼开口说话。不过周围的声音,我还是能听见的。你……你和那个书生的对话,我大概都听见了。”布布问完之后。去看點點。却发现點點居然垂着头,似乎有些魂不守舍地样子。对于自己的问题,这个女人居然仿佛丝毫没听见一样,眼神有些迷离茫然。布布又叫了一声。點點才猛然反应过来,只是表情却有些惊慌扭捏地样子。 她没有回答布布的话,却忽然用一个极低的声音。低声道:“你……我问你,你为什么不答应那个书生地条件呢?我……我也是自愿加入你的梦界,生死也早就在这注定了。” “别开玩笑了!”布布撇撇嘴道:“如果我把你换了出去,最后他又没杀你,你到时候不得回来找我报复啊!!”點點的眼神里立刻露出了一丝失落,忍不住低声道:“难道……只是因为这个?”听了布布的话。不声不响的低下头去,仿佛情绪很是低落。 过了好久,布布拿出了食物和水,递给她地时候,點點才终于再次抬起头来。这次,她美丽地眼睛里。却闪动着光芒。仿佛是用尽了所有地勇气,直直的盯着布布,虽然脸色绯红,可是语气却很坚定。 “布布……你。我听见你和那个书生的对话,你说‘我的點點’。这话,是……是什么意思?” 布布呆了一呆。然后哈哈一笑:“啊?我什么时候说过啊?快看,有星星啊!今晚天气不错啊!太阳真大!”略微休息了一下,布布问點點能否继续赶路。點點似乎反应很是冷淡,简短地说了一声可以。然后看着布布又要来抱自己,點點却不知道哪里来地力气,一把推开了布布。怒道:“我已经醒过来了!不用你抱!” 可惜。她毕竟断骨没有愈合。刚勉强走了两步,就跌倒了。 布布叹了口气,从后面走了过去,不容置疑,一把就把她强行抱了起来。 點點再次被布布横抱了起来,却忽然就安静了下来,双手还顺从地勾住了布布地脖子。布布就感觉到肩膀上一暖,點點地脸蛋已经贴在了自己的肩窝处。这个角度,布布看不见點點的脸色,此刻他也不敢多想这些,认准了方向,就重新施展驭风术,朝着北方继续飞行了。 “休息一会儿,然后我们往北走!茫茫大山环绕,书生又不是神。大海捞针,没那么容易找到我们的!”算算时间,从昨晚跟书生分别之后,现在时间已经过了一天了。现在书生应该是上路来追赶自己了吧。 布布忍着疲惫,再起抱起點點,想了一想,继续这么飞下去,实在太累。而且现在情况不明,就这么拼命消耗自己的体力和魔力,不是什么明智的举动。 布布看了看储物戒里面还有啥?翻来翻去,发现了一辆自信车!这个是在学校偷得!看到这辆车不错,晚上就把他塞在了储物戒里!“ 哎,早知道有天遇到这些情况,就偷一辆摩托车去了!” 就这样布布骑着一架自行车,带着一个美丽的女孩,驰骋在野外... 布布卖力的蹬着自行车,毕竟骑车的速度虽然比飞要慢很多,但是却非常节省力气。一时间,身后點點身上的幽香传到鼻子里。點點的秀发扬起,还不时抚过布布的耳朵,有些痒痒地感觉,而女孩儿温软的身子紧紧贴着自己,不禁让人有些心思遐想的味道。 嘎吱! 车轮陡然停了下来,布布双脚支撑住了地面,然后忽然幽幽的叹了口气:“點點,下来吧。” “嗯……”點點的眼神里还是一片迷醉,直到布布说了第二次,她才茫然道:“怎么了,你累了吗?” “累倒是不累,只不过,我们被追上了。” 布布笑了笑,转身看了點點一眼,轻轻拍了拍她的脸,然后指着前面不远处。 “布布阁下,我真的对你越来越好奇了。你骑的这个东西,是人类的什么东西?” “哈哈,这个叫自行车,没坐过吧?人类的烤肉你应该也没品尝过,有空请你去吃!你会满意的!” 书生的眼珠转了转,盯着布布:“布布阁下,似乎这么快就被我追上,你一点都不惊讶啊。” 布布已经把點點抱下了车,横抱在怀里,淡淡道:“我早就知道,第一次多半会被你追上的。你既然有把握和我打这个赌,自然是有所依仗的。” 书生愣了一下,随即也大笑道:“好!好气度!那么现在,你已经用掉了第一次机会了。现在我在这里再等一天,你这就赶紧跑吧。”“不急!不急!”布布却仿佛一副懒洋洋的样子,抱着點點,然后把自行车收进了储存戒指里,缓缓的走到了书生的近前,看了看天上的星空:“不是还有一整天时间吗。反正早跑一个时辰,晚跑一个时辰,差别也不大。现在都已经是晚上啦,我可是晚餐还没有吃呢。书生先生,想必你也饿着肚子吧?我看这里环境不错,虽然偏僻了一些,不过我们以大地为席,以天空为顶,以星光为灯。在这里共进晚餐,也是一个不错的选择。就不用别时别地了,则是不如撞日,今天就请你吃烤肉!”布布这不慌不忙高深莫测的样子,却引起了书生的一丝疑惑:“你……你真的不着急?我说了只等一天的,你多在这里耽误一会儿,就少了一分优势。”“人是铁,饭是钢!肚子饿的时候,跑路也没力气啊。”布布笑着,已经脱去了自己的外套铺在了地上,然后把點點放在了上面。點點似乎想说什么,但是看了布布的眼神,立刻就闭上了嘴巴,保持沉默了。布布动作很是熟练,立刻从周围搜集了一些枯枝干草,发射一个火球,很快就升起了一堆篝火来,然后从储存戒指里取出了一些食物和水来,甚至连锅碗、饭盒都有。在书生惊奇的眼神里,布布自嘲的一笑:“老实说,既然在梦界、人界、鬼界闯荡,我担心以后总有露宿野外的时候,所以就准备的周全了一些。免得到时候在野外饿肚子啊。”说完,布布居然又弄出了七八个小瓶子来,居然是各种佐料。然后他卷起袖子,就借着篝火,烤起肉来,不多片刻,便香气四溢。书生一直静静的看着布布,直到此刻,才不由得叹了口气:“布布阁下,你当真是一个妙人!” ..... "昨天你虽然先走了一天。但是你一路奔跑,施展魔法飞行,路过地地方。周围的空气之中的魔法元素都会被你惊动。就仿佛水面留下的波纹……就算距离很远,就算你已经走了足足一天。空气之中的魔法元素的波动,水面的波纹已经渐渐平息,变得很细微地。但是以我地魔法感应力。依然可以轻易的就辨认出,你曾经经过这里." “看在你请我吃烤肉的份上,就透露一些信息给你。说实话,现在我开始享受这种捕猎的感觉了!哈哈",随后他施展身形,几个跳跃,已经不见了身影。 咳嗽了一声之后,布布不声不响的扶着點點站好,尽量用平缓的语气道:“嗯……你现在有力气吗?能走吗?” 點點也收起了扭捏的小模样,毕竟两人现在还没有脱险,深深吸了口气,脸色也认真起来:“勉强能走……可是,刚才从那个书生的话看来,就算我们现在拼命逃走,只怕一天之后,他还是能轻易的追上我们……他说的那个追踪空气之中的魔法元素的波动,我仔细想来,如果不是那个书生吹牛的话,那么他的魔法感应力也实在太惊人了。” “嗯……鬼族嘛,天赋上自然比我们人类要强一些。更何况他是鬼族之王。”布布也陷入了苦思之中:“还有这个魔法印记也是头疼,怎么想个办法……” 过了会儿,布布忽然脸色一动:“或许,这个法子可以尝试一下。” 接着,布布辨认了一下方向,拉着點點继续赶路了,两人降落的地方,正是靠近山脚。身边,一座延绵不绝的大山,就横在眼前,山峦起伏,远远看去,仿佛看不到尽头。“我试试看,能不能想办法解决这个该死的魔法印记。”布布叹了口气:“还有一个白天时间,就要出发来找我们了。如果不能把这个该死的魔法印记驱除掉,总是不行的。我需要冥想,你来给我望风吧。”说完,布布已经严肃的坐好,闭上了眼睛,进入了深层次的冥想,一偻意识,缓缓的流淌进了自己的意识空间里。 等布布好不容易终于平静了下来,再次检查自己的精神意识空间之后,却目瞪口呆的发现了一个让他哭笑不得的事实! 紫色!! 自己的整个意识空间,完全变成了一团淡淡的紫色。最重要地是……在意识空间的深入之下。 郁闷了一会,布布站了起来,却伸手拍了拍點點的脸,语气里有些兴奋:“哈哈哈!错有错着!让我想到了一个不是办法的办法!” 但见他把书生注入的紫色魔力注入一个储物戒的一个水晶里,然后运气,一把劈成了十几块! 布布看到丛林里跑出来的野狼,有荒原上的野兔,甚至还有几只只山鹰,以及快的速度把晶石碎片摄入他们的体内! 做完了这些之后,布布笑得很愉快,拍了拍手,拉起了點點:“走!我们进山吧!” 大约就在布布进山之后的一个时辰之后,就看见两人刚才所在的原地,空气之中陡然闪现出了一条裂缝,随后书生书生飘逸的身影,从容的从时空裂缝之中迈步走了出来。 “夷?” 书生刚走出了裂缝,就感觉到了一丝不对,明明自己是感应到了魔法印记,利用这个坐标才切开了时空裂缝,然后远距离瞬移来到这里的。 可是,却没看到布布?! 书生立刻闭上了眼睛,去搜索魔法印记… 这一搜索,结果却让书生大吃一惊! “怎么可能!!”书生脸色一变,他还以为自己是弄错了,赶紧又仔细的感应了一下魔法印记的来源。 然后,他才真的愣住了。十几个!!夕阳的余晖之下,书生的脸色终于严肃了起来。 游戏,似乎开始增加难度了啊....两人进山以来,已经走了有一个时辰了。眼看點點似乎坚持不住了,布布伸出了手来,揽住了點點的后背。从她的腋下插了进去,半架着她前进。这样以来,點點的一半的重量都落在了布布的身上。“啊!”一个不留神,點點惨叫了一声,身子立刻倒在了布布的怀里。布布一皱眉,低头看去,就看见點點的靴子已经磨破了,脚踝的地方,被刚才草丛里一根尖锐的带着锯齿的植物割出了一道口子。 看着點點血淋淋的脚踝,布布叹了口气,从储存戒指里取出了干净的衣服,撕下了布条,给她包扎了一下:“可惜,这种小伤。我们现在没时间+也不能施展梦界愈合术来治疗,你先忍耐一会儿吧。”布布说着,提起头来看了點點一眼,却发现點點脸色有异,直勾勾的盯着自己,虽然黑暗之中,她那双明亮的眸子,却异常的真切…… 虽然环境黑暗,但是點點刚才的脸部表情的变化,布布其实都已经看得清清楚楚了。 布布心中叹了口气,表面上却依然装作若无其事的样子。两人各自想着心思,却都陷入了沉默之中。 过了一会,點點心跳渐渐平复,才忍不住低声道:“我们……还要走多远?” “嗯,差不多了。”布布想了想:“我正在寻找一个合适的地方……想必这么一片树林里总应该有我要找的地方吧。”说完,布布忽然转过身去,然后在點點的面前蹲了下来。 “嗯,你,你干什么?” “背你。”布布简短的回答。 點點感觉自己脸上的温度刚刚才降下来,此刻又开始发烫了:“我,我不要!我自己有腿能走,为什么要你背!” 布布却哪里管这么多,干脆强行就把點點背了起来。點點低呼了一声,也不知道什么心态作怪,象征性的挣扎了一下,就任凭布布将自己伏在身上往前前进了。 走了一会儿,听见布布呼吸渐渐粗重。點點不禁心中痛惜,虽然脸上还是死挺,但是口气却不免就露了出来:“喂,你还行不行啊!如果不行地话……就,就把我放下来吧。”布布一面喘息,却一面哈哈一笑。黑暗之中,他的调侃的语气,却充满了坚定:“难道没人告诉你吗?男人是绝对不能说不行的!”终于,两人又走了好一会儿,穿过了一片林子之后,面前忽然一开阔。 之间这森林,面前居然是一片大约有方圆不到半里的平坦地带,在这周围都是树林环抱的空旷地带,面前地地面。满是厚厚的一片树叶,隐隐的,还传来了一股扑鼻的腐臭味道。 布布刚往前迈了一步,就觉得脚下一软。赶紧就后退回来。看了看脚底的泥浆,才骂道:“幸好退的快,原来是一个沼泽!”原来这面前的看似平坦铺了一层枯萎树叶树枝的地面,其实之下,根本就是柔软地泥浆,是深不见底的沼泽!如果运气不好的话,只怕一脚踩过,就陷进去了。 走路的时候,起起伏伏,布布明显的感觉到背后来自點點身上的两团柔软的东西顶在自己的后背上,那感觉,简直就是一种快乐和折磨错综复杂…… 點點自然也是觉的不妥,她原本还试图支撑着手臂,但是渐渐力气耗尽之后,身子不由自主就贴在了布布的背上,两人“紧密”地贴在一起,點點被布布放下来地时候,身子都软成了一滩,靠在树干上喘息,一双眸子却盯着布布,眼睛里几乎都要滴出水来,咬着嘴唇,俏脸涨红,眼神里满是幽怨之色。两人各怀心思,布布沉吟了片刻,正要开口说话。陡然之间,就听见远处传来了一个冷漠的声音!“布布!你以为你能躲的了吗!!”四周都传来了这种声音,”我已经发现你了!“點點脸色一变,正要张嘴惊呼,布布却已经一把就抱住了她,用力的捂住了她的嘴巴,低声道:“别紧张!他没找到咱们,这是用魔法催动声音!吓唬我们呢!真发现了我们早现身出来了”果然一会儿过后,,音波却忽大忽小、朝着西边,一路飘荡而去..."我想到了个办法!"说着布布从储物戒取出一块布条,先是把點點的嘴绑住了,然后掏出一个头套,整个套在了點點头上,同样的把自己全副武装就像cs的反恐精英那样,"忍着点啊!"说完,拉着點點,就一步往前走去。终于,两人脚下踏入了柔软的沼泽里,脚下一软,仿佛无数恶心滑腻的泥浆流淌进了自己的鞋子里,點點只觉得心都颤抖了,不由自主,身子一歪,“嘤咛”一声,软倒在了布布的怀里…… 沉到了下面,布布立刻拿出一个避水的扩展箱开辟了一个狭小的空间,两人在这里难免有些狭窄!身体接触也是没有办法的事情了."就是这里实在是太脏了一些,不然真可以在这躲避一些时日!""岂只是脏!简直就是地狱!臭不可闻!" 此刻两人都是一身地黑泥,将本来的面目都掩住了,全身裹在这冰冷的臭泥里,滋味自然是不好受的。 忽而又声音一低,似乎扭捏了一下,然后下了很大的决心,低声道:“但是..有你在这里陪我,就算是这些泥浆臭一些……也……也……”布布有些尴尬,咳嗽了一声,故意道:“也什么?” 點點看布布的样子,不由得有些气恼,恶狠狠道:“这泥浆虽然臭,哪里有你臭!!你..你....你简直臭死了!”说完,羞愤之下,用力狠狠推了布布一把。布布被點點一推,就立刻朝后倒去。这避水箱弄出来的空间原本就不大,两人面对面站着还会接触到,这么一推,顿时就把布布推出了避水箱的范围,噗哧一声,布布身子就往后倒进了泥浆之中……眼看布布往后倒进了泥浆里,點點吓了一跳,赶紧伸手去抓布布,却抓了一个空。眼看布布扑腾了几下,自己挣扎着重新游了回来,怒道:“你怎么做事情没轻没重的!”说着,哇的一声,张口就吐出了一口黑泥来,愁眉苦脸道:“你……你这家伙,害我吞了不知道什么东西进肚子里了……”布布呕吐了半天,实在吐不出什么东西了,恼火之下,重重甩开了點點的手,然后在储存里里掏出了一些干净衣服和毛巾之类的东西。 毕竟布布的魔法储物戒里,挟带的水也不多,还要留着饮用,所以两人虽然很想用水擦洗身上的泥浆,此刻却不能够了。只能用毛巾胡乱擦了一下,用清水漱口洗脸,清洗了一下口鼻耳朵,然后各自换上一身干净衣服。 布布换完了一身干净衣服,虽然没有洗澡,身上依然还有很多污泥,但是换了干净衣服,倒觉得舒服了很多,却看着點點涨红了脸站在这儿不动,布布想了想,就哈哈一笑:“啊!我差点忘记了,你是女人……嗯,我转过身去不看你,你换衣服好了。” 點點立刻急道 :“这,这怎么行!” 布布皱眉:“那也没别的办法,这里地方就这么大,你让我避到哪里去?你就将就一下换换吧。我们两人身处陷阱,你就别挑三拣四了。现在没有别地办法。如果你不肯的话……要不,你就穿着这件臭泥衣服吧。”點點这才泄气了,无奈之下,只得命令布布转过身去。布布背对着點點,就听见身后传来悉悉索索的衣袂动静,忽然不知道怎么的,就想起刚才自己在外面背着鼓鼓的胸膛贴在自己背后的那种旖旎来,心中也不知道是什么滋味,不由得就有些想入非非。正想得出神,立刻就又醒悟过来,抬起手来就轻轻地抽了自己一下,暗骂道:什么时候了。还起色心! 點點勉强换了衣服。只是这衣服是布布储存戒指里挟带的男装,她穿来不免就过大了。再加上里面的那些脏衣服都脱了去,就连内衣都脱了。此刻就穿着这么一套空荡荡的衣服,不免有些忐忑。却忽然看见布布抽了自己一下,好奇道:“夷?你这是做什么?”布布脸一红,幸好他脸上有黑泥污迹,也没有让點點看出来,嘟囔道:“我……我打虫子。”點點没好气道:“这里水下哪来什么虫……啊!”刚说到这里,立刻猛然醒悟过来。两人你看着我,我看着你。一时都无话了。过了好久。點點用力咬了咬嘴唇,才低声道:“...布布。”“嗯。”布布的声音似乎恢复了平静。“我们……我们难道要在这里待上一个月吗?”點點说这话的时候,虽然心中有些紧张。不过内心伸出,却反而不知道怎么的,生出了一丝隐隐的期盼来。虽然这里臭气熏天,又黑又湿,地方又狭窄,暗无天日……可是,如果能和他单独在一起待上一个月的话……那…… 那也很好啊。 “怎么可能呢!!这种地方待上一个月?只怕我们两人都死了!我身上地储存戒里,带了食物和水,但是也不多了。两个人的话,也只够两天用的,如果节省一点的话,最多能坚持四五天罢了。我们也不用在这里待太久。如果我计划成功地,三天就足够了!" "三天?" “是地,如果一切顺利,真的骗过了那个书生的话,三天之后,我们就可以大摇大摆的上去了。" 书生皱眉自言自语。“唉..怎么居然是这么一个地方..可第四个魔法印记的感应。地确是在这里啊,嗯..仔细的感应了一下,似乎就在这沼泽下面呢!”他刚才先后瞬间移动了三次,从南到北,从东到西。虽然他魔力强大,但是这种远距离的瞬间移动,需要劈开空间裂缝,还是很耗费魔力的。 可气的是,第一次他找到的是一群正在往东南飞的野雁!书生在一只野雁的内脏里一个紫色水晶,立刻就明白了这魔法印记感应的来源!“别的都在移动?这个似乎?一直在下面静止呢?好小子!原来在这下面!不过这么臭,他都敢?下去?”说完皱了皱眉。下面那两个正在得意呢!“布布,真有你的!居然想出这么一个办法!快出来吧!我已经看破了你的花招了。”这声音落入耳朵里,布布和點點都是同时脸色巨变!“是那个臭书生!”點點猛然站了起来,神色惊恐。 布布稍微镇定了一下,败了败手,按住了點點的肩膀,低声道:“先别着急,听听再说。”说着,伸手捏住了點點的手,却感觉到點點的手已经冰凉。 两人沉默了会儿,又听见外面传来书生的声音,一字一字都是那么清晰:“布布,你们两人在这个地方藏着,难道不嫌那里太脏了吗?”點點变色,低声道:“他,他会不会又是在使诈,骗我们自投罗网的?”布布摇头,面色凝重:“不会,他这声音能准确的传到这里来,还听见了我们刚才的对话,那就不是诈我们了,而是真的发现了我们。”布布却忽然笑了笑,不回答點點的话,然后深深吸了口气,大声喝道:“书生,既然你已经找到了我们的藏身处,为什么不下来抓我们呢?这里虽然臭了一些,不过也好玩的很呢!”“布布,所谓愿赌服输。我承认你这次差点就骗过了我。不过既然被我找到了,迟早你要出来的,何必现在还耍赖?你难道是这种纠缠不清的人物吗?这样的做派,可没有一点强者的风度啊。”书生听了,沉默了会儿,再次传来了声音就有些不善:“好吧,既然我已经好言劝告过了,那么你不出来。我只有用点其他手段了。”
声音落下之后,布布赶紧全力施展开自己的精神力,扩张开之后,就忽然的听见了一丝轻盈的乐曲从外面传来!
那曲子悠扬动听之极。让人听了。不禁心旷神怡,隐隐的传来了一个极为轻柔动听的嗓音,那嗓音宛如天籁一半,娇柔婉转之处,让人不禁荡气回肠。这声音不是别人,正是书生! 布布先是一愣……难道这书生疯了?等得不耐烦了,就在上面一个人唱歌自娱自乐? 可随后,这歌谣的声音渐渐加重,一字一字落在人的耳朵里。就仿佛无数个钩子。拼命地撩拨着内心深处的那根弦,让人全身蠢蠢欲动,仿佛忍不住就要随着这曲子跳起来。翩翩起舞! 布布心中骇然,只觉得全身乏力,意识都有些模糊了,忽然之间,就感觉到自己的手指轻轻一动,已经不由自主的随着歌谣地节奏轻轻敲动起来。布布一惊,立刻强行压抑住内心地骚动,凝聚精神,立刻用精神力缓缓的搜索自己的意识空间。他这么一凝神,立刻全身的那种失控的感觉顿时一松,只不过刚才就这么短短的片刻,就已经出了一身的大汗!旁边却传来了一声轻轻的“嗯“的声音,布布一惊,却看见點點满脸潮红,已经随着歌谣的韵律翩然站了起来,一双手臂轻轻地张开,身子不由自主地细细扭动,鼻子也渐渐粗重起来,那一双眸子似乎都要滴出水来,眼波流转,媚眼如丝。 布布赶紧去拉點點,可是點點却猛然一挣脱,手臂虽然看似轻柔舞动,却仿佛力气极大。“你怎么了?快凝聚精神,这是一种魔法!” 點點身子无力的舞动,口中却无奈道:“我……我没法控制自己……啊!” ”如果你再不出来,后面的曲子渐渐激烈,只怕你们经受不住啦。后面我再继续唱下去可就不是跳舞这么简单了,被我控制只后,我稍一发力,就可以让人发狂的!” 书生说话的时候,點點才终于感到身子一松,立刻无力的软倒了下来,全身的袍子都被汗水浸透了。她原本就换过了衣服,只穿了一件外套,此刻一身汗水之后,这外套已经贴在了身上,加上一身汗水,满面潮红,原本就傲人的曲线更是毕露无疑。她这么一倒,就正倒在了布布的怀里。布布慌忙一把抱住了點點,只觉得一个诱人温软的身子抱了个满怀,那峰峦凸起的曲线就在自己的手掌之下,看着點點急促的喘息和水色的眸子,不由得心中大动。點點似乎连站的力气都没有了,倒在布布的怀里,心里也是一慌,赶紧就伸手去推布布:“你……你别这么抱着我……”到最后,就连声音都软得不成样子了。终于,就在布布发现自己的身子已经不由控制的站了起来,双腿已经开始了轻轻的动作,他知道自己也已经到了极限了,赶紧就大声叫了一句:“好啦好啦!书生,算你狠!这一次我认输就是了!我这就出来!”随着布布的喊叫,那歌谣终于停止了,點點和布布两人一起坐倒在了地上,都是大口喘息,汗流浃背。 ......书生皱眉盯着布布看了几眼,确定了布布脸上的笑容居然并不是强颜欢笑的硬挺,书生不由得有些吃惊:“你这个家伙,可是带给了我越来越多的惊喜了啊!布布……你为什么到现在还是这么一副自信满满的模样呢?”布布理直气壮道:“我为什么要沮丧?书生先生,这才是你第二次抓住我们。我还有机会!我这个人,在事情结束之前,是不会放弃的。”书生怔了怔。随即哈哈一笑:“不错不错……布布,我真的越来越佩服你了。躲在这里,这个办法。你是怎么想出来的?其实,这个计策,几乎就差那么一点儿,就已经骗过我了。 ”布布这才叹了口气,很没有顾忌的往地上一坐:“那又怎么样?差一点骗过你,又不是真的骗过了你。”说完,布布才收敛起玩笑地样子,认认真真的看着书生:“好啦,书生阁下。是否可以指点一下,你到底是怎么看破了我的这个计策呢?”书生失笑道:“你这人真有些怪异,总是不肯吃亏。哪有你这样的,每次输了,就要我把诀窍告诉你。”书生无奈的叹了口气,似乎拿布布没有办法:"另外几个魔法印记都是用飞禽走兽来蒙骗你的,那些大雁和动物,行动地速度极快,而你却静止了后来,自然就会露出破绽来了."说完这些,布布忽然又扬起笑脸来:“书生阁下,多谢你提醒我啊!不过下次,我一定不会再犯这种错误啦!”当布布和點點在附近找了山泉清洗完毕之后,按照约定,将开始第三轮的追捕了!这次,告别之前,书生不无嘲弄的笑道:“布布阁下,下次选择藏身地点的时候。可千万别在挑选这种地方了!同样的计策,对我使第二遍,可就不灵了。”布布却只摆了摆手,然后对书生龇牙咧嘴一笑:“差点儿忘记对你说了。书生先生……谢谢你!”书生张了张嘴巴:“谢,谢谢我?” “不错,谢谢你。”布布笑得有些神秘,然后挥挥手,拉着點點。转身就告别了,语气柔和:“书生先生。谢谢你给我唱地那支《镇魂曲》。你真是个好人!” 书生还怔怔的愣在当场,浑然不明白布布的话里所指意思——骄傲的书生并不知道。布布已经具备了鬼族魔法的属性!而他更不知道的是,他唱地那一曲书生的深奥魔法《镇魂曲》,布布只听了一遍,就学会了! 布布从储物戒拿了一些东西,看着眼前数十块紫色水晶——布布阴险的一笑,然后看了點點一眼:“帮我全部砸碎了!” 面前地上摆放着一大堆紫色的碎片,大大小小,密密麻麻,只怕有数百上千块之多!! 布布念了几句召唤的兽语,但见整片天空都是黑的了,遮天的“乌云”一股脑过来,靠近以后,點點惊得张大了嘴,这是一大片赶来的乌鸦群! 布布很吃力的,在精神力的勉强支撑下,给这群乌鸦都喂了一些晶石,随后乌鸦四散而去! 哈哈!这下书生有的忙了! (谔谔!我实在是写不下去了,快吐血了。) 下面简述下主角的命运: 布布點點一路北上,这么几千片的晶石,总算暂时骗过了书生。2位成功到了丘比特城,并且进入了城堡,也有幸见到了梦界最神圣的力量群体,说来巧合,那个群体简称“多拉A梦比特团”。书生赶到时,还差3天就是赌局的一月之期,书生要求进入城堡赢得赌局。布布当然要求比特团 拦下这位强敌!于是大战过后,书生感慨万千:"我输了,输的有些冤!由于见到點點身法及其类似我鬼界无上身法,简直是如出一辙,于是打算带回去研究明白.没想到我因为高傲输在了一个后生小子手中!错了,错了!不是输在了他手里,哆啦a梦比特团确实是我所不敌,我是 输在了骄傲自大上!" 不久獸鬼聯軍對人類展開了戰爭,不到三年,由於戰爭,深中停課了,好多童鞋都參軍迎戰去了!最後戰爭也沒有勝者,因為毀滅道義的東西是不可能勝利的! 萬古不變的唯有愛情而已! 最終布布點點两人历经磨难日久生情,在哆啦a梦比特团的见证下,结为伉俪。兩位因修煉得道,最後隱居深山,長生不老.也不断去人界寻找有缘人,将最最古老的梦界发扬光大!
]]>和风醉柳,花香熏人,正是华北盛夏刚过的季节。中考已过去2个月,我怀着忐忐忑忑的心,肩上背着行李,走进这陌生的深中。首先就看到一个池塘,里面倒是清澈的水,在咕噜咕噜的流着,金黄色的小鱼儿们争先恐后的游着,生怕这小虫给了别人吃。不太强烈的阳光照在身上,走在路上,还是襟襟的汗。那几棵梧桐树上,禅也在大声的叫着,如此陌生的景象,也不知道禅为谁鸣呢?我推门而进,随便占了一个铺。那个时候真是啥也没有啊!除了每个月的小生活费,和洗漱用品,There‘s nothing !(注:说了写成一本小说,下面以第三人称开始叙事,既然是小说,那么人物情节环境必不可少,先假以名讳:主人公我-布布,而你是女猪脚-點點。写小说我就先在第一章蓄一些流水吧,后面的肯定有虚构,不然小说就没有了他的灵魂-情节! 在七夕节前夕我就以这个虚拟的小说来纪念我们的求学时光啦!妞妞,不要怪我写得慢啊,这东西需要构思,这不是几百字的散文。以后我尽量每天抽出一小时来完成一章!有时间我也会来想象后面情节的发展,我尽量写得精彩一些 ! 绝对不是 流!水!)布布走进教室,扫了一眼,(由于在宿舍聊天时布布了解到和大建家里离的近,所以他俩走得近一些)然后和大建走到后排,随便找了个座位,开始闲扯:大建:"你初中在哪上的啊?"。 布布:"长明,你呢 ?"“我在榆科”“哦,我离那挺近的,自行车的话,半个多小时就能到。一月一放?”“不是,半月一放。。。”"......"
这时候老师走了进来,带着个大黑框眼睛,一看就是那种闷骚的类型。“大家好!我叫张策,高一就是你们的班主任了,希望你们好好表现,咱们共同努力,争取高二还能在一起!好,接下来,大家先上台来介绍下自己吧,有什么兴趣爱好也来分享下,下面一号先来吧”“大家好,我叫陈阳阳,很高兴来到10班.”“大家好,我叫郭峻岭,我没什么兴趣爱好。。。。”......“很好,看来大家的精神面貌很不错,接下来我们出去排个队吧,先按大小个排一下,一会进来按这个顺序分桌,明天军训也按这个顺序来战队!” 不出意外的,布布被分在了第一排,由于男女都有,结果,他右边是个小女生。问了问她叫“點點”当时留着条辫子,看起来绝对的温柔老实,乖乖女的形象,说话都小声小语的。班上大部分女生都是这种类型的女生。而布布的同桌就是他宿舍那个阴冰,小个子,天天和女生吹牛,宣扬初中时自己的泡妞大法。“妈的,那点破事儿,还是追求失败的糗事,怎么总和别人说呢,这小子是不是脑子有病啊?!”小布布观察了2天,也就和他离得远了。连话都不多说。话说布布这个半大小子,绝对的小乡村出来的,内向的很。平时除了死党,也没啥能和女生接触到的。毕竟是同桌,时间长了,也就熟悉了,说哈也不至于那么害羞了。于是乎,在一个大课间,由于刚上完大黑眼睛班主任的物理课,闲着也没啥事,就问點點,黑板上那個向量怎么求值。这个妞儿,就真的在一张报纸上画图讲解啊。布布这货就看着答案,说:“不是吧,人家不是这么说的,你看着答案。”“想考第一名吗?想变聪明吗?想成为尖子生吗?不敢想象?!有了《尖子生》,这一切将成为现实!.....”这货开始读答案后的广告。。 结果“點點”那妞嘿嘿的笑了。虽然还是那么没有底气的笑....时光飞逝,很快就迎来了高中第一次考试,虽然是月考,不过毕竟是第一次,一个月浑浑噩噩,能学会啥?会的没多点,不过还好,布布考得还不错,比入学成绩好一些。而他的死党大建,考的很不好,比入学时差的太远,班主任直接把他调到了前排,呵呵,这俩货总算混到了同桌的位置。这样一来,就不怕生活闲出个鸟了。每个班都有几个不学好的小子和姑娘,很明显的,10 班也不例外。有一姑娘就假以名讳“王萌”吧,还有个小子叫“张旭”,不知怎么这么早熟呢?刚没多久就搞到一起去了,每天晚上绝对是最后离开教室的。至于没人的时候,他们做了什么?这个反正平时有个课间他们在教室公共场合都亲吻。“喂!听说了没?”“什么?”“听说昨天晚上,张旭和王萌晚上没回宿舍啊!”“真的假的?”“这个,应该是真的吧!听有人说晚上出来路过教室门前听到了**声音。”“我擦,这么high,高中生就做这些事...”"就是!世风日下啊!像我这么纯洁的孩纸真是越来越少了。。。"“滚!就你?闲着没事就拿借来的p4在多媒体上考片看。。谁不知道你那点事啊..”"......"总算熬到下了晚课,布布拍拍大建的肩膀,“走你!” 这俩踱着清闲的步子,走到了书店里,看了又看,集资买了本《鬼吹灯》。奶奶的,新华书店怎么没有那大厚本的书呢?有名的唐家三少,番茄的经典大作都没有,买这个凑合着看看。晚上回去了,吹会牛就到了睡觉点。布布躺下盖起被子准备欣赏了,拿出手电筒,看了看,越看越心惊,这作者想象力还真是丰富啊!墓地的事情怎么这么清楚啊,就像身临其境的感受一样,那些景象像过电影一样进入了布布的脑海...伴随着情节的深入,总算越来越困倦,进入了梦乡....周边全是黑暗暗的,连一丝光亮都没有,布布手里不知在哪里来的火柴,点了一根,发现地上有个半截的蜡烛,点了发现眼前就是个墓穴啊!顿时吓了一身冷汗,怎么这是小说的景象吗?好奇心的驱使下 ,他还是硬着头皮走了进去。除了漆黑以外,还有蛐蛐的叫声,他小心翼翼的往里面走去,一段狭长的圆洞路过后,总算看到点实物,他发现了一个菱形的门!没错!是菱形的,还是四半不同颜色的组成花色门。这个...布布按照平常在电视看到的办法,网门的右侧找去,沿着这道墙走去,发现除了凹凸不平的墙坑以外,坑里都填满了一个数字,还有少数是类似音乐符号的东东。这时候突然传来了一些细微的声音,像毒蛇吐信子那样,丝丝的,然后更让人意想不到的事情发生了:那些墙坑有节奏的跳动了起来,伴随着彩色的颜色,形成了一段乐曲。然后映射到墙上,形成了一个数字。他略微心思了一下,就发现这个是菱形边长度和面积的关系比例。于是按照长度值和面积的数值分别敲击在墙坑里,这时候,那种丝丝的声音愈发强烈了!敲击一下,音符就响一下!不断跳跃的音乐和颜色汇成了一句话。很显然这不是现代语言,根本听不懂说的啥?紧接着,门里面仿佛有了感应,这道门以细胞分裂的形态散到了旁边。再然后一股意识传到了布布的大脑中!传来了一句话。显然也不是他能听懂的话,不过意识里立时有个弹出框显示出了汉字!布布惊呆了,怎么会有这种事?而且还发生在自己身上,这肯定是个梦境!“你是怎么进来这的?”,意识弹出框显示信息!。“我也不知道..”他脑中想着这句话,然后顺嘴说了出来,发现意识弹出框变成了这句话。“不知道?你年方16,能进来这个地方,也算是难得的仅了”。“这话怎么说?”“2年以前,也有一个小子进入了这里!”,顿了下,接着说道:“不过,他对声音的天赋实在是太一般,完全是靠想象力进入的这里!”,“恐怕你不会想到,他并没有通过测试!梦界月神只是强制加了译符到他的意识里,以便他能听懂梦界语言。他也算个有缘之人,月神给他讲了梦界、人界、鬼界的起源还有当年那些经历。哦对,他的名字翻译成人界语言称为;‘张牧野’”!“额,那不是.......”,布布绝对震惊到了极点!,那不是鬼吹灯作者“天下霸唱”吗?,这也太扯了吧?这种狗血的事情怎么能发生在“我”身上!“好了,现在开始测试啦。”忽明忽暗的显现了一个身形出来,隐隐约约能看到一个瘦小的带着个魔术师尖帽子的,衣服却是道士的打扮的人。但见他的左手一挥,那扇菱形的门却像破碎的玻璃渣组合在一起那样恢复了原样。接着四周的墙壁上爬来了一堆堆的蜘蛛,以V字形爬了开来,形成了一些字符,范围越缩越小。布布看着这些字符,大。脑飞速旋转着。就在毒蜘蛛刚要出碰到他的身体时,布布几乎是下意识的念出了这些音符,就是进这道门之前他听过的一段咒语!这些蜘蛛立刻化成灰烬散到地上,顷刻间混入泥土中!头顶一道道紫光连接在一起,闪现出一个紫色女神来,淡淡的对布布说道:“天赋不错,听一遍就能记住从没学过的语言和声音,并吟唱出来!你很不错,能来到我梦界,也算个有缘之人。”说完,也没理会布布的想法,伸出修长的手臂,对着布布的手臂一点,一只漂亮、梦幻的蝴蝶印在了上面。“你是来到我梦界的第二人,但是天赋正好,第一人只是想象力很不错,天赋极其一般,因而只是给他讲了些3界的故事,以便衰落下去连三界的传说都失传。”顿了顿,“从今天起,你是我梦界的传人,宣我意志,将梦界发扬光大!你不仅是梦界的传人,更是梦界在人间的代言人!你将寻找有缘之人,适当加以辅助,拉入梦界,为我梦界效力!”“叮铃铃.....” 一声铃响,伴随着“起来!不愿做奴隶的人们......”的汹涌国歌,将布布拉回了现实。不过昨晚上的梦中场景历历在目!而他一看左臂,那漂亮的紫色蝴蝶就在那,不过不像梦中那样闪闪发光了。布布愣在了那里,衣服也忘了穿。大建胳膊上甩着水珠走了过来,推了布布一下:“楞啥呢,快穿衣服走啊!一会迟到了,跑操还得罚站!”“哦哦”他穿上衣服,神不守舍的跑了出去。。
]]>interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs; type TForm2 = class(TForm) procedure CreateParams(var Params: TCreateParams);override; private { Private declarations } public { Public declarations } end;
var Form2: TForm2; implementation {$R *.DFM} procedure TForm2.CreateParams(var Params:TcreateParams); begin inherited; params.WndParent:=getdesktopwindow; end; end.
]]>窗体保持在最上面。但是,使用这种方法后,在切换窗体的模式时,窗体将闪烁。
为了避免切换窗体模式时的闪烁,可以使用Windows API函数SetWindowPos来解决
这一问题,使用方法如下:
SetWindowPos(Form1.handle, HWND_TOPMOST, Form1.Left, Form1.Top,
Form1.Width, Form1.Height,0);
用实际窗体名称代替 “Form1 “,调用这个命令就可以将窗体设置为保留在桌面的最
上面。如要将窗体切换回正常的窗体,调用下面的命令:
SetWindowPos(Form1.handle, HWND_NOTOPMOST, Form1.Left, Form1.Top,
Form1.Width, Form1.Height,0);
]]>最新的这版我用了2天,感觉很好用,听本地歌曲,搜网上歌词。。大家可以试试
////////////////////////////////////////////////////////////////历史更新/////////////////////////////////////////
小步静听 主要构建历史
第一个版本,比较简陋的界面和功能
下载地址: http://pan.baidu.com/share/link?shareid=85286&uk=3860164064
第二个版本,初步写出了歌词秀同步功能和换肤功能,(将*.skn文件放在应用程序同目录下即可切换皮肤)
下载地址:http://pan.baidu.com/share/link?shareid=85287&uk=3860164064
第三个版本,用时钟控制实现了磁性窗体,继续研究…
下载地址:http://pan.baidu.com/share/link?shareid=85288&uk=3860164064
第四个版本,调用bass.dll,完全颠覆了以前的算法,界面漂亮不少,能自己调节均衡器,bug很少,功能有待增加…
下载地址:http://pan.baidu.com/share/link?shareid=85288&uk=3860164064
第五个版本,磁性窗体控件化,win7可以完美体现.
下载地址:http://pan.baidu.com/share/link?shareid=85290&uk=3860164064
第六个版本,实现基本所有功能,就不截图了,
下载地址:http://pan.baidu.com/share/link?shareid=85291&uk=3860164064
第七个版本,自动记录播放列表并加载,还有好多新功能,换了皮肤编写控件,写了2个皮肤,没记录位置替换然后那样太墨迹了,生成2个程序,凭自己喜好,下载使用吧
古铜:http://pan.baidu.com/share/link?shareid=85293&uk=3860164064
winamp版:http://pan.baidu.com/share/link?shareid=85292&uk=3860164064
1.7:http://pan.baidu.com/share/link?shareid=85302&uk=3860164064
1.7.1:http://pan.baidu.com/share/link?shareid=85304&uk=3860164064
1.7.2:http://pan.baidu.com/share/link?shareid=85305&uk=3860164064
最后,有几个皮肤文件,下载后解压,2-6个版本可以用,放到他们执行程序的目录即可切换皮肤了…
下载皮肤:http://pan.baidu.com/share/link?shareid=85298&uk=3860164064
没了………..
小步静听更新到1.8,(1.8–1.8.2三种桌面歌词效果),win7 下显示效果还不错,
修正bug,欢迎下载试用!(XP效果一般),
经我测试,bug比1.7少了好多,能正常使用,歌词部分正在努力!
原来delphi写了磁性窗体的代码,在XP下仍然很鸡肋,win7效果很好!
2.添加类似qq的靠近侧边自动隐藏伸缩功能
种桌面歌词风格,随你喜好
.所有版本都是下载后解压再用!
.1.8.2版本桌面歌词我添加了显示图片,那个文件夹下必须有张“bruce.png”,你可以随意使用自己喜欢的图片,但是名称必须一致,否则会提示“out of memory…”
.所有版本都是单文件可执行程序,没有调用dll,一大堆人家老外开发的东西,调用后,多累赘!
没了….
下载地址:
1.8: http://pan.baidu.com/share/link?shareid=85307&uk=3860164064
1.8.1: http://pan.baidu.com/share/link?shareid=85308&uk=3860164064
1.8.2: http://pan.baidu.com/share/link?shareid=85308&uk=3860164064
//————————————————————————————————————————–
放假了,闲着没事又研究研究,更新一些功能—————2012/10/1 11:55
加入读取id3标签,可以自己定义艺术家、专辑、年代、、、、一键定义,特简易。
2.加入识别操作系统版本,进而来控制窗体的磁性效果。
加入桌面歌词保持最前端功能,我不知道千千静听的实现算法,自己想当然用时钟控制的,反正实现了就行,以后有新的想法再修改吧–
4.修正一些bug5.依然未解决的:桌面歌词偶尔空指针问题,这个比较困难,暂时还没想到好的解决办法.
不要多开,只运行一个。否则读取播放列表文件时,会提示资源正在占用。
下载:1.8.3: http://pan.baidu.com/share/link?shareid=85311&uk=3860164064
//——————————————————————————————————–
刚考完试了,更新一些bug,—————2012/10/23 16:59
1.修正标签修改后不立即显示的bug
2.修改桌面歌词滚动的算法,
3.添加一些小优化
下载:1.8.4 :http://pan.baidu.com/share/link?shareid=89446&uk=3860164064
Blythe-Player-build-history
a personal MP3 player ,build by bruce (series),if you want to try ,first change your default language:Simple Chinese,enjoy!
The first version, more simple interface and function Download http://pan.baidu.com/share/link?shareid=85286&uk=3860164064
The second version, preliminary wrote the lyrics show synchronization function and peel function, (will *. SKN files on the application with directory can switch the skin) Download address: http://pan.baidu.com/share/link?shareid=85287&uk=3860164064
The third version, use a clock control to realize the magnetic form, continue to study… Download address:http://pan.baidu.com/share/link?shareid=85288&uk=3860164064
The fourth version, call bass. DLL, completely overturned previous algorithm, interface beautiful many, can adjust equalizer, bug rarely, the function to increase…
Download address: http://pan.baidu.com/share/link?shareid=85288&uk=3860164064
The fifth version, magnetic form control change, win7 can perfect embodiment. Download address: http://pan.baidu.com/share/link?shareid=85290&uk=3860164064
The sixth edition, realize the basic function of all, there is no screenshots, Download address:http://pan.baidu.com/share/link?shareid=85291&uk=3860164064
The seventh version, automatic recording playlists and loading, there are a lot of new features in the skin to write control, wrote two skin, no record position replace then that’s ink, generating two procedures, with their preferences, download to use it Bronze:http://pan.baidu.com/share/link? … uk=3860164064Winamp version::http://pan.baidu.com/share/link?shareid=85292&uk=3860164064
1.7:http://pan.baidu.com/share/link?shareid=85302&uk=3860164064
1.7.1:http://pan.baidu.com/share/link?shareid=85304&uk=3860164064
1.7.2:http://pan.baidu.com/share/link?shareid=85305&uk=3860164064
Finally, there are several skin file, download after decompression, 2-6 version can use, in their executive directory can switch the skin… Download skin: http://pan.baidu.com/share/link?shareid=85298&uk=3860164064
No……
Small step listen updated to 1.8, (1.8-1.8.2 three desktop lyrics effect), win7 next display effect is good. Fixed bug, welcome to download the trial! (XP effect general), After I test, bug ratio of 1.7 little a lot of, can the normal use, lyrics part are trying to! The original Delphi wrote a magnetic form code, in XP is still under ‘t-hurt-you-but-can ‘t-help-you, win7 effect is very good! 2. Add similar qq near the side to be automatic hidden expansion function Kind of desktop lyrics style, with your be fond of . All versions are after download decompression to use! . 1.8.2 version desktop lyrics I added display pictures, that folder must have a “Bruce. PNG”, you can freely use their like picture, but name must agree, otherwise you will prompt “out of memory…” . All versions are single file executable program, no call DLL, a lot of people foreigners development of thing, after the call, encumbrance! No…
Download address:
1.8: http://pan.baidu.com/share/link?shareid=85307&uk=3860164064 1.8.1: http://pan.baidu.com/share/link?shareid=85308&uk=3860164064 1.8.2: http://pan.baidu.com/share/link?shareid=85308&uk=3860164064
/ / - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Had a holiday, nothing to do research and study, update some function - - - - - - - - - - - - - - - 2012/10/1 now To read the id3 tags, can define your own artist, album, s,,,, a key definition, characteristic summary. 2. Join recognition operating system version, and then to control the form of magnetic effect. Join desktop lyrics maintain the front end function, I don’t know his thousands of listening algorithm, oneself take it for granted
Download address:
1.8.3: http://pan.baidu.com/share/link?shareid=85311&uk=3860164064
//—————————————————————————————————–
Just finished the test, update some bug, - - - - - - - - - - - - - - - for 2012/10/23
更新的帖子 :http://bbs.kafan.cn/thread-1754895-1-1.html
//FAQ
功能:
1.打开单个文件
2.打开多个文件
3.打开文件夹的所有文件
4.打开播放列表(自定义)
5.隐藏播放列表
6.同步/滚动歌词秀
7.歌曲快进快退
8.歌曲tag信息读取编辑,,可以自己定义艺术家、专辑、年代、、、、一键定义,特简易。
9.歌曲tag信息循环滚动展示效果
9.桌面歌词同步滚动
10.桌面歌词总在窗口最上方
11.定位文件的路径
12.音量大小调节
13.5种播放模式调节
14.迷你模式
15.常见计算机指令-关机+重启+注册表+ping+打开推进光驱。。
16.在线搜歌词,自动保存歌词
17.打开/编辑本地歌词
18.在任务栏右下角弹出正在播放媒体文件的信息
19.进度条随时调节播放进度
20.磁性吸附窗口,自动判断操作系统来调节相应效果
21.关于界面鼠标滑过,水波特效。。
22.最小化到任务栏
23.可视化效果调节可选 频谱分析或者示波显示,线条粗细间隔颜色都可以自定义
24.类似于qq的窗体自动隐藏伸缩功能,一贴近电脑边缘,自动隐藏。完全不影响工作。。
25.自定义歌词显示的字体
26.支持多个文件拖动,拖进去自动播放
27.可以自己制作皮肤
28.单例模式,只能运行一个实例。。
29.自动记录播放列表
30.搜索歌词时自动检索歌曲信息进行匹配
Then, how can we lessen stress and have a happy life? Firstly, we should have enough confidence to pursuit of happiness and hold an open and clear feeling. Secondly, a balance point should be maintained no matter what difficulties we have to face. Only in this way can we overcome the stress from the inside. In contrast, our government also should make laws in order to beat the stress from the outside for us. Therefore, a bright and harmonious society can be construction.
If I has made some mistakes in this composition, please tell me.
unit lyric;interface uses Classes,SysUtils;type TOneLyric=Record time:longint; lyStr:string; end; TLyric=class private FFilename:string; FOffset:integer; //时间补偿值 其单位是毫秒,正值表示整体提前,负值相反。这是用于总体调整显示快慢的。 FAur:string; //艺人名 FBy:string; //编者(指编辑LRC歌词的人) FAl:string; //专辑名 FTi:string; //曲名 FCount:integer; FLyricArray : array of TOneLyric; function GetLyric(i:integer): TOneLyric ; function ExistTime(vTime:longint):boolean; procedure sortLyric; procedure ResetLyrics(FTxt:Tstrings);
protected public constructor Create; destructor Destroy; override; procedure loadLyric(afilename:string); procedure SetTxt(aLyrics:Tstrings); procedure UnloadLyric(strs:Tstrings); //整体提前(正值)或整体延后(负值)atime毫秒 function ChgOffset(atime:integer):boolean; //提前(正值)/延后(负值)某一句歌词 aTime毫秒 function ChgOneLyric(oldTime:longint;aTime:integer):boolean; //保存歌词内容到文件 function SaveLyricsToFile(vFileName:string):boolean; property filename:string read ffilename; property Ar:string read FAur; property By:string read FBy; property Al:string read FAl; property Ti:string read FTi; property Offset :integer read FOffset; property LyricArray[i:integer]: TOneLyric read GetLyric ; property Count:integer read FCount; end;implementationconstructor TLyric.Create;begin inherited Create; FTi:=''; FAur:=''; Fal:=''; FBy:=''; FOffset:=0;end;function TLyric.ExistTime(vTime:longint):boolean;var i:integer;begin result:=false; for i:=0 to length(FLyricArray) -1 do if FLyricArray[i].time =vTime then begin result:=true; break; end;end;procedure TLyric.loadLyric(afilename:string);var FTxt:Tstrings;begin FTi:=''; FAur:=''; Fal:=''; FBy:=''; FOffset:=0; FFilename:=afilename; //载入歌词 FTxt:=TStringlist.create; FTxt.LoadFromFile(Ffilename); ResetLyrics(FTxt); FTxt.Clear; FTxt.free;end;procedure TLyric.SetTxt(aLyrics:Tstrings);begin FTi:=''; FAur:=''; Fal:=''; FBy:=''; FOffset:=0; //载入歌词 ResetLyrics(aLyrics);end;//根据歌词文件 载入每行歌词procedure TLyric.ResetLyrics(FTxt:Tstrings);var i:integer; function makeOneLyric(CurLyric:string):string; var p1,p2,p3:integer; timestr,lyricstr:string; time1,time2:longint; isFuSign:boolean; begin p1:=pos('[',CurLyric);//第一个‘[’位置 if p1=0 then begin //判断是否非法行 result:= CurLyric; //无 '[' exit; end; p2:=pos(']',CurLyric); //第一个‘]’位置 if p2=0 then begin result:= CurLyric; exit; //无 ']' end; timestr:=copy(curLyric,p1+1,p2-p1-1); //左右两边为歌词 lyricStr:= copy(curLyric,1,p1-1) + copy(curLyric,p2+1,length(curLyric)-p2) ; lyricStr:=makeOneLyric(lyricStr); if copy(Lowercase(timeStr),1,2)='ar' then //作者信息 FAur:= copy(timestr,4,length(timestr)-3) else if copy(Lowercase(timeStr),1,2)='ti' then //曲目标题 FTi:= copy(timestr,4,length(timestr)-3) else if copy(Lowercase(timeStr),1,2)='al' then //专辑名 FAl:= copy(timestr,4,length(timestr)-3) else if copy(Lowercase(timeStr),1,2)='by' then //编辑LRC歌词的人 FBy:= copy(timestr,4,length(timestr)-3) else if copy(Lowercase(timeStr),1,6)='offset' then //时间补偿值 try FOffset:= strtoint(copy(timestr,8,length(timestr)-7)) except FOffset:=0; end else //此时为 时间标记 begin p3:= pos(':',timestr) ; if p3>0 then begin //判断是否非法行 isFuSign:=false; try time1:=strtoint(copy(timestr,1,p3-1))*1000; if time1<0 then isFuSign:=true; //记录 该 时间标签 为 负(小于零) except //非法歌词行 exit; //分钟有误 end; try time2:= trunc( strtofloat(copy(timestr,p3+1,length(timestr)-p3)) *1000); if isFuSign then time2:=-time2; except exit; //秒 有误 end; if not ExistTime(time1*60+time2) then begin setLength(FLyricArray,length(FLyricArray)+1); //if trim(lyricStr)=' then // lyricStr:= '(Music)'; with FLyricArray[length(FLyricArray)-1] do begin time :=time1*60+time2; lystr:=lyricStr; end; result:=lyricStr; end; end; end; end;begin SetLength(FLyricArray,0); //解析歌词各部分 for i:= 0 to FTxt.count-1 do begin makeOneLyric(FTxt[i]) ; end; FCount:=length(FLyricArray) ; sortLyric; {if FTi<>' then FTi:= replaceWithchr(FTi,'&','&&'); if FAur<>' then FAur:= replaceWithchr(FAur,'&','&&'); if FAl<>' then FAl:= replaceWithchr(FAl,'&','&&'); if FBy<>' then FBy:= replaceWithchr(FBy,'&','&&'); } //根据 整体时间偏移,重新计算每句歌词时间 for i:=0 to length(FLyricArray)-1 do begin //FLyricArray[i].lyStr := replaceWithchr(FLyricArray[i].lyStr,'&','&&'); if FLyricArray[i].time >= 0 then begin if FLyricArray[i].time - FOffset>=0 then FLyricArray[i].time:=FLyricArray[i].time - FOffset ; end else FLyricArray[i].time:=0; end;end;procedure TLyric.UnloadLyric(strs:Tstrings);var i:integer;begin SetLength(FLyricArray,strs.Count ); FCount:= strs.Count; for i:=0 to strs.Count -1 do begin FLyricArray[i].time :=0; FLyricArray[i].lyStr := strs[i]; end; FTi:=''; FAur:=''; Fal:=''; FBy:=''; FOffset:=0;end;destructor TLyric.Destroy;begin SetLength(FLyricArray,0); inherited Destroy;end;function TLyric.GetLyric(i:integer): TOneLyric ;begin if (i>=0) and (i<length(FLyricArray)) then result:=FLyricArray[i] ;end;procedure TLyric.sortLyric;var i,j:integer; tmpLyric:TOneLyric;begin for i:=0 to length(FLyricArray)-2 do begin for j:=i to length(FLyricArray)-1 do begin if FLyricArray[j].time < FLyricArray[i].time then begin tmpLyric:= FLyricArray[i]; FLyricArray[i]:= FLyricArray[j]; FLyricArray[j]:= tmpLyric; end; end; end;end;function TLyric.ChgOffset(atime:integer):boolean;//提前(正值)或延后(负值)atime毫秒var i,numberLine:integer; p1,p2,p3:integer; timestr,lyricstr,CurLyric,signStr:string; aOffset:longint; afind:boolean; FTxt:Tstrings;begin Result:=false; //修改offset 保存文件 afind:=false; aOffset:=0; numberLine:=-1; FTxt:=TStringlist.create; try FTxt.LoadFromFile(FFilename); for i:=0 to FTxt.Count-1 do begin curLyric:=fTxt[i]; p1:=pos('[',CurLyric);//第一个‘[’位置 if p1=0 then begin //判断是否非法行 continue; //无 '[' end; p2:=pos(']',CurLyric); //第一个‘]’位置 if p2=0 then begin continue; //无 ']' end; timestr:=copy(curLyric,p1+1,p2-p1-1); //左右两边为歌词 lyricStr:= copy(curLyric,1,p1-1) + copy(curLyric,p2+1,length(curLyric)-p2) ; if copy(Lowercase(timeStr),1,6)='offset' then //找到 时间补偿串 begin try aOffset:= strtoint(copy(timestr,8,length(timestr)-7)); except continue; end ; fTxt[i]:=copy(curLyric,1,p1-1) +'[offset:'+inttostr(aOffset+aTime) +']' +copy(curLyric,p2+1,length(curLyric)-p2); if aOffset+aTime=0 then FTxt.Delete(i); fTxt.SaveToFile(FFilename); afind:=true; break; end else begin if numberLine=-1 then begin signStr:=copy(Lowercase(timeStr),1,2) ; if (signStr<>'ar') and (signStr<>'al') and (signStr<>'ti') and (signStr<>'by') then begin p3:= pos(':',timestr) ; if p3>0 then //为时间标记 numberLine:=i; //记录行号 end; end; end; end; if (not afind) and (numberLine<>-1) then begin //fTxt.Add('[offset:'+inttostr(aOffset+aTime) +']'); fTxt.Insert(numberline, '[offset:'+inttostr(aOffset+aTime) +']'); fTxt.SaveToFile(FFilename); end; //重新载入歌词 loadLyric(FFilename); result:=true; FTxt.Clear; finally FTxt.free; end;end;function TLyric.ChgOneLyric(oldTime:longint;aTime:integer):boolean;var i:integer; FTxt:Tstrings; AjustOk:boolean; thisLyric:string; function AjustOneLine(var curLyric:string):boolean; var isFu:boolean;//该时间是否为负. p1,p2,p3:integer; timestr,left_lyric,right_lyric:string; // time1,time2:longint; findok:boolean; isValid:boolean;// NewTimeLabel:string; NewTime:longint; UseMS:boolean; begin //---------该串内 是否有标签 ---------- p1:=pos('[',CurLyric);//第一个‘[’位置 if p1=0 then begin //判断是否非法行 Result:=false; //无 '[' exit; end; p2:=pos(']',CurLyric); //第一个‘]’位置 if p2=0 then begin result:= false; exit; //无 ']' end; //==========串内 是否有标签========== //----------目前标签 是否是 指定时间的 标签---------- timestr:=copy(curLyric,p1+1,p2-p1-1); Left_lyric:= copy(curLyric,1,p1-1); Right_lyric:= copy(curLyric,p2+1,length(curLyric)-p2) ; isValid:=true; findok:=false; p3:= pos(':',timestr) ; if p3>0 then begin //判断是否 合法时间标签 isFu:=false; try time1:=strtoint(copy(timestr,1,p3-1))*1000; if time1<0 then isFu:=true; except //非法歌词行 isValid:=false; //分钟有误 end; try if isValid then begin time2:= trunc( strtofloat(copy(timestr,p3+1,length(timestr)-p3)) *1000); if isFu then time2:=-time2; end; except isValid:=false; //秒 有误 end; if isValid and (time1*60+time2 - FOffset = oldtime) then //找到 指定时间串 findOk:=true; //找到 啦 啦 啦 ... end; //非法行 判断结束 //==========是否找到 指定时间标签========== //-------找到 和 没找到 之后的处理 ---------- if findok then begin Result:= true; //根据 老时间标签(timerstr) //生成 新的时间标签 NewTime:=time1*60+time2 - aTime; //根据 原来是否使用毫秒 和 目前调整的偏移时间是否属于毫秒级别 // 来决定 是否 使用毫秒 UseMS:= (Pos('.',timestr)>0) or (aTime mod 1000 <>0); //转化时间为时间串例如: 72秒 ==>> '00:01:12.000' NewTimeLabel:=ConvertTimeToTimestr(NewTime,0,false,true,UseMS,true); //返回 新的歌词部分 curLyric := left_Lyric + '[' + NewTimeLabel + ']'+ Right_Lyric; end else //还 没找到 begin //在 该行歌词剩余部分 找找看 if AjustOneLine(Right_lyric) then begin //在剩余部分内找到了 curLyric:= Left_lyric+ '[' + timestr +']' +Right_lyric ; Result:=true; end //else 剩余部分内 没有的话 ,只好返回 false 啦 // (函数开始处,已经默认=false) end; //=========找到 和 没找到 之后的处理========== end; //end function AjustOneLinebegin AjustOk:=false; Result:=false; FTxt:=TStringlist.create; try FTxt.LoadFromFile(FFilename); for i:=0 to FTxt.Count-1 do begin thisLyric:=FTxt[i]; if AjustOneLine(thisLyric) then begin AjustOk:=true; //更新 新生成 的歌词行 FTxt[i]:=thisLyric; break; end; end; if AjustOk then begin //保存歌词文件 FTxt.SaveToFile(FFilename); //重新载入歌词 loadLyric(FFilename); Result:=true; end; FTxt.Clear; finally FTxt.free; end;end;//保存歌词内容到文件function TLyric.SaveLyricsToFile(vFileName:string):boolean;var i:integer; aTxt:Tstrings ;begin result:=false; if FCount<=0 then exit; aTxt:=Tstringlist.Create; try if FTi <>' then aTxt.Add('歌名:'+FTi); if FAur <>' then aTxt.Add('歌手:'+FAur); if Fal <>' then aTxt.Add('专辑:'+Fal); // if Fby <>' then // lbxpreview.Items.Add('歌词编辑:'+Fby); if aTxt.Count >0 then aTxt.Add('--- --- --- --- --- --- ---'); for i:=0 to FCount-1 do aTxt.Add(LyricArray[i].lyStr ) ; aTxt.SaveToFile(vFilename); Result:=true; finally aTxt.Free; end;end;end.
]]>原文地址:Delphi 我为何离开你作者:杯具de橙子
作为一个用了 Delphi 好多年,甚至还在 Borland 参加过实习的人,在近几年内也被问起为什么不愿意再用 Delphi,本来不愿意多说什么,毕竟用什么语言,那是个人喜好,或者说,做什么事用什么工具,我对语言并没有特别的爱好或是信仰。但是近期也看到了一些不那么好玩的现象,比如说 RAD Studio XE2 的预览版,这次我没有申请参加内测,但是却有很多朋友,拿到内测后,特地发给我,让我来玩玩,当然了,拿了别人的东西,也就不能再沉默了,多少也要说点什么。这次完全不想写什么评测,只是谈谈自己对这次 XE2 的看法。
在 Borland 把 IDE 相关的东西卖给了 Embarcadero 之后,曾有一度的上浮,这个必须承认,比如说 Unicode 的支持,语言特性上的增强等等,本来以为 Embarcadero 会吸取 Borland 的教训,踏踏实实的把接手的东西做好,但是我又一次失望了。说到底,Embarcadero 也是一家只知道骗钱的公司。
看一下从 Delphi2010 到现在的 XE2,有什么进步?不要说什么支持 Unicode 了,支持 Json 了,支持泛型了,这些东西不是该好几年前就实现的么?新版本无非就是修 bug,然后从外边合作一些东西内置进来。Embarcadero 语:无他,唯钱多尔,又无他,唯用户傻尔。
VCL 已显老态,虽然目前来看,依然是 windows 下最快的 GUI 构建框架,但是其健壮性却让人不敢恭维。这里也没有必要和其他平台来比较,只看 C++,MFC 已是大家公认的比较落后的框架,开发不便,但是它就是可以做出比 VCL 健壮得多的应用来,这是为什么?可能很多人不乐意了,说 C++ 都积累了那么多年了,给 C++ 写库,修 bug 的人是 Delphi 的多少多少倍,其实这都不是问题,问题在于,为什么大家愿意给 C++ 写东西。是 C++ 本身就多么优秀吗?我倒是觉得不见得。
Delphi 的控件也不少,甚至可以说,它是我见过控件最多的,这也是为什么当初能成为 VB 杀手的原因之一。但是现在来看,除了能杀 VB,还能杀谁?我不否认 Delphi 在开发 GUI 应用上的效率,但是也仅限简单的界面开发。举个简单的例子,要做到 360 软件管理器那样的复杂列表,或是 QQ 那样的界面效果,容易么?也许任何一个有经验的 Delphi 程序员都会毫不犹豫的说,这对我来说太容易了,但是我就想问,你实现这些花多大精力?很多东西老了,跟不上时代了,也跟不上更懒的程序员了,VCL 是一个什么样的框架呢?从上往下看,扩展很容易,控件很多,从下往上看,一滩死水。
这次的 XE2,给我的感觉不知是什么,或许我对它已经没有感情,装好后用了一整天,就不愿再用了。再说一下新的 UI 解决方案,可选 VCL 原生的 UI 方案,也可以选择 FireMonkey,这是一个从 vgScene 和 dxScene 改来的方案。但是不得不说,FireMonkey 实在太难以使用了,原本的 vgScene 或 dxScene 都非常简单易用,易扩展,但是现在,我只想得到一个词形容它,那就是坑爹。
再说编译器优化的问题,其实我早就不愿说了,都什么年代的事情了,编译效率和执行效率,你会选哪个?另外我居然还在一些论坛上看到有人说执行效率不重要,开发效率高就好了,这都是一群什么样的程序员啊,爱这么玩就自己玩,玩蛋去。
另外,还有 iOS 的支持,我不知道是谁那么大言不惭的说出支持这两个字,但是事实太残酷了。XE2 可以在 Windows 下建立 iOS 工程,并且把那个工程编译成 win32 程序来调试,但是问题就在于,真正要上 iOS 了,却必须将工程导出为 xcode 工程,然后在 mac 上使用 FPC 来编译。这个过程我自己试过,极其麻烦,还不如直接用 xcode,顺便说一句,xcode 在可用性上,远超 Delphi 的 IDE,我有什么理由用 Delphi 去写代码,再导给 xcode 编译?Embarcadero 的各位大神,你们倒是给我理由。iOS 开发是很热,那没错,Delphi 想支持它,也没错,但是现在这样,你敢说支持吗?
最后再提一下 Android 开发的支持,很抱歉我又一次违心的打出了支持二字。一个垃圾的 datasnap + websnap 解决方案,做出来的东西全是跑在 WebView 上,还需要 JDK 来编译,这不是坑爹是什么?一个正常的 Android 开发者都不会去使用如此的解决方案吧。
还有一些其他的东西,也懒得说了,当初做 Ribbon 的界面支持,没有 DevExpress 做得好,现在做 2D/3D,又没有 KsDev 原本做得好,Embarcadero 你们到底在干什么?无怪乎网上会有人称这种营销方式为“bug营销”,而且这每次的 bug 修正,除去收费昂贵不说,还带出了一大堆新的 bug,等待下次修正,于是又能赚到很多钱了吧。顺便再提一句,某次还真的想购买一个 Delphi 2010,但是官方网上看到的报价是 18,000 元,而真正到了要买的时候,他们却不认这个价,只出售 4 万元的版本,这是属于商业欺诈?还是说另一种营销方式?
以前我曾经说过,lazarus 和 Delphi 相比还差很远,到现在我依然这么说,但是这种差距必然会越来越小,我看得到 lazarus,看得到 FPC 的明显进步,但是 Delphi 呢?也许 Embarcadero 的人现在在笑着跟别人说,谁有钱呀我有钱,谁无耻呀我无耻,只可惜苦的是用户,痛的是那些现在或曾经深爱 Delphi 的程序员。
Embarcadero,虽然我在这里说的你们肯定看不到,但是我还是要说,你们是时候该醒醒了,如果你们想再次毁掉这个产品,那么不如趁早再卖给别人。钱要赚,但是也要凭良心赚。
]]>1 | $ hexo new "My New Post" |
More info: Writing
1 | $ hexo server |
More info: Server
1 | $ hexo generate |
More info: Generating
1 | $ hexo deploy |
More info: Deployment
]]>baguvix = 无限健康
hesoyam = 恢复生命值, 防弹衣, $250k
xjvsnaj = 总是午夜的
aezakmi = 不被通缉
aeduwnv = 不会饥饿
ehibxqs = 最大性感max sex appeal
priebj = 玩趣屋主题
munasef = 肾上腺素模态
wanrltw = 无限弹药, 没有再装填
ncsgdag = 武器熟练度全满
vqimaha = 更好的驾驶技能
uzumymw = 超级武器
asnaeb = 清除警星(偷渡和闯如军事基地无效)
lxgiwyl = 一般武器
kjkszpj = 暴力武器
ouiqdmw = 当驾驶的时候可以在车内使用准星瞄准攻击
ohdude = 猎人(ah-64阿帕奇战斗机) akjjyglc = 四轮摩托车 amomhrer = 超长拖粪车 eegcyxt = 推土机 urkqsrk = 杂技飞机spawn stunt plane agbdlcid = 越野型大脚车 osrblhh = 增加两颗警星 afzllqll = 好天气 icikpyh = 非常好的天气 alnsfmzo = 变阴暗天气 auifrvqs = 下雨的天气 cfvfgmj = 雾深的天气 ysohnul = 时间过的更快 ppgwjht = 快速游戏 liyoaay = 慢速游戏 ajlojyqy = 暴动 bagowpg = 街上的人见了你都逃跑(胆大者会向你开枪) foooxft = 行人拥有武器 aiwprton = 坦克 cqzijmb = 破旧的车 jqntdmh = 农场工人的车 pdnejoh = 赛车1 vpjtqwv = 赛车2 aqtbcodx = 葬礼车 krijebr = 环座型贵宾车 ubhyzhq = 垃圾车 rzhsuew = 高尔夫车 cpktnwt = 附近所有车爆炸 xicwmd = 看不见的汽车 pggomoy = 完美的处理 szcmawo = 自杀 zeiivg = 所有的红绿灯变绿灯 ylteicz = 攻击性的驾驶员 llqpfbn = 粉红的交通(所有车变粉红色) iowdlac = 黑色的交通(所有车变黑色) afsnmsmw = 船可以飞 btcdbcb = 肥胖 jysdsod = 强壮值全满 kvgyzqk = 薄的 asbhgrb = elvis 在各处 bgluawml = peds 用武器攻击你, 火箭发射者 cikgcgx = 海滩党 mroemzh = 各处一组成员 bifbuzz = 团队控制街道 afphultl = 忍者主题 bekknqv = 所有丑女被你吸引 bgkgtjh = 交通是便宜的汽车 gusnhde = 交通是快速的汽车 ripazha = 汽车会飞 jhjoecw = 未知 jumpjet = 战斗机spawn hydra kgggdkp = 水翼船spawn vortex hovercraft jcnruad = 非常的繁荣 coxefgu = 所有的汽车有nitro all cars have nitro(氮气) bsxsggc = 未知cars float away when hit ofviac = 橘色天空 21:00 mghxyrm = 雷雨 cwjxuoc = 沙暴 lfgmhal = 跳的更高 cvwkxam = 无限氧气 aiypwzqp = 降落伞 yecgaa = 火箭飞行器jetpack ljspqk = 警星全满 iavenjq = 百万打洞器 iojufzn = 暴动模态 thgloj = 交通畅通 fvtmnbz = 交通是国家车辆 sjmahpe = 补充每一个子弹 bmtpwhr = 国家车辆和 peds,拿天生的 2个卡车用具 zsoxfsq = 补充每一个(火箭筒) ogxsdag = 最大威望max respect vkypqcf = taxis 车可以跳舞
]]>㈠《Shawshank Redemption肖申克的救赎》
1.You know some birds are not meant to be caged, their feathers are just too bright.
你知道,有些鸟儿是注定不会被关在牢笼里的,它们的每一片羽毛都闪耀着自由的光辉。
2.There is something inside ,that they can’t get to , that they can’t touch. That’s yours.
那是一种内在的东西, 他们到达不了,也无法触及的,那是你的。
3.Hope is a good thing and maybe the best of things. And no good thing ever dies.
希望是一个好东西,也许是最好的,好东西是不会消亡的。
㈡《Forrest Gump 阿甘正传》
1.Life was like a box of chocolates, you never know what you’re gonna get.
生命就像一盒巧克力,结果往往出人意料。
2.Stupid is as stupid does.
蠢人做蠢事,也可理解为傻人有傻福。
3.Miracles happen every day.
奇迹每天都在发生。
4.Jenny and I was like peas and carrots.
我和珍妮形影不离。
5.Have you given any thought to your future?
你有没有为将来打算过呢。
6. You just stay away from me please.
求你离开我。
7. If you are ever in trouble, don’t try to be brave, just run, just run away.
你若遇上麻烦,不要逞强,你就跑,远远跑开。
8. It made me look like a duck in water.
它让我如鱼得水。
9. Death is just a part of life, something we’re all destined to do.
死亡是生命的一部分,是我们注定要做的一件事。
10. I was messed up for a long time.
这些年我一塌糊涂。
11. I don’t know if we each have a destiny, or if we’re all just floating around accidentally―like on a breeze.
我不懂我们是否有着各自的命运,还是只是到处随风飘荡
㈢《The Lion King狮子王》
Everything you see exists together in a delicate balance.
世界上所有的生命都在微妙的平衡中生存。
I laugh in the face of danger.
越危险就越合我心意。
I’m only brave when I have to be. Being brave doesn’t mean you go looking for trouble.
我只是在必要的时候才会勇敢,勇敢并不代表你要到处闯祸。
When the world turns its back on you, you turn your back on the world.
如果这个世界对你不理不睬,你也可以这样对待它。
It’s like you are back from the dead.
好像你是死而复生似的。
You can’t change the past.
过去的事是不可以改变的。
Yes, the past can hurt. But I think you can either run from it or learn from it. 对,过去是痛楚的,但我认为你要么可以逃避,要么可以向它学习。
This is my kingdom. If I don’t fight for it, who will?
这是我的国土,我不为她而战斗,谁为呢?
Why should I believe you? Everything you ever told me was a lie.
我为何要相信你?你所说的一切都是谎话。
I’ll make it up to you, I promise.
我会补偿你的,我保证。
㈣《Gone with The Wind 乱世佳人》
1.Land is the only thing in the world worth working for, worth fighting for, worth dying for. Because it’s the only thing that lasts.
土地是世界上唯一值得你去为之工作, 为之战斗, 为之牺牲的东西,因为它是唯一永恒的东西。
2.I wish I could be more like you.
我要像你一样就好了。
3.Whatever comes, I’ll love you, just as I do now. Until I die.
无论发生什么事,我都会像现在一样爱你,直到永远
4.I think it’s hard winning a war with words.我认为纸上谈兵没什么作用。
6.I never give anything without expecting something in return. I always get paid.
我做任何事不过是为了有所回报,我总要得到报酬。
7.In spite of you and me and the whole silly world going to pieces around us, I love you.
哪怕是世界末日我都会爱着你。
8.I love you more than I’ve ever loved any woman. And I’ve waited longer for you than I’ve waited for any woman.
此句只可意会不可言传。。。。。
9.If I have to lie, steal, cheat or kill, as God as my witness, I’ll never be hungry again!
即使让我撒谎,去偷,去骗,去杀人,上帝作证,我再也不要挨饿了。
10.Now I find myself in a world which for me is worse than death. A world in which there is no place for me.
现在我发现自己活在一个比死还要痛苦的世界,一个无我容身之处的世界。
11.You’re throwing away happiness with both hands. And reaching out for something that will never make you happy.
你把自己的幸福拱手相让,去追求一些根本不会让你幸福的东西。
12.Home. I’ll go home. And I’ll think of some way to get him back. After all, tomorrow is another day.
家,我要回家.我要想办法让他回来.不管怎样,明天又是全新的一天。
㈤《TITANIC泰坦尼克号》
1.Outwardly, I was everything a well-brought up girl should be. Inside, I was screaming.
外表看,我是个教养良好的小姐,骨子里,我很反叛.
3.There is nothing I couldn’t give you, there is nothing I would deny you, if you would not deny me. Open you’re heart to me.
如果你不违背我,你要什么我就能给你什么,你要什么都可以.把你 的心交给我吧.
4.What the purpose of university is to find a suitable husband.
读大学的目的是找一个好丈夫.(好像有些片面,但比较真实)
5.Remember, they love money, so just pretend like you own a goldmine and you’re in the club.
只要你装得很有钱的样子他们就会跟你套近乎。
6.All life is a game of luck.
生活本来就全靠运气。
7.I love waking up in the morning and not knowing what’s going to happen, or who I’m going to meet, where I’m going to wind up.
我喜欢早上起来时一切都是未知的,不知会遇见什么人,会有什么样的结局。
8.I figure life is a gift and I don’t intend on wasting it. You never know what hand you’re going to get dealt next. You learn to take life as it comes at you. 我觉得生命是一份礼物,我不想浪费它,你不会知道下一手牌会是什么,要学会接受生活。
9.To make each day count.
要让每一天都有所值。
10.We’re women. Our choices are never easy.
我们是女人,我们的选择从来就不易。
11.You jump, I jump.
(another touching sentence)
12.Will you give us a chance to live?
能不能给我们留一条生路?
13.God shall wipe away all the tears from their eyes, and there shall be no more death. Neither shall there be sorrow or dying, neither shall there be any more pain, for the former world has passed away.
上帝擦去他们所有的眼泪.死亡不再有,也不再有悲伤和生死离别,不再有痛苦,因往事已矣.
㈥《Sleepless in Seattle西雅图不眠夜》
1.Work hard! Work will save you. Work is the only thing that will see you through this.
努力工作吧!工作能拯救你.埋头苦干可令你忘记痛楚.
2.You make millions of decisions that mean nothing and then one day your order takes out and it changes your life.
你每天都在做很多看起来毫无意义的决定,但某天你的某个决定就能改变你的一生.
3.Destiny takes a hand.命中注定.
4.You know, you can tell a lot from a person’s voice.
从一个人的声音可以知道他是怎样的人.
5.People who truly loved once are far more likely to love again.
真爱过的人很难再恋爱.
6.You know it’s easier to get killed by a terrorist than get married over the age of 40.
你知道,女人过了40想出嫁就难了,被恐怖分子杀死都比这容易.
7.You are the most attractive man I ever laid ears.
你是我听过的最帅的男士.
8.Why would you want to be with someone who doesn’t love you?
为什么留恋一个不爱你的人?
9.When you’re attracted to someone it just means that your subconscious is attracted to their subconscious, subconsciously. So what we think of as fate, is just two neuroses knowing they’re a perfect match.
当你被某个人吸引时,那只是意味着你俩在潜意识里相互吸引.因此,所谓命运,就只不过是两个疯子认为他们自己是天造一对,地设一双.
10.Everybody panics before they get married.每个人婚前都会紧张的.
11.Your destiny can be your doom.命运也许会成为厄运.
12.The reason I know this and you don’t is because I’m younger and pure. So I’m more in touch with cosmic forces.
之所以我知道而你不知道是因为我年幼纯洁,所以我比较能接触宇宙的力量.
13.I don’t want to be someone that you’re settling for. I don’t want to be someone that anyone settles for.
我不想要你将就,我也不想成为将就的对象.
14.What if something had happened to you? What if I couldn’t get to you? What would I have done without you? You’re my family. You’re all I’ve got.
要是你出了事怎么办?要是我找不到你怎么办?如果没有你我该怎么办?你是我的家人,你是我的一切.
㈦《GARFIELD加菲猫》
Money is not everything. There’s MasterCard.
钞票不是万能的, 有时还需要信用卡。
One should love animals. They are so tasty.
每个人都应该热爱动物, 因为它们很好吃。
Save water. Shower with your girlfriend.
要节约用水, 尽量和女友一起洗澡。
Love the neighbor. But don’t get caught.
要用心去爱你的邻居, 不过不要让她的老公知道。
Behind every successful man, there is a man. And behind every unsuccessful man, there are two.
每个成功男人的背后, 都有一个女人. 每个不成功男人的背后, 都有两个。
Every man should marry. After all, happiness is not the only thing in life.
再快乐的单身汉迟早也会结婚, 幸福不是永久的嘛。
The wise never marry, and when they marry they become otherwise.
聪明人都是未婚? 结婚的人很难再聪明起来。
Success is a relative term. It brings so many relatives.
成功是一个相关名词, 他会给你带来很多不相关的亲戚 (联系)
Love is photogenic. It needs darkness to develop.
爱情就象照片, 需要大量的暗房时间来培养。
Children in backseats cause accidents. Accidents in backseats cause children.
后排座位上的小孩会生出意外, 后排座位上的意外会生出小孩。
Your future depends on your dreams. So go to sleep.
现在的梦想决定着你的将来, 所以还是再睡一会吧
There should be a better way to start a day than waking up every morning.
应该有更好的方式开始新一天, 而不是千篇一律的在每个上午都醒来。
Hard work never killed anybody. But why take the risk?
努力工作不会导致死亡! 不过我不会用自己去证明。
Work fascinates me. I can look at it for hours!
工作好有意思耶! 尤其是看着别人工作。
God made relatives; Thank God we can choose our friends.
神决定了谁是你的亲戚, 幸运的是在选择朋友方面他给了你留了余地
来源:俺的新浪博客– http://blog.sina.com.cn/s/blog_6ddfacf30100msxb.html
]]>就像上次我回衡水,到了火车站以后,已经很晚了,眼见着没有了倒车的公交车,我正在那里找呢,这是一个和颜悦色,很和蔼可亲与面善的大阿叔,过来了,与我攀谈起来,说是在等他弟弟,那时也就5:40吧,他说的八点才恁个等到他的弟弟。
他问道:“小子,你是哪的?”
“我是徐口的,你知道李村吗,那是挨着的。”我说。
“那怎么不知道,我是大麻森的,经常去那里。”他和我攀起了老乡。
“现在时间好早,我送你回去吧,送你到开发区,你再倒车,对吧小子。”他用一口憨厚的乡音和我聊着。
“要是别人,他给我多少钱我也不能带他 ,咱是老乡,这些就不谈了……”后面说了一些让人反胃的话,到这时候我还以为他是开出租车的呢.。
“走吧。”他领着我走到哪里去了,一看就一个破摩托 ,这是我知道哦啊他是骑摩托来接他弟弟的。
上去后他就说一些家乡的事情,什么他们大麻森的土地全被开发区占了,去盖一些大楼,只给一些微薄的补贴等等废话,我对这些没甚么兴趣。随后他就又说你几点到开发区不会误点啊,我说记得不错的话应该是六点吧。
当开到20公里时,他问:“小子,这有25公里的路程,你给我多少钱啊?” ,我想,你终于到了正题了,“咱们是老乡,要不这麽大冷的天你给我多少钱我也不会带你呀,是吧小子?”
“是是是…”,我极其不耐烦的答复着,我想这人这懡面善怎麽这样啊,真是虚伪的可以。
我问他:“你等你弟弟怎莫会在刚才我遇见你的那里呀?”
“噢,刚才我和那些司机聊天来着,怎懡你还怀疑我啊?这麽远你从城市回家我还逗着你玩吗?”我看出他的脸色明显有一些变化。
“以前我是当兵的,在部队里我还是挺有号召力的。”我真没甚么心情听他吹牛。所以用一种无所谓的口气对他说:“没事,就算…我也没事,咱是练过的人。”
“你练过武呀?”他问,我说:“练过好几年呢,洪拳,三五长拳,跆拳道,都学过。”吹牛谁不会呢,是吧。
“小子,你给我多少钱啊,这25公里呢,现在有很多开出租车的都不在市里开了,直接从衡水到武强,一次一百元呢!”
“那你说吧,你说多少钱吧?”,真的,我早就受不了了,不耐烦散了。
“当然是你说了,咱们老乡吗,是吧小子……”哎,真是虚伪的恶心。
。。。。。
目的地终于到了啊,我给了他20元钱,他说:“小子,这嬷远我送你过来,再添点…..,要不还给你你吧,咱老乡争论这些不好,真的又给我了。”
我又掏出了10元,我说:“大叔,就这些了,您要是还不满意,咱的老乡情谊就到了这里了,我就真走了,可一点也不给您了啊。”
他慌忙抄了过去,小子,要是没车,去那找我啊。说完,他指着一栋居民楼说:“看到没有,我现在就住在那里,有事就去找我啊。”
我无语的应答着他。坐出租车也就15元搞定吧…..
现实生活就是这样,我原本以为生活在陌生大都市里,是不会有什么善意和美的,原来我错了,生活还是充满希望的。有的人长着善意的面孔,心里却虚伪得像岳不群;有的人长着善意的面孔,心里名副其实充满善意;还有的人,虽然面孔冰冷,心里却是非常之热情。
生活在尘世,经历了和见识了这些陌生的善意和欺骗,虽然都是体现在对金钱的态度上,却反映出了浮世的现况,所以在这里留下一墨尘世的走笔。