MySQL之架构简单分析


 

 

上图为MySQL的简易架构图,给您有一个大概的概念,下面我将为您进行进一步的分析。

连接器:

当连接MySQL数据库时,等待的将是MySQL服务端的连接器;连接器的职责是和客户端建立连接、获取权限、维持和管理连接。客户端连接命令一般是如下所示(建议:不要在命令中显示添加登入密码):

mysql -h$ip -p$port -u$user -p$password

查询缓存:

建立完连接后,就可执行select语句。执行就会走向查询缓存。

MySQL拿到这个查询请求后,就会先到缓存中看看,之前是不是执行了该语句。在查询缓存中数据是以key-value形式存在的,key为执行的查询请求,value为查询结果。如果执行了就直接从缓存中把结果返回给客户端,请求结束。如果语句不在缓存中,就执行后续复杂操作。

在大多数情况下不建议使用查询缓存,为什么呢?因为查询缓存往往弊大于利。

查询缓存的失效非常频繁,只要对表进行了更新,该表的中查询缓存全部清除。所以往往很多时候,缓存还没使用就被清除了。对于更新压力很大的数据库来说,查询缓存的命中率很低。如果你的业务中存在一张静态表,很长时间才会更新一次。比如,系统配置表,那这张表的查询才适合查询缓存。

该功能是自动配置的。可以将参数 query_cache_type 设置成 DEMAND ,这样对于默认的SQL语句就不会使用查询缓存。而对于确定的查询语句,可以使用 SQL_CACHE 显示指定,比如如下语句:

select SQL_CACHE * from t where id = 1;

注意:在MySQL8.0之后的版本,把查询缓存模块移除了。

分析器

 在没有命中查询缓存后,MySQL开始真正执行语句了。这时MySQL对该语句进行解析。

分析器首先进行词法分析,一条sql由多个字符串和空格组成,MySQL需要分析出这些字符串是什么,代表什么。

做完识别之后,进行语法分析。根据词法分析的结果,语法分析器会根据语法规则对sql语句进行分析,是否符合MySQL的语法规则。

优化器

执行完分析器后,MySQL就知道该sql语句要干什么了。在开始执行之前,要经过优化器的处理。 

优化器在表里存在多个索引时,选择执行哪个索引;或者一个语句有多表关联时(join),选择各表的连接顺序。

执行器

MySQL通过分析器知道了该语句要做什么,通过优化器知道该怎么做,于是进入到了执行器阶段,开始执行语句。

开始执行之前,首先会判断用户是否有对表的执行权限(如果是在查询缓存得到结果,会在返回结果之前进行权限校验),如果没有会报错.。如果有权限,就打开表继续执行语句。打开表的时候,执行器会根据表的引擎定义,去使用引擎提供的接口。

存储引擎

MySQL区别于其他数据的最具有点的是存储引擎接口模块,MySQL可进行插拔存储引擎。

MySQL的存储引擎有很多种,比如:InnoDB、MyISAM、ISAM、Memory等。在MySQL5.6之前,默认存储引擎是MyISAM,而在该版本之后默认的是InnoDB。下表是两者之前的区别:

  InnoDB MyISAM
存储文件 .frm 表定义文件,.ibd 数据文件和索引文件 .frm 表定义文件,.myd 数据文件, .myi 索引文件
表锁、行锁 表锁
事务 支持 不支持
CRUD 读、写 读多
count 扫表 专门存储的地方
索引结构 B+树 B+树

 

近期在学习MySQL数据库, 后续将会持续更新学习随笔。

 

 

 

SQL实用技巧:如何将表中某一列的部分数据合并到一行中

select *,stuff(select ‘,’ + fieldname from table1 for xml path(”)),1,1,”)  as  field from table2

for xml path(”) ,自从 SQL Server2005及更高版本提供了一个新查询语法 ,主要是用于将一列中的部分数据合并到一个单元格中;

stuff()函数主要是用于将制定位置的字符串用特定的字符串替换;

Windows安装MySQL

1、安装包下载

2、安装教程

  (1)配置环境变量

  (2)生成data文件

  (3)安装MySQL

  (4)启动服务

  (5)登录MySQL

  (6)查询用户密码

  (7)设置修改用户密码

  (8)退出

3、异常处理:

  (1)登录提示密码无效

  (2)caching_sha2_password

 

开始安装:

1、安装包下载:

下载地址:https://dev.mysql.com/downloads/mysql/

 点击下载之后,可以选择注册Oracle账号,也可以跳过直接下载。

 

 

 

 

 下载完成后,选择一个磁盘内放置并解压。

2、安装教程

(1)配置环境变量

新建【系统】变量。

变量名:MYSQL_HOME

变量值:D:mysql-8.0.18-winx64

PATH增加D:mysql-8.0.18-winx64in

(2)生成data文件

以管理员身份运行cmd

进入E:pythonmysqlmysql-8.0.12-winx64in>下

执行命令:mysqld –initialize-insecure –user=mysql  在E:pythonmysqlmysql-8.0.12-winx64in目录下生成data目录

(3)安装MySQL

继续执行命令:mysqld -install


(4)启动服务

继续执行命令:net start MySQL


(5)登录MySQL

登录mysql:(因为之前没设置密码,所以密码为空,不用输入密码,直接回车即可)

继续执行命令:mysql -uroot -p


(6)查询用户密码

继续执行命令:select host,user,authentication_string from mysql.user;


(7)设置修改用户密码

继续执行命令:update mysql.user set authentication_string=password(“123456″) where user=”root”; 

如果出现错误:ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near “(“123″) where mysql.user=”root”” at line 1,

则使用,update mysql.user set authentication_string(“123456″) where user=”root”; 

如果这两者中有一成功即可。

flush privileges;  #作用:相当于保存,执行此命令后,设置才生效,若不执行,还是之前的密码不变

(8)退出

继续执行命令:exit;

 

3、异常处理:

(1)登录提示密码无效

ERROR 1045 (28000): Access denied for user “root”@”localhost”

把mysql里的data文件夹删掉,安装的时候这个data文件夹就是用mysqld –initialize –console
生成data目录,我们把他删掉重新用mysqld –initialize –console生成一次,生成后别关cmd 仔细看
console后面会告诉你给你的临时密码。

用这个改密码: ALTER user “root”@”localhost” IDENTIFIED BY “新密码”;

(2)caching_sha2_password

# 查看用户的身份验证器

select host,user,plugin from mysql.user;

# 修改用户的身份验证器,并更新密码 

ALTER USER “root”@”localhost” IDENTIFIED WITH mysql_native_password BY “123456”;

# 生效变更

flush privileges;

SQL实用技巧:如何判断一个值是否为数字的方法

 

检测是不是数字型的数据, 两种方法

1. ISNUMERIC ( expression )

2. PATINDEX ( ‘%pattern%‘ , expression )

1. ISNUMERIC ( expression )

如果是数字类型则返回 1 ,不是则返回 0

但ISNUMERIC有时是不可靠的,如果你不允许expression包含有任何字母,则会判断错:

如:ISNUMERIC(‘23e4’)返回 1

   ISNUMERIC(‘23d4’)返回 1

2. PATINDEX ( ‘%pattern%‘ , expression )

返回值为第一个满足pattern的位置, 如果没有满足的则返回值为0

判断是否为数字类型

PATINDEX(‘%[^0-9]%’, expression)

返回值为0,则是纯数字类型

对于支持小数点和正负数写法是

PATINDEX(‘%[^0-9|.|-|+]%’, expression)

SQL实用技巧:如何分割字符串

create function f_split(@c varchar(2000),@split varchar(2)) 
returns @t table(col varchar(20))
as
begin

while(charindex(@split,@c)<>0)
begin
insert @t(col) values (substring(@c,1,charindex(@split,@c)-1))
set @c = stuff(@c,1,charindex(@split,@c),"")
end
insert @t(col) values (@c)
return
end
go

select * from dbo.f_split("dfkd,dfdkdf,dfdkf,dffjk",",")

drop function f_split

oracle数据库锁表,什么SQL引起了锁表?ORACLE解锁的方法

–查询数据库锁表记录

select sess.sid,
       sess.serial#,
       lo.oracle_username,
       lo.os_user_name,
       ao.object_name,
       lo.locked_mode
  from v$locked_object lo, dba_objects ao, v$session sess, v$process p
where ao.object_id = lo.object_id
   and lo.session_id = sess.sid   
 --  and object_name = "表名称"
 ;

 –什么SQL引起了锁表

select l.session_id sid,
       s.serial#,
       l.locked_mode,
       l.oracle_username,
       s.user#,
       l.os_user_name,
       s.machine,
       s.terminal,
       a.sql_text,
       a.action
  from v$sqlarea a, v$session s, v$locked_object l
where l.session_id = s.sid
   and s.prev_sql_addr = a.address
order by sid, s.serial#;

–ORACLE解锁的方法

alter system kill session "SID,serial#";

 

 

数据库存数据时,逻辑上防重了为啥还会出现重复记录?

在很多异常情况下,比如高并发、网络糟糕的时候,数据库里偶尔会出现重复的记录。

假如现在有一张书籍表,结构类似这样

+----+--------------+
| id | name         |
+----+--------------+
|  1 | 世界简史     |
+----+--------------+

在异常情况下,可能会出现下面这样的记录

+----+--------------+
| id | name         |
+----+--------------+
|  1 | 世界简史     |
|  2 | 人类简史     |
|  3 | 人类简史     |
+----+--------------+

但是,想了想,自己在处理相关数据的时候也加了判重的相关逻辑,比如,新增时当图书 name 相同时,会提示图书重复而返回。

初次遇到这个情况的时候,感觉有点摸不着头脑,后面想了想,还是理清了,其实这和数据库的事务隔离级别有一定关系。

先简单说下数据库事务的 4 个隔离级别,然后重现下上述问题,最后说说解决办法。

1 数据库事务的 4 个隔离级别

1.1 未提交读

顾名思义,当事务隔离级别处于这个设置的时候,不同事务能读取其它事务中未提交的数据。

便于说明,我开了两个客户端(A 以及 B),并设置各自的隔离级别为未提交读。(并没有全局设置)

设置隔离级别命令

SET [SESSION | GLOBAL] TRANSACTION ISOLATION LEVEL {READ UNCOMMITTED | READ COMMITTED | REPEATABLE READ | SERIALIZABLE}

好了,开始。

Client A

mysql> SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
Query OK, 0 rows affected (0.00 sec)

mysql> SELECT @@session.tx_isolation;
+------------------------+
| @@session.tx_isolation |
+------------------------+
| READ-UNCOMMITTED       |
+------------------------+
1 row in set (0.00 sec)

mysql> start transaction;
Query OK, 0 rows affected (0.01 sec)

mysql> select * from books;
+----+--------------+
| id | name         |
+----+--------------+
|  1 | 世界简史     |
+----+--------------+
1 row in set (0.00 sec)

mysql> insert into books(name) values('人类简史');
Query OK, 1 row affected (0.01 sec)

mysql> select * from books;
+----+--------------+
| id | name         |
+----+--------------+
|  1 | 世界简史     |
|  4 | 人类简史     |
+----+--------------+
2 rows in set (0.00 sec)

当 A 中的事务没有关闭的时候,我们去 B 中看下数据

Client B

mysql> SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
Query OK, 0 rows affected (0.00 sec)

mysql> SELECT @@session.tx_isolation;
+------------------------+
| @@session.tx_isolation |
+------------------------+
| READ-UNCOMMITTED       |
+------------------------+
1 row in set (0.00 sec)

mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from books;
+----+--------------+
| id | name         |
+----+--------------+
|  1 | 世界简史     |
|  4 | 人类简史     |
+----+--------------+
2 rows in set (0.00 sec)

B 中可以读取 A 未提交的数据,所谓未提交读就是这样。

最后,记得把各个事务提交。

Client A & Client B

mysql> commit;

继续阅读数据库存数据时,逻辑上防重了为啥还会出现重复记录?

ms sql事务输出错误

begin try 

 

语句

end try
begin catch

 

 –ERROR_NUMBER() 返回错误号。 
–ERROR_SEVERITY() 返回严重性。 
–ERROR_STATE() 返回错误状态号。 
–ERROR_PROCEDURE() 返回出现错误的存储过程或触发器的名称。 
–ERROR_LINE() 返回导致错误的例程中的行号。 
–ERROR_MESSAGE() 返回错误消息的完整文本。 该文本可包括任何可替换参数所提供的值,如长度、对象名或时间。

select ERROR_MESSAGE(),ERROR_NUMBER()
end catch

TICK技术栈(三)InfluxDB安装及使用

1.什么是InfluxDB?

InfluxDB是一个用Go语言开发的时序数据库,用于处理高写入和查询负载,专门为带时间戳的数据编写,对DevOps监控,IoT监控和实时分析等应用场景非常有用。通过自定义配置让InfluxDB保留规定时间内的数据,并自动从系统中删除不在规定时间内的数据,可以节省计算机上的空间。InfluxDB还提供了一种类似于SQL的查询语言来与数据进行交互,并且InfluxDB相比传统关系型数据库更关注数据的实时性和并发插入时的承受能力。github地址:https://github.com/influxdata/influxdb

2.InfluxDB如何使用?

2.1安装

官方文档:https://docs.influxdata.com/influxdb/v1.7/introduction/getting-started/
系统:CenterOS7.2
安装版本:1.7
InfluxDB下载地址:https://portal.influxdata.com/downloads/
首先下载你对应系统的InfluxDB版本,如果是windows的,直接下载,然后启动使用就行了:

  • influxd.exe是InfluxDB的服务
  • influx.exe是InfluxDB的客户端

CenterOS7.2上安装InfluxDB:

  • 首先下载对应版本:
  • 运行下载命令
wget https://dl.influxdata.com/influxdb/releases/influxdb-1.7.8.x86_64.rpm
  • 运行安装命令
sudo yum localinstall influxdb-1.7.8.x86_64.rpm
  • 启动InfluxDB
sudo systemctl start influxdb
  • 连接InfluxDB
influx -precision rfc3339


注意:此命令可将InfluxDB中时间换算成正常时间,但是是UTC时间,与北京时间差8小时


2.2InfluxDB的使用

  • 创建数据库
create database dbname
  • 删除数据库
drop database dbname
  • 查看已存在的数据库信息
show databases
  • 查看某个数据库中的所有表
show measurements
  • 将后续的命令行操作绑定到某个数据库上面
use dbname
  • 往库里面插入表以及数据
insert tableName,name="温度" value=10

注意:此时InfluxDB自己会默认插入一个当前时间进去

  • 删除表
delete from tableName

参考资料

  • InfluxDB官方文档
  • InfluxDB中文参考文档

如果文中有错误或其它问题,欢迎在评论区及时指正和提出来,我会积极的进行处理的。

Oracle备份、还原数据库

备份数据库

创建备份目录(用sys账号),若已创建备份目录,此步可忽略

create directory db_bak as "D: ECIMS_DB"
--查看创建的目录
select * from dba_directories

--删除已创建的目录
drop directory DB_BAK

格式:
drop directory 目录名

备份(导出)数据库(cmd状态下)

expdp XXX/XXX@XXX schemas=XXX dumpfile=XXX_20181130.dump logfile=XXX_20181130.LOG DIRECTORY=DB_BAK

语法:
expdp 用户名/密码@实例名 schemas=用户名 dumpfile=导出dump文件名.dump logfile=导出日志文件名.LOG DIRECTORY=DB_BAK

导入数据库

步骤一、导入前,先删除账号(plsql状态下)

drop user XXX cascade;

格式:
drop user 用户名 cascade;

注:若删除不掉,需先删除所有会话!!!

select username, sid, serial# from v$session where username="XXX" --找到用户SESSION

格式:
select username, sid, serial# from v$session where username="用户名" --找到用户SESSION

注:若有多条会员,需批量删除

alter system kill session "249,57377" --杀掉用户SESSION "sid,serial#"
alter system kill session "250,57376" --杀掉用户SESSION "sid,serial#"
alter system kill session "251,57375" --杀掉用户SESSION "sid,serial#"

格式:
alter system kill session "sid,serial" 

步骤二、创建账号,赋予权限(plsql状态下)

create user XXX identified by XXX default tablespace USERS
temporary tablespace TEMP
profile DEFAULT;

-- Grant/Revoke role privileges 
grant connect to XXX;
grant dba to XXX;
grant resource to XXX;

-- Grant/Revoke system privileges 
grant alter any sequence to XXX;
grant alter any table to XXX;
grant alter any trigger to XXX;
grant change notification to XXX;
grant create any procedure to XXX;
grant create any sequence to XXX;
grant create any table to XXX;
grant create any type to XXX;
grant create any view to XXX;
grant unlimited tablespace to XXX;

--------------------------------------------------------------
格式:
create user 用户名 identified by 密码 default tablespace USERS
temporary tablespace TEMP
profile DEFAULT;

-- Grant/Revoke role privileges 
grant connect to 用户名;
grant dba to 用户名;
grant resource to 用户名;

-- Grant/Revoke system privileges 
grant alter any sequence to 用户名;
grant alter any table to 用户名;
grant alter any trigger to 用户名;
grant change notification to 用户名;
grant create any procedure to 用户名;
grant create any sequence to 用户名;
grant create any table to 用户名;
grant create any type to 用户名;
grant create any view to 用户名;
grant unlimited tablespace to 用户名;

步骤三、导入数据(cmd状态下)

impdp XXX/XXX DIRECTORY=db_bak DUMPFILE=XXX.dump logfile=XXX.log REMAP_SCHEMA=XXX:XXX remap_tablespace=XXX:XXX

格式
impdp 用户名/密码 DIRECTORY=db_bak DUMPFILE=备份文件名.dump logfile=备份日志文件名.log REMAP_SCHEMA=导出用户名:导入用户名 remap_tablespace=导出表空间:导入表空间

mysql设置用户密码规则

一.查看密码规则

SHOW VARIABLES LIKE 'validate_password%';

二.规则显示介绍

| Variable_name                        | Value  |
+--------------------------------------+--------+
| validate_password_check_user_name    | OFF    |
| validate_password_dictionary_file    |        |   #规则文件保存路径 
| validate_password_length             | 8      |   #密码长度
| validate_password_mixed_case_count   | 1      |   #整个密码中至少要包含大/小写字母的总个数;
| validate_password_number_count       | 1      |   #整个密码中至少要包含阿拉伯数字的个数;
| validate_password_policy             | MEDIUM |   #密码的验证强度等级
| validate_password_special_char_count | 1      |   #密码中特殊字符至少的歌声
+--------------------------------------+--------+
'''
其中验证等级
0/LOW:只验证长度;
1/MEDIUM:验证长度、数字、大小写、特殊字符;
2/STRONG:验证长度、数字、大小写、特殊字符、字典文件;
'''

三.操作代码

#设置全局的

 #set global 上面参数的名字=6;  

比如长度为5
set global validate_password_length=5;

mySql创建带解释的表及给表和字段加注释的实现代码

1、创建带解释的表

CREATE TABLE test_table( 
  t_id INT(11) PRIMARY KEY AUTO_INCREMENT COMMENT "设置主键自增",
  t_name VARCHAR(64) COMMENT "列注释"
) COMMENT="表注释";

2、修改表注释

ALTER TABLE test_table COMMENT "修改表注释";

3、查看表注释

SELECT TABLE_COMMENT FROM information_schema.TABLES WHERE TABLE_SCHEMA = "test" AND TABLE_NAME = "test_table"

4、添加列带注释

alter table test_table add t_age int(11) comment "年龄"

5、修改列名带注释

ALTER TABLE test_table CHANGE t_age t_sex VARCHAR(4) COMMENT "性别"

6、修改列注释

ALTER TABLE test_table MODIFY COLUMN t_sex VARCHAR(30) NOT NULL COMMENT "男女"

  注意:修改时要把该字段的完整定义写上,如字段类型、属性等。千万不要因为只是为了修改一个注释,而把该字段定义的其它属性给覆盖掉了。

7、删除列

alter table test_table drop column t_sex

8、查询表字段信息

SHOW FULL COLUMNS FROM test_table

9、删除表

-- 单删
  drop table 表  
-- 批删
  drop table 表1,表2,表3

10、批量修改某个字段值

UPDATE test_table SET t_name = REPLACE(t_name, "test", "t")

例:运行前

      

运行后:

      

python redis的连接及相关操作

1、redis连接、及存取值

import redis
r = redis.Redis(host="192.168.2.22",port=6379,db=2,password= "redis")
r.set("name","Delia")       # 在redis里面放置对应的key,value
v = r.get("name")           # 获取key值对应的value值
print(v)
redis-py使用connection pool来管理对一个redis server的所有连接,避免每次建立、释放连接的开销。
默认,每个Redis
实例都会维护一个自己的连接池。可以直接建立一个连接池,然后作为参数Redis,这样就可以实现多个Redis实例共享一个连接池。
import redis
pool = redis.ConnectionPool(host="192.168.2.22", port=6379, db=2, password="redis")
r = redis.Redis(connection_pool=pool)
r.set("name","Delia")       # 在redis里面放置对应的key,value
v = r.get("name")           # 获取key值对应的value值
print(v)

输出结果里面的b代表的是二进制

 

 2、批量存取值

import redis
pool = redis.ConnectionPool(host="192.168.2.22", port=6379, db=2, password="redis")
r = redis.Redis(connection_pool=pool)
r.mset({"name":"Delia","age":"18"})     # mset(mapping=""),批量设置值 {k1 = "" ,k2 = ""}
v = r.mget({"name","age"})  # 批量获取值
print(v)


输出结果:
[b"Delia", b"18"]

3、存取value的某个范围

import redis
pool = redis.ConnectionPool(host="192.168.2.22", port=6379, db=2, password="redis")
r = redis.Redis(connection_pool=pool)
r.set("name","Delia1234")
r.setrange("name",4,"00")     #setrange(name,offset,value)修改字符串内容,从指定位置开始替换
v = r.getrange("name",4,5)  #getrange(key,start,end)获取子序列
print(v)


输出结果:
b"00"

4、在value中追加内容

import redis
pool = redis.ConnectionPool(host="192.168.2.22", port=6379, db=2, password="redis")
r = redis.Redis(connection_pool=pool)
r.set("name","Delia1234")
r.append("name","aaa")     #append(key,value)在redis name对应的值后面追加内容
v = r.get("name")  
print(v)


输出结果:
b"Delia1234aaa"

 

TICK技术栈(二)Telegraf安装及使用

1.什么是Telegraf?

Telegraf是一个用Go语言开发的代理程序,可用于收集和报告指标。Telegraf插件直接从其运行的系统中获取各种指标,从第三方API中提取指标,甚至通过StatsD和Kafka消费者服务来监听指标。它还具有输出插件,可以将指标发送到各种其他数据存储,服务和消息队列,包括InfluxDB,Graphite,OpenTSDB,Datadog,Librato,Kafka,MQTT,NSQ等。github开源地址:https://github.com/influxdata/telegraf

2.Telegraf如何使用?

2.1安装

官方文档地址:https://docs.influxdata.com/telegraf/v1.12/introduction/installation/
系统:CenterOS7.2
安装版本为:1.12.3
telegraf下载地址:https://portal.influxdata.com/downloads/
找到你安装系统的对应版本:

运行下载页面提供的下载rpm文件命令:

wget https://dl.influxdata.com/telegraf/releases/telegraf-1.12.3-1.x86_64.rpm


下载完成之后对应的目录会多出一个rpm的包:


然后执行下载页面提供的安装命令:

sudo yum localinstall telegraf-1.12.3-1.x86_64.rpm


安装成功:

2.2使用

配置:

telegraf -sample-config -input-filter cpu:mem -output-filter influxdb > telegraf.conf


启动:

sudo service telegraf start


然后去查看一下安装好的influxdb数据库,发现此时多了一个数据库:


查看一下这个数据库,发现里面有8个表,然后我们查看其中的cpu表:


然后到grafana目录下启动grafana,然后访问:


外部访问,ip+端口(如果访问不到,记得把服务器的防火墙关掉):


cpu表字段:


指标字段介绍地址:https://developer.qiniu.com/insight/manual/4902/cpu-monitoring
例如CPU的:

2.3通过grafana搭建一个服务器指标监控dashboard:

参考资料

  • Telegraf介绍
  • Telegraf官方文档

如果文中有错误或其它问题,欢迎在评论区及时指正和提出来,我会积极的进行处理的。

MySQL执行SQL脚本问题 :错误代码2006、1153

今天用mysql执行了一个60M的SQL脚本遇到了一些错误,经由网上查询如下:

1.#2006 – MySQL server has gone away 出现该错误代码原因如下:

1、应用程序长时间的执行批量的MySQL语句。

2、执行一个SQL,但SQL语句过大或者语句中含有BLOB或者longblob字段。

2. 1153 – Got a packet bigger than “max_allowed_packet” bytes

在mysql的my.ini配置文件中添加以下代码,重启mysql:

 #max_allowed_packet 参数的作用是用来控制其通信缓冲区的最大长度
 max_allowed_packet=256M
 wait_timeout=288000
 interactive_timeout = 288000

Windows重启mysql,进入cmd执行:

启动:输入 net stop mysql
停止:输入 net start mysql

 

  

Mysql主从同步的实现原理与配置实战

1、什么是mysql主从同步?

当master(主)库的数据发生变化的时候,变化会实时的同步到slave(从)库。

2、主从同步有什么好处?

  • 水平扩展数据库的负载能力。
  • 容错,高可用。Failover(失败切换)/High Availability
  • 数据备份。

3、主从同步的原理是什么?

首先我们来了解master-slave的体系结构。如下图:

不管是delete、update、insert,还是创建函数、存储过程,所有的操作都在master上。当master有操作的时候,slave会快速的接收到这些操作,从而做同步。

 

但是,这个机制是怎么实现的呢?在master机器上,主从同步事件会被写到特殊的log文件中(binary-log);在slave机器上,slave读取主从同步事件,并根据读取的事件变化,在slave库上做相应的更改。

如此,就实现了主从同步了!

下面我们来详细的了解。3.1主从同步事件有哪些上面说到:在master机器上,主从同步事件会被写到特殊的log文件中(binary-log);

主从同步事件有3种形式:statement、row、mixed。

  1. statement:会将对数据库操作的sql语句写入到binlog中。
  2. row:会将每一条数据的变化写入到binlog中。
  3. mixed:statement与row的混合。Mysql决定什么时候写statement格式的,什么时候写row格式的binlog。

3.2在master机器上的操作

当master上的数据发生改变的时候,该事件(insert、update、delete)变化会按照顺序写入到binlog中。binlog dump线程当slave连接到master的时候,master机器会为slave开启binlog dump线程。当master 的 binlog发生变化的时候,binlog dump线程会通知slave,并将相应的binlog内容发送给slave。

 

3.3在slave机器上的操作

当主从同步开启的时候,slave上会创建2个线程。

  • I/O线程。该线程连接到master机器,master机器上的binlog dump线程会将binlog的内容发送给该I/O线程。该I/O线程接收到binlog内容后,再将内容写入到本地的relay log。
  • SQL线程。该线程读取I/O线程写入的relay log。并且根据relay log的内容对slave数据库做相应的操作。

3.4如何在master、slave上查看上述的线程?

使用SHOW PROCESSLIST命令可以查看。如图,在master机器上查看binlog dump线程

如图,在slave机器上查看I/O、SQL线程。

接下来给大家讲解mysql主从同步实战系统环境:系统的话大同小异,都差不多,我这里用的是ubuntu16.04+mysql5.7,用到两台服务器:其中master IP:192.168.33.22,另一个slave IP:192.168.33.33

 

master机器上的操作

1、更改配置文件我们找到文件/etc/mysql/mysql.conf.d/mysqld.cnf

配置如下:

bind-address = 192.168.33.22 #your master ip
server-id = 1 #在master-slave架构中,每台机器节点都需要有唯一的server-id
log_bin = /var/log/mysql/mysql-bin.log #开启binlog

 

2、重启mysql,以使配置文件生效。

sudo systemctl restart mysql

 

3、创建主从同步的mysql user。

$ mysql -u root -p
Password:

##创建slave1用户,并指定该用户只能在主机192.168.33.33上登录。
mysql> CREATE USER "slave1"@"192.168.33.33" IDENTIFIED BY "slavepass";
Query OK, 0 rows affected (0.00 sec)

##为slave1赋予REPLICATION SLAVE权限。
mysql> GRANT REPLICATION SLAVE ON *.* TO "slave1"@"192.168.33.33";
Query OK, 0 rows affected (0.00 sec)

 

4、为MYSQL加读锁为了主库与从库的数据保持一致,我们先为mysql加入读锁,使其变为只读。

mysql> FLUSH TABLES WITH READ LOCK;
Query OK, 0 rows affected (0.00 sec)

 

5、记录下来MASTER REPLICATION LOG 的位置该信息稍后会用到。

mysql> SHOW MASTER STATUS;
+------------------+----------+--------------+------------------+-------------------+
| File    | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set |
+------------------+----------+--------------+------------------+-------------------+
| mysql-bin.000001 |  613 |    |     |     |
+------------------+----------+--------------+------------------+-------------------+
1 row in set (0.00 sec)
 

 

6、将master DB中现有的数据信息导出

$ mysqldump -u root -p --all-databases --master-data > dbdump.sql

 

7、接触master DB的读锁

mysql> UNLOCK TABLES;

 

8、将步骤6中的dbdump.sql文件copy到slave

scp dbdump.sql ubuntu@192.168.33.33:/home/ubuntu

 

 

slave机器上的操作

1、更改配置文件我们找到文件/etc/mysql/mysql.conf.d/mysqld.cnf

更改配置如下:

bind-address = 192.168.33.33 #your slave ip
server-id = 2 #master-slave结构中,唯一的server-id
log_bin = /var/log/mysql/mysql-bin.log #开启binlog

 

2、重启mysql,以使配置文件生效

sudo systemctl restart mysql

 

 

3、导入从master DB。导出的dbdump.sql文件,以使master-slave数据一致

$ mysql -u root -p < /home/ubuntu/dbdump.sql

 

4、使slave与master建立连接,从而同步

$ mysql -u root -p
Password:

mysql> STOP SLAVE;
Query OK, 0 rows affected, 1 warning (0.00 sec)

mysql> CHANGE MASTER TO
 -> MASTER_HOST="192.168.33.22",
 -> MASTER_USER="slave1",
 -> MASTER_PASSWORD="slavepass",
 -> MASTER_LOG_FILE="mysql-bin.000001",
 -> MASTER_LOG_POS=613;
Query OK, 0 rows affected, 2 warnings (0.01 sec)

mysql> START SLAVE;
Query OK, 0 rows affected (0.00 sec)

MASTER_LOG_FILE="mysql-bin.000001"与MASTER_LOG_POS=613的值,是从上面的SHOW MASTER STATUS得到的。经过如此设置之后,就可以进行master-slave同步了

 

~

mysql 实现全连接

mysql不支持全连接,但可以通过左外连接+ union+右外连接实现


SELECT * FROM t1
LEFT JOIN t2 ON t1.id = t2.id
UNION
SELECT * FROM t1
RIGHT JOIN t2 ON t1.id = t2.id

Python 使用redis报:No module named redis问题

初次使用redis时,在链接Redis后,运行报错“No module named redis”。

具体代码如下:

import redis
r = redis.Redis(host="192.168.2.22",port=6379,db=2)
r.set("name","Delia")
print(r.get("name"))

报错如下:

 

 尝试性解决方法一:

在Python安装路径下使用pip安装redis,重新运行程序;

pip install redis

尝试无果。

 

 尝试性解决方法二:

在pycharm中file –> setting –> Project Interpreter 下选择redis进行导入,完成后,重新运行。

尝试无果。

 

 尝试性解决方法三:

(1)手动下载redis模块,地址为:https://github.com/andymccurdy/redis-py,

(2)然后进行解压:redis-py-master.zip,

(3) cd redis-py-master  进入到该路径下,

(4)执行 python setup.py install 命令安装redis。

尝试无果。

 

尝试性解决方法四:

新手常犯错误。

检查运行文件名称与模块名称是否重复。如果重复,需要将文件名称重新命名。

 

 

 

 

Mysql 8.0版本导出的sql文件在Mysql 5.5中运行出错

出现错误

[Err] 1064 – You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near “(0) NULL DEFAULT CURRENT_TIMESTAMP(0) ON UPDATE CURRENT_TIMESTAMP(0),
`descri” at line 8

解决思路

关于timestamp错误可以参考:https://www.cnblogs.com/lonelyxmas/p/10000175.html

即使修改了MySQL的model,依旧有很多错误:

Mysql 8.0和Mysql 5.7还是有很大的不同的

详细的可以参考:https://blog.csdn.net/CN_mengxin/article/details/81518800

解决办法:更换mysql 8.0吧,毕竟想要在MySQL5.5上运行要改的太多太多

发现过程

1. sql文件中开头注释的含义

Navicat Premium Data Transfer

Source Server : Link1 (源服务器)

Source Server Type : MySQL (源服务器类型)

Source Server Version : 80018 (源服务器版本)

Source Host : 127.0.0.1:3306 (源主机)

Source Schema : lanyue_v_3 (源数据库名称)

Target Server Type : MySQL (目标服务器类型)

Target Server Version : 80018 (目标服务器版本)

File Encoding : 65001 (支持编码)

Date: 06/11/2019 16:09:17 (日期)

使用注释,当在其他数据库中会被当做注释处理,而在MySQL中,会执行这些注释(MySQL通过注释的方式对sql进行拓展)

2. 如何查看自己的MySQL版本:

  • 第一步:进入cmd命令窗口

  • 第二步:进入Mysql的bin目录下

  • 第三步:输入mysql -hlocalhost -uroot -proot

    -u后的root为用户名、-p后的root为密码、-h为服务器地址

  • 第四步:输入status;

3. 对比MySQL5.5 和 MySQL 8.0sql文件

发现sql语句也多了,明显 5.5 和 8.0不兼容

mysql全局变量和局部变量

全局变量和局部变量

在服务器启动时,会将每个全局变量初始化为其默认值(可以通过命令行或选项文件中指定的选项更改这些默认值)。然后服务器还为每个连接的客户端维护一组会话变量,客户端的会话变量在连接时使用相应全局变量的当前值初始化。

举一个例子,在服务器启动时会初始化一个名为default_storage_engine,作用范围为GLOBAL的系统变量。之后每当有一个客户端连接到该服务器时,服务器都会单独为该客户端分配一个名为default_storage_engine,作用范围为SESSION的系统变量,该作用范围为SESSION的系统变量值按照当前作用范围为GLOBAL的同名系统变量值进行初始化。

很显然,通过启动选项设置的系统变量的作用范围都是GLOBAL的,也就是对所有客户端都有效的,因为在系统启动的时候还没有客户端程序连接进来呢。了解了系统变量的GLOBAL和SESSION作用范围之后,我们再看一下在服务器程序运行期间通过客户端程序设置系统变量的语法:

SET [GLOBAL|SESSION] 系统变量名 = 值;

或者写成这样也行:

SET [@@(GLOBAL|SESSION).]var_name = XXX;

比如我们想在服务器运行过程中把作用范围为GLOBAL的系统变量default_storage_engine的值修改为MyISAM,也就是想让之后新连接到服务器的客户端都用MyISAM作为默认的存储引擎,那我们可以选择下边两条语句中的任意一条来进行设置:

语句一:SET GLOBAL default_storage_engine = MyISAM;
语句二:SET @@GLOBAL.default_storage_engine = MyISAM;

如果只想对本客户端生效,也可以选择下边三条语句中的任意一条来进行设置:

语句一:SET SESSION default_storage_engine = MyISAM;
语句二:SET @@SESSION.default_storage_engine = MyISAM;
语句三:SET default_storage_engine = MyISAM;

从上边的语句三也可以看出,如果在设置系统变量的语句中省略了作用范围,默认的作用范围就是SESSION。也就是说SET 系统变量名 = 值和SET SESSION 系统变量名 = 值是等价的。

查看不同作用范围的系统变量
既然系统变量有作用范围之分,那我们的SHOW VARIABLES语句查看的是什么作用范围的系统变量呢?

答:默认查看的是SESSION作用范围的系统变量。

当然我们也可以在查看系统变量的语句上加上要查看哪个作用范围的系统变量,就像这样:

SHOW [GLOBAL|SESSION] VARIABLES [LIKE 匹配的模式];

MySQL5.6与MySQL5.7安装的区别

一.MySQL5.6与MySQL5.7安装的区别

  • 1、cmake的时候加入了boost 下载boost.org
  • 2、初始化时 cd /application/mysql/bin/mysql 使用mysqld –initialize 替代mysql_install_db,其它参数没有变化:–user= –basedir= –datadir=
  • 3、–initialize会生成一个临时密码
  • 4、还可以用另外一个参数–initialize-insecure (加上生不成密码)

mysql 5.7使用弱密码

validate_password_length 8 # 密码的最小长度,此处为8。
validate_password_mixed_case_count 1 # 至少要包含小写或大写字母的个数,此处为1。
validate_password_number_count 1 # 至少要包含的数字的个数,此处为1。
validate_password_policy MEDIUM # 强度等级,其中其值可设置为0、1、2。分别对应:
【0/LOW】:只检查长度。
【1/MEDIUM】:在0等级的基础上多检查数字、大小写、特殊字符。
【2/STRONG】:在1等级的基础上多检查特殊字符字典文件,此处为1。
validate_password_special_char_count 1 # 至少要包含的个数字符的个数,此处为1。
[root@db02 mysql-5.7.20]# yum install -y gcc gcc-c++ automake autoconf
[root@db02 mysql-5.7.20]# yum install make cmake bison-devel ncurses-devel libaio-devel
[root@db02 mysql-5.7.20]#
wget httpss://dl.bintray.com/boostorg/release/1.65.1/source/boost_1_59_0.tar.gz
#登录boost.org下载也可以
[root@db02 mysql-5.7.20]# tar xf boost_1_59_0.tar.gz -C /usr/local/
[root@db02 mysql-5.7.20]#
cmake . -DCMAKE_INSTALL_PREFIX=/application/mysql-5.7.20 
-DMYSQL_DATADIR=/application/mysql-5.7.20/data 
-DMYSQL_UNIX_ADDR=/application/mysql-5.7.20/tmp/mysql.sock 
-DDOWNLOAD_BOOST=1 
-DWITH_BOOST=/usr/local/boost_1_59_0 
-DDEFAULT_CHARSET=utf8 
-DDEFAULT_COLLATION=utf8_general_ci 
-DWITH_EXTRA_CHARSETS=all 
-DWITH_INNOBASE_STORAGE_ENGINE=1 
-DWITH_FEDERATED_STORAGE_ENGINE=1 
-DWITH_BLACKHOLE_STORAGE_ENGINE=1 
-DWITHOUT_EXAMPLE_STORAGE_ENGINE=1 
-DWITH_ZLIB=bundled 
-DWITH_SSL=bundled 
-DENABLED_LOCAL_INFILE=1 
-DWITH_EMBEDDED_SERVER=1 
-DENABLE_DOWNLOADS=1 
-DWITH_DEBUG=0 

生成密码位置不一样

select user,host passwd from mysql.user

查看有没有密码

5.6

mysql 5.7

二.MySQL用户权限管理

  • 1.MySQL用户基础操作

Linux用户的作用:

  • 1)登陆系统
  • 2)管理系统文件

Linux用户管理:

  • 1)创建用户:useradd adduser
  • 2)删除用户:userdel
  • 3)修改用户:usermod

MySQL用户的作用:

  • 1)登陆MySQL数据库
  • 2)管理数据库对象

MySQL用户管理:

  • 1)创建用户:create user
#创建用户
create user zls@'%';
#创建用户同时给密码(5.7)如果用户不存在没法使用grant
create user qls@'%' identified by '123';
grant all on *.* to qls@'%';
  • 2)删除用户:delete user drop user (user这里是一个命令,不是一个用户)
mysql> drop user root@'db02';
Query OK, 0 rows affected (0.00 sec)
mysql> drop user ''@'db02';
Query OK, 0 rows affected (0.00 sec)
  • )修改用户:update alter grant
mysql> update mysql.user set password=PASSWORD('123') where user='root' and
host='localhost';
#进入库,在操作

用户的定义:

  • 1) username@’主机域’

    root@”%” select

    root@”%” 超级用户

    root@”172.0.0.0″ 超级用户

  • 2)主机域:可以理解为是MySQL登陆的白名单

  • 3)主机域格式:

    • ’10.0.0.51’
    • ’10.0.0.5%’
    • ’10.0.0.%’
    • ’10.0.%.%’
    • ’10.%.%.%’
    • ‘%’
    • ‘db01’
    • ’10.0.0.51/255.255.255.0’
    • ’10.0.0.0/24 不能用,不识别

用户管理实战

刚装完MySQL数据库该做的事情

  • 1、设定初始密码(root@localhost)
[root@db02 mysql-5.7.20]# mysqladmin -uroot -p password 'oldboy123'
  • 2、修改密码
  • 3、使用密码登陆
[root@db02 mysql-5.7.20]# mysql -uroot -p123
  • 4、清理无用的用户

误删除了所有用户

#关闭数据库
[root@db02 mysql-5.7.20]# /etc/init.d/mysqld stop #通用
#启动数据库
[root@db02 mysql-5.7.20]# mysqld_safe --skip-grant-tables --skip-networking &
#跳过授权表,跳过网路,只能sockect连接,不能tcpip连接
#使用mysql库
mysql> use mysql

#错误方法1、创建root用户
mysql> create user root@’localhost’;
#错误方法2、创建root用户
mysql> insert into user(user,host,password) values('root','10.0.0.55',PASSWORD('123'));
#错误方法
mysql> insert into user(user,host,password,ssl_cipher,x509_issuer,x509_subject)
values('root','localhost',PASSWORD('123'),'null','null','null');
#没有权限,表中没有权限
#正确方法创建root用户
mysql>  insert into mysql.user values ('localhohost','root',PASSWORD('123'),
-> 'Y',
-> 'Y',
-> 'Y',
-> 'Y',
-> 'Y',
-> 'Y',
-> 'Y',
-> 'Y',
-> 'Y',
-> 'Y',
-> 'Y',
-> 'Y',
-> 'Y',
-> 'Y',
-> 'Y',
-> 'Y',
-> 'Y',
-> 'Y',
-> 'Y',
-> 'Y',
-> 'Y',
-> 'Y',
-> 'Y',
-> 'Y',
-> 'Y',
-> 'Y',
-> 'Y',
-> 'Y',
-> 'Y',
-> '',
-> '',
-> '',
-> '',0,0,0,0,'mysql_native_password','','N');
#重启mysqld
/etc/init.d/mysqld start

方法二:

#关闭数据库
[root@db02 mysql-5.7.20]# /etc/init.d/mysqld stop #通用
#启动数据库
[root@db02 mysql-5.7.20]# mysqld_safe --skip-grant-tables --skip-networking & 

mysql>flush privileges;#刷新授权表,update 修改密码时才能生效,危险
mysql> all on *.* to root@'127.0.0.1' identified by '1' 
with grant option;#超级用户
#5.7版本,如果用户不存在不能使用grant

方法三:

#导库,在另一台服务器上导出系统库
mysqldump -uroot -p1 -B mysql >/tmp/mysql.sql

方法四:(不适合生产环境)

#初始化
cd /application/mysql/
rm-fr data/ #注意data目录不要删错

忘记root密码

#关闭数据库
[root@db02 mysql-5.7.20]# /etc/init.d/mysqld stop
#启动数据库
[root@db02 mysql-5.7.20]# mysqld_safe --skip-grant-tables --skip-networking &

#1.update修改密码
update mysql.user set password=PASSWORD('123') where user='root' and host='localhost';
mysql> flush privileges;
#2.set
mysql> set password=PASSWORD('1');
#3.grant
mysql> grant all on *.* to root@'localhost' identified by '2';
#4.mysqladmin
[root@db01 ~]# mysqladmin -uroot -p2 password '123'
#5.alter
  • 2.用户管理及权限管理_

1)创建用户

mysql> create user oldboy@'10.0.0.%' identified by '123';

2)查看用户

mysql>  select user,host from mysql.user;

3)删除用户

mysql>  drop user oldboy@‘10.0.0.%’;#drop user sql语句

4)修改密码

select database();#pwd
desc mysql.user;查看表权限
#插入表内容 insert into user(user,host.password) values()

#进入库里面修改密码,或者使用绝对路径

mysql> set password
mysql> update user set password=PASSWORD('oldboy123') where user='root' and host='localhost';#mysql.user
mysql> grant all privileges on *.* to oldboy@’10.0.0.%’ identified by ‘123’;

5)用户权限介绍

MySQL的权限定义:
作用对象:库、表
权限

INSERT,SELECT, UPDATE, DELETE, CREATE, DROP, RELOAD, SHUTDOWN,  PROCESS, FILE, REFERENCES, INDEX, ALTER, SHOW DATABASES, SUPER, CREATE TEMPORARY TABLES, LOCK TABLES, EXECUTE, REPLICATION SLAVE, REPLICATION CLIENT, CREATE VIEW, SHOW VIEW, CREATE ROUTINE, ALTER ROUTINE, CREATE USER, EVENT, TRIGGER, CREATE TABLESPACE

给开发开权限

grant INSERT,SELECT, UPDATE, DELETE on *.* to dev@'xxx';

权限最小化,最小级别不是表级别,是列级别

grant select on mysql.user.user to dev@'10.0.0.5%' identified by '123';
use vip;
use tables;

主机域:10.0.0.5% 可以连接:10.0.0.50-59 10.0.0.5

脱敏:脱离敏感信息

grant select(user,host) on wzry.user to no_vip@'%' identified by '123';

数据库集群:10.0.0.51 52 53 54

归属
每次设定只能有一个属主,没有属组或其他用户的概念

grant all privileges on *.* to oldboy@’10.0.0.%’ identified by ‘123’;
        权限          作用对象        归属          密码

作用对象分解

. [当前MySQL实例中所有库下的所有表]
wordpress.* [当前MySQL实例中wordpress库中所有表(单库级别)]
wordpress.user [当前MySQL实例中wordpress库中的user表(单表级别)]

思考下面场景:
开发:你把root用户给我呗?

你:emmmmm…….. SO?


实验思考问题:

#创建wordpress数据库
create database wordpress;
#使用wordpress库
use wordpress;
#创建t1、t2表
create table t1 (id int);
create table t2 (id int);
#创建blog库
create database blog;
#使用blog库
use blog;
#创建t1表
create table tb1 (id int);

授权:

1、grant select on *.* to wordpress@’10.0.0.5%’ identified by ‘123’;
2、grant insert,delete,update on wordpress.* to wordpress@’10.0.0.5%’ identified by ‘123’;
3、grant all on wordpress.t1 to wordpress@’10.0.0.5%’ identified by ‘123’;

问:
一个客户端程序使用wordpress用户登陆到10.0.0.51的MySQL后,

  • 1、对t1表的管理能力?
  • 2、对t2表的管理能力?
  • 3、对tb1表的管理能力?

解:

  • 1、同时满足1,2,3,最终权限是1+2+3
  • 2、同时满足了1和2两个授权,最终权限是1+2
  • 3、只满足1授权,所以只能select

但列级别是最小级别

create database wordpress;
use wordpress;
create table t1 (id int);
create table t2 (id int);
create database blog;
use blog;
create table tb1 (id int);
mysql> show databases;
+--------------------+
| Database |
+--------------------+
| blog |
| wordpress |
+--------------------+
mysql> show tables from wordpress;
+---------------------+
| Tables_in_wordpress |
+---------------------+
| t1 |
| t2 |
+---------------------+
mysql> show tables from blog;
+----------------+
| Tables_in_blog |
+----------------+
| tb1 |
+----------------+
1、grant select on *.* to wordpress@’10.0.0.5%’ identified by ‘123’;
针对wordpress@'10.0.0.5%' 授权 所有库所有表 查询权限 密码是 123
2、grant insert,delete,update on wordpress.* to wordpress@’10.0.0.5%’ identified by ‘123’;
针对wordpress@'10.0.0.5%' 授权 插入、删除、修改 wordpress库中的所有表 密码是 123
3、grant all on wordpress.t1 to wordpress@’10.0.0.5%’ identified by ‘123’;
针对wordpress@'10.0.0.5%' 授权所有权限 wordpress库中的t1表 密码是123

一个客户端程序使用wordpress用户登录到10.0.0.51的mysql后

[root@db02 ~]# mysql -uwordpress -p123 -h10.0.0.51

1、对t1(wordpress)表的管理能力? t1:select ,insert,delete,update all
所有权限
2、对t2(wordpress)表的管理能力?t2:select,insert,delete,update
insert update delete select
3、对tb1表的管理能力?
只有select

结论:

1.如果在不同级别都包含某个表的管理能力时,权限是相加关系

2.但是我们不推荐在多级别定义重复权限

3.最常用的权限设定方式是单库级别授权,即:wordpres.*[单库级别]

在企业中,开发让你给他开一个MySQL的用户:

grant insert,delete,update on wordpress.* to wordpress@’ identified by ‘123’;

1.权限是什么?insert,delete,update

2.哪个库,哪几张表?哪些列? wordpress.*

3.你从哪里连接?‘10.0.0.5%’

4.用户名和密码是什么?

5.发邮件,走流程

三.MySQL连接管理

1.连接工具

  • 1)MySQL自带的连接工具

mysql

常见的特定于客户机的连接选项:
-u:指定用户
-p:指定密码
-h:指定主机
-P:指定端口
-S:指定sock
-e:指定SQL
–protocol=name:指定连接方式

  • 2)第三方的连接工具

sqlyog、navicat
应用程序连接MySQL
注意:需要加载对应语言程序的API

2.连接方式

  • 1) socket连接
mysql -uroot -poldboy123 -S/application/mysql/tmp/mysql.sock
mysql -uroot -poldboy123
  • 2) TCP/IP
mysql -uroot -poldboy123 -h10.0.0.51 -P3306
  • 问题:你怎么判断你的MySQL数据库可以对外提供服务?

四.MySQL启动关闭流程

启动

/etc/init.d/mysqld start
service mysqld start
systemctl start mysqld
mysqld_safe --defaults-file=/etc/my.cnf &

关闭

/etc/init.d/mysqld stop 
mysqladmin -uroot -poldboy123 shutdown
service mysqld stop
systemctl stop mysqld
kill -9 pid ?
killall mysqld ?
pkill mysqld ?

出现问题:
– 1、如果在业务繁忙的情况下,数据库不会释放pid和sock文件
– 2、号称可以达到和Oracle一样的安全性,但是并不能100%达到
– 3、在业务繁忙的情况下,丢数据(补救措施,高可用)

五.MySQL实例初始化配置

1.初始化配置文件的作用

场景:我要启动实例

问题:
1)我不知道我的程序在哪?
2)我也不知道我将来启动后去哪找数据库?
3)将来我启动的时候启动信息和错误信息放在哪?
4)我启动的时候sock文件pid文件放在哪?
5)我启动,你们给了我多少内存?

N)我还有很多问题需要在我启动之前告诉我,emmmmm….

  • 1)预编译:cmake去指定,硬编码到程序当中去
  • 2)在命令行设定启动初始化配置
--skip-grant-tables 
--skip-networking
--datadir=/application/mysql/data
--basedir=/application/mysql
--defaults-file=/etc/my.cnf
--pid-file=/application/mysql/data/db01.pid
--socket=/application/mysql/data/mysql.sock
--user=mysql
--port=3306
--log-error=/application/mysql/data/db01.err
  • 3)初始化配置文件(/etc/my.cnf)

配置文件读取顺序:

说明:

$MYSQL_HOME/my.cnf >>>>>>/application/mysql/my.cnf

defaults-extra-file>>>>>/tmp/my.cnf

#查询server_id
mysql -uroot -p1 -e "show variables like 'server_id'"

–defaults-file:默认配置文件
如果使用./bin/mysqld_safe 守护进程启动mysql数据库时,使用了 –defaults-file=

参数,这时只会使用这个参数指定的配置文件。


思考:

#cmake:
socket=/application/mysql/tmp/mysql.sock
#命令行:
--socket=/tmp/mysql.sock
#配置文件:
/etc/my.cnf中[mysqld]标签下:socket=/opt/mysql.sock
#default参数:
--defaults-file=/tmp/a.txt配置文件中[mysqld]标签下:socket=/tmp/test.sock

socket文件会生成在哪???文件名叫什么???

/tmp/mysql.sock

优先级结论:

  • 1、命令行
  • 2、defaults-file
  • 3、配置文件(覆盖)
  • 4、预编译(cmake)

2.初始化配置文件的使用

初始化配置文件功能

1)影响服务端的启动(mysqld)
2)影响到客户端的连接

  • mysql
  • mysqldump
  • mysqladmin

如何配置初始化配置文件

1)配置标签分类
[client]所有客户端程序
mysql
mysqldump

[server]所有服务器程序
mysqld
mysqld_safe

六.MySQL多实例配置

  • 1.什么是多实例

1)多套后台进程+线程+内存结构

2)多个配置文件
a.多个端口
b.多个socket文件
c.多个日志文件
d.多个server_id

3)多套数据

  • 启动脚本

  • 2.多实例实战

1.准备多个配置文件

#创建多个存放配置文件的目录
[root@db02 ~]# mkdir /data/{3307,3308,3309} -p
[root@db02 ~]# tree /data/
/data/
├── 3307
├── 3308
└── 3309
#创建多个配置文件
[root@db02 ~]# vim /data/3307/my.cnf
[mysqld]
basedir=/application/mysql
datadir=/data/3307/data
socket=/data/3307/data/mysql.sock
port=3307
log_error=/data/3307/data/3307.err
log-bin=/data/3307/mysql-bin
server_id=7
pid_file=/data/3307/data/3307.pid
[client]
socket=/data/3307/mysql.sock
------------------------------------------------------------[root@db02 ~]# vim /data/3308/my.cnf
[mysqld]
basedir=/application/mysql
datadir=/data/3308/data
socket=/data/3308/data/mysql.sock
port=3308
log_error=/data/3308/data/3308.err
server_id=8
pid_file=/data/3308/data/3308.pid
------------------------------------------------------------[root@db02 ~]# vim /data/3309/my.cnf
[mysqld]
basedir=/application/mysql
datadir=/data/3309/data
socket=/data/3309/data/mysql.sock
port=3309
log_error=/data/3309/data/3309.err
server_id=9
pid_file=/data/3309/data/3309.pid
------------------------------------------------------------[root@db02 ~]# tree /data
/data
├── 3307
│ └── my.cnf
├── 3308
│ └── my.cnf└── 3309
└── my.cnf

2.初始化多个datadir

[root@db02 ~]# cd /application/mysql/scripts/
#初始化3307的数据目录
[root@db02 scripts]#./mysql_install_db --defaults-file=/data/3307/my.cnf --user=mysql --basedir=/application/mysql --datadir=/data/3307/data
#3308
[root@db02 scripts]# ./mysql_install_db --defaults-file=/data/3308/my.cnf --user=mysql --
basedir=/application/mysql --datadir=/data/3308/data
#3309
[root@db02 scripts]# ./mysql_install_db --defaults-file=/data/3309/my.cnf --user=mysql --
basedir=/application/mysql --datadir=/data/3309/data
[root@db02 scripts]# tree -L 2 /data
/data
├── 3307
│ ├── data
│ └── my.cnf
├── 3308
│ ├── data
│ └── my.cnf
└── 3309
├── data
└── my.cnf

3.启动多实例

mysqld_safe --defaults-file=/data/3307/my.cnf &
mysqld_safe --defaults-file=/data/3308/my.cnf &
mysqld_safe --defaults-file=/data/3309/my.cnf &
#检查端口
[root@db02 scripts]# netstat -lntup
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program
name
tcp6 0 0 :::3306 :::* LISTEN
13052/mysqld
tcp6 0 0 :::3307 :::* LISTEN
13598/mysqld
tcp6 0 0 :::3308 :::* LISTEN
13428/mysqld
tcp6 0 0 :::3309 :::* LISTEN
13768/mysqld

4.设置密码

[root@db02 scripts]# mysqladmin -uroot -p -S/data/3307/data/mysql.sock password '3307'
[root@db02 scripts]# mysqladmin -uroot -p -S/data/3308/data/mysql.sock password '3308'
[root@db02 scripts]# mysqladmin -uroot -p -S/data/3309/data/mysql.sock password '3309'

5.连接mysqsl

[root@db02 scripts]# mysql -uroot -p3307 -S /data/3307/data/mysql.sock
[root@db02 scripts]# mysql -uroot -p3308 -S /data/3308/data/mysql.sock
[root@db02 scripts]# mysql -uroot -p3309 -S /data/3309/data/mysql.sock

#小技巧
#system管理
[root@db01 ~]# vim /usr/lib/systemd/system/mysqld.service 

[Unit]
Description=MySQL Server
Documentation=man:mysqld(8)
Documentation=https://dev.mysql.com/doc/refman/en/using-systemd.html
After=network.target
After=syslog.target
[Install]
WantedBy=multi-user.target
[Service]
User=mysql
Group=mysql
ExecStart=/application/mysql/bin/mysqld --defaults-file=/etc/my.cnf #给下路径
LimitNOFILE = 5000

#mysql连接
[root@db02 bin]# vim mysql3309
mysql -uroot -p3309 -S /data/3309/data/mysql.sock
[root@db02 bin]# chmod +x mysql3309

高可用:

主从复制

主库操作

修改配置文件

#编辑mysql配置文件
[root@mysql-db01 ~]# vim /etc/my.cnf
#在mysqld标签下配置
[mysqld]
#主库server-id为1,从库不等于1
server_id =1
#开启binlog日志
log_bin=mysql-bin

创建主从复制用户

#登录数据库
[root@mysql-db01 ~]# mysql -uroot -poldboy123
#创建rep用户
mysql> grant replication slave on *.* to rep@'10.0.0.%' identified by '123';

从库操作

修改配置文件

#修改mysql-db02配置文件
[root@mysql-db02 ~]# vim /etc/my.cnf
#在mysqld标签下配置
[mysqld]
#主库server-id为1,从库必须大于1
server_id =5
#开启binlog日志
log_bin=mysql-bin
#重启mysql
[root@mysql-db02 ~]# /etc/init.d/mysqld restart
#修改mysql-db03配置文件
[root@mysql-db03 ~]# vim /etc/my.cnf
#在mysqld标签下配置
[mysqld]
#主库server-id为1,从库必须大于1
server_id =10
#开启binlog日志
log_bin=mysql-bin
#重启mysql
[root@mysql-db03 ~]# /etc/init.d/mysqld restart

==基于binlog日志的主从复制,则必须记住主库的master状态信息==

mysql> show master status;
+------------------+----------+
| File             | Position |
+------------------+----------+
| mysql-bin.000002 |      120 |
+------------------+----------+

从库

mysql>change master to
    master_host='127.0.0.1',
    master_user='rep',
    master_password='123',
    master_log_file='mysql-bin.000001',
    master_log_pos=120,
    master_port=3306;
    
    start slave;
    show slave statusG

如果IO线程为NO

  • 1.检查网路

    ping 127.0.0.

  • 2.检查端口

    telnet 127.0.0.1 3307

  • 检查账号,密码

    mysql -urep -p123 -h127.0.0.1 -P 3307

GROUP BY中的WITH CUBE、WITH ROLLUP原理测试及GROUPING应用

    前几天,看到一个群友用WITH ROLLUP运算符。由于自个儿没用过,看到概念及结果都云里雾里的,所以突然来了兴趣对生成结果测了一番。

    一、概念:

    WITH CUBE:生成的结果集显示了所选列中值的所有组合的聚合。

    WITH ROLLUP:生成的结果集显示了所选列中值的某一层次结构的聚合。

    GROUPING:当行由 WITH CUBE或WITH ROLLUP运算符添加时,该函数将导致附加列的输出值为 1;当行不由 CUBE 或 ROLLUP 运算符添加时,该函数将导致附加列的输出值为 0。仅在与包含 CUBE 或 ROLLUP 运算符的 GROUP BY 子句相关联的选择列表中才允许分组。

    二、测试:

    1、建立临时表

CREATE TABLE #T0
(
    [GRADE] [VARCHAR](50) NULL,     --年级
    [CLASS] [VARCHAR](50) NULL,     --班级
    [NAME] [VARCHAR](50) NULL,      --姓名
    [COURSE] [VARCHAR](50) NULL,    --学科
    [RESULT] [NUMERIC](8,2) NULL    --成绩
)

CREATE TABLE #T1
(
    [ID] [INT] IDENTITY(1,1) NOT NULL,    --序号
    [GRADE] [VARCHAR](50) NULL,           --年级
    [CLASS] [VARCHAR](50) NULL,           --班级
    [NAME] [VARCHAR](50) NULL,            --姓名
    [COURSE] [VARCHAR](50) NULL,          --学科
    [RESULT] [NUMERIC](8,2) NULL          --成绩
)

CREATE TABLE #T2
(
    [ID] [INT] IDENTITY(1,1) NOT NULL,    --序号
    [GRADE] [VARCHAR](50) NULL,           --年级
    [CLASS] [VARCHAR](50) NULL,           --班级
    [NAME] [VARCHAR](50) NULL,            --姓名
    [COURSE] [VARCHAR](50) NULL,          --学科
    [RESULT] [NUMERIC](8,2) NULL          --成绩
)

     2、插入测试数据

INSERT INTO #T0 (GRADE,CLASS,NAME,COURSE,RESULT)
SELECT "2019","CLASS1","9A01","C#",100
UNION
SELECT "2019","CLASS1","9A02","C#",100
UNION
SELECT "2019","CLASS2","9B01","C#",100
UNION
SELECT "2019","CLASS2","9B02","C#",100
UNION
SELECT "2018","CLASS1","8A01","JAVA",100
UNION
SELECT "2018","CLASS1","8A02","JAVA",100
UNION
SELECT "2018","CLASS2","8B01","JAVA",100
UNION
SELECT "2018","CLASS2","8B02","JAVA",100

    查询T0表结果:

    3、GROUP BY

    抛砖引玉,看看常用的GROUP BY排序:默认以SELECT字段顺序(GRADE->CLASS->NAME->COURSE)进行排序,以下两种查询结果是一样的。

SELECT GRADE,CLASS,NAME,COURSE,SUM(RESULT) RESULT
FROM #T0
GROUP BY GRADE,CLASS,NAME,COURSE

SELECT GRADE,CLASS,NAME,COURSE,SUM(RESULT) RESULT
FROM #T0
GROUP BY GRADE,CLASS,NAME,COURSE
ORDER BY GRADE,CLASS,NAME,COURSE

    4、WITH CUBE

    原理1:以GROUP BY字段依次赋以NULL值进行分组聚合。

    原理2:第1个字段(即GRADE字段)生成结果:除原始数据外,以第1个字段固定赋以NULL值,然后其它字段依次赋以NULL值进行分组聚合,结果由右往左进行排序

    下面开始测第1个字段的结果是怎么来的:

INSERT INTO #T1 (GRADE,CLASS,NAME,COURSE,RESULT)
SELECT GRADE,CLASS,NAME,COURSE,SUM(RESULT) RESULT 
FROM #T0 
GROUP BY GRADE,CLASS,NAME,COURSE

INSERT INTO #T1 (GRADE,CLASS,NAME,COURSE,RESULT)
SELECT "ZZ" GRADE,CLASS,NAME,COURSE,SUM(RESULT) RESULT 
FROM #T0 
GROUP BY CLASS,NAME,COURSE

INSERT INTO #T1 (GRADE,CLASS,NAME,COURSE,RESULT)
SELECT "ZZ" GRADE,"ZZ" CLASS,NAME,COURSE,SUM(RESULT) RESULT 
FROM #T0 
GROUP BY NAME,COURSE

INSERT INTO #T1 (GRADE,CLASS,NAME,COURSE,RESULT)
SELECT "ZZ" GRADE,"ZZ" CLASS,"ZZ" NAME,COURSE,SUM(RESULT) RESULT 
FROM #T0 
GROUP BY COURSE

INSERT INTO #T1 (GRADE,CLASS,NAME,COURSE,RESULT)
SELECT "ZZ" GRADE,"ZZ" CLASS,"ZZ" NAME,"ZZ" COURSE,SUM(RESULT) RESULT 
FROM #T0

--第1个字段结果排序由右往左
INSERT INTO #T2 (GRADE,CLASS,NAME,COURSE,RESULT)
SELECT GRADE,CLASS,NAME,COURSE,RESULT FROM #T1 WHERE ID BETWEEN 1 AND 27 ORDER BY COURSE,NAME,CLASS,GRADE

UPDATE #T2 SET GRADE=NULL WHERE GRADE="ZZ"
UPDATE #T2 SET CLASS=NULL WHERE CLASS="ZZ"
UPDATE #T2 SET NAME=NULL WHERE NAME="ZZ"
UPDATE #T2 SET COURSE=NULL WHERE COURSE="ZZ"

    WITH CUBE的结果:

SELECT GRADE,CLASS,NAME,COURSE,SUM(RESULT) RESULT
FROM #T0
GROUP BY GRADE,CLASS,NAME,COURSE
WITH CUBE

    自已测试的结果:

SELECT * FROM #T2

    结果与上面一致。

    其它字段优先跟哪个字段组合、最终怎样排序?呃,测过,没搞清楚……

    5、WITH ROLLUP

    原理1:除原始数据外,以GROUP BY最后1个字段(即COURSE字段)固定赋以NULL值,然后其它字段依次赋以NULL值进行分组聚合,结果由左往右进行排序

    这个跟WITH CUBE的第1个字段非常相象:一个是第1个字段,一个是最后1个字段;一个结果是由右往左排序,一个结果是由左往右排序。

    下面开始测结果是怎么来的:

TRUNCATE TABLE #T1
TRUNCATE TABLE #T2

INSERT INTO #T1 (GRADE,CLASS,NAME,COURSE,RESULT)
SELECT GRADE,CLASS,NAME,COURSE,SUM(RESULT) RESULT 
FROM #T0 
GROUP BY GRADE,CLASS,NAME,COURSE

INSERT INTO #T1 (GRADE,CLASS,NAME,COURSE,RESULT)
SELECT GRADE,CLASS,NAME,"ZZ" COURSE,SUM(RESULT) RESULT 
FROM #T0 
WHERE NOT EXISTS (SELECT 1 FROM #T1 WHERE GRADE=#T0.GRADE AND CLASS=#T0.GRADE AND NAME=#T0.NAME AND COURSE="ZZ")
GROUP BY GRADE,CLASS,NAME

INSERT INTO #T1 (GRADE,CLASS,NAME,COURSE,RESULT)
SELECT GRADE,CLASS,"ZZ" NAME,"ZZ" COURSE,SUM(RESULT) RESULT 
FROM #T0 
WHERE NOT EXISTS (SELECT 1 FROM #T1 WHERE GRADE=#T0.GRADE AND CLASS=#T0.CLASS AND NAME="ZZ" AND COURSE="ZZ")
GROUP BY GRADE,CLASS

INSERT INTO #T1 (GRADE,CLASS,NAME,COURSE,RESULT)
SELECT GRADE,"ZZ" CLASS,"ZZ" NAME,"ZZ" COURSE,SUM(RESULT) RESULT 
FROM #T0 
WHERE NOT EXISTS (SELECT 1 FROM #T1 WHERE GRADE=#T0.GRADE AND CLASS="ZZ" AND NAME="ZZ" AND COURSE="ZZ")
GROUP BY GRADE

INSERT INTO #T1 (GRADE,CLASS,NAME,COURSE,RESULT)
SELECT "ZZ" GRADE,"ZZ" CLASS,"ZZ" NAME,"ZZ" COURSE,SUM(RESULT) RESULT 
FROM #T0 

--结果排序由左往右
INSERT INTO #T2 (GRADE,CLASS,NAME,COURSE,RESULT)
SELECT GRADE,CLASS,NAME,COURSE,RESULT FROM #T1 ORDER BY GRADE,CLASS,NAME,COURSE

UPDATE #T2 SET GRADE=NULL WHERE GRADE="ZZ"
UPDATE #T2 SET CLASS=NULL WHERE CLASS="ZZ"
UPDATE #T2 SET NAME=NULL WHERE NAME="ZZ"
UPDATE #T2 SET COURSE=NULL WHERE COURSE="ZZ"

    WITH ROLLUP的结果:

SELECT GRADE,CLASS,NAME,COURSE,SUM(RESULT) RESULT
FROM #T0
GROUP BY GRADE,CLASS,NAME,COURSE
WITH ROLLUP

    自己测试的结果:

SELECT * FROM #T2

    结果与上面一致。

    6、GROUPING

    这个就比较容易理解了,WITH CUBE与WITH ROLLUP用法一样,先看结果:

SELECT GRADE,CLASS,NAME,COURSE,SUM(RESULT) RESULT,GROUPING(COURSE) [GROUPING]
FROM #T0
GROUP BY GRADE,CLASS,NAME,COURSE
WITH ROLLUP

    上面GROUPING的是COURSE字段,有NULL值就是WITH ROLLUP额外添加的,GROUPING结果值为1。

    有了GROUPING,那做小计、总计就方便了。

SELECT 
    GRADE,
    CASE WHEN GROUPING(GRADE)=1 AND GROUPING(CLASS)=1 THEN "总计" WHEN GROUPING(GRADE)=0 AND GROUPING(CLASS)=1 THEN "小计" ELSE CLASS END CLASS,
    NAME,COURSE,SUM(RESULT) RESULT
FROM #T0
GROUP BY GRADE,CLASS,NAME,COURSE
WITH ROLLUP

     好了,原理测试及应用就到这里结束了。

oracle数据库不小心删除了数据

1.select * from SYS_DICT as of timestamp to_timestamp(“2019-11-05 10:00:00″,”yyyy-mm-dd hh24:mi:ss”);

时间点在删除数据之前,又尽量精确    sys_dict 是表名

2.insert into SYS_DICT (select * from SYS_DICT as of timestamp to_timestamp(“2019-11-05 10:00:00″,”yyyy-mm-dd hh24:mi:ss”));

把数据重新插入

Oracle 导入DBF故障存储文件

创建表空间及用户
CREATE TABLESPACE OracleDBF
DATAFILE “D:appzhoulxoradatadcOracleDBF.DBF” SIZE 100M AUTOEXTEND ON NEXT 20M MAXSIZE UNLIMITED — 这里是你设置数据库存放的地方,可以自己选定位置
PERMANENT
DEFAULT STORAGE(INITIAL 64K MINEXTENTS 1 MAXEXTENTS 2147483645)
MINIMUM EXTENT 64K
LOGGING
ONLINE
/
create user OracleDBF identified by salis default tablespace OracleDBF temporary tablespace temp;
/
GRANT DBA TO OracleDBF WITH ADMIN OPTION
/
GRANT SELECT ANY DICTIONARY TO OracleDBF WITH ADMIN OPTION
/
GRANT SELECT ANY SEQUENCE TO OracleDBF WITH ADMIN OPTION
/
GRANT SELECT ANY TABLE TO OracleDBF WITH ADMIN OPTION
/
GRANT SELECT ANY TRANSACTION TO OracleDBF WITH ADMIN OPTION
/
GRANT UNLIMITED TABLESPACE TO OracleDBF WITH ADMIN OPTION
/
GRANT CREATE SESSION,RESOURCE TO OracleDBF WITH ADMIN OPTION
/
GRANT CREATE ANY VIEW to OracleDBF
/

数据泵导入
查询目录
select * from dba_directories
创建目录
create directory DATABASE as “e:database” –这一步可以忽略

——————————————————————————————————–

这一步在命令行输入
导入
impdp OracleUser/OracleuserPassWord@Oracle地址(例如:127.0.0.1) directory=”e:database” dumpfile=OracleDBF.dmp schemas=OracleDBF  — E:database 是你存放DBF文件的位置

–imp OracleUser/OracleuserPassWord@Oracle地址(例如:127.0.0.1) directory=”e:database” dumpfile=OracleDBF.dmp schemas=OracleDBF  — E:database 是你存放DBF文件的位置
导出
expdp OracleUser/OracleuserPassWord@Oracle地址(例如:127.0.0.1)directory=”e:database” dumpfile=OracleDBF.dmp schemas=OracleDBF  — E:database 是你存放DBF文件的位置

–exp OracleUser/OracleuserPassWord@Oracle地址(例如:127.0.0.1)directory=”e:database” dumpfile=OracleDBF.dmp schemas=OracleDBF  — E:database 是你存放DBF文件的位置
———————————————————————————————————
删除用户再创建
drop user OracleDBF cascade;
/
create user OracleDBF identified by salis default tablespace OracleDBF temporary tablespace temp;
/
GRANT DBA TO OracleDBF WITH ADMIN OPTION
/
GRANT SELECT ANY DICTIONARY TO OracleDBF WITH ADMIN OPTION
/
GRANT SELECT ANY SEQUENCE TO OracleDBF WITH ADMIN OPTION
/
GRANT SELECT ANY TABLE TO OracleDBF WITH ADMIN OPTION
/
GRANT SELECT ANY TRANSACTION TO OracleDBF WITH ADMIN OPTION
/
GRANT UNLIMITED TABLESPACE TO OracleDBF WITH ADMIN OPTION
/
GRANT CREATE SESSION,RESOURCE TO OracleDBF WITH ADMIN OPTION
/
GRANT CREATE ANY VIEW to OracleDBF
/

通过SSH通道来访问MySQL

 许多时候当要使用Mysql时,会遇到如下情况:

1. 信息比较重要,希望通信被加密。
2. 一些端口,比如3306端口,被路由器禁用。

对第一个问题的一个比较直接的解决办法就是更改mysql的代码,或者是使用一些证书,不过这种办法显然不是很简单。

这里要介绍另外一种方法,就是利用SSH通道来连接远程的Mysql,方法相当简单。

一 建立SSH通道

只需要在本地键入如下命令:

ssh -fNg -L 3307:127.0.0.1:3306 myuser@remotehost.com

The command tells ssh to log in to remotehost.com as myuser, go into the background (-f) and not execute any remote command (-N), and set up port-forwarding (-L localport:localhost:remoteport ). In this case, we forward port 3307 on localhost to port 3306 on remotehost.com.

二 连接Mysql

现在,你就可以通过本地连接远程的数据库了,就像访问本地的数据库一样。

mysql -h 127.0.0.1 -P 3307 -u dbuser -p db

The command tells the local MySQL client to connect to localhost port 3307 (which is forwarded via ssh to remotehost.com:3306). The exchange of data between client and server is now sent over the encrypted ssh connection.

或者用Mysql Query Brower来访问Client的3307端口。

类似的,用PHP访问:

<?php

$smysql = mysql_connect( “127.0.0.1:3307”, “dbuser”, “PASS” );

mysql_select_db( “db”, $smysql );

?>

Making It A Daemon

A quick and dirty way to make sure the connection runs on startup and respawns on failure is to add it to /etc/inittab and have the init process (the, uh, kernel) keep it going.

Add the following to /etc/inittab on each client:

sm:345:respawn:/usr/bin/ssh -Ng -L 3307:127.0.0.1:3306 myuser@remotehost.com

And that should be all you need to do. Send init the HUP signal ( kill -HUP 1 ) to make it reload the configuration. To turn it off, comment out the line and HUP init again.

基于Redis实现分布式锁

我们知道分布式锁的特性是排他、避免死锁、高可用。分布式锁的实现可以通过数据库的乐观锁(通过版本号)或者悲观锁(通过for update)、Redis的setnx()命令、Zookeeper(在某个持久节点添加临时有序节点,判断当前节点是否是序列中最小的节点,如果不是则监听比当前节点还要小的节点。如果是,获取锁成功。当被监听的节点释放了锁(也就是被删除),会通知当前节点。然后当前节点再尝试获取锁,如此反复)

 

redis.png

本篇文章,主要讲如何用Redis的形式实现分布式锁。后续文章会讲解热点KEY读取,缓存穿透和缓存雪崩的场景和解决方案、缓存更新策略等等知识点,理论知识点较多。

Redis配置

spring:
  redis:
    port: 6379
    database: 0
    host: 127.0.0.1
    password:
    jedis:
      pool:
        max-active: 8
        max-wait: -1ms
        max-idle: 8
        min-idle: 0
    timeout: 5000ms 

Jedis工具类

public class JedisConnectionUtil {

    private static JedisPool pool = null;

    static {
        JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
        jedisPoolConfig.setMaxTotal(100);
        pool = new JedisPool(jedisPoolConfig, "127.0.0.1", 6379);
    }

    public static Jedis getJedis(){
        return pool.getResource();
    }
}

  

分布式锁分析与编码

下面进入正文。因为分布式系统之间是不同进程的,单机版的锁无法满足要求。所以我们可以借助中间件Redis的setnx()命令实现分布式锁。setnx()命令只会对不存在的key设值,返回1代表获取锁成功。对存在的key设值,会返回0代表获取锁失败。这里的value是System.currentTimeMillis() (获取锁的时间)+锁持有的时间。我这里设置锁持有的时间是200ms,实际业务执行的时间远比这200ms要多的多,持有锁的客户端应该检查锁是否过期,保证锁在释放之前不会过期。因为客户端故障的情况可能是很复杂的。比如现在有A,B俩个客户端。A客户端获取了锁,执行业务中做了骚操作导致阻塞了很久,时间应该远远超过200ms,当A客户端从阻塞状态下恢复继续执行业务代码时,A客户端持有的锁由于过期已经被其他客户端占有。这时候A客户端执行释放锁的操作,那么有可能释放掉其他客户端的锁。

我这里设置的客户端等待锁的时间是200ms。这里通过轮询的方式去让客户端获取锁。如果客户端在200ms之内没有锁的话,直接返回false。实际场景要设置合适的客户端等待锁的时间,避免消耗CPU资源。

获取锁

/**
 * 获取锁
 * @param lockName 锁的名字
 * @param acquireTimeout 或得所的超时时间
 * @param lockTimeout 所本身的超时时间
 * @return
 */
public String acquireLock(String lockName, long acquireTimeout, long lockTimeout){
    // 锁标识
    String identifler = UUID.randomUUID().toString();
    String lockKey = "lock" + lockName;
    int lockExpire = (int) (lockTimeout / 1000);
    Jedis jedis = null;

    try {
        jedis = JedisConnectionUtil.getJedis();

        long end = System.currentTimeMillis() + acquireTimeout;
        // 获取锁的限定时间
        while (System.currentTimeMillis() < end) {
            if (jedis.setnx(lockKey, identifler) == 1) {
                // 设置成功
                // 设置超时时间
                jedis.expire(lockKey, lockExpire);
                // 或得锁成功
                return identifler;
            }
            if (jedis.ttl(lockKey) == -1) {
                // 如果一直没有或得锁设置超时时间
                jedis.expire(lockKey, lockExpire);
            }
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }finally {
        jedis.close();
    }
    return null;
}

  

所以,我们要加上锁过期,然后获取锁的策略。通过realKey获取当前的currentValue。currentValue也就是获取锁的时间 + 锁持有的时间。 如果currentValue不等于null 且 currentValue 小于当前时间,说明锁已经过期。这时候如果突然来了C,D两个客户端获取锁的请求,不就让C,D两个客户端都获取锁了吗。如果防止这种现象发生,我们采用getSet()命令来解决。getSet(key,value)的命令会返回key对应的value,然后再把key原来的值更新为value。也就是说getSet()返回的是已过期的时间戳。如果这个已过期的时间戳等于currentValue,说明获取锁成功。
 

释放锁

/**
 * 释放锁
 * @param lockName 锁的名字
 * @param identifler 锁标识
 * @return
 */
public boolean releaseLock(String lockName, String identifler){
    System.out.println(lockName + "释放锁" + identifler);
    String lockKey = "lock:" + lockName;
    Jedis jedis = null;
    boolean isrelease = false;
    try {
        jedis = JedisConnectionUtil.getJedis();
        while (true){
            jedis.watch(lockKey);
            // 判断是否是同一把锁
            if (identifler.equals(jedis.get(lockKey))){
                Transaction transaction = jedis.multi();
                transaction.del(lockKey);
                if (transaction.exec().isEmpty()){
                    continue;
                }
                isrelease = true;
            }
            // TODO 异常
            jedis.unwatch();
            break;
        }
    }finally {
        jedis.close();
    }
    return isrelease;
}

编写测试类用多线程模拟高并发

public class UnitTest extends Thread {

    @Override
    public void run() {
        while (true){
            DistributedLock distributedLock = new DistributedLock();
            String rs = distributedLock.acquireLock("updateOrder", 2000, 5000);
            if (rs != null){
                System.out.println(Thread.currentThread().getName() + "-> 或得锁" + rs);
                try {
                    Thread.sleep(1000);
                    distributedLock.releaseLock("updateOrder", rs);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                break;
            }
        }
    }

    public static void main(String[] args) {
        UnitTest unitTest = new UnitTest();
        for (int i = 0; i < 10; i++) {
            new Thread(unitTest, "tName:" + i).start();
        }
    }
}

代码运行结果

tName:0-> 或得锁a86a2867-a5f9-4b81-8be6-105d50b8a1ab
updateOrder释放锁a86a2867-a5f9-4b81-8be6-105d50b8a1ab
tName:1-> 或得锁bcdeb19c-a47c-4a12-86df-8e5d90b2ecb0
updateOrder释放锁bcdeb19c-a47c-4a12-86df-8e5d90b2ecb0
tName:6-> 或得锁147d9fc8-5e15-4e86-8da6-e029a3e2d92f
updateOrder释放锁147d9fc8-5e15-4e86-8da6-e029a3e2d92f
tName:2-> 或得锁93deb41d-2439-45e7-beb1-2e4ce672e8ca
updateOrder释放锁93deb41d-2439-45e7-beb1-2e4ce672e8ca
tName:4-> 或得锁094f921d-fe9b-46ba-873b-aee2ce974f16
updateOrder释放锁094f921d-fe9b-46ba-873b-aee2ce974f16
tName:5-> 或得锁216e0799-6d22-4ae4-bb83-9efe1e5f80c9
updateOrder释放锁216e0799-6d22-4ae4-bb83-9efe1e5f80c9
tName:9-> 或得锁678e2099-651c-4e23-a648-9fb588ecb42b
updateOrder释放锁678e2099-651c-4e23-a648-9fb588ecb42b
tName:7-> 或得锁f35cdbad-6fde-4f1e-a4c1-321805a39374
updateOrder释放锁f35cdbad-6fde-4f1e-a4c1-321805a39374
tName:3-> 或得锁0913a4a0-805a-48e2-ac5a-c0762b8c4072
updateOrder释放锁0913a4a0-805a-48e2-ac5a-c0762b8c4072
tName:8-> 或得锁a3964a2a-6b9c-4ca3-9ea5-53de854c23ce
updateOrder释放锁a3964a2a-6b9c-4ca3-9ea5-53de854c23ce

 
 
 
本文文字部分参考文章:https://www.jianshu.com/p/83224c0f3bb9

查询代码在哪个视图、存储过程、函数、触发中使用过

    工作中偶尔会出现:想用A数据表替换B数据表,然后把B数据表删除。但是,又不知道B数据表在哪个视图、存储过程、函数、触发器中使用过?

    经过一番度娘,看到实现方法也不难,主要涉及两个系统表:sysobjects及syscomments。

    1、先来复习一下sysobjects表结构。

列名

数据类型

描述

name

sysname

对象名。

id

int

对象标识号。

xtype

char(2)

对象类型。可以是下列对象类型中的一种: 

C = CHECK 约束

D = 默认值或 DEFAULT 约束

F = FOREIGN KEY 约束

L = 日志

FN = 标量函数 

IF = 内嵌表函数 

P = 存储过程 

PK = PRIMARY KEY 约束(类型是 K)

RF = 复制筛选存储过程

S = 系统表 

TF = 表函数 

TR = 触发器 

U = 用户表 

UQ = UNIQUE 约束(类型是 K)

V = 视图 

X = 扩展存储过程

uid

smallint

所有者对象的用户 ID。

info

smallint

保留。仅限内部使用。

status

int

保留。仅限内部使用。

base_schema_ ver

int

保留。仅限内部使用。

replinfo

int

保留。供复制使用。

parent_obj

int

父对象的对象标识号(例如,对于触发器或约束,该标识号为表 ID)。

crdate

datetime

对象的创建日期。

ftcatid

smallint

为全文索引注册的所有用户表的全文目录标识符,对于没有注册的所有用户表则为 0。

schema_ver

int

版本号,该版本号在每次表的架构更改时都增加。

stats_schema_ ver

int

保留。仅限内部使用。

type

char(2) 

对象类型。可以是下列值之一:

C = CHECK 约束

D = 默认值或 DEFAULT 约束

F = FOREIGN KEY 约束

FN = 标量函数

IF = 内嵌表函数

K = PRIMARY KEY 或 UNIQUE 约束

L = 日志

P = 存储过程

R = 规则

RF = 复制筛选存储过程

S = 系统表

TF = 表函数

TR = 触发器

U = 用户表

V = 视图

X = 扩展存储过程

userstat

smallint 

保留。

sysstat

smallint 

内部状态信息。

indexdel

smallint

保留。

refdate

datetime

留用。

version

int 

保留。

deltrig 

int 

保留。

instrig

int 

保留。

updtrig

int 

保留。

seltrig

int 

保留。

category

int

用于发布、约束和标识。

cache

smallint 

保留。

    注:上表来源于:https://blog.csdn.net/xuchaofu/article/details/3458716

    2、顺便记录一下sysobjects的一些经典用法,比如说查表是否存在?

--方法1:
IF EXISTS (SELECT 1 FROM DBO.SYSOBJECTS WHERE ID=OBJECT_ID(N"[DBO].[表名]") AND OBJECTPROPERTY(ID, N"ISUSERTABLE")=1)
    DROP TABLE [DBO].[表名]

--方法2:
IF EXISTS (SELECT 1 FROM SYSOBJECTS WHERE XTYPE="U" AND NAME="表名")
    DROP TABLE [DBO].[表名]

    3、言归正传,重点来了:比如查一个表如[RC_位数]在哪些代码中使用过?

SELECT A.NAME 来源名称,B.TEXT 代码内容,
    CASE
        WHEN A.XTYPE="V" THEN "视图"
        WHEN A.XTYPE="P" THEN "存储过程"
        WHEN A.XTYPE="FN" THEN "标量函数"
        WHEN A.XTYPE="TF" THEN "表函数"
        WHEN A.XTYPE="TR" THEN "触发器"
        ELSE A.XTYPE
    END 类型
FROM SYSOBJECTS A INNER JOIN SYSCOMMENTS B ON A.ID=B.ID
WHERE B.TEXT LIKE "%RC_位数%"
ORDER BY 类型

    结果如下:

    需要说明的是,假如代码如存储过程使用WITH ENCRYPTION等方式加密过时,是查不到结果的。因为加密过的内容,在syscomments中会显示为NULL。

--RC_COST_CO是加密过的存储过程
SELECT A.NAME 来源名称,B.TEXT 代码内容,
    CASE
        WHEN A.XTYPE="V" THEN "视图"
        WHEN A.XTYPE="P" THEN "存储过程"
        WHEN A.XTYPE="FN" THEN "标量函数"
        WHEN A.XTYPE="TF" THEN "表函数"
        WHEN A.XTYPE="TR" THEN "触发器"
        ELSE A.XTYPE
    END 类型
FROM SYSOBJECTS A INNER JOIN SYSCOMMENTS B ON A.ID=B.ID
WHERE A.NAME="RC_COST_CO"
ORDER BY 类型

    结果如下:

 

MySQL执行SQL脚本问题

今天用mysql执行了一个60M的SQL脚本遇到了一些错误,经由网上查询如下:

1.#2006 – MySQL server has gone away 出现该错误代码原因如下:

1、应用程序长时间的执行批量的MySQL语句。

2、执行一个SQL,但SQL语句过大或者语句中含有BLOB或者longblob字段。

2. 1153 – Got a packet bigger than “max_allowed_packet” bytes

在mysql的my.ini配置文件中添加以下代码,重启mysql:

 #max_allowed_packet 参数的作用是用来控制其通信缓冲区的最大长度
 max_allowed_packet=256M
 wait_timeout=288000
 interactive_timeout = 288000

  

Windows重启mysql,进入cmd执行:

启动:输入 net stop mysql

停止:输入 net start mysql

  

如何提高MySQL分布式并发量的方法与方法

答:MySQL分布式并发量是指在多个MySQL节点之间共享负载,以提高系统性能和可伸缩性。以下是提高MySQL分布式并发量的技巧和方法:

1. 使用分区表:将大表分成多个小表,每个小表都是独立的,可以在不同的MySQL节点上运行。这样可以减少单个MySQL节点的负载,提高整个系统的并发量。

2. 使用读写分离:将读操作和写操作分离到不同的MySQL节点上,以减少单个MySQL节点的负载。可以使用MySQL提供的主从复制功能来实现读写分离。

cached或Redis等缓存系统来实现缓存功能。

4. 使用索引:使用索引可以加快查询速度,从而减少MySQL节点的负载。可以使用MySQL提供的索引功能来实现索引。

5. 使用分布式锁:使用分布式锁可以避免并发冲突,从而提高系统的并发量。可以使用ZooKeeper等分布式锁系统来实现分布式锁功能。

总之,提高MySQL分布式并发量需要从多个方面来考虑,包括使用分区表、读写分离、缓存、索引和分布式锁等技巧和方法。通过合理地使用这些技巧和方法,可以提高MySQL分布式并发量,从而提高系统的性能和可伸缩性。

php实现的redis缓存类定义与使用方法示例


(adsbygoogle = window.adsbygoogle || []).push({});

本文实例讲述了PHP实现的redis缓存类定义与使用方法。分享给大家供大家参考,具体如下:

PHP+redis缓存类

<div class="keywords" >

 rush:PHP;">
host = "127.0.0.1"; $this->port = "6379"; $redis = new Redis(); $redis->pconnect($this->host,$this->port); $this->redis=$redis; $this->cacheid = $this->getcacheid(); $this->lifetime = $lifetime; $this->data=$redis->hMGet($this->cacheid,array('content','creattime')); //print_r($this->redis); //print_r($this->data); } /** * 检查缓存是否有效 */ private function isvalid(){ $data=$this->data; if (!$data['content']) return false; if (time() - $data['creattime'] > $this->lifetime) return false; return true; } /** * 写入缓存 * $mode == 0,以浏览器缓存的方式取得页面内容 */ public function write($mode=0,$content='') { switch ($mode) { case 0: $content = ob_get_contents(); break; default: break; } ob_end_flush(); try { $this->redis->hMset($this->cacheid,array('content'=>$content,'creattime'=>time())); $this->redis->expireAt($this->cacheid,time() + $this->lifetime); } catch (Exception $e) { $this->error('写入缓存失败!'); } } /** * 加载缓存 * exit() 载入缓存后终止原页面程序的执行,缓存无效则运行原页面程序生成缓存 * ob_start() 开启浏览器缓存用于在页面结尾处取得页面内容 */ public function load() { if ($this->isvalid()) { echo $this->data['content']; exit(); } else { ob_start(); } } /** * 清除缓存 */ public function clean() { try { $this->redis->hDel($this->cacheid,'creattime')); } catch (Exception $e) { $this->error('清除缓存失败!'); } } /** * 取得缓存文件路径 */ private function getcacheid() { return $this->dir.md5($this->geturl()).$this->ext; } /** * 取得当前页面完整url */ private function geturl() { $url = ''; if (isset($_SERVER['REQUEST_URI'])) { $url = $_SERVER['REQUEST_URI']; } else { $url = $_SERVER['PHP_SELF']; $url .= empty($_SERVER['QUERY_STRING'])?'':'?'.$_SERVER['QUERY_STRING']; } return $url; } /** * 输出错误信息 */ private function error($str) { echo '
<div style="color:red;" >'.$str.'

'; } } //用法: // require_once('redisCache.PHP'); // $cache = new redisCache(10); //设置缓存生存期 // if ($_GET['clearCache']) $cache->clean(); // else $cache->load(); //装载缓存,缓存有效则不执行以下页面代码 // //页面代码开始 // //页面代码结束 // $cache->write(); //首次运行或缓存过期,生成缓存 ?>

(adsbygoogle=window.adsbygoogle||[]).push({});

小编说

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

PHP实现驼峰样式字符串(首字母大写)转换成下划线样式字符串的方法示例


(adsbygoogle = window.adsbygoogle || []).push({});

本文实例讲述了PHP实现驼峰样式字符串(首字母大写)转换成下划线样式字符串的方法。分享给大家供大家参考,具体如下:

1、如何在PHP中把驼峰样式的字符串转换成下划线样式的字符串。例:输入是FooBar的话,输出则是foo_bar

以下是用正则的方式去完成,既然用到正则,方法肯定就不只一种,我们看下下面的方式

<div class="keywords" >

 rush:PHP;"> echo strtolower(preg_replace('/(?<=[a-z])([A-Z])/','_$1','fooBar')); //output:foo_bar echo "

"; echo strtolower(preg_replace('/(?<=[a-z])([A-Z])/','foo')); //output:foo echo "

"; echo strtolower(preg_replace('/(?<=[a-z])([A-Z])/','fooBarB')); //output:foo_bar_b echo "

";

下面我们来解释下,上面正则的意思。具体正则的基本知识,这里篇幅有限就不具体介绍了,文章末尾会附带几篇总结的比较好的正则表达式的文章。

上面的正则里面主要用到了正则表达式中的<span style="color: #0000ff" >

环视边界匹配的语法。具体定义如下(摘抄):

环视的字面意思就是左右看看,需要左右符合一些条件,本质上,它也是匹配边界,对边界有一些要求,这个要求是针对左边或右边的字符串的,根据要求不同,分为四种环视:

<span style="color: #0000ff" >肯定顺序环视,语法是(?="...),要求右边的字符串匹配指定的表达式,比如表达式abc(?=def),(?=def)在字符c右面,即匹配c右面的边界,对这个边界的要求是,它的右边有def,比如abcdef,如果没有,比如abcd,则不匹配;

<span style="color: #0000ff" >否定顺序环视,语法是(?!...),要求右边的字符串不能匹配指定的表达式,比如表达式s(?!ing),匹配一般的s,但不匹配后面有ing的s;

<span style="color: #0000ff" >肯定逆序环视,语法是(?<="...),要求左边的字符串匹配指定的表达式,比如表达式(?<=\s)abc,(?<=\s)在字符a左边,即匹配a左边的边界,对这个边界的要求是,它的左边必须是空白字符;

<span style="color: #0000ff" >否定逆序环视,语法是(?<!...),要求左边的字符串不能匹配指定的表达式,比如表达式(?<!\w)cat,(?<!\w)在字符c左边,即匹配c左边的边界,对这个边界的要求是,它的左边不能是单词字符。

可以看出,环视也使用括号(),不过,它不是分组,不占用分组编号。

继续回到我们上面的正则表达式,第一个小括号(?<=[a-z]),这是肯定逆序环视的语法,要求匹配的字符串的左边必须有小写的字母。第二个小括号则是一个分组,匹配大写的字母,注意正则中的分组编号是从1开始的,这和我们传统编程下标一般是从0开始不同。而第一个小括号本身就是语法,其不占用分组编号,所以后面的$1,则是匹配的第二个小括号中的内容,并将其前面添加一个_符号,最后再整体将整个字符串转换成小写。

既然我们已经能把驼峰法转为下划线的样式了,如果反过来又该怎办呢?

2、如何在PHP中把下划线样式的字符串转换成驼峰样式的字符串。例:输入是foo_bar的话,输出则是FooBar

<div class="brush:php;" >

  _b [1] => b ) return strtoupper($matches[1]); },'foo_bar'); echo $str; //fooBar echo "

"; $str = preg_replace_callback('/_+([a-z])/',function($matches){ return strtoupper($matches[1]); },'foo'); echo $str; //foo echo "

"; $str = preg_replace_callback('/_+([a-z])/','foo_bar_b'); echo $str; //fooBarB echo "

";

这里我们用到了preg_replace_callback函数,该函数执行一个正则表达式搜索并且使用一个回调进行替换。换言之,就是第一个参数是正则表达式,第二个参数是一个匹配到结果的回调函数,第三个参数是需要匹配的字符串。注意,回调函数具体什么时候调用,是每次匹配到结果则调用,调用次数不只为一次,匹配不到则不调用。并且该回调函数的参数是匹配的结果,是完整的匹配,matches[0]是完整的匹配,matches[1]是第一个捕获子组的匹配,以此类推。且回调函数需要把更改的结果return出去,不然则忽略捕获的字符串

正则表达式比较简单,这里就不具体分析了。

正则速查表

详细的正则表达式语法可参考:

正则表达式30分钟入门教程

<span style="color: #800000" >

PS:这里再为大家提供2款非常方便的正则表达式工具供大家参考使用:

<span style="color: #ff6600" >

JavaScript正则表达式在线测试工具:

<span style="color: #ff6600" >

正则表达式在线生成工具:

(adsbygoogle=window.adsbygoogle||[]).push({});

小编说

CI框架(CodeIgniter)公共模型类定义与用法示例


(adsbygoogle = window.adsbygoogle || []).push({});

本文实例讲述了CI框架(CodeIgniter)公共模型类定义与用法。分享给大家供大家参考,具体如下:

我们都知道,操作数据库的方法都写在模型中。但是一般情况下,一张表往往至少对应4个操作,也就是所谓crud。那么如果20张表,所对应的模型方法,就达到了80个,重复的操作显然这已经是一个体力活儿。

那么就对单表操作时,我们进行一下简单的封装。如下是ci框架的示例:

<div class="keywords" >

 rush:PHP;">
$val) $key为要操作的字段,$val为要操作的值 * array('name !=' => $name,'id <' => $id,'date >' => $date); * like(模糊查询) * array('title' => $match,'page1' => $match,'page2' => $match) * customStr(自定义字符串): * "name='Joe' AND status='boss' OR status='active'" * in: * array('userName' => array('Frank','Todd','James')) * @param string $page 当前页数(查询全部数据时,设置为空) * @param string $limit 查询条数(查询全部数据时,设置为空) * @param array $order 排序条件: * array($key => $val) * $key为排序依据的字段, * $val为排序的方式【asc (升序,默认)或 desc(降序),或 random(随机)】 * @$isReturnCount boole 是否返回总条数 * @return array|boolean * */ public function pageData($model,$table,$param = array(),$select_fields = '',$page = '1',$limit = '15',$order = array(),$isReturnCount = true){ if(empty($model) || empty($table)){ return false; } $this -> load -> model($model); $table = $this->db->dbprefix.$table; //处理查询字段 if(!empty($select_fields)){ $this->db->select($select_fields)->from($table); }elseif(isset($this -> $model -> selectFields)){ $this->db->select($this -> $model -> selectFields)->from($table); }else{ $this->db->select('*')->from($table); } //处理查询条件 if (is_array($param) && count($param) > 0){ $this -> parseParam($param); } //统计总数 if($isReturnCount){ $rs['count'] = $this->db->count_all_results('',false);//不重置查询构造器 array_push($this -> errors,$this->db->last_query()); } //分页数据处理 if(isset($page) && isset($param['limit'])){ //分页边界值 设置 $offset = $param['page'] <= 1 ? 0 : ($param['page']-1) * $param['limit']; $this->db->limit($param['limit'],$offset); } //排序规则的组合 if (!empty($order) && is_array($order)) { foreach ($order as $key => $val) { $this->db->order_by($key,$val); } }else{ //默认按照此表的主键倒序 $primary = $this->getPrimary(); if(!empty($primary)) { $this->db->order_by($primary,'DESC'); } } $query = $this->db->get(); array_push($this -> errors,$this->db->last_query()); $rs['list'] = $query->result_array(); return $rs; } /** * 解析参数 */ private function parseParam($param){ if(isset($param['compare'])){ foreach ($param['compare'] as $key => $val){ if (!empty($val)) $this->db->where($key,$val); } } if(isset($param['like'])){ foreach ($param['like'] as $key => $val){ if (!empty($val)) $this->db->like($key,$val); } } if(isset($param['in'])){ foreach ($param['in'] as $key => $val){ if (!empty($val)) $this->db->where_in($key,$val); } } if(isset($param['customStr'])){ if (!empty($val)) $this->db->where($param['customStr']); } } /** * 新增信息 * @param string $table 表名称 * @param array $param 数据变量 * @return INT ID */ public function add($table = '',$param = array()) { if(empty($table) || !is_array($param) || empty ($param)){ return FALSE; } //写入数据表 $this->db->insert($table,$param); array_push($this -> errors,$this->db->last_query()); //返回记录ID return $this->db->insert_id(); } /** * 更新分类信息 * @param string $table 表名称 * @param string $primary 表主键 * @param int $id 分类ID * @param array $param 更新的数据 * @return type */ public function update($table = '',$primary = '',$id = 0,$param = array()) { if(empty($table) || empty($primary) || empty($param) || empty($id)) { return FALSE; } $id = (int)$id; $this->db->where($primary,$id) ->limit(1) ->update($table,$param); array_push($this -> errors,$this->db->last_query()); return $this->db->affected_rows(); } /** * 删除指定ID记录 * @param string $table 表名称 * @param string $primary 表主键 * @param array $id 分类ID * @return int */ public function delete($table = '',$id = array()){ if(empty($table) || empty($primary) || empty($id)){ return FALSE; } $this->db->where_in($primary,$id) ->delete($table); array_push($this -> errors,$this->db->last_query()); return $this->db->affected_rows(); } /** * 获取表的主键 * @param string $database 数据库名称 * @param strting $table 表名称 */ public function getPrimary($table = '',$database = self::dataBase) { if(empty($database) || empty($table)) { return FALSE; } $sql = "SELECT k.column_name FROM information_schema.table_constraints t JOIN information_schema.key_column_usage k USING (constraint_name,table_schema,table_name) WHERE t.constraint_type='PRIMARY KEY' AND t.table_schema='qndnew' AND t.table_name='qnd_user'"; $query = $this->db->query($sql)->result_array(); return isset($query[0]['column_name']) ? $query[0]['column_name'] : false; } /** * debug sql语句 */ public function debugsql(){ if(count($this->errors) > 0){ foreach($this->errors as $val){ echo $val.'

'; } } } }

具体的业务逻辑模型如下:

<div class="keywords" >

 rush:PHP;"> class User_model extends My_model { const USER = 'qnd_user'; public $selectFields = array( 'id','guid','phone','userName','password','headPortraits','nickName','createTime',); const SMS_ROLE = 'qnd_role'; public function __construct() { } }

控制器中测试如下:

<div class="brush:php;" >

  load -> model('User_model'); // 載入 model $whereArr = array( 'compare'=>array( 'userName' => 'Frank',),); $rs = $this -> User_model -> pageData('User_model','user',$whereArr); print_r($rs); $this -> User_model -> debugsql(); }

(adsbygoogle=window.adsbygoogle||[]).push({});

小编说

PHP弱类型语言中类型判断操作实例详解


(adsbygoogle = window.adsbygoogle || []).push({});

本文实例讲述了PHP弱类型语言中类型判断操作。分享给大家供大家参考,具体如下:

1、PHP一个数字和一个字符串进行比较或者进行运算时,PHP会把字符串转换成数字再进行比较。PHP转换的规则的是:若字符串以数字开头,则取开头数字作为转换结果,若无则输出0。

例如:123abc转换后应该是123,而abc则为0,0==0这当然是成立的.具体可以参考官方手册:如果比较一个整数和字符串,则字符串会被转换为整数

<div class="keywords" >

 rush:PHP;"> $a = ($b=4)+5; echo $a; //9 echo '

'; var_dump(1=='1a'); //true echo '

'; var_dump(1=='2a'); //false echo '

'; var_dump(1=='a1'); //false echo '

'; var_dump("1"=='1a'); //false echo '

'; var_dump('51a'+6); //57 echo '

'; var_dump('a51'+6); //6

2、在PHP中,== 会先进行类型转换,再进行对比,而===会先比较类型,如果类型不同直接返回不相等,参考如下示例

<div class="keywords" >

 rush:PHP;"> $a = null; $b = ''; $c = 0; echo ($a == $b) ? 1 : 0; // 输出1 echo ($a === $b) ? 1 : 0; // 输出0 echo ($a == $c) ? 1 : 0; // 输出1 echo ($a === $c) ? 1 : 0; // 输出0 echo ($b == $c) ? 1 : 0; // 输出1 echo ($b === $c) ? 1 : 0; // 输出0

3、PHP中的empty和isset函数

1)变量为:0,"0",null,'',false,array()时,使用empty函数,返回的都为true

2)变量未定义或者为null时,isset函数返回的为false,其他都未true

<div class="keywords" >

 rush:PHP;"> $a = null; $b = ''; $c = 0; $d = "0"; $e = false; $f = array(); var_dump(empty($a)); //true echo '

'; var_dump(empty($b)); //true echo '

'; var_dump(empty($c)); //true echo '

'; var_dump(empty($d)); //true echo '

'; var_dump(empty($e)); //true echo '

'; var_dump(empty($f)); //true echo '

'; var_dump(isset($a)); //false echo '

'; var_dump(isset($b)); //true echo '

'; var_dump(isset($c)); //true echo '

'; var_dump(isset($d)); //true echo '

'; var_dump(isset($e)); //true echo '

'; var_dump(isset($f)); //true echo '

'; var_dump(isset($g)); //false

4、使用strpos之类的函数要用恒等于来判断

<div class="keywords" >

 rush:PHP;"> $sms = "abc"; if(strpos($sms,'a') != false ){ echo 1; }else{ echo 2; }

这种情况,很明显是判断abc字符串中,是否包含a,预期结果应该是要输出1的,实际结果输出为2。这是因为strpos函数匹配不到目标字符串时,返回false,匹配到了目标字符串时,会返回目标字符串,在搜索字符串中的索引位置,此处返回了0。

<div class="keywords" >

 rush:PHP;"> $sms = "abc"; if(strpos($sms,'a') !== false ){ echo 1; }else{ echo 2; }

改成恒等于,才是正确的。

5、换行需要双引号

<div class="keywords" >

 rush:PHP;"> $time = date('Y-m-d H:i:s').'\r\n'; file_put_contents('filename.txt',$time,FILE_APPEND);

如果这样写,打开文件是这样的2016-09-02 08:04:04\r\n2016-09-02 08:04:05\r\n2016-09-02 08:04:05\r\n2016-09-02 08:04:05\r\n2016-09-02 08:04:22

正确的写法是\r\n,使用双引号包起来

(adsbygoogle=window.adsbygoogle||[]).push({});

小编说

详解PHP使用日期时间处理器Carbon人性化显示时间


(adsbygoogle = window.adsbygoogle || []).push({});

本文介绍了PHP使用日期时间处理器Carbon人性化显示时间,分享给大家,具体如下:

Carbon 日期时间处理库可以很方便的处理时间,github地址为https://github.com/briannesbitt/carbon

可以通过 Composer 很方便的来安装 Carbon

<div class="keywords" >

 rush:plain;"> # composer require nesbot/carbon

使用方法也很简单

<div class="keywords" >

 rush:PHP;">
timestamp; //人性化显示时间 echo Carbon::createFromTimestamp($ts)->diffForHumans();

上面的打印结果是1天前

在 Laravel 框架中的使用方法

首先为了显示中文,在app/Providers/AppServiceProvider.PHP中添加 \Carbon\Carbon::setLocale('zh');boot()方法中,如下:

<div class="keywords" >

 rush:PHP;"> public function boot(){ \Carbon\Carbon::setLocale('zh'); }

然后就可以使用了,例如在ArticleController中的一个方法中人性化显示文章发表日期,假如发表日期为时间戳,在头部引用一下Carbon,添加如下代码

<div class="keywords" >

 rush:PHP;"> use Carbon\Carbon;

人性化发表时间

<div class="brush:php;" >

 diffForHumans();

(adsbygoogle=window.adsbygoogle||[]).push({});

小编说

php使用 readfile() 函数设置文件大小大小的方法


(adsbygoogle = window.adsbygoogle || []).push({});

使用PHP ZipArchive生成的压缩包,小的压缩包都能下载,今天遇到个150M以上的就报404错误,第一想到的就是文件大小超出了PHP默认设置,修改方法有两个:

PHP.ini:memory_limit

memory_limit是设置内存限制的,如果使用readfile()读取文件就会和这个有关,直接修改这个值保存后重启PHP-fpm即可。

<div class="keywords" >

 rush:PHP;"> memory_limit = 128M

最后记得:service PHP-fpm restart

ini_set

PHP ini_set用来设置PHP.ini的值,在函数执行的时候生效,那我们直接用来修改内存执行大小即可,有些朋友用的如果是虚拟空间的话,这个函数就是救星了。

<div class="keywords" >

 rush:PHP;"> ini_set('memory_limit','512M');

完整的示例:

<div class="keywords" >

 rush:PHP;"> set_time_limit(0); ini_set('memory_limit','512M'); header("Cache-Control: public"); header("Content-Description: File Transfer"); header('Content-disposition: attachment; filename=' . basename($zipfile)); header("Content-Type: application/zip"); header("Content-transfer-encoding: binary"); header('Content-Length: ' . filesize($zipfile)); ob_clean(); flush(); @readfile($zipfile); unlink($zipfile);

总结

(adsbygoogle=window.adsbygoogle||[]).push({});

小编说

浅谈PHP中new self()和new static()的区别


(adsbygoogle = window.adsbygoogle || []).push({});

本文介绍了PHP中new self()和new static()的区别,分享给大家,也给自己留个笔记。

1.new static()是在PHP5.3版本中引入的新特性。

2.无论是new static()还是new self(),都是new了一个新的对象。

3.这两个方法new出来的对象有什么区别呢,说白了就是new出来的到底是同一个类实例还是不同的类实例呢?

为了探究上面的问题,我们先上一段简单的代码:

<div class > <pre father {

public function getNewFather() {

return new self();

}

public function getNewCaller() {
return new static();
}

}

$f = new Father();

print get_class($f->getNewFather());
print get_class($f->getNewCaller());

注意,上面的代码get_class()方法是用于获取实例所属的类名。

这里的结果是:无论调用getNewFather()还是调用getNewCaller()返回的都是Father这个类的实例。

打印的结果为:FatherFather

到这里,貌似new self()和new static()是没有区别的。我们接着往下走:

<div class > <pre sun1 extends father {

}

class Sun2 extends Father {

}

$sun1 = new Sun1();
$sun2 = new Sun2();

print get_class($sun1->getNewFather());
print get_class($sun1->getNewCaller());
print get_class($sun2->getNewFather());
print get_class($sun2->getNewCaller());

看上面的代码,现在这个Father类有两个子类,由于Father类的getNewFather()和getNewCaller()是public的,所以子类继承了这两个方法。

打印的结果是:FatherSun1FatherSun2

我们发现,无论是Sun1还是Sun2,调用getNewFather()返回的对象都是类Father的实例,而getNewCaller()则返回的是调用者的实例。

即$sun1返回的是Sun1这个类的实例,$sun2返回的是Sun2这个类的实例。

现在好像有点明白new self()和new static()的区别了。

首先,他们的区别只有在继承中才能体现出来,如果没有任何继承,那么这两者是没有区别的。

然后,new self()返回的实例是万年不变的,无论谁去调用,都返回同一个类的实例,而new static()则是由调用者决定的。

(adsbygoogle=window.adsbygoogle||[]).push({});

小编说

浅谈Laravel中的一个后期静态绑定


(adsbygoogle = window.adsbygoogle || []).push({});

关于 PHP 的 new static 延迟静态绑定,或者叫后期静态绑定,在 Laravel 中遇到一个使用上的问题。如下,在 Laravel 中调用 Model 新增数据的时候,首先给 Model 加了一个获取分表的方法:

<div class="brush:php;" > <pre protected function addtomessage($msgtype,$userid,$commentid,$replycommentid,$replyuserid,$gameid) { if (!$userid) return false; }

$table = ‘t
message‘ . hashID($userID,100);

$this->message->setTable($table)->create([

‘msg_type’ => $msgType,’user_id’ => $userID,’comment_id’ => $commentID,’reply_comment_id’ => $replyCommentID,’reply_user_id’ => $replyUserID,’game_id’ => $gameID,’is_read’ => 0,’created_at’ => date(‘Y-m-d H:i:s’),]);

return true;

}

这里 setTable 方法是在 Model 里定义的获取分表的方法:

<div class="brush:php;" >

 table = $table; return $this; }

从报错日志中发现 $this->table 并没有生效,但实际上在调用 create 方法之前打印表名的时候是期望的值,这里调用 create 方法为什么 $this->table 没有被重置呢?

这里 $this->message 是一个继承 Model 类的模型类,其中 create 方法:

<div class="brush:php;" > <pre public static function create(array $attributes="[])" { $model="new" static($attributes);

$model->save();

return $model;
}

位于 vendor\laravel\framework\src\Illuminate\Database\Eloquent\Model.PHP Line 557.

因为 Laravel 框架的这个 Model 类是一个 abstract 类型,PHP 中 abstract 类可以用 new static 后期静态绑定的方式实例化,而 create 方法里 $model = new static($attributes) 实际上就是重新实例化了并返回,而调用者 Model 类没有定义 table 属性,所以这个时候 $this->table 是没有值的。

<p style="text-align: center" >

解决办法是用 save 方法即可,如图所示。实际上 create 方法也调用了 save 方法。

实验

一个抽象类 A,有个 create 方法,通过延迟静态绑定实例化并返回。B 类继承 A,test 方法中修改父类的 name 属性。

<div class="keywords" >

 rush:PHP;">
<!--?php
abstract class A

{

protected $name = "tanteng";

public static function create()
{
return new static();
}
}

class B extends A
{
//protected $name = '纸牌屋弗兰克';

public function test()
{
$this->name = "Tony Tan";
return $this;
}
}

$obj1 = (new B)->test();
$obj2 = (new B)->test()->create();
var_dump($obj1);
var_dump($obj2);

结果显示 $obj1 和 $obj2 这两个实例都是 B 的实例,调用 test 方法属性 name 改变了,但是调用 create 方法后,name 属性并没有改变。 这也就是在本文中说的在 Lavarel 中遇到的场景。 (这里如果把注释打开,打印的 name 就是重写的值)

如果把抽象类 A 改成普通类,new static 改成 new self 的方式实例化,结果就不同了,打印的属性 name 都是各自类的属性。

参考链接

(adsbygoogle=window.adsbygoogle||[]).push({});

小编说

php readfile()修改文件上传大小设置


(adsbygoogle = window.adsbygoogle || []).push({});

使用PHP ZipArchive生成的压缩包,小的压缩包都能下载,今天遇到个150M以上的就报404错误,第一想到的就是文件大小超出了PHP默认设置,修改方法有两个:

PHP.ini:memory_limit

memory_limit是设置内存限制的,如果使用readfile()读取文件就会和这个有关,直接修改这个值保存后重启PHP-fpm即可。

PHP 下载文件大小设置PHP

<div class="keywords" >

 rush:PHP;"> memory_limit = 128M

最后记得:service PHP-fpm restart

ini_set

PHP ini_set用来设置PHP.ini的值,在函数执行的时候生效,那我们直接用来修改内存执行大小即可,有些朋友用的如果是虚拟空间的话,这个函数就是救星了。

PHP 设置PHP.ini值PHP

<div class="keywords" >

 rush:PHP;"> ini_set('memory_limit','512M');

完整的示例:

<div class="keywords" >

 rush:PHP;"> set_time_limit(0); ini_set('memory_limit','512M'); header("Cache-Control: public"); header("Content-Description: File Transfer"); header('Content-disposition: attachment; filename=' . basename($zipfile)); header("Content-Type: application/zip"); header("Content-transfer-encoding: binary"); header('Content-Length: ' . filesize($zipfile)); ob_clean(); flush(); @readfile($zipfile); unlink($zipfile);

(adsbygoogle=window.adsbygoogle||[]).push({});

小编说

PHP验证码无法显示的原因及解决办法


(adsbygoogle = window.adsbygoogle || []).push({});

PHP验证码无法显示的原因及解决办法

一、如果是utf-8,就有可能是BOM没有清除

二、在Header(“Content-type: image/PNG”); 之前有输出

<div class="keywords" >

 rush:PHP;"> $image_width=70; //设置图像宽度 $image_height=18; //设置图像高度 $new_number=$_GET[num]; //$new_number=5; $num_image=imagecreate($image_width,$image_height); //创建一个画布 imagecolorallocate($num_image,255,255); //设置画布的颜色 $black=imagecolorallocate($num_image,0); /**/for($i=0;$i
<strlen($new_number);$i++){ 循环读取session变量中的验证码 $font="mt_rand(3,5);" 设置
随机的字体 $x=mt_rand(1,8)+$image_width*$i/4; //设置随机字符所在位置的X坐标 $y=mt_rand(1,$image_height/4); //设置随机字符所在位置的Y坐标 $color=imagecolorallocate($num_image,mt_rand(0,100),150),200)); //设置字符的颜色 imagestring($num_image,$font,$x,$y,$new_number[$i],$color); //水平输出字符 } header("content-type:image/png"); //设置创建图像的格式 imagepng($num_image); //生成PNG格式的图像 imagedestroy($num_image); //释放图像资源

(adsbygoogle=window.adsbygoogle||[]).push({});

小编说

Redis在Laravel项目中的应用实例详解


(adsbygoogle = window.adsbygoogle || []).push({});

前言

本文主要给大家介绍了关于Redis在Laravel项目中的应用实例,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍:

在初步了解Redis在Laravel中的应用 那么我们试想这样的一个应用场景 一个文章或者帖子的浏览次数的统计 如果只是每次增加一个浏览量

就到数据库新增一个数据 如果请求来那个太大这对数据库的消耗也就不言而喻了吧 那我们是不是可以有其他的解决方案

这里的解决方案就是 即使你的网站的请求量很大 那么每次增加一个访问量就在缓存中去进行更改 至于刷新MysqL数据库可以自定义为

多少分钟进行刷新一次或者访问量达到一定数量再去刷新数据库 这样数据也是准确的 效率也比直接每次刷新数据库要高出许多了

既然给出了相应的解决方案 我们就开始实施

我们以一篇帖子的浏览为例 我们先去创建对应的控制器

<div class="keywords" >

 rush:PHP;"> $ PHP artisan make:controller PostController

再去生成需要用到的 Model

<div class="keywords" >

 rush:PHP;"> $ PHP artisan make:model Post -m

填写posts的迁移表的字段内容

<div class="brush:php;" >

 increments('id'); $table->string("title"); $table->string("content"); $table->integer('view_count')->unsigned(); $table->timestamps(); });

还有就是我们测试的数据的Seeder填充数据

<div class="brush:php;" >

 define(App\Post::class,function (Faker\Generator $faker) { return [ 'title' => $faker->sentence,'content' => $faker->paragraph,'view_count' => 0 ]; });

定义帖子的访问路由

<div class="keywords" >

 rush:PHP;"> Route::get('/post/{id}','PostController@showPost');

当然我们还是需要去写我们访问也就是浏览事件的(在app/providers/EventServiceProvider中定义)

<div class="brush:php;" >

  [ // 'App\Listeners\EventListener','App\Listeners\PostEventListener',],];

执行事件生成监听

<div class="keywords" >

 rush:PHP;"> $ PHP artisan event:generate

之前定义了相关的路由方法 现在去实现一下:

<div class="brush:php;" >

 cacheExpires,function () use ($id) { return Post::whereId($id)->first(); }); 

//获取客户端请求的IP
$ip = $request->ip();

//触发浏览次数统计时间
event(new PostViewEvent($post,$ip));

return view('posts.show',compact('post'));
}

这里看的出来就是以Redis作为缓存驱动 同样的 会获取获取的ip目的是防止同一个ip多次刷新来增加浏览量

Redis的key的命名以:分割 这样可以理解为一个层级目录 在可视化工具里就可以看的很明显了

<div class="keywords" >

 rush:xhtml;">  <Meta charset="utf-8"> <Meta http-equiv="X-UA-Compatible" content="IE=edge"> <Meta name="viewport" content="width=device-width,initial-scale=1">
Bootstrap Template

html,body{
width: 100%;
height: 100%;
}
*{
margin: 0;
border: 0;
}
.jumbotron{
margin-top: 10%;
}
.jumbotron>span{
margin: 10px;
}

PHP实现的自定义图像居中裁剪函数示例【测试可用】


(adsbygoogle = window.adsbygoogle || []).push({});

本文实例讲述了PHP实现的自定义图像居中裁剪函数。分享给大家供大家参考,具体如下:

图像居中裁减的大致思路:

1.首先将图像进行缩放,使得缩放后的图像能够恰好覆盖裁减区域。(imagecopyresampled — 重采样拷贝部分图像并调整大小)

2.将缩放后的图像放置在裁减区域中间。(imagecopy — 拷贝图像的一部分)

3.裁减图像并保存。(imagejpeg | imagepng | imagegif — 输出图象到浏览器或文件)

具体代码:

<div class="brush:php;" >

  ($target_w / $target_h)); $resize_w = $judge ? ($source_w * $target_h) / $source_h : $target_w; $resize_h = !$judge ? ($source_h * $target_w) / $source_w : $target_h; $start_x = $judge ? ($resize_w - $target_w) / 2 : 0; $start_y = !$judge ? ($resize_h - $target_h) / 2 : 0; /* 绘制居中缩放图像 */ $resize_img = imagecreatetruecolor($resize_w,$resize_h); imagecopyresampled($resize_img,$image,$resize_w,$resize_h,$source_w,$source_h); $target_img = imagecreatetruecolor($target_w,$target_h); imagecopy($target_img,$resize_img,$start_x,$start_y,$resize_h); /* 将图片保存至文件 */ if (!file_exists(dirname($target))) mkdir(dirname($target),0777,true); switch (exif_imagetype($source)) { case IMAGETYPE_JPEG: imagejpeg($target_img,$target); break; case IMAGETYPE_PNG: imagepng($target_img,$target); break; case IMAGETYPE_GIF: imagegif($target_img,$target); break; } // return boolval(file_exists($target));//PHP5.5以上可用boolval()函数获取返回的布尔值 return file_exists($target)?true:false;//兼容低版本PHP写法 }

<div class="keywords" >

 rush:PHP;"> //==================函数使用方式==================== // 原始图片的路径 $source = '../source/img/middle.jpg'; $width = 480; // 裁剪后的宽度 $height = 480;// 裁剪后的高度 // 裁剪后的图片存放目录 $target = '../source/temp/resize.jpg'; // 裁剪后保存到目标文件夹 if (image_center_crop($source,$target)) { echo "原图1440*900为:"; echo ""; echo "修改后图片480*480为:"; }

运行效果:

原图1440*900为:


修改后图片480*480为:

同理,480*320,、800*600等尺寸的图片只需修改相应参数即可。

<span style="font-size: medium" ><span >

附:代码测试中遇到的问题

报错:

call an undefined function exif_imagetype()

解决方法:

打开扩展 extension=PHP_exif.dll

并将extension=PHP_mbstring.dll,放到extension=PHP_exif.dll前边

另:

boolval()函数为PHP5.5版本以上才能使用的函数,本文测试代码中为兼容低版本,使用如下语句代替:

<div class="keywords" >

 rush:PHP;"> return file_exists($target)?true:false;

<span style="color: #800000" >

PS:这里再为大家推荐几款相关的图片在线工具供大家参考使用:

<span style="color: #ff6600" >

在线图片格式转换(jpg/bmp/gif/png)工具:

<span style="color: #ff6600" >

在线PS图像处理工具:

<span style="color: #ff6600" >

ICO图标在线生成工具:

(adsbygoogle=window.adsbygoogle||[]).push({});

小编说

如何利用预加载优化Laravel Model查询详解


(adsbygoogle = window.adsbygoogle || []).push({});

前言

本文主要给大家介绍了关于利用预加载优化Laravel Model查询的相关内容,分享出来供大家参考学习,话不多说了,来一起看看详细的介绍:

介绍

对象关系映射(ORM)使数据库的工作变得非常简单。 在以面向对象的方式定义数据库关系时,可以轻松查询相关的模型数据,开发人员可能不会注意底层数据库调用。

下面将通过一些例子,进一步帮助您了解如何优化查询。

假设您从数据库收到了100个对象,并且每个记录都有1个关联模型(即belongsTo)。 默认使用ORM将产生101个查询; 如下所示:

<div class="brush:php;" >

 get(); //一次查询 

$authors = array_map(function($post) {
// 对作者模型生成查询
return $post->author->name;
},$posts);

我们在查询时没有告诉Post模型,我们还需要所有的作者,所以每次从单个Post模型实例获取作者的名字时,都会发生单独的查询。

array_maps时发生100次查询,加上先前一次查询,累计产生101次查询。

预加载

接下来,如果我们打算使用关联的模型数据,我们可以使用预加载将该101个查询总数减少到2个查询。 只需要告诉模型你需要什么来加载。如下:

<div class="brush:php;" >

 limit(100)->get();//2次查询 

$authors = array_map(function($post) {
// 对作者模型生成查询
return $post->author->name;//这里讲不在产生查询
},$posts);

如果你开启了sql日志,你将看到上述预加载将只会产生两条查询:

<div class="keywords" >

 rush:PHP;"> select * from `posts` select * from `authors` where `authors`.`id` in (?,?,?) [1,2,3,4,5]

如果您有多个关联模型,则可以使用数组加载它们:

<div class="brush:php;" >

 get();

接下来我们重新定义如下关系

<div class="brush:php;" >

  belongsTo -> Author //每个文章只属于一个用户 Author -> hasMany -> Post //每个用户拥有多个文章 Author -> hasOne -> Profile //每个用户只有一个简介

考虑下述情况:获取已发布文章所属作者的个人简介。

<div class="brush:php;" >

 get();//两次查询 

//根据每个 作者 获取其简介
$posts->map(function ($post) {
//虽然我们直接通过$author = $post->author不会产生查询,
//但当调用$author->profile时,每次都会产生一个新查询
return $post->author->profile;
});

假设上述App\Post::with('author')->get()有100条记录,将会产生多少条查询呢?

通过优化预加载,我们可以避免嵌套关系中的额外查询。

<div class="brush:php;" >

 get();//三次查询 

$posts->map(function ($post) {
//不在产生新查询
return $post->author->profile;
});

你可以打开你的sql日志看到对应的三条查询。

<div class="keywords" >

 rush:PHP;"> select * from `posts` select * from `authors` where `authors`.`id` in (?,?) [.....] select * from `profiles` where `profiles`.`author_id` in (?,?) [.....]

懒惰加载

有时候您可能只需要根据条件收集相关联的模型。 在这种情况下,您可以懒惰地调用相关数据的其他查询:

<div class="brush:php;" > <pre $posts="App\Post::all();//一次查询"

$posts->load('author.profile');//两次查询

$posts->map(function ($post) {

//不在产生新查询

return $post->author->profile;

});

查看您的sql日志,总共看到三个查询,但只有调用$posts->load()时才会显示。

结论

希望您更加了解有关加载型号的更多信息,并了解其在更深层次上的工作原理。 Laravel相关的文档已经很全面了,希望额外的实践练习可以帮助您更有信心优化关系查询。

总结

(adsbygoogle=window.adsbygoogle||[]).push({});

小编说

关于PHP中协程和阻塞的一些理解与思考


(adsbygoogle = window.adsbygoogle || []).push({});

前言

本文主要给大家介绍了关于PHP中协程和阻塞的理解与思考,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍:

进程、线程、协程

关于进程、线程、协程,有非常详细和丰富的博客或者学习资源,我不在此做赘述,我大致在此介绍一下这几个东西。

PHP中的协程实现基础 yield

yield的根本实现是生成器类,而迭代器类是迭代器接口的实现:

<div class="keywords" >

 rush:PHP;"> Generator implements Iterator { public mixed current ( void ) // 返回当前产生的值 public mixed key ( void ) // 返回当前产生的键 public void next ( void ) // 生成器继续执行 public void rewind ( void ) // 重置迭代器,如果迭代已经开始了,这里会抛出一个异常。 // renwind的执行将会导致第一个yield被执行,并且忽略了他的返回值. public mixed send ( mixed $value ) // 向生成器中传入一个值,并且当做 yield 表达式的结果,然后继续执行生成器。如果当这个方法被调用时,生成器 // 不在 yield 表达式,那么在传入值之前,它会先运行到第一个 yield 表达式。 public void throw ( Exception $exception ) // 向生成器中抛入一个异常 public bool valid ( void ) // 检查迭代器是否被关闭 public void __wakeup ( void ) // 序列化回调,抛出一个异常以表示生成器不能被序列化。 }

以上解析可以参考PHP官方文档。

...

以及这篇详细文档:

我就以他实现的协程多任务调度为基础做一下例子说明并说一下关于我在阻塞方面所做的一些思考。

自定义简单定时执行任务示例:

(此例子必须依赖于以上鸟哥实现的协程调度代码)

<div class >

 start = time(); $this->timer = $timer; $this->callback = $callback; } public function run() { if($this->valid()) { $callback = $this->callback; $callback($this->value ++,$this); $this->start = time(); } } /** * 定时执行检查 */ public function valid() { $end = time(); if($end - $this->start >= $this->timer) { return true; } else { return false; } } public function setEnd($isEnd) { $this->isEnd = $isEnd; } public function getEnd() { return $this->isEnd; } } 

/**

以上实现的是:

思考:

我为什么要做以上这件事情呢?因为我发现协程实现虽然很强大也很有意思,能让多任务并行,但是我在其中一个任务里调用系统函数 sleep() 的时候,阻塞任务会阻止协程切换,其实从协程的实现原理上来书也是这么回事。

那么,我也就想模拟协程阻塞,但是不产生阻塞看是否可行。PHP本身只提供了生成器为协程调用提供了支撑,如果不依赖扩展,没有提供多线程的程序实现方式,没有java那么强大,可以开子线程进行实现。

我印象中java的子线程是独立执行且不会相互阻塞的,所以我在想,PHP既然可以实现类似于多线程这样的机制,那么能不能实现调用过程中非阻塞呢?

经过这样一个实现和思考,一开始是陷入了一个误区的,是由于PHP原生函数 sleep() 阻塞造成的思维误区,那就是认为要想真正实现非阻塞或者说实现异步的话,是必须依赖于语言底层的。

后来,我想明白了一个道理,既然某个方法或者函数在执行过程中,会产生阻塞,那么把当前这个方法换成自定义的,做成非阻塞(相对于整个协程调度来说)不就行了吗?比如上面的定时执行我自己实现了一个。

而另一方面,协程调度本身的目的也是为了把任务执行过程切成尽量小片,从而快速切换执行,达到并行的目的。从这方面来看,协程应该也算是一种程序设计思想。

以下是一个程序切成尽量小片执行的例子:

<div class="keywords" >

 rush:PHP;"> // 一个简单的例子
<!--?PHP function xrange($start,$end,$step = 1) { for ($i = $start; $i <= $end; $i += $step) { yield $i; } }

foreach (xrange(1,1000000) as $num) {
echo $num,"\n";
}

这个例子是把原本用 range 生成一个很大的整型数组的方式切换为分片执行,也就是说在遍历的时候再去取到指定的值,从代码上来看,内存消耗相对于之前来说就非常小了。

总结

(adsbygoogle=window.adsbygoogle||[]).push({});

小编说

javascript Array对象length属性

length 属性可设置或返回数组中元素的数目。

语法:
arrayObject.length

数组的 length 属性总是比数组中定义的最后一个元素的下标大 1。对于那些具有连续元素,而且以元素 0 开始的常规数组而言,属性 length 声明了数组中的元素的个数。

数组的 length 属性在用构造函数 Array() 创建数组时被初始化。给数组添加新元素时,如果必要,将更新 length 的值。

设置 length 属性可改变数组的大小。如果设置的值比其当前值小,数组将被截断,其尾部的元素将丢失。如果设置的值比它的当前值大,数组将增大,新的元素被添加到数组的尾部,它们的值为 undefined。

例子:

<script type="text/javascript">

var arr = new Array(3)
arr[0] = "John"
arr[1] = "Andy"
arr[2] = "Wendy"

document.write("Original length: " + arr.length)
document.write("<br />")

arr.length=5
document.write("New length: " + arr.length)

</script>

 

打印:

Original length: 3
New length: 5

如何高效的检测一个数组是否包含某一个值

如何检测一个数组(未排序)是否包含一个指定的值?这在Java中是一个非常有用且常见的操作。这还是一个在stackoverflow投票最多的一个问题。在投票最多的答案中,有几种不同的方式来完成这个问题。但是时间复杂度存在很大的差异。下面,我将展示每个方法所花费的时间。

1.检测数组中是否包含某一个值的四种方式

1)使用List

public static boolean useList(String[] arr, String targetValue) {
	return Arrays.asList(arr).contains(targetValue);
}

2)使用Set

public static boolean useSet(String[] arr, String targetValue) {
	Set<String> set = new HashSet<String>(Arrays.asList(arr));
	return set.contains(targetValue);
}

3)使用一个简单的循环

public static boolean useLoop(String[] arr, String targetValue) {
	for(String s: arr){
		if(s.equals(targetValue))
			return true;
	}
	return false;
}

4)使用Arrays.binarySearch()

*下面的代码时错误的,为了完整性我们列举了出来。一个排序后的数组才能使用binarySearch()方法。运行下面的代码你会发现结果有点怪异。

public static boolean useArraysBinarySearch(String[] arr, String targetValue) {	
	int a =  Arrays.binarySearch(arr, targetValue);
	if(a > 0)
		return true;
	else
		return false;
}

2.时间复杂度

利用下面的代码我们可以计算出每种方式大致花费的时间。基本思路就是在大小分别为5,1k,10k的数组中进行搜索。这种方法可能不太精确,但是思路清晰简单。

public static void main(String[] args) {
	String[] arr = new String[] {  "CD",  "BC", "EF", "DE", "AB"};
 
	//use list
	long startTime = System.nanoTime();
	for (int i = 0; i < 100000; i++) {
		useList(arr, "A");
	}
	long endTime = System.nanoTime();
	long duration = endTime - startTime;
	System.out.println("useList:  " + duration / 1000000);
 
	//use set
	startTime = System.nanoTime();
	for (int i = 0; i < 100000; i++) {
		useSet(arr, "A");
	}
	endTime = System.nanoTime();
	duration = endTime - startTime;
	System.out.println("useSet:  " + duration / 1000000);
 
	//use loop
	startTime = System.nanoTime();
	for (int i = 0; i < 100000; i++) {
		useLoop(arr, "A");
	}
	endTime = System.nanoTime();
	duration = endTime - startTime;
	System.out.println("useLoop:  " + duration / 1000000);
 
	//use Arrays.binarySearch()
	startTime = System.nanoTime();
	for (int i = 0; i < 100000; i++) {
		useArraysBinarySearch(arr, "A");
	}
	endTime = System.nanoTime();
	duration = endTime - startTime;
	System.out.println("useArrayBinary:  " + duration / 1000000);
}

结果:

useList:  13
useSet:  72
useLoop:  5
useArraysBinarySearch:  9

使用更大一点的数组(1k):

String[] arr = new String[1000];
 
Random s = new Random();
for(int i=0; i< 1000; i++){
	arr[i] = String.valueOf(s.nextInt());
}

结果:

useList:  112
useSet:  2055
useLoop:  99
useArrayBinary:  12

使用更大一点的数组(10k):

String[] arr = new String[10000];
 
Random s = new Random();
for(int i=0; i< 10000; i++){
	arr[i] = String.valueOf(s.nextInt());
}

结果:

useList:  1590
useSet:  23819
useLoop:  1526
useArrayBinary:  12

很明显:使用循环比使用集合效率要高。很多开发者使用第一种方法,但它是低效的。Pushing the array to another collection requires spin through all elements to read them in before doing anything with the collection type。 如果使用Arrays.binarySearch()方法,必须先对数组进行排序。在这例子中,数组并没有排序,所有不应该binarySearch()这个方法。 实际上,如果你确实需要检测一些数组/集合中是否包含某个值,你可以使用一个排序的列表或树,其时间复杂度为O(log(n))或者使用hashset,其时间复杂度为O(1)。

Java实现串口通信的小样例

用Java实现串口通信(windows系统下),须要用到sun提供的串口包 javacomm20-win32.zip。当中要用到三个文件,配置例如以下:

1.comm.jar放置到 JAVA_HOME/jre/lib/ext;
2.win32com.dll放置到 JAVA_HOME/bin;
3.javax.comm.properties 两个地方都要放

    jre/lib(也就是在JAVA目录下的jre)

   JAVA_HOME/jre/lib

 

说一下我应用的环境。电子秤称重时,计算机通过串口给称重控制显示器发送一次命令“R”,控制显示器则发送一次重量数据给串口,计算机再读取将数据显示在网页上。这样就构成了一个实时称重系统。

读写串口的代码例如以下:

package com.chengzhong.tools;
import java.io.*;

import javax.comm.CommPortIdentifier;
import javax.comm.*;

/**
*
* This bean provides some basic functions to implement full duplex
* information exchange through the serial port.
*
*/

public class SerialBean
{

public static String PortName;
public static CommPortIdentifier portId;
public static SerialPort serialPort;
public static OutputStream out;
public static InputStream in;

//保存读数结果
public static String result="";
public static int openSignal=1;

/**
*
* Constructor
*
* @param PortID the ID of the serial to be used. 1 for COM1,
* 2 for COM2, etc.
*
*/

public SerialBean(int PortID)
{
  PortName = "COM" +PortID;

}

/**
*
* This function initialize the serial port for communication. It starts a
* thread which consistently monitors the serial port. Any signal captured
* from the serial port is stored into a buffer area.
*
*/

public int Initialize()
{

	openSignal=1;

	try
	{
		
	portId = CommPortIdentifier.getPortIdentifier(PortName);
	

	try
	{
	serialPort = (SerialPort)
	portId.open("Serial_Communication", 2000);
	
	} catch (PortInUseException e)
	{
		
		if(!SerialBean.portId.getCurrentOwner().equals("Serial_Communication"))
		{
			
			openSignal=2;  //该串口被其他程序占用
		}else if(SerialBean.portId.getCurrentOwner().equals("Serial_Communication")){
			
			openSignal=1;
			return  openSignal;
		}
		
	  return openSignal;
	}

	//Use InputStream in to read from the serial port, and OutputStream
	//out to write to the serial port.

	try
	{
	in = serialPort.getInputStream();
	out = serialPort.getOutputStream();

	} catch (IOException e)
	{
		
		  openSignal=3;   //输入输出流错误
		  return openSignal;

	}

	//Initialize the communication parameters to 9600, 8, 1, none.

	try
	{
	serialPort.setSerialPortParams(9600,
	SerialPort.DATABITS_8,
	SerialPort.STOPBITS_1,
	SerialPort.PARITY_NONE);
	} catch (UnsupportedCommOperationException e)
	{
		
		  openSignal=4;   //參数不对
		  return openSignal;
	}
	} catch (NoSuchPortException e)
	{
		 
		  portId=null;
		  openSignal=5;  //没有该串口
		 
		  return openSignal;
	}

	// when successfully open the serial port, create a new serial buffer,
	// then create a thread that consistently accepts incoming signals from
	// the serial port. Incoming signals are stored in the serial buffer.

	 
// return success information

return openSignal;
}



/**
*
* This function returns a string with a certain length from the incoming
* messages.
*
* @param Length The length of the string to be returned.
*
*/

public static void ReadPort()
{
	SerialBean.result="";
int c;
try {
	if(in!=null){
		while(in.available()>0)
		{
			c = in.read();
			Character d = new Character((char) c);
			SerialBean.result=SerialBean.result.concat(d.toString());
		}
	}
	
} catch (IOException e) {
	// TODO Auto-generated catch block
	e.printStackTrace();
}

}

/**
*
* This function sends a message through the serial port.
*
* @param Msg The string to be sent.
*
*/

public static void WritePort(String Msg)
{

try
{
	if(out!=null){
		for (int i = 0; i < Msg.length(); i++)
		     out.write(Msg.charAt(i));
	}
  
} catch (IOException e) {
	 
	return;
	
}
}

/**
*
* This function closes the serial port in use.
*
*/


public void ClosePort()
{
 
  serialPort.close();
}
}

这样通过 SerialBean.result 就可得到读数结果。

至于把数据放到网页上,就要用到Ajax了,这里用到了一个Ajax框架dwr, dwr类Put.java 例如以下:

package com.chengzhong.dwr;

import java.io.IOException;

import com.chengzhong.tools.Arith;
import com.chengzhong.tools.SerialBean;

public class Put {
	
	//2011.9.17
	public String write(){
		
		
		//发送指令R,仪器发送一次净重数据
		SerialBean.WritePort("R");
		//读取数据
		SerialBean.ReadPort();
		String temp=SerialBean.result.trim();   //我这里temp是形如 wn125.000kg 的数据
		if(!temp.equals("") && temp.length()==11)
		{
			 return (change(temp)).toString();
			
		}else{
			return "";
		}
		
		
	}
	
	//响应開始称重
	public String startWeight(String num){
		
		 int n=Integer.parseInt(num.trim());
		 SerialBean SB = new SerialBean(n);
		 SB.Initialize();
		 return SerialBean.openSignal+"";  //返回初始化信息
		
	}
	
//响应停止称重
	
	public void endWeight(){
		

		
		try {
			//关闭输入、输出流
			SerialBean.in.close();
			SerialBean.out.close();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		if(SerialBean.serialPort!=null){
			SerialBean.serialPort.close();  //关闭串口
		}
		
		SerialBean.serialPort=null;
		SerialBean.portId=null;
		
		SerialBean.result="";
		
	}
	/**
           * 将形如 wn125.000kg 格式的重量转换为 125.000 (kg)(四舍五入,小数点后保留两位)
	 */
	 public  String change(String source){
		 Double result=0.0;
		 String s1=source.substring(2,9);
		 try{
			 result=Double.parseDouble(s1);
			 result=Arith.round(result,2);
		 }catch(Exception e){
			 e.printStackTrace();
			 return "";
		 }
		 
		 return result.toString();
	 }
	 

	 
}

 

注:Arith.java是一个java 的高精度计算文件。

package com.chengzhong.tools;
import java.math.BigDecimal;

/**
* 因为Java的简单类型不可以精确的对浮点数进行运算,这个工具类提供精
* 确的浮点数运算,包含加减乘除和四舍五入。
*/

public class Arith{
    //默认除法运算精度
    private static final int DEF_DIV_SCALE = 10;
    //这个类不能实例化
    private Arith(){
    }

    /**
     * 提供精确的加法运算。
     * @param v1 被加数
     * @param v2 加数
     * @return 两个參数的和
     */
    public static double add(double v1,double v2){
        BigDecimal b1 = new BigDecimal(Double.toString(v1));
        BigDecimal b2 = new BigDecimal(Double.toString(v2));
        return b1.add(b2).doubleValue();
    }
    /**
     * 提供精确的减法运算。
     * @param v1 被减数
     * @param v2 减数
     * @return 两个參数的差
     */
    public static double sub(double v1,double v2){
        BigDecimal b1 = new BigDecimal(Double.toString(v1));
        BigDecimal b2 = new BigDecimal(Double.toString(v2));
        return b1.subtract(b2).doubleValue();
    }
    /**
     * 提供精确的乘法运算。
     * @param v1 被乘数
     * @param v2 乘数
     * @return 两个參数的积
     */
    public static double mul(double v1,double v2){
        BigDecimal b1 = new BigDecimal(Double.toString(v1));
        BigDecimal b2 = new BigDecimal(Double.toString(v2));
        return b1.multiply(b2).doubleValue();
    }

    /**
     * 提供(相对)精确的除法运算,当发生除不尽的情况时,精确到
     * 小数点以后10位,以后的数字四舍五入。
     * @param v1 被除数
     * @param v2 除数
     * @return 两个參数的商
     */
    public static double div(double v1,double v2){
        return div(v1,v2,DEF_DIV_SCALE);
    }

    /**
     * 提供(相对)精确的除法运算。当发生除不尽的情况时,由scale參数指
     * 定精度,以后的数字四舍五入。
     * @param v1 被除数
     * @param v2 除数
     * @param scale 表示表示须要精确到小数点以后几位。
     * @return 两个參数的商
     */
    public static double div(double v1,double v2,int scale){
        if(scale<0){
            throw new IllegalArgumentException(
                "The scale must be a positive integer or zero");
        }
        BigDecimal b1 = new BigDecimal(Double.toString(v1));
        BigDecimal b2 = new BigDecimal(Double.toString(v2));
        return b1.divide(b2,scale,BigDecimal.ROUND_HALF_UP).doubleValue();
    }

    /**
     * 提供精确的小数位四舍五入处理。
     * @param v 须要四舍五入的数字
     * @param scale 小数点后保留几位
     * @return 四舍五入后的结果
     */
    public static double round(double v,int scale){

        if(scale<0){
            throw new IllegalArgumentException(
                "The scale must be a positive integer or zero");
        }
        BigDecimal b = new BigDecimal(Double.toString(v));
        BigDecimal one = new BigDecimal("1");
        return b.divide(one,scale,BigDecimal.ROUND_HALF_UP).doubleValue();
    }
}

 

 

网页页面上:

<script type="text/javascript" src="/ChengZhong/dwr/engine.js"></script>
<script type="text/javascript" src="/ChengZhong/dwr/util.js"></script> 
<script type=‘text/javascript‘ src=‘/ChengZhong/dwr/interface/ss.js‘ ></script>
<script type=‘text/javascript‘ >
 var ID;
     function begin(){
       ID=window.setInterval(‘get()‘,500); //每隔半秒自己主动调用 get(),取得毛重数据填入文本框中
     }
 function get()
     {       
        ss.write(readIt);    //调用dwr类 Put.java  中的write方法   
     }
     function readIt(Data){
     
       if(Data!=null && Data!="")
       {
           document.getElementById("mzBF").value=Data;
                }
       
     } 
</script>

 

dwr的使用就不说了

Python下搜索文件

一个搜索文件的程序,可以选择搜索文件还是目录;也可以选择搜索模式,如果严格模式开启,只能匹配与搜索字相等的文件或者目录,如果不开启,只要一个文件或者目录存在关键词即匹配。

 1 #!/usr/bin/python
 2 
 3 import os
 4 
 5 class SearchEngine():
 6     def __init__(self,path):
 7         self.path=path
 8     
 9     def search(self,word,Type=‘file‘,strict=False):
10         """word:the keyword that you want to search.
11         Type:you want to search file or directory.
12         strict:if True,filename or directory name must equal to keyword;
13             if False,keyword may be a part of a filename or directory name """
14         if os.path.isdir(self.path):
15             for root,dirs,files in os.walk(self.path):
16                 if Type==‘file‘:
17                     for filename in files:
18                         if strict==False:
19                             if word in filename:
20                                 print ‘/‘.join([root,filename])
21                         elif strict==True:
22                             if word==os.path.splitext(filename)[0]:
23                                 print ‘/‘.join([root,filename])
24                         else:
25                             print "strict:False/True"
26                 elif Type==‘directory‘:
27                     for dirname in dirs:
28                         if strict==False:
29                             if word in dirname:
30                                 print ‘/‘.join([root,dirname])
31                         elif strict==True:
32                             if word==dirname:
33                                 print ‘/‘.join([root,dirname])
34                         else:
35                             print "strict:False/True"
36                             
37                 else:
38                     print "Type:file/directory"
39         else:
40             if Type==‘file‘:
41                 if strict==False:
42                     if word in self.path:
43                         print self.path
44                 elif strict==True:
45                     if word==os.path.split(self.path)[1]:
46                         print self.path
47             else:
48                 print "You input a filename,Not matched."
49     
50 def test():
51     search=SearchEngine(‘/home/tmyyss‘)
52     search.search(‘test‘,‘file‘,False)
53 
54 if __name__==‘__main__‘:
55     test()

 

ACM 算法实现

实验一 统计数字问题

1、问题描述:
一本书的页码从自然数1 开始顺序编码直到自然数n。书的页码按照通常的习惯编排,每个页码都不含多余的前导数字0。例如,第6 页用数字6 表示,而不是06 或006 等。数字计数问题要求对给定书的总页码n,计算出书的全部页码中分别用到多少次数字0,1, 2,…,9。
2、题目分析:
考虑由0,1,2,…,9组成的所有n位数。从n个0到n个9共有个n位数,在这些n位数中,0,1,2,…,9每个数字使用次数相同,设为。 满足如下递归式:
由此可知,。
据此,可从低位向高位进行统计,再减去多余的0的个数即可。
3、算法设计:
定义数组a[10]存放0到9这10个数出现的次数,个位为第0位,第j位的数字为r。采用while循环从低位向高位统计: 
a. 统计从个位算起前j位0~9个数;
b. 如果j+1位为0,去掉第j+1位补0个数;
c. 统计第j+1位出现1~(r-1)个数;
d. 统计第j+1位出现r个数。
4、源程序:

#include 
int main() 

long int sn[10]; 
int i,n,c,k,s,pown; 
for(i=0;i<10;i++) br=””> sn[i]=0; 
cin>>n;
for(k=s=0,pown=1;n>0;k++,n/=10,pown*=10) 

c=n%10; 
for(i=0;i<10;i++) br=””> sn[i]+=c*k*(pown/10); 
for(i=0;i<c;i++) br=””> sn[i]+=pown; 
sn[c]+=1+s; 
sn[0] -=pown; 
s+=c*pown; 

for(i=0;i<10;i++) br=””> cout<<sn[i] n=”” br=””> }

5、算法分析:
函数count()的复杂度为O(1),主函数调用count(),故该算法的时间复杂度为O(1)。

实验二 最大间隙问题

1、问题描述:
最大间隙问题:给定n 个实数x1 , x2 ,… , xn,求这n 个数在实轴上相邻2 个数之间的最大差值。假设对任何实数的下取整函数耗时O(1),设计解最大间隙问题的线性时间算法。 对于给定的n 个实数x1 , x2 ,… , xn,编程计算它们的最大间隙。

2、题目分析:
考虑到实数在实轴上按大小顺序排列,先对这n个数排序,再用后面的数减去前面的数,即可求出相邻两数的差值,找出差值中最大的即为最大差值。

3、算法设计:
a. 用快速排序算法对这n个数排序,快速排序算法是基于分治策略的一个排序算
法。其基本思想是,对于输入的子数组a[p:r],按以下三个步骤进行排序:
①分解:以a[p]为基准元素将a[p:r]划分为3段a[p:q-1],a[q]和a[q+1:r],使a[p:q-1]中任何一个元素小于等于a[p],而a[q+1:r]中任何一个元素大于等于a[q]。下标q在划分过程中确定。
②递归求解:通过递归调用快速排序算法分别对a[p:q-1]和a[q+1:r]进行排序。
③合并:由于对a[p:q-1]和a[q+1:r]的排序是就地进行的,所以在a[p:q-1]和
a[q+1:r]都已排好的序后,不需要执行任何计算,a[p:r]就已排好序。
b. 用函数maxtap()求出最大差值。

4、源程序:

#include
#include
double a[1000000];

template
void swap(Type &x,Type &y)
{
Type temp=x;
x=y;
y=temp;
}

template
int Partition(Type *a,int low,int high)
{
Type pivotkey;
int mid=(low+high)/2;
if((a[low]<a[mid]&&a[mid] a=”” high=”” low=””>a[mid]&&a[mid]>a[high]))
swap(a[low],a[mid]);
if((a[low]<a[high]&&a[mid]>a[high])||(a[low]>a[high]&&a[mid]<a[high])) br=””> swap(a[low],a[high]);
pivotkey=a[low];
int i=low;
int j=high+1;
while(true)
{
while(a[++i]<pivotkey); br=””> while(a[–j]>pivotkey);
if(i>=j) break;
swap(a[i],a[j]);
}
a[low]=a[j];
a[j]=pivotkey;
return j;
}

template
void Quicksort(Type *a,int low,int high)
{
if(low<high) br=””> {
int q=Partition(a,low,high);
Quicksort(a,low,q-1);
Quicksort(a,q+1,high);
}
}

template
Type maxtap(Type *a,int n)

Type maxtap=0;
for(int i=0;i<n-1;i++) br=””> maxtap=(a[i+1]-a[i])>maxtap (a[i+1]-a[i]):maxtap;
return maxtap;
}

main()
{
int i,n;
scanf(“%d”,&n);
for(i=0;i<n;i++) br=””> scanf(“%lf”,&a[i]);
Quicksort(a,0,n-1);
printf(“%lf”,maxtap(a,n));
return 0;

5、算法分析:
快速排序的运行时间与划分是否对称有关,其最坏情况发生在划分过程产生的两个区域分别包含n-1个元素和1个元素的时候。由于函数Partition的计算时间为O(n),所以如果算法Partition的每一步都出现这种不对称划分,则其计算时间复杂性T(n)满足:

解此递归方程可得。 
在最好情况下,每次划分所取得基准都恰好为中值,即每次划分都产生两个大小为n/2的区域,此时,Partition的计算时间T(n)满足:

其解为。
可以证明,快速排序算法在平均情况下的时间复杂性也是。

实验三 众数问题

1、问题描述:
给定含有n个元素的多重集合S,每个元素在S中出现的次数称为该元素的重数。多重集S中重数最大的元素称为众数。例如,S={1,2,2,2,3,5}。多重集S的众数是2,其重数为3。 对于给定的由n 个自然数组成的多重集S,编程计算S 的众数及其重数。

2、题目分析:
题目要求计算集合中重复出现最多的数以及该数出现的次数,若两个数出现的次数相同,则取较小的数。先对集合中的元素从小到大排序,再统计重数,找出最大的重数以及它对应的元素。

3、算法设计:
a. 建立数组a[n]存储集合中的元素;
b. 调用库函数sort(a,a+n)对集合中的元素从小到大排序;
c. 采用for循环依次对排序后的元素进行重数统计:
①用m标记元素,用s统计该元数的重数,分别用 max和t标记出现的最大重数和该重数对应的元素。
②若当前元素不同于前一个元素,且前一个元素的重数s>max,更新max和t。

4、源程序:

#include
using namespace std;
#include

main()
{
int n,i,t,m=0,s,max=0,*a;
scanf(“%d”,&n);
a=new int[n];
for(i=0;i<n;i++) br=””> scanf(“%d”,&a[i]);
sort(a,a+n);
for(i=0;i<n;i++) br=””> {
if(m!=a[i])
s=1;
else
s++;
m=a[i];
if(s>max)
{
t=m;
max=s;
}

printf(“%d\n%d\n”,t,max);
return 0;
}

5、算法分析:
主函数内调用的库函数sort(a,a+n)的时间复杂度为,for循环的时间杂度为Ο(n),故该算法的时间杂度为。

实验四 半数集问题

1、问题描述:
给定一个自然数n,由n开始可以依次产生半数集set(n)中的数如下:(1) n∈set(n);(2) 在n的左边加上一个自然数,但该自然数不能超过最近添加的数的一半;(3) 按此规则进行处理,直到不能再添加自然数为止。
例如:set(6)={6,16,26,126,36,136},半数集set(6)中有6 个元素。注意半数集
是多重集,对于给定的自然数n,编程计算半数集set(n)中的元素个数。

2、题目分析:
题目要求输入有多行,每行给出一个整数n (0 < n < 1000) ,输入以文件结束标志EOF结束。半数集set(n)中的元素个数即为所有半数集set(j) (j<=n 2=”” 1=”” br=””> 
3、算法设计:
a. 用数组count[i]计算set(i)中的元素个数,即计算所有j<=i 2=”” set=”” j=”” br=””> 其自身的个数之和;
b. 用数组count_sum[i]计算所有j<=i的set(j) br=””> c. 采用for循环分别对count[i]、count_sum[i]进行累加,即可求出半数集set(n)中的
元素个数。

4、源程序:

#include 

int set(int n)
{
int i,count[1001],count_sum[1001];
count[1]=1; 
count_sum[1]=1; 
for(i=2;i<=n;i++) br=””> { 
count[i]=count_sum[i/2]+1; 
count_sum[i]=count_sum[i-1]+count[i]; 

return count[n];
}

main() 
{
int n;
while(scanf(“%d”,&n)!=EOF)
printf(“%d\n”,set(n));
return 0;
}

5、算法分析:
函数set(n)的时间复杂度为Ο(n),主函数调用函数set(n),故该算法的时间复杂度为Ο(n)。

实验五 集合划分问题

2、题目分析:
题目要求计算n个元素的集合共有多少个划分(其中每个划分都由不同的非空子集组成),n个元素的集合划分为m个块的划分数为F(n,m)=F(n-1,m-1)+m*F(n-1,m),m从1到n的划分个数相加即可求得总的划分数。

3、算法设计:
a. 这一题的结果数很大,需要使用64位长整型:__int64;
b. 函数div()采用递归的方式计算n个元素的集合划分为i个块的划分数:
①div (n,1)=1,div (n,n)=1; 
②div (n,i)= div (n-1,i-1)+i*div (n-1,i)
c. 主函数采用for循环调用函数div(n,i)(1≤i≤n)对划分数进行累加统计。
4、源程序:

#include

__int64 div(__int64 n,__int64 i)
{
if(i==1||i==n)
return 1;
else return div(n-1,i-1)+i*div(n-1,i);
}

main()
{
__int64 i,n,s=0;
scanf(“%I64d”,&n);
for(i=1;i<=n;i++) br=””> s=s+div(n,i);
printf(“%I64d”,s);
return 0;
}

5、算法分析:
函数div()的时间复杂度为,主函数内for循环的时间复杂度为O(n),函数div()嵌套在for循环内,故该算法的时间复杂度为。

实验七 编辑距离问题

1、问题描述:
设A 和B 是2 个字符串。要用最少的字符操作将字符串A 转换为字符串B。这
里所说的字符操作包括 (1)删除一个字符; (2)插入一个字符; (3)将一个字符改为另
一个字符。将字符串A变换为字符串B 所用的最少字符操作数称为字符串A到B 的编
辑距离,记为 d(A,B)。试设计一个有效算法,对任给的2 个字符串A和B,计算出它
们的编辑距离d(A,B)。

2、题目分析:
题目要求计算两字符串的编辑距离,可以采用动态规划算法求解,由最优子结构性质可建立递归关系如下:
其中数组d[i][j] 存储长度分别为i、j的两字符串的编辑距离;用edit标记所比较
的字符是否相同,相同为0,不同为1;用m、n存储字符串a、b的长度。

3、算法设计:
a. 函数min()找出三个数中的最小值;
b. 函数d()计算两字符串的编辑距离:
①用edit标记所比较的字符是否相同,相同为0,不同为1;
②分别用m、n存储字符串a、b的长度,用数组d[i][j] 存储长度分别为i、j的两字符串的编辑距离,问题的最优值记录于d[n][m]中;
③利用递归式写出计算d[i][j]的递归算法。

4、源程序:

#include 
using namespace std;

int min(int a, int b, int c)
{
int temp = (a < b a : b);
return (temp < c temp : c);
}

int d(char* a, char* b)
{
int m = strlen(a); 
int n = strlen(b);
int i,j ,temp;
int **d;
d=(int **)malloc(sizeof(int *)*(m+1)); 
for(i=0;i<=m;i++) br=””> {
d[i]=(int *)malloc(sizeof(int)*(n+1)); 
}
d[0][0]=0;
for(i=1;i<=m;i++) br=””> d[i][0]=i; 
for(j=1;j<=n;j++) br=””> d[0][j]=j; 
for( i=1;i<=m;i++) br=””> {
for(j=1;j<=n;j++) br=””> {
int edit=( a[i-1] == b[j-1] 0 : 1);
d[i][j]=min((d[i-1][j-1]+edit), 
(d[i][j-1]+1),(d[i-1][j]+1)); 
}
}
temp = d[m][n];
free(d);
return temp;
}

main()
{
char a[10000],b[10000];
scanf(“%s\n%s”,a,b);
printf(“%d”,d(a,b));
return 0;
}

5、算法分析:
函数d()采用了双重循环,里层的for循环复杂度为O(n),外层的for循环复杂度为O(m),主函数调用了函数d(),故该算法的时间复杂度为O(mn)。

实验八 程序存储问题

1、问题描述:
设有n 个程序{1,2,…, n }要存放在长度为L的磁带上。程序i存放在磁带上的长度是i l , 1 ≤i ≤n。程序存储问题要求确定这n 个程序在磁带上的一个存储方案,使得能够在磁带上存储尽可能多的程序。对于给定的n个程序存放在磁带上的长度,编程计算磁带上最多可以存储的程序数。

2、题目分析:
题目要求计算给定长度的磁带最多可存储的程序个数,先对程序的长度从小到大排序,再采用贪心算法求解。

3、算法设计:
a. 定义数组a[n]存储n个程序的长度,s为磁带的长度;
b. 调用库函数sort(a,a+n)对程序的长度从小到大排序;
c. 函数most()计算磁带最多可存储的程序数,采用while循环依次对排序后的程序
长度进行累加,用i计算程序个数,用sum计算程序累加长度(初始i=0,sum=0):
① sum=sum+a[i];
② 若sum<=s,i加1,否则i为所求; br=””> ③ i=n时循环结束;
d. 若while循环结束时仍有sum<=s,则n为所求。 br=””> 
4、源程序:

#include
using namespace std;
#include
int a[1000000];

int most(int *a,int n,int s)
{
int i=0,sum=0;
while(i<n) br=””> {
sum=a[i]+sum;
if(sum<=s) br=””> i++;
else return i;
}
return n;
}

main()
{
int i,n,s;
scanf(“%d %d\n”,&n,&s);
for(i=0;i<n;i++) br=””> scanf(“%d”,&a[i]);
sort(a,a+n);
printf(“%d”,most(a,n,s));
return 0;
}

5、算法分析:
函数most()的时间杂度为Ο(n),主函数调用的库函数sort(a,a+n)的时间复杂度为,且主函数调用函数most(),故该算法的时间杂度为。

实验九 最优服务次序问题

1、问题描述:
设有n 个顾客同时等待一项服务。顾客i需要的服务时间为ti ,1 ≤i≤n。应如何安排n 个顾客的服务次序才能使平均等待时间达到最小 平均等待时间是n 个顾客等待服务时间的总和除以n。对于给定的n个顾客需要的服务时间,编程计算最优服务次序。

2、题目分析:
考虑到每个顾客需要的服务时间已给定,要计算最优服务次序,可以先对顾客需要的服务时间由短到长排序,再采用贪心算法求解。

3、算法设计:
a. 定义数组a[n]存储n个顾客需要的服务时间;
b. 调用库函数sort(a,a+n)对顾客需要的服务时间由短到长排序;
c. 函数time()计算最小平均等待时间,采用while循环依次对顾客等待服务时间进
行累加,用a[i]标记第i+1个顾客需要的服务时间,用b[i]计算第i+1个顾客的等待服务
时间,用t计算前i+1个顾客等待服务时间的总和(初始i=1, t=a[0],b[0]=a[0]):
① b[i]=a[i]+b[i-1],t=b[i]+t;
② i++;
③ i=n时循环结束;
d. t/n为所求。

4、源程序:

#include
using namespace std;
#include
int a[1000000],b[1000000];
double time(int *a,int n)
{
int i=1,j=0;
double t=a[0];
b[0]=a[0];
while(i<n) br=””> {
b[i]=a[i]+b[i-1];
t=b[i]+t;
i++; 
}
return t/n;
}

main()
{
int i,n;
scanf(“%d”,&n);
for(i=0;i<n;i++) br=””> scanf(“%d”,&a[i]);
sort(a,a+n);
printf(“%0.2lf”,time(a,n));
return 0;
}

5、算法分析:
函数time()的时间杂度为Ο(n),主函数调用的库函数sort(a,a+n)的时间复杂度为,且主函数调用函数time(),故该算法的时间杂度为。

实验十 汽车加油问题

1、问题描述:
一辆汽车加满油后可行驶n公里。旅途中有若干个加油站。设计一个有效算法,指出应在哪些加油站停靠加油,使沿途加油次数最少。并证明算法能产生一个最优解。对于给定的n和k个加油站位置,编程计算最少加油次数。

2、题目分析:
题目要求编程计算最少加油次数,若无法到达目的地,则输出“No Solution”。该题 可以采用贪心算法求解,从出发地开始进行判别:油足够则继续行驶;油不够则加油,计算加油次数;油满仍不够则“No Solution”。

3、算法设计:
a. n表示汽车加满油后可行驶n公里,k表示出发地与目的地之间有k个加油站; 
b. 定义数组a[k+1]存储加油站之间的距离:用a[i]标记第i个加油站与第i+1个加
油站之间的距离(第0 个加油站为出发地,汽车已加满油;第k+1 个加油站为目的地);
c. 用m计算加油次数,用t标记在未加油的情况下汽车还能行驶t公里,采用for
循环从出发地开始(即i=0)依次计算加油次数:
① 若a[i]>n,则输出“No Solution”;
② 若t<a[i],则加油一次:t=n,m++; br=””> ③ 行驶a[i]公里后,汽车还能行驶t- a[i]公里;
④ i=k+1时循环结束;
d. m即为所求。

4、源程序:

#include
main()
{
int i,n,k,t,m,a[10000];
scanf(“%d%d”,&n,&k);
for(i=0;i<=k;i++) br=””> scanf(“%d”,&a[i]);
m=0;
t=n;
for(i=0;i<=k;i++) br=””> {
if(a[i]>n)
{
printf(“No Solution”); 
break; 
}
else if(t<a[i]) br=””> {
t=n; m++;
}
t=t-a[i];
}
if(i==k+1)
printf(“%d”,m);
return 0;
}

5、算法分析:
主函数内for循环的时间复杂度为O(k),故该算法的时间杂度为O(k)。

实验十一 工作分配问题

1、问题描述:
设有n件工作分配给n个人。将工作i分配给第j个人所需的费用为cij 。试设计一个算法,为每一个人都分配1 件不同的工作,并使总费用达到最小。设计一个算法,对于给定的工作费用,计算最佳工作分配方案,使总费用达到最小。

2、题目分析:
考虑到每个人对应的每项工作费用已给定,要计算最佳工作分配方案,使总费用达到最小,可以采用回溯法求解。由于回溯法是对解空间的深度优先搜索,因此此题可用排列树来表示解空间。

3、算法设计:
a. 用c[i][j]存储将工作i分配给第j个人所需的费用,用v[j] 标记第j个人是否已分
配工作;
b. 用递归函数backtrack (i, total)来实现回溯法搜索排列树(形式参数i表示递归深
度,n用来控制递归深度,形式参数total表示当前总费用,s表示当前最优总费用):
① 若total>=s,则不是最优解,剪去相应子树,返回到i-1层继续执行;
② 若i >n,则算法搜索到一个叶结点,用s对最优解进行记录,返回到i-1层
继续执行;
③ 采用for循环针对n个人对工作i进行分配(1≤j≤n):
1> 若v[j]==1 ,则第j个人已分配了工作,找第j+1个人进行分配;
2> 若v[j]==0,则将工作i分配给第j个人(即v[j]=1 ),对工作i+1调用递归函数backtrack(i+1,total+c[i][j])继续进行分配;
3> 函数backtrack(i+1,total+c[i][j])调用结束后则返回v[j]=0,将工作i对
第j+1个人进行分配;
4> 当j>n时,for循环结束;
④ 当i=1时,若已测试完c[i][j]的所有可选值,外层调用就全部结束;
c. 主函数调用一次backtrack(1,0)即可完成整个回溯搜索过程,最终得到的s即为
所求最小总费用。

4、源程序:

#include
#define MAX 1000;
int n,c[21][21],v[21],s,total;

void backtrack(int i,int total)
{
int j;
if(total>=s)
return;
if(i>n)
{
s=total;
return;
}
else
for(j=1;j<=n;j++) br=””> {
if(v[j]==1)
continue;
if(v[j]==0)
{
v[j]=1; backtrack(i+1,total+c[i][j]);
v[j]=0;
}
}
}

main()
{
int i,j; 
scanf(“%d”,&n);
for(i=1;i<=n;i++) br=””> {
for(j=1;j<=n;j++) br=””> {
scanf(“%d”,&c[i][j]);
v[j]=0;
}
}
s=MAX;
backtrack(1,0);
printf(“%d”,s);
return 0;
}

5、算法分析:
递归函数backtrack (i, total)遍历排列树的时间复杂度为O(n!),主函数调用递归函
数backtrack(1,0),故该算法的时间复杂度为O(n!)。

实验十二 0-1背包问题

1、问题描述:
给定n 种物品和一个背包。物品i 的重量是wi ,其价值为vi ,背包的容量为C。应如何选择装入背包的物品,使得装入背包中物品的总价值最大 在选择装入背包的物品时,对每种物品i只有2 种选择,即装入背包或不装入背包。不能将物品i 装入背包多次,也不能只装入部分的物品i。 0-1 背包问题形式化描述:给定C>0, wi >0, vi >0,1≤i≤n,要求n 元0-1向量( x1 , x2 ,…, xn ),xi = 0或1,1≤i≤n,使得,而且达到最大。
2、题目分析:
考虑到每种物品只有2 种选择,即装入背包或不装入背包,并且物品数和背包容量已给定,要计算装入背包物品的最大价值和最优装入方案,可用回溯法搜索子集树的算法进行求解。

3、算法设计:
a. 物品有n种,背包容量为C,分别用p[i]和w[i]存储第i种物品的价值和重量,用
x[i]标记第i种物品是否装入背包,用bestx[i]存储第i种物品的最优装载方案;
b. 用递归函数Backtrack (i,cp,cw)来实现回溯法搜索子集树(形式参数i表示递归深
度,n用来控制递归深度,形式参数cp和cw表示当前总价值和总重量,bestp表示当前
最优总价值):
① 若i >n,则算法搜索到一个叶结点,判断当前总价值是否最优:
1> 若cp>bestp,更新当前最优总价值为当前总价值(即bestp=cp),更新
装载方案(即bestx[i]=x[i]( 1≤i≤n));
② 采用for循环对物品i装与不装两种情况进行讨论(0≤j≤1):
1> x[i]=j;
2> 若总重量不大于背包容量(即cw+x[i]*w[i]<=c),则更新当前总价 br=””> 值和总重量(即cw+=w[i]*x[i],cp+=p[i]*x[i]), 对物品i+1调用递归函
数Backtrack(i+1,cp,cw) 继续进行装载;
3> 函数Backtrack(i+1,cp,cw)调用结束后则返回当前总价值和总重量
(即 cw-=w[i]*x[i],cp-=p[i]*x[i]);
4> 当j>1时,for循环结束;
③ 当i=1时,若已测试完所有装载方案,外层调用就全部结束;
c. 主函数调用一次backtrack(1,0,0)即可完成整个回溯搜索过程,最终得到的bestp和bestx[i]即为所求最大总价值和最优装载方案。

4、源程序:

#include
int n,c,bestp;
int p[10000],w[10000],x[10000],bestx[10000];

void Backtrack(int i,int cp,int cw)

int j;
if(i>n)
{
if(cp>bestp)
{
bestp=cp;
for(i=0;i<=n;i++) br=””> bestx[i]=x[i];
}
}
else 
for(j=0;j<=1;j++) br=””> {
x[i]=j;
if(cw+x[i]*w[i]<=c) br=””> {
cw+=w[i]*x[i];
cp+=p[i]*x[i];
Backtrack(i+1,cp,cw);
cw-=w[i]*x[i];
cp-=p[i]*x[i];
}
}
}

main()
{
int i;
bestp=0; 
scanf(“%d%d”,&n,&c);
for(i=1;i<=n;i++) br=””> scanf(“%d”,&p[i]);
for(i=1;i<=n;i++) br=””> scanf(“%d”,&w[i]);
Backtrack(1,0,0);
printf(“Optimal value is\n”);
printf(“%d\n”,bestp);
for(i=1;i<=n;i++) br=””> printf(“%d “,bestx[i]);
return 0;
}

5、算法分析:
递归函数Backtrack (i,cp,cw)遍历子集树的时间复杂度为,主函数调用递归函
数Backtrack(1,0,0),故该算法的时间复杂度为。

实验十三 最小重量机器设计问题

1、问题描述:
设某一机器由n个部件组成,每一种部件都可以从m个不同的供应商处购得。设 wij 是从供应商j 处购得的部件i的重量,cij 是相应的价格,给出总价格不超过d的最小重量机器设计。

2、题目分析:
考虑到从每一处供应商购得每一种部件的重量和相应价格以及总价格的上限已给定,要设计最小重量机器方案计算最小重量,可用回溯法搜索排列树的算法进行求解。

3、算法设计:
a. 部件有n个,供应商有m个,分别用w[i][j]和c[i][j]存储从供应商j 处购得的部
件i的重量和相应价格,d为总价格的上限。
b. 用递归函数Knapsack(i,cs,ws)来实现回溯法搜索排列树(形式参数i表示递归深
度,n用来控制递归深度,形式参数cs和ws表示当前总价格和总重量,bestw表示当前
最优总重量):
① 若cs>d,则为不可行解,剪去相应子树,返回到i-1层继续执行;
② 若ws>=bestw,则不是最优解,剪去相应子树,返回到i-1层继续执行;
③ 若i >n,则算法搜索到一个叶结点,用bestw对最优解进行记录,返回到
i-1层继续执行;
④ 采用for循环对部件i从m个不同的供应商购得的情况进行讨论(1≤j≤m):
1> 调用递归函Knapsack(i+1,cs+c[i][j],ws+w[i][j])对部件i+1进行购买;
2> 当j>m时for循环结束;
⑤ 当i=1时,若已测试完所有购买方案,外层调用就全部结束;
c. 主函数调用一次Knapsack(1,0,0)即可完成整个回溯搜索过程,最终得到的bestw
即为所求最小总重量。

4、源程序:

#include
#define MIN 1000
int n,m,d,cs,ws,bestw;
int w[100][100],c[100][100];

void Knapsack(int i,int cs,int ws)
{
int j;
if(cs>d)
return;
else if(ws>=bestw)
return;
else if(i>n)
{
bestw=ws;
return;
}
else 
{
for(j=1;j<=m;j++) knapsack=”” i=”” 1=”” cs=”” c=”” j=”” ws=”” w=”” br=””> }
}

main()
{
int i,j;
scanf(“%d%d%d”,&n,&m,&d);
for(i=1;i<=n;i++) br=””> {
for(j=1;j<=m;j++) br=””> scanf(“%d”,&c[i][j]);
}
for(i=1;i<=n;i++) br=””> {
for(j=1;j<=m;j++) br=””> scanf(“%d”,&w[i][j]);
}
bestw=MIN;
Knapsack(1,0,0);
if(bestw==MIN)
printf(“No Solution!”);
else
printf(“%d”,bestw);
return 0;
}

5、算法分析:
递归函数Knapsack(i,cs,ws)遍历排列树的时间复杂度为O(n!);主函数的双重for
循环的时间复杂度为O(mn), 且主函数调用递归函数Knapsack(1,0,0),故该算法的时
间复杂度为O(n!)。 

实验十四 最小权顶点覆盖问题

1、问题描述:
给定一个赋权无向图G=(V,E),每个顶点v∈V都有一个权值w(v)。如果U包含于V,且对于(u,v)∈E 有u∈U 且v∈V-U,则有v∈K.如:U = {1}, 若有边(1,2), 则有2属于K. 若有集合U包含于V使得U + K = V, 就称U 为图G 的一个顶点覆盖。G 的最小权顶点覆盖是指G 中所含顶点权之和最小的顶点覆盖。

2、题目分析:
考虑到每个顶点有在与不在顶点覆盖集中两种情况,并且每个顶点的权值已给定,
要计算最小权顶点覆盖的顶点权之和,可用回溯法搜索子集树的算法进行求解。

3、算法设计:
a. 给定的图G 有n 个顶点和m条边,用w[i]存储顶点i的权值,用e[i][j]标记两顶
点为i和j的边是否存在,用c[i]标记顶点i是否在顶点覆盖集中;
b. 用函数cover()判断图G 是否被顶点覆盖(用t标记):
① 初始t=0;
② 采用while循环对每个顶点i(1≤i≤n)进行讨论:
1> 若顶点i不在顶点覆盖集中(即c[i]==0),则查找与之有边连接的顶点j(即e[i][j]==1),判断所有顶点j:
若存在顶点j在顶点覆盖集中(即c[j]==0),则t=1;
若所有顶点j都不在顶点覆盖集中(即t==0),则图G 未被顶点
覆盖(return 0);
2> 当i>n时循环结束;
③ return 1;
c. 用递归函数cut(i, s) 来实现回溯法搜索子集树(形式参数i表示递归深度,n用
来控制递归深度,形式参数s表示当前顶点权之和): 
① 若s>=bestw,则不是最优解,剪去相应子树,返回到i-1层继续执行;
② 若i >n,则算法搜索到一个叶结点,调用函数cover()对图G进行判断:
若cover()为真,则用bestw对最优解进行记录,返回到i-1层继续执行;
③ 对顶点i分在与不在顶点覆盖集中两种情况进行讨论:
1> 若顶点i不在顶点覆盖集中(即c[i]==0),则调用函数cut(i+1,s);
2> 若顶点i在顶点覆盖集中(即c[i]==1),则调用函数cut(i+1,s+w[i]);
④ 当i=1时,若已测试完所有顶点覆盖方案,外层调用就全部结束;
d. 主函数调用一次cut(1,0)即可完成整个回溯搜索过程,最终得到的bestw即为所
求最小顶点权之和。

4、源程序:

#include
#define MIN 100000
int m,n,u,v,bestw;
int e[100][100],w[100],c[100];

int cover()
{
int i,j,t;
i=1;
while (i<=n) br=””> {
t=0;
if(c[i]==0)
{
j=1;
while(j<i) br=””> {
if(e[j][i]==1&&c[j]==1)
t=1;
j++;
}
j++;
while(j<=n) br=””> {
if(e[i][j]==1&&c[j]==1)
t=1;
j++;
}
if(t==0)
return 0;
}
i++;
}
return 1;
}

void cut(int i,int s)
{
if(s>=bestw)
return;
if(i>n)
{
if(cover())
bestw=s;
return;
}
c[i]=0;
cut(i+1,s);
c[i]=1;
cut(i+1,s+w[i]);
}

main()
{
int i,j,k;
scanf(“%d%d”,&n,&m);
for(i=1;i<=n;i++) br=””> {
scanf(“%d”,&w[i]);
c[i]=0;

}

for(i=1;i<=n;i++) br=””> for(j=1;j<=n;j++) br=””> e[i][j]=0;

for(k=1;k<=m;k++) br=””> {
scanf(“%d%d”,&u,&v);
e[u][v]=1;
}

bestw=MIN;
cut(1,0);
printf(“%d”,bestw);
return 0;

5、算法分析:
函数cover()的双重while循环的时间复杂度为,递归函数cut(i, s)遍历子集
树的时间复杂度为,且函数cover()嵌套在函数cut(i, s)内,所以递归函数cut(i, s)
的时间复杂度为;主函数的双重for循环的复杂度为,且主函数调用
递归函数cut(1, 0),故该算法的时间复杂度为。 

实验十五 集合相等问题

1、问题描述:
给定2个集合S和T,试设计一个判定S和 T是否相等的蒙特卡罗算法。

2、题目分析:
题目要求用蒙特卡罗算法进行求解,随机选择集合S中的元素与集合T中的元素进行比较,若随机选择很多次都能从集合T中找到与之对应的相等,则集合S和T相等。

3、算法设计:
a. 蒙特卡罗算法Majority对从集合S中随机选择的数组元素x,测试集合T中是否有
与之相等的元素:若算法返回的结果为true,则集合T中有与x相等的元素;若返回false,
则集合T中不存在与x相等的元素,因而集合S和 T不相等。
b. 算法MajorityMC重复调用次算法Majority,调用过程中若Majority
返回true则继续调用,否则可以判定集合S和 T不相等(MajorityMC返回false)。
c. 主函数调用算法MajorityMC:若返回true,则集合T和集合S相等;若返回false,
则集合S和 T不相等。

4、源程序:

#include
#include 
#include
#include 

bool Majority(int *S,int *T,int n)
{
int i,j,x;
bool k;
time_t t; 
//利用随机函数rand求0—n-1的随机数i
srand((unsigned)time(&t)); 
i=rand()%(n)-1;
x=S[i];
k=0;
for(j=0;j<n;j++) br=””> {
if(T[j]==x)
k=1;
}
return k;
}

bool MajorityMC(int *S,int *T,int n)
{//重复次调用算法Majority
int k;
double e;
e=0.00001;
//利用函数ceil求
k=(int)ceil(log(1/e));
for(int i=1;i<=k;i++) br=””> {
if(!Majority(S,T,n))
return 0;
}
return 1;
}

main()
{
int i,n;
int S[100000],T[100000];
scanf(“%d”,&n);
for(i=0;i<n;i++) br=””> scanf(“%d”,&S[i]);
for(i=0;i<n;i++) br=””> scanf(“%d”,&T[i]);
if(MajorityMC(S,T,n))
printf(“YES”);
else printf(“NO”);
return 0;
}

5、算法分析:
蒙特卡罗算法Majority的时间复杂度为O(n);算法MajorityMC重复调用
次算法Majority,时间复杂度为;主函数调用算法MajorityMC,故该算
法的时间复杂度为。

实验十六 战车问题

1、问题描述:
在 n×n格的棋盘上放置彼此不受攻击的车。按照国际象棋的规则,车可以攻击与之处在同一行或同一列上的棋子。在棋盘上的若干个格中设置了堡垒,战车无法穿越 堡垒攻击别的战车。对于给定的设置了堡垒的 n×n格棋盘,设计一个概率算法,在棋盘上放置尽可能多彼此不受攻击的车。

2、题目分析:
从第一行开始随机产生一个位置,看战车在该位置上能否放置,分三种情况讨论:
a. 该随机位置对应在棋盘上是一个堡垒,则不能放置;
b. 该位置与前面放置的战车相冲突,即在受前面放置战车攻击的位置上,则
也不能放置;
c. 该随机位置不受攻击也不是堡垒,则可以放置;
如果不能放置,则重新产生一个随机位置再判断,如果可以放置, 则放在该位置上并记录战车个数和该战车可能攻击的位置。以这种方法从第一行开始放置,第一行不能放置战车后再放第二行,直至第n行结束。
3、算法设计:
a. 建立随机数类CRandomNumber;
b. 函数CheckPlace判断是否可以放入战车,同时查看所放战车的攻击位置;
c. 函数MaxChes产生随机位置,放置并记录战车数。

4、源程序:

#include
#include
#include
#include
const unsigned long multiplier=1194211693L;
const unsigned long adder=12345L;

class CRandomNum 
{
private:
unsigned long nSeed;
public:
CRandomNum(unsigned long s=0); 
unsigned short Random(unsigned long n); 
};

CRandomNum::CRandomNum(unsigned long s)
{
if(s==0)
nSeed=time(0);
else 
nSeed=s;
}

unsigned short CRandomNum::Random(unsigned long n)
{
nSeed=multiplier*nSeed+adder;
return (unsigned short)((nSeed>>16)%n);
}

bool CheckPlaceChe(int **b,char **a,int x,int y,int n) 
{
if(b[x][y]==1)return false;
else
{
b[x][y]=1;
if((y>=1)&&(y<=n)) br=””> { //上搜索 
for(int i=y-1;i>=0;i–)
{
if ((b[x][i]==1) &&(a[x][i]==‘X‘))
break;
else b[x][i]=1;
}
}

if((y<=n-1)&&(y>=0))
{ //下搜索
for(int i=y+1;i<n;i++) br=””> {
if((b[x][i]==1)&&(a[x][i]==‘X‘)) 
break;
else b[x][i]=1;
}
}

if((x>=1)&&(x<=n)) br=””> { //左搜索
for(int j=x-1;j>=0;j–)
{
if((b[j][y]==1) &&(a[j][y]==‘X‘))
break;
else b[j][y]=1;
}
}
if((x>=0)&&(x<=n-1)) br=””> { //右搜索
for(int j=x+1;j<n;j++) br=””> {
if((b[j][y]==1) &&(a[j][y]==‘X‘))
break;
else b[j][y]=1;
}
}
return true;
}
}
int MaxChes(int **b,char**a,int *x,int n)
{
int max1=0;
static CRandomNum rnd;
for(int i=0;i<n;i++) br=””> {
bool flag=false;
do{
for(int j=0;j<n;j++) br=””> {
if(b[i][j]==0){ flag=true;break;} else {flag=false;continue;}
}
if(flag)
{
x[i]=rnd.Random(n); 
if(CheckPlaceChe(b,a,i,x[i],n))
++max1;

}while(flag);
}
return max1;
}

int main()
{
int n,i,j;
cin>>n;
char **a=new char*[n]; 
for(i=0;i<n;i++) br=””> a[i]=new char[n];
for(i=0;i<n;i++) br=””> for(j=0;j<n;j++) br=””> cin>>a[i][j];
int **b=new int*[n]; 
for(i=0;i<n;i++) br=””> b[i]=new int[n];
int max=0;
int *x=new int[n];
for(i=0;i<n;i++) br=””> x[i]=0;

int t=0;
while(t<15000) br=””> {
for(i=0;i<n;i++) br=””> { for(int j=0;j<n;j++) br=””> {
if(a[i][j]==‘X‘)
b[i][j]=1;
else
b[i][j]=0;
}
}

int max2=MaxChes(b,a,x,n);
if(max<max2) max=”max2;<br/”> t++;

cout<<max; br=””> delete []x;
for(i=0;i<n;i++) br=””> delete[]a[i];
delete[]a;
for(i=0;i<n;i++) br=””> delete[]b[i];
delete []b;
return 0;
}

5、算法分析:
算法的时间主要在判断是否可以放入战车和产生随机位置上,若重复的次数为K,则时间复杂度为O(Kn2)。

如何配置java环境

1.右键打开计算机->属性->高级系统设置->高级->环境变量

2.新建系统变量JAVA_HOME和CLASSPATH(大小写都可以)

变量名(N):
JAVA_HOME

变量值(V):D:\Program Files\Java\jdk1.8.0_11    //(这里的地址是根据你自己java jdk的目录而定的)

例:

变量名(N):CLASSPATH

变量值(V):
.;%JAVA_HOME%\lib\dt.jar;%JAVA_HOME%\lib\tools.jar;%JAVA_HOME%\lib\mysql-connector-java-5.1.34-bin.jar;D:\Program Files\Java\jre8\lib\rt.jar;    //(%JAVA_HOME%你就把它看成替换上面设置的JAVA_HOME变量值就行)注意前面的  .;别丢了

3.选择“系统变量”中变量名为“Path”的环境变量,双击该变量,先使用半角英文的分号和已有的路径进行分隔,然后把jdk安装路径中bin目录的绝对路径,添加到Path变量的值中,

变量名(N):Path

变量值(V):;%JAVA_HOME%\bin;%JAVA_HOME%\jre\bin;



其中:%JAVA_HOME%\lib\mysql-connector-java-5.1.34-bin.jar中的mysql-connector-java-5.1.34-bin.jar

是需要自行下载的,下载完之后复制到%JAVA_HOME%\lib中,这个是用来跟MYSQL进行通信的驱动,这样一来就不会出现java.lang.ClassNotFoundException: com.mysql.jdbc.Driver和Sorry,can`t find the Driver!这样的情况了,我一并在下面提供了下载地址

jdk-8u11-windows-x64-8.0.11.12.1406275777:http://yunpan.cn/cyMQ4n697VVNR  提取码 3eaa

MyEclipse_10.7:http://yunpan.cn/cyMQzr6zQ4IVE  提取码 a544

mysql-connector-java-5.1.34-bin.jar:http://yunpan.cn/cyMwSvBii9Uwj  提取码 7be3

c++ bind1st bind2nd的使用

 

 

  看了下面这篇文章后,简短总结:bind2nd( 参数1,参数2)   表示的是一个判断条件,假如参数1是个小于号<,而参数2是个100,那么表达的就“小于100”的意思,将两个东西结合在一起表示。bind1st( 参数1,参数2)  表达的与bind2nd恰好相反,即假如参数1与参数2与bind2nd完全一样,表达的是“大于100”的意思。 

  以上只是一总抽象的总结性,你理解了这个大概的意思,再看下面具体的介绍之后比较容易理解。

 

 

转载自http://blog.csdn.net/simahao/article/details/405455

本篇适合不熟悉这两个函数的读者

        以前在使用stl的过程中发现bind1st和bind2nd这两个函数,当时不太理解什么意思,今天在网上查了一下相关资料发现竟然很简单,下面我就具体解释一下他们的用法。

        bind1st和bind2nd函数用于将一个二元算子(binary functor,bf)转换成一元算子(unary functor,uf)。为了达到这个目的,它们需要两个参数:要转换的bf和一个值(v)。

 

         可能这么解释以后大家还不是很清楚,那么就说点白话吧。我们在做比较的时候所写的表达式像 x > k ,x < k,这里的k是一个参数表示你程序里面的表达式要和k值去比较。上面这两个表达式对应的应该是bind2nd ,简单的理解就是把k作为比较表达式的第二个参数。如果使用bind1st则对应的表达式是 k > x,k < x,也就是把k作为比较表达式的第一个参数。大家可能会注意到这里面没有=的比较,先别着急,后面将会说道如何实现=的比较。先举两个例子看看bind1st和bind2nd的用法。

int a[] = {1, 2, 100, 200};

std::vector< int> arr(a, a + 4);

// 移除所有小于100的元素
arr.erase( std::remove_if( arr.begin(),  arr.end(),
    std::bind2nd( std::less< int>(), 100)), arr.end());

这里的比较表达式相当于arr.value < 100

如果用bind1st则表达的意思就恰恰相反

// 移除所有大于100的元素
arr.erase( std::remove_if( arr.begin(),  arr.end(),
    std::bind1st( std::less< int>(), 100)), arr.end());

这里的表达式相当于100 < arr.value

当然为了实现删除大于100的元素你同样可以使用bind2nd

// 移除所有大于100的元素
arr.erase( std::remove_if( arr.begin(),  arr.end(),
    std::bind2nd( std::greater< int>(), 100)), arr.end());

前面说道=的比较,比如说x <= k怎么实现呢,std又提供了一个好东西not1,我们可以说 !(x > k) 和 x <= k是等价的,那么我们看看下面的表达式:

// 移除所有小于等于100的元素
arr.erase( std::remove_if( arr.begin(),  arr.end(),
    std::not1(std::bind2nd( std::greater< int>(), 100))), arr.end());

说明:not1是否定返回值是单目的函数,std中还有not2它是否定返回值是双目的函数

例子需要包含头文件

#include <vector>

#include <algorithm>

#include <functional>

javascript Array对象concat()方法

concat() 方法用于连接两个或多个数组。

该方法不会改变现有的数组,而仅仅会返回被连接数组的一个副本。

语法:
arrayObject.concat(arrayX,arrayX,……,arrayX)

 
arrayX 必需。该参数可以是具体的值,也可以是数组对象。可以是任意多个。

返回值:
返回一个新的数组。该数组是通过把所有 arrayX 参数添加到 arrayObject 中生成的。如果要进行 concat() 操作的参数是数组,那么添加的是数组中的元素,而不是数组。

例子 1:
把 concat() 中的参数连接到数组 a 中:

<script type="text/javascript">

var a = [1,2,3];
document.write(a.concat(4,5));

</script>

输出:

1,2,3,4,5

 

例子 2:
创建了两个数组,用 concat() 把它们连接起来:

<pre class="javascript" name="code"><script type="text/javascript">

var arr = new Array(3)
arr[0] = "George"
arr[1] = "John"
arr[2] = "Thomas"

var arr2 = new Array(3)
arr2[0] = "James"
arr2[1] = "Adrew"
arr2[2] = "Martin"

document.write(arr.concat(arr2))

</script>

 

输出:

George,John,Thomas,James,Adrew,Martin

Java的Arrays部分算法详解

java的java.util.Arrays工具类提供了很多有用的方法,而且有很多方法是重载(overload)的,现在来研究一些部分算法的应用。

1. 二分查找double数组

public static int binarySearch(double[] a, int fromIndex, int toIndex,
                                   double key) {
        rangeCheck(a.length, fromIndex, toIndex);
        return binarySearch0(a, fromIndex, toIndex, key);
    }

    // Like public version, but without range checks.
    private static int binarySearch0(double[] a, int fromIndex, int toIndex,
                                     double key) {
        int low = fromIndex;
        int high = toIndex - 1;

        while (low <= high) {
            int mid = (low + high) >>> 1;
            double midVal = a[mid];

            if (midVal < key)
                low = mid + 1;  // Neither val is NaN, thisVal is smaller
            else if (midVal > key)
                high = mid - 1; // Neither val is NaN, thisVal is larger
            else {
                long midBits = Double.doubleToLongBits(midVal);
                long keyBits = Double.doubleToLongBits(key);
                if (midBits == keyBits)     // Values are equal
                    return mid;             // Key found
                else if (midBits < keyBits) // (-0.0, 0.0) or (!NaN, NaN)
                    low = mid + 1;
                else                        // (0.0, -0.0) or (NaN, !NaN)
                    high = mid - 1;
            }
        }
        return -(low + 1);  // key not found.
    }

需要注意的是,对于两个double类型的数m和n,不能够使用m==n来判断二者是不是相等,只能通过Math.abs(m-n)<=e(e是精确度,比如0.0000001)来判定。

这里使用了long java.lang.Double.doubleToLongBits(double value)来实现相等判断的。

long midBits = Double.doubleToLongBits(midVal);
long keyBits = Double.doubleToLongBits(key);

long java.lang.Double.doubleToLongBits(double value)是为了

“Returns a representation of the specified floating-point value according to the IEEE 754 floating-point “double format” bit layout.(根据IEEE754规范,返回一个代表着一个浮点数的bit数) ”,

该方法源码的实现中涉及到了一个本地方法,没找到源代码==

public static native long doubleToRawLongBits(double value);

2. 判断两个数组A,B是否相等

过程:

判断引用是不是相等,相等,返回true;

A和B中是不是null,有null,返回false;

判断A和B的长度是不是相等,不相等,返回false;

遍历A和B中的元素,是不是相等,不相等,返回fasle;

返回true;

public static boolean equals(boolean[] a, boolean[] a2) {
        if (a==a2)
            return true;
        if (a==null || a2==null)
            return false;

        int length = a.length;
        if (a2.length != length)
            return false;

        for (int i=0; i<length; i++)
            if (a[i] != a2[i])
                return false;

        return true;
    }

3. public static void sort(double[] a, int fromIndex, int toIndex)的实现

public static void sort(double[] a, int fromIndex, int toIndex) {
        rangeCheck(a.length, fromIndex, toIndex);//检查参数是否合法
        DualPivotQuicksort.sort(a, fromIndex, toIndex - 1);
    }

java.util.DualPivotQuicksort的优势在于,通过判断数组的长度,选择合适的排序算法,如插入、merge、快排、计数排序等。会在下一篇博客中介绍DualPivotQuicksort类。

delphi 中几种多线程操作方式

在了解多线程之前我们先了解一下进程和线程的关系

一个程序至少有一个主进程,一个进程至少有一个线程。

为了保证线程的安全性请大家看看下面介绍 多线程同步的一些处理方案大家可以参考:

主线程又程为UI线程。

进程和线程的主要差别在于它们是不同的操作系统资源管理方式。进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一个进程中的不同执行路径。线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些。但对于一些要求同时进行并且又要共享某些变量的并发操作,只能用线程,不能用进程。如果有兴趣深入的话,我建议你们看看《现代操作系统》或者《操作系统的设计与实现》。对就个问题说得比较清楚。

多线程应该是编程工作者的基础技能, 但这个基础我从来没学过,所以仅仅是看上去会一些,明白了2+2的时候,其实我还不知道1+1。

开始本应该是一篇洋洋洒洒的文字, 不过我还是提倡先做起来, 在尝试中去理解.

先试试这个:

procedure TForm1.Button1Click(Sender: TObject); var 
  i: Integer; begin 
  for i := 0 to 500000 do 
  begin 
    Canvas.TextOut(10, 10, IntToStr(i)); 
  endend

上面程序运行时, 我们的窗体基本是 “死” 的, 可以在你在程序运行期间拖动窗体试试…

Delphi 为我们提供了一个简单的办法(Application.ProcessMessages)来解决这个问题:

procedure TForm1.Button1Click(Sender: TObject); var 
  i: Integer; begin 
  for i := 0 to 500000 do 
  begin 
    Canvas.TextOut(10, 10, IntToStr(i)); 
    Application.ProcessMessages; 
  endend

这个 Application.ProcessMessages; 一般用在比较费时的循环中, 它会检查并先处理消息队列中的其他消息.

但这算不上多线程, 譬如: 运行中你拖动窗体, 循环会暂停下来…

在使用多线程以前, 让我们先简单修改一下程序:

function MyFun: Integer; var 
  i: Integer; begin 
  for i := 0 to 500000 do 
  begin 
    Form1.Canvas.Lock; 
    Form1.Canvas.TextOut(10, 10, IntToStr(i)); 
    Form1.Canvas.Unlock; 
  end; 
  Result := 0; end; 
 procedure TForm1.Button1Click(Sender: TObject); begin 
  MyFun; end

细数上面程序的变化:
1、首先这还不是多线程的, 也会让窗体假 “死” 一会;
2、把执行代码写在了一个函数里, 但这个函数不属于 TForm1 的方法, 所以使用 Canvas 是必须冠以名称(Form1);
3、既然是个函数, (不管是否必要)都应该有返回值;
4、使用了 500001 次 Lock 和 Unlock.

Canvas.Lock 好比在说: Canvas(绘图表面)正忙着呢, 其他想用 Canvas 的等会;
Canvas.Unlock : 用完了, 解锁!

在 Canvas 中使用 Lock 和 Unlock 是个好习惯, 在不使用多线程的情况下这无所谓, 但保不准哪天程序会扩展为多线程的; 我们现在学习多线程, 当然应该用.

在 Delphi 中使用多线程有两种方法: 调用 API、使用 TThread 类; 使用 API 的代码更简单.

function MyFun(p: Pointer): Integer; stdcall; var 
  i: Integer; begin 
  for i := 0 to 500000 do 
  begin 
    Form1.Canvas.Lock; 
    Form1.Canvas.TextOut(10, 10, IntToStr(i)); 
    Form1.Canvas.Unlock; 
  end; 
  Result := 0; end; 
 procedure TForm1.Button1Click(Sender: TObject); var 
  ID: THandle; begin 
  CreateThread(nil, 0, @MyFun, nil, 0, ID); end

代码分析:
CreateThread 一个线程后, 算上原来的主线程, 这样程序就有两个线程、是标准的多线程了;
CreateThread 第三个参数是函数指针, 新线程建立后将立即执行该函数, 函数执行完毕, 系统将销毁此线程从而结束多线程的故事.

CreateThread 要使用的函数是系统级别的, 不能是某个类(譬如: TForm1)的方法, 并且有严格的格式(参数、返回值)要求, 不管你暂时是不是需要都必须按格式来;
因为是系统级调用, 还要缀上 stdcall, stdcall 是协调参数顺序的, 虽然这里只有一个参数没有顺序可言, 但这是使用系统函数的惯例.

CreateThread 还需要一个 var 参数来接受新建线程的 ID, 尽管暂时没用, 但这也是格式; 其他参数以后再说吧.

这样一个最简单的多线程程序就出来了, 咱们再用 TThread 类实现一次

type 
  TMyThread = class(TThread) 
  protected 
    procedure Execute; override; 
  end; 
 procedure TMyThread.Execute; var 
  i: Integer; begin 
  FreeOnTerminate := True; {这可以让线程执行完毕后随即释放} 
  for i := 0 to 500000 do 
  begin 
    Form1.Canvas.Lock; 
    Form1.Canvas.TextOut(10, 10, IntToStr(i)); 
    Form1.Canvas.Unlock; 
  endend; 
 procedure TForm1.Button1Click(Sender: TObject); begin 
  TMyThread.Create(False); end; 
 

TThread 类有一个抽象方法(Execute), 因而是个抽象类, 抽象类只能继承使用, 上面是继承为 TMyThread.

继承 TThread 主要就是实现抽象方法 Execute(把我们的代码写在里面), 等我们的 TMyThread 实例化后, 首先就会执行 Execute 方法中的代码.

按常规我们一般这样去实例化:

procedure TForm1.Button1Click(Sender: TObject); var 
  MyThread: TMyThread; begin 
  MyThread := TMyThread.Create(False); end

因为 MyThread 变量在这里毫无用处(并且编译器还有提示), 所以不如直接写做 TMyThread.Create(False);

我们还可以轻松解决一个问题, 如果: TMyThread.Create(True) ?
这样线程建立后就不会立即调用 Execute, 可以在需要的时候再用 Resume 方法执行线程, 譬如:

procedure TForm1.Button1Click(Sender: TObject); var 
  MyThread: TMyThread; begin 
  MyThread := TMyThread.Create(True); 
  MyThread.Resume; end; 
 //可简化为: procedure TForm1.Button1Click(Sender: TObject); begin 
  with TMyThread.Create(True) do Resume; end

一、入门
㈠、

function CreateThread( 
  lpThreadAttributes: Pointer;           {安全设置} 
  dwStackSize: DWORD;                    {堆栈大小} 
  lpStartAddress: TFNThreadStartRoutine; {入口函数} 
  lpParameter: Pointer;                  {函数参数} 
  dwCreationFlags: DWORD;                {启动选项} 
  var lpThreadId: DWORD                  {输出线程 ID } 
): THandle; stdcall;                     {返回线程句柄} 

在 Windows 上建立一个线程, 离不开 CreateThread 函数;
TThread.Create 就是先调用了 BeginThread (Delphi 自定义的), BeginThread 又调用的 CreateThread.
既然有建立, 就该有释放, CreateThread 对应的释放函数是: ExitThread, 譬如下面代码:

procedure TForm1.Button1Click(Sender: TObject); begin 
  ExitThread(0); {此句即可退出当前程序, 但不建议这样使用} end

代码注释:
当前程序是一个进程, 进程只是一个工作环境, 线程是工作者;
每个进程都会有一个启动线程(或叫主线程), 也就是说: 我们之前大量的编码都是写给这个主线程的;
上面的 ExitThread(0); 就是退出这个主线程;
系统不允许一个没有线程的进程存在, 所以程序就退出了.
另外: ExitThread 函数的参数是一个退出码, 这个退出码是给之后的其他函数用的, 这里随便给个无符号整数即可.

或许你会说: 这个 ExitThread 挺好用的; 其实不管是用 API 还是用 TThread 类写多线程, 我们很少用到它; 因为:
1、假如直接使用 API 的 CreateThread, 它执行完入口函数后会自动退出, 无需 ExitThread;
2、用 TThread 类建立的线程又绝不能使用 ExitThread 退出; 因为使用 TThread 建立线程时会同时分配更多资源(譬如你自定义的成员、还有它的祖先类(TObject)分配的资源等等), 如果用 ExitThread 给草草退出了, 这些资源将得不到释放而导致内存泄露. 尽管 Delphi 提供了 EndThread(其内部调用 ExitThread), 这也不需要我们手动操作(假如非要手动操作也是件很麻烦的事情, 因为很多时候你不知道线程是什么时候执行完毕的).
除了 CreateThread, 还有一个 CreateRemoteThread, 可在其他进程中建立线程, 这不应该是现在学习的重点;
现在先集中精力把 CreateThread 的参数搞彻底.

倒着来吧, 先谈谈 CreateThread 将要返回的 “线程句柄”.

“句柄” 类似指针, 但通过指针可读写对象, 通过句柄只是使用对象;
有句柄的对象一般都是系统级别的对象(或叫内核对象); 之所以给我们的是句柄而不是指针, 目的只有一个: “安全”;
貌似通过句柄能做很多事情, 但一般把句柄提交到某个函数(一般是系统函数)后, 我们也就到此为止很难了解更多了; 事实上是系统并不相信我们.

不管是指针还是句柄, 都不过是内存中的一小块数据(一般用结构描述), 微软并没有公开句柄的结构细节, 猜一下它应该包括: 真实的指针地址、访问权限设置、引用计数等等.

既然 CreateThread 可以返回一个句柄, 说明线程属于 “内核对象”.
实际上不管线程属于哪个进程, 它们在系统的怀抱中是平等的; 在优先级(后面详谈)相同的情况下, 系统会在相同的时间间隔内来运行一下每个线程, 不过这个间隔很小很小, 以至于让我们误以为程序是在不间断地运行.

这时你应该有一个疑问: 系统在去执行其他线程的时候, 是怎么记住前一个线程的数据状态的?
有这样一个结构 TContext, 它基本上是一个 CPU 寄存器的集合, 线程是数据就是通过这个结构切换的, 我们也可以通过 GetThreadContext 函数读取寄存器看看.

附上这个结构 TContext(或叫: CONTEXT、_CONTEXT) 的定义:

PContext = ^TContext; 
_CONTEXT = record 
  ContextFlags: DWORD; 
  Dr0: DWORD; 
  Dr1: DWORD; 
  Dr2: DWORD; 
  Dr3: DWORD; 
  Dr6: DWORD; 
  Dr7: DWORD; 
  FloatSave: TFloatingSaveArea; 
  SegGs: DWORD; 
  SegFs: DWORD; 
  SegEs: DWORD; 
  SegDs: DWORD; 
  Edi: DWORD; 
  Esi: DWORD; 
  Ebx: DWORD; 
  Edx: DWORD; 
  Ecx: DWORD; 
  Eax: DWORD; 
  Ebp: DWORD; 
  Eip: DWORD; 
  SegCs: DWORD; 
  EFlags: DWORD; 
  Esp: DWORD; 
  SegSs: DWORD; end

CreateThread 的最后一个参数是 “线程的 ID”;
既然可以返回句柄, 为什么还要输出这个 ID? 现在我知道的是:
1、线程的 ID 是唯一的; 而句柄可能不只一个, 譬如可以用 GetCurrentThread 获取一个伪句柄、可以用 DuplicateHandle 复制一个句柄等等.
2、ID 比句柄更轻便.

在主线程中 GetCurrentThreadId、MainThreadID、MainInstance 获取的都是主线程的 ID.
㈡、启动选项

function CreateThread( 
  lpThreadAttributes: Pointer; 
  dwStackSize: DWORD; 
  lpStartAddress: TFNThreadStartRoutine; 
  lpParameter: Pointer; 
  dwCreationFlags: DWORD; {启动选项} 
  var lpThreadId: DWORD 
): THandle; stdcall;

CreateThread 的倒数第二个参数 dwCreationFlags(启动选项) 有两个可选值:
0: 线程建立后立即执行入口函数;
CREATE_SUSPENDED: 线程建立后会挂起等待.

可用 ResumeThread 函数是恢复线程的运行; 可用 SuspendThread 再次挂起线程.
这两个函数的参数都是线程句柄, 返回值是执行前的挂起计数.

什么是挂起计数?
SuspendThread 会给这个数 +1; ResumeThread 会给这个数 -1; 但这个数最小是 0.
当这个数 = 0 时, 线程会运行; > 0 时会挂起.
如果被 SuspendThread 多次, 同样需要 ResumeThread 多次才能恢复线程的运行.

在下面的例子中, 有新线程不断给一个全局变量赋随机值;
同时窗体上的 Timer 控件每隔 1/10 秒就把这个变量写在窗体标题;
在这个过程中演示了 ResumeThread、SuspendThread 两个函数.

//上面图片中演示的代码。 unit Unit1; 
 interface 
 uses 
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, 
  Dialogs, StdCtrls, ExtCtrls; 
 type 
  TForm1 = class(TForm) 
    Button1: TButton; 
    Button2: TButton; 
    Button3: TButton; 
    Timer1: TTimer; 
    procedure Button1Click(Sender: TObject); 
    procedure Button2Click(Sender: TObject); 
    procedure Button3Click(Sender: TObject); 
    procedure FormCreate(Sender: TObject); 
    procedure Timer1Timer(Sender: TObject); 
  end; 
 var 
  Form1: TForm1; 
 implementation 
 {$R *.dfm} 
 var 
  hThread: THandle; {线程句柄} 
  num: Integer;     {全局变量, 用于记录随机数} 
 {线程入口函数} function MyThreadFun(p: Pointer): Integer; stdcall; begin 
  while True do {假如线程不挂起, 这个循环将一直循环下去} 
  begin 
    num := Random(100); 
  end; 
  Result := 0; end; 
 {建立并挂起线程} procedure TForm1.Button1Click(Sender: TObject); var 
  ID: DWORD; begin 
  hThread := CreateThread(nil, 0, @MyThreadFun, nil, CREATE_SUSPENDED, ID); 
  Button1.Enabled := False; end; 
 {唤醒并继续线程} procedure TForm1.Button2Click(Sender: TObject); begin 
  ResumeThread(hThread); end; 
 {挂起线程} procedure TForm1.Button3Click(Sender: TObject); begin 
  SuspendThread(hThread); end; 
 procedure TForm1.FormCreate(Sender: TObject); begin 
  Timer1.Interval := 100; end; 
 procedure TForm1.Timer1Timer(Sender: TObject); begin 
  Text := IntToStr(num); end; 
 end.

㈢、入口函数的参数

function CreateThread( 
  lpThreadAttributes: Pointer; 
  dwStackSize: DWORD; 
  lpStartAddress: TFNThreadStartRoutine; 
  lpParameter: Pointer;  {入口函数的参数} 
  dwCreationFlags: DWORD; 
  var lpThreadId: DWORD 
): THandle; stdcall;

线程入口函数的参数是个无类型指针(Pointer), 用它可以指定任何数据; 本例是把鼠标点击窗体的坐标传递给线程的入口函数, 每次点击窗体都会创建一个线程.

运行效果图:

//上面演示的代码 unit Unit1; 
 interface 
 uses 
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, 
  Dialogs; 
 type 
  TForm1 = class(TForm) 
    procedure FormMouseUp(Sender: TObject; Button: TMouseButton; 
      Shift: TShiftState; X, Y: Integer); 
  end; 
 var 
  Form1: TForm1; 
 implementation 
 {$R *.dfm} 
 var 
  pt: TPoint; {这个坐标点将会已指针的方式传递给线程, 它应该是全局的} 
 function MyThreadFun(p: Pointer): Integer; stdcall; var 
  i: Integer; 
  pt2: TPoint;       {因为指针参数给的点随时都在变, 需用线程的局部变量存起来} begin 
  pt2 := PPoint(p)^; {转换} 
  for i := 0 to 1000000 do 
  begin 
    with Form1.Canvas do begin 
      Lock; 
      TextOut(pt2.X, pt2.Y, IntToStr(i)); 
      Unlock; 
    end; 
  end; 
  Result := 0; end; 
 procedure TForm1.FormMouseUp(Sender: TObject; Button: TMouseButton; 
  Shift: TShiftState; X, Y: Integer); var 
  ID: DWORD; begin 
  pt := Point(X, Y); 
  CreateThread(nil, 0, @MyThreadFun, @pt, 0, ID); 
  {下面这种写法更好理解, 其实不必, 因为 PPoint 会自动转换为 Pointer 的} 
  //CreateThread(nil, 0, @MyThreadFun, Pointer(@pt), 0, ID); end; 
 end.

这个例子还有不严谨的地方: 当一个线程 Lock 窗体的 Canvas 时, 其他线程在等待; 线程在等待时, 其中的计数也还在增加. 这也就是说: 现在并没有去处理线程的同步; 同步是多线程中最重要的课题, 快到了.

另外有个小技巧: 线程函数的参数是个 32 位(4个字节)的指针, 仅就本例来讲, 可以让它的 “高16位” 和 “低16位” 分别携带 X 和 Y; 这样就不需要哪个全局的 pt 变量了.
其实在 Windows 的消息中就是这样传递坐标的, 在 Windows 的消息中一般高字节是 Y、低字节是 X; 咱们这么来吧, 这样还可以使用给消息准备的一些方便的函数.

重写本例代码(当然运行效果和窗体文件都是一样的):

unit Unit1; 
 interface 
 uses 
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, 
  Dialogs; 
 type 
  TForm1 = class(TForm) 
    procedure FormMouseUp(Sender: TObject; Button: TMouseButton; 
      Shift: TShiftState; X, Y: Integer); 
  end; 
 var 
  Form1: TForm1; 
 implementation 
 {$R *.dfm} 
 function MyThreadFun(p: Pointer): Integer; stdcall; var 
  i: Integer; 
  x,y: Word; begin 
  x := LoWord(Integer(p)); 
  y := HiWord(Integer(p)); 
  {如果不使用 LoWord、HiWord 函数可以像下面这样: } 
  //x := Integer(p); 
  //y := Integer(p) shr 16; 
  for i := 0 to 1000000 do 
  begin 
    with Form1.Canvas do begin 
      Lock; 
      TextOut(x, y, IntToStr(i)); 
      Unlock; 
    end; 
  end; 
  Result := 0; end; 
 procedure TForm1.FormMouseUp(Sender: TObject; Button: TMouseButton; 
  Shift: TShiftState; X, Y: Integer); var 
  ID: DWORD; 
  num: Integer; begin 
  num := MakeLong(X, Y); 
  {如果不使用 MekeLong、MakeWParam、MakeLParam、MakeResult 等函数, 可以像下面这样: } 
  //num := Y shl 16 + X; 
  CreateThread(nil, 0, @MyThreadFun, Ptr(num), 0, ID); 
  {上面的 Ptr 是专门将一个数字转换为指针的函数, 当然也可以这样: } 
  //CreateThread(nil, 0, @MyThreadFun, Pointer(num), 0, ID); end; 
 end.

㈣、入口函数的指针

function CreateThread( 
  lpThreadAttributes: Pointer; 
  dwStackSize: DWORD; 
  lpStartAddress: TFNThreadStartRoutine; {入口函数的指针} 
  lpParameter: Pointer;  
  dwCreationFlags: DWORD; 
  var lpThreadId: DWORD 
): THandle; stdcall;

到了入口函数了, 学到这个地方, 我查了一个入口函数的标准定义, 这个函数的标准返回值应该是 DWORD, 不过这函数在 Delphi 的 System 单元定义的是: TThreadFunc = function(Parameter: Pointer): Integer; 我以后会尽量使用 DWORD 做入口函数的返回值.

这个返回值有什么用呢?
等线程退出后, 我们用 GetExitCodeThread 函数获取的退出码就是这个返回值!

如果线程没有退出, GetExitCodeThread 获取的退出码将是一个常量 STILL_ACTIVE (259); 这样我们就可以通过退出码来判断线程是否已退出.

还有一个问题: 前面也提到过, 线程函数不能是某个类的方法! 假如我们非要线程去执行类中的一个方法能否实现呢?
尽管可以用 Addr(类名.方法名) 或 MethodAddress(‘published 区的方法名‘) 获取类中方法的地址, 但都不能当做线程的入口函数, 原因可能是因为类中的方法的地址是在实例化为对象时动态分配的.
后来换了个思路, 其实很简单: 在线程函数中再调用方法不就得了, 估计 TThread 也应该是这样.

下面的例子就尝试了用线程调用 TForm1 类中的方法, 并测试了退出码的相关问题.

unit Unit1; 
 interface 
 uses 
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, 
  Dialogs, StdCtrls; 
 type 
  TForm1 = class(TForm) 
    Button1: TButton; 
    Button2: TButton; 
    procedure Button1Click(Sender: TObject); 
    procedure Button2Click(Sender: TObject); 
    private 
      procedure FormProc; {准备给线程使用的方法} 
  end; 
 var 
  Form1: TForm1; 
 implementation 
 {$R *.dfm} 
 var 
  hThread: THandle; 
 {线程入口函数} function MyThreadFun(p: Pointer): DWORD; stdcall; begin 
  Form1.FormProc; {调用 TForm1 类的方法} 
  Result := 99;   {这个返回值将成为线程的退出代码, 99 是我随意给的数字} end; 
 {TForm1 的方法, 本例中是给线程的入口函数调用的} procedure TForm1.FormProc; var 
  i: Integer; begin 
  for i := 0 to 200000 do 
  begin 
    with Form1.Canvas do begin 
      Lock; 
      TextOut(10, 10, IntToStr(i)); 
      Unlock; 
    end; 
  endend; 
 {建立并执行线程} procedure TForm1.Button1Click(Sender: TObject); var 
  ID: DWORD; begin 
  hThread := CreateThread(nil, 0, @MyThreadFun, nil, 0, ID); end; 
 {获取线程的退出代码, 并判断线程是否退出} procedure TForm1.Button2Click(Sender: TObject); var 
  ExitCode: DWORD; begin 
  GetExitCodeThread(hThread, ExitCode); 
 
  if hThread = 0 then 
  begin 
    Text := ‘线程还未启动‘; 
    Exit; 
  end; 
 
  if ExitCode = STILL_ACTIVE then 
    Text := Format(‘线程退出代码是: %d, 表示线程还未退出‘, [ExitCode]) 
  else 
    Text := Format(‘线程已退出, 退出代码是: %d‘, [ExitCode]); end; 
 end.

㈤、堆栈大小

function CreateThread( 
  lpThreadAttributes: Pointer; 
  dwStackSize: DWORD;  {堆栈大小} 
  lpStartAddress: TFNThreadStartRoutine;  
  lpParameter: Pointer;  
  dwCreationFlags: DWORD; 
  var lpThreadId: DWORD 
): THandle; stdcall;

CreateThread 的第二个参数是分配给线程的堆栈大小.
这首先这可以让我们知道: 每个线程都有自己独立的堆栈(也拥有自己的消息队列).

什么是堆栈? 其实堆是堆、栈是栈, 有时 “栈” 也被叫做 “堆栈”.
它们都是进程中的内存区域, 主要是存取方式不同(栈:先进后出; 堆:先进先出);
“栈”(或叫堆栈)适合存取临时而轻便的变量, 主要用来储存局部变量; 譬如 for i := 0 to 99 do 中的 i 就只能存于栈中, 你把一个全局的变量用于 for 循环计数是不可以的.

现在我们知道了线程有自己的 “栈”, 并且在建立线程时可以分配栈的大小.

前面所有的例子中, 这个值都是 0, 这表示使用系统默认的大小, 默认和主线程栈的大小一样, 如果不够用会自动增长;
那主线程的栈有多大? 这个值是可以设定的: Project -> Options -> linker -> memory size(如图)

栈是私有的但堆是公用的, 如果不同的线程都来使用一个全局变量有点乱套;
为解决这个问题 Delphi 为我们提供了一个类似 var 的 ThreadVar 关键字, 线程在使用 ThreadVar 声明的全局变量时会在各自的栈中留一个副本, 这样就解决了冲突. 不过还是尽量使用局部变量, 或者在继承 TThread 时使用类的成员变量, 因为 ThreadVar 的效率不好, 据说比局部变量能慢 10 倍.

在下面的例子就测试了用 var 和 ThreadVar 定义变量的不同.
使用 var 效果图:

使用 ThreadVar 效果图:

unit Unit1; 
 interface 
 uses 
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, 
  Dialogs, StdCtrls; 
 type 
  TForm1 = class(TForm) 
    Button1: TButton; 
    procedure Button1Click(Sender: TObject); 
  end; 
 var 
  Form1: TForm1; 
 implementation 
 {$R *.dfm} 
 //var num: Integer;     {全局变量} threadvar num: Integer; {支持多线程的全局变量} 
 function MyThreadFun(p: Pointer): DWORD; stdcall; var 
  py: Integer; begin 
  py := Integer(p); 
  while True do 
  begin 
    Inc(num); 
    with Form1.Canvas do begin 
      Lock; 
      TextOut(20, py, IntToStr(num)); 
      Unlock; 
    end; 
    Sleep(1000); {然线程挂起 1 秒钟再继续} 
  endend; 
 procedure TForm1.Button1Click(Sender: TObject); var 
  ID: DWORD; begin 
  {借入口函数的参数传递了一个坐标点中的 Y 值, 以让各线程把结果输出在不同位置} 
  CreateThread(nil, 0, @MyThreadFun, Ptr(20), 0, ID); 
  CreateThread(nil, 0, @MyThreadFun, Ptr(40), 0, ID); 
  CreateThread(nil, 0, @MyThreadFun, Ptr(60), 0, ID); end; 
 end.

㈥、安全设置

function CreateThread( 
  lpThreadAttributes: Pointer; {安全设置} 
  dwStackSize: DWORD; 
  lpStartAddress: TFNThreadStartRoutine;  
  lpParameter: Pointer;  
  dwCreationFlags: DWORD; 
  var lpThreadId: DWORD 
): THandle; stdcall;

CreateThread 的第一个参数 lpThreadAttributes 是指向 TSecurityAttributes 结构的指针, 一般都是置为 nil, 这表示没有访问限制; 该结构的定义是:

//TSecurityAttributes(又名: SECURITY_ATTRIBUTES、_SECURITY_ATTRIBUTES) 
_SECURITY_ATTRIBUTES = record 
  nLength: DWORD;                {结构大小} 
  lpSecurityDescriptor: Pointer; {默认 nil; 这是另一个结构 TSecurityDescriptor 的指针} 
  bInheritHandle: BOOL;          {默认 False, 表示不可继承} end; 
 //TSecurityDescriptor(又名: SECURITY_DESCRIPTOR、_SECURITY_DESCRIPTOR) 
_SECURITY_DESCRIPTOR = record 
  Revision: Byte; 
  Sbz1: Byte; 
  Control: SECURITY_DESCRIPTOR_CONTROL; 
  Owner: PSID; 
  Group: PSID; 
  Sacl: PACL; 
  Dacl: PACL; end;

够复杂的, 但我们在多线程编程时不需要去设置它们, 大都是使用默认设置(也就是赋值为 nil).

我觉得有必要在此刻了解的是: 建立系统内核对象时一般都有这个属性(TSecurityAttributes);
在接下来多线程的课题中要使用一些内核对象, 不如先盘点一下, 到时碰到这个属性时给个 nil 即可, 不必再费神.

{建立事件} function CreateEvent( 
  lpEventAttributes: PSecurityAttributes; {!} 
  bManualReset: BOOL; 
  bInitialState: BOOL; 
  lpName: PWideChar 
): THandle; stdcall; 
 {建立互斥} function CreateMutex( 
  lpMutexAttributes: PSecurityAttributes; {!} 
  bInitialOwner: BOOL; 
  lpName: PWideChar 
): THandle; stdcall; 
 {建立信号} function CreateSemaphore( 
  lpSemaphoreAttributes: PSecurityAttributes; {!} 
  lInitialCount: Longint; 
  lMaximumCount: Longint; 
  lpName: PWideChar 
): THandle; stdcall; 
 {建立等待计时器} function CreateWaitableTimer( 
  lpTimerAttributes: PSecurityAttributes; {!} 
  bManualReset: BOOL; 
  lpTimerName: PWideChar 
): THandle; stdcall; 

上面的四个系统内核对象(事件、互斥、信号、计时器)都是线程同步的手段, 从这也能看出处理线程同步的复杂性; 不过这还不是全部, Windows Vista 开始又增加了 Condition variables(条件变量)、Slim Reader-Writer Locks(读写锁)等同步手段.

不过最简单、最轻便(速度最快)的同步手段还是 CriticalSection(临界区), 但它不属于系统内核对象, 当然也就没有句柄、没有 TSecurityAttributes 这个安全属性, 这也导致它不能跨进程使用; 不过写多线程时一般不用跨进程, 所以 CriticalSection 应该是最常用的同步手段.

二、临界区。
先看一段程序, 代码文件:

unit Unit1; 
 interface 
 uses 
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, 
  Dialogs, StdCtrls; 
 type 
  TForm1 = class(TForm) 
    ListBox1: TListBox; 
    Button1: TButton; 
    procedure FormCreate(Sender: TObject); 
    procedure Button1Click(Sender: TObject); 
  end; 
 var 
  Form1: TForm1; 
 implementation 
 {$R *.dfm} 
 function MyThreadFun(p: Pointer): DWORD; stdcall; var 
  i: Integer; begin 
  for i := 0 to 99 do Form1.ListBox1.Items.Add(IntToStr(i)); 
  Result := 0; end; 
 procedure TForm1.Button1Click(Sender: TObject); var 
  ID: DWORD; begin 
  CreateThread(nil, 0, @MyThreadFun, nil, 0, ID); 
  CreateThread(nil, 0, @MyThreadFun, nil, 0, ID); 
  CreateThread(nil, 0, @MyThreadFun, nil, 0, ID); end; 
 procedure TForm1.FormCreate(Sender: TObject); begin 
  ListBox1.Align := alLeft; end; 
 end.

在这段程序中, 有三个线程几乎是同时建立, 向窗体中的 ListBox1 中写数据, 最后写出的结果是这样的:

能不能让它们别打架, 一个完了另一个再来? 这就要用到多线程的同步技术.
前面说过, 最简单的同步手段就是 “临界区”.

先说这个 “同步”(Synchronize), 首先这个名字起的不好, 我们好像需要的是 “异步”; 其实异步也不准确…
管它叫什么名字呢, 它的目的就是保证不冲突、有次序、都发生.

“临界区”(CriticalSection): 当把一段代码放入一个临界区, 线程执行到临界区时就独占了, 让其他也要执行此代码的线程先等等; 这和前面用的 Lock 和 UnLock 差不多; 使用格式如下:

var CS: TRTLCriticalSection;   {声明一个 TRTLCriticalSection 结构类型变量; 它应该是全局的} 
InitializeCriticalSection(CS); {初始化} 
EnterCriticalSection(CS);      {开始: 轮到我了其他线程走开} 
LeaveCriticalSection(CS);      {结束: 其他线程可以来了} 
DeleteCriticalSection(CS);     {删除: 注意不能过早删除} 
 //也可用 TryEnterCriticalSection 替代 EnterCriticalSection.

用上临界区, 重写上面的代码, 运行效果图:

//用临界区重写后的代码文件: unit Unit1; 
 interface 
 uses 
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, 
  Dialogs, StdCtrls; 
 type 
  TForm1 = class(TForm) 
    ListBox1: TListBox; 
    Button1: TButton; 
    procedure FormCreate(Sender: TObject); 
    procedure FormDestroy(Sender: TObject); 
    procedure Button1Click(Sender: TObject); 
  end; 
 var 
  Form1: TForm1; 
 implementation 
 {$R *.dfm} 
 var 
  CS: TRTLCriticalSection; 
 function MyThreadFun(p: Pointer): DWORD; stdcall; var 
  i: Integer; begin 
  EnterCriticalSection(CS); 
  for i := 0 to 99 do Form1.ListBox1.Items.Add(IntToStr(i)); 
  LeaveCriticalSection(CS); 
  Result := 0; end; 
 procedure TForm1.Button1Click(Sender: TObject); var 
  ID: DWORD; begin 
  CreateThread(nil, 0, @MyThreadFun, nil, 0, ID); 
  CreateThread(nil, 0, @MyThreadFun, nil, 0, ID); 
  CreateThread(nil, 0, @MyThreadFun, nil, 0, ID); end; 
 procedure TForm1.FormCreate(Sender: TObject); begin 
  ListBox1.Align := alLeft; 
  InitializeCriticalSection(CS); end; 
 procedure TForm1.FormDestroy(Sender: TObject); begin 
  DeleteCriticalSection(CS); end; 
 end.

Delphi 在 SyncObjs 单元给封装了一个 TCriticalSection 类, 用法差不多, 代码如下:

unit Unit1; 
 interface 
 uses 
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, 
  Dialogs, StdCtrls; 
 type 
  TForm1 = class(TForm) 
    ListBox1: TListBox; 
    Button1: TButton; 
    procedure FormCreate(Sender: TObject); 
    procedure FormDestroy(Sender: TObject); 
    procedure Button1Click(Sender: TObject); 
  end; 
 var 
  Form1: TForm1; 
 implementation 
 {$R *.dfm} 
 uses SyncObjs; 
 var 
  CS: TCriticalSection; 
 function MyThreadFun(p: Pointer): DWORD; stdcall; var 
  i: Integer; begin 
  CS.Enter; 
  for i := 0 to 99 do Form1.ListBox1.Items.Add(IntToStr(i)); 
  CS.Leave; 
  Result := 0; end; 
 procedure TForm1.Button1Click(Sender: TObject); var 
  ID: DWORD; begin 
  CreateThread(nil, 0, @MyThreadFun, nil, 0, ID); 
  CreateThread(nil, 0, @MyThreadFun, nil, 0, ID); 
  CreateThread(nil, 0, @MyThreadFun, nil, 0, ID); end; 
 procedure TForm1.FormCreate(Sender: TObject); begin 
  ListBox1.Align := alLeft; 
  CS := TCriticalSection.Create; end; 
 procedure TForm1.FormDestroy(Sender: TObject); begin 
  CS.Free; end; 
 end.

三、等待函数 WaitForSingleObject
一下子跳到等待函数 WaitForSingleObject, 是因为下面的 Mutex、Semaphore、Event、WaitableTimer 等同步手段都要使用这个函数; 不过等待函数可不止 WaitForSingleObject 它一个, 但它最简单.

function WaitForSingleObject( 
  hHandle: THandle;      {要等待的对象句柄} 
  dwMilliseconds: DWORD  {等待的时间, 单位是毫秒} 
): DWORD; stdcall;       {返回值如下:} 
 
WAIT_OBJECT_0  {等着了, 本例中是: 等的那个进程终于结束了} 
WAIT_TIMEOUT   {等过了点(你指定的时间), 也没等着} 
WAIT_ABANDONED {好不容易等着了, 但人家还是不让咱执行; 这一般是互斥对象} 
 //WaitForSingleObject 的第二个参数一般给常数值 INFINITE, 表示一直等下去, 死等. 

WaitForSingleObject 等待什么? 在多线程里就是等待另一个线程的结束, 快来执行自己的代码; 不过它可以等待的对象可不止线程; 这里先来一个等待另一个进程结束的例子, 运行效果图:

//WaitForSingleObject的示例代码文件: 
 unit Unit1; 
 interface 
 uses 
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, 
  Dialogs, StdCtrls; 
 type 
  TForm1 = class(TForm) 
    Button1: TButton; 
    procedure Button1Click(Sender: TObject); 
  end; 
 var 
  Form1: TForm1; 
 implementation 
 {$R *.dfm} 
 var 
  hProcess: THandle; {进程句柄} 
 {等待一个指定句柄的进程什么时候结束} function MyThreadFun(p: Pointer): DWORD; stdcall; begin 
  if WaitForSingleObject(hProcess, INFINITE) = WAIT_OBJECT_0 then 
    Form1.Text := Format(‘进程 %d 已关闭‘, [hProcess]); 
  Result := 0; end; 
 {启动一个进程, 并建立新线程等待它的结束} procedure TForm1.Button1Click(Sender: TObject); var 
  pInfo: TProcessInformation; 
  sInfo: TStartupInfo; 
  Path: array[0..MAX_PATH-1] of Char; 
  ThreadID: DWORD; begin 
  {先获取记事本的路径} 
  GetSystemDirectory(Path, MAX_PATH); 
  StrCat(Path, ‘\notepad.exe‘); 
 
  {用 CreateProcess 打开记事本并获取其进程句柄, 然后建立线程监视} 
  FillChar(sInfo, SizeOf(sInfo), 0); 
  if CreateProcess(Path, nilnilnil, False, 0, nilnil, sInfo, pInfo) then 
  begin 
    hProcess := pInfo.hProcess;                           {获取进程句柄} 
    Text := Format(‘进程 %d 已启动‘, [hProcess]);  
    CreateThread(nil, 0, @MyThreadFun, nil, 0, ThreadID); {建立线程监视} 
  endend; 
 end.

javascript Array对象的constructor属性

定义和用法
constructor 属性返回对创建此对象的数组函数的引用。

语法
object.constructor

例子:

<script type="text/javascript">

var test=new Array();

if (test.constructor==Array)
{
document.write("This is an Array");
}
if (test.constructor==Boolean)
{
document.write("This is a Boolean");
}
if (test.constructor==Date)
{
document.write("This is a Date");
}
if (test.constructor==String)
{
document.write("This is a String");
}

</script>

打印:

This is an ArrayTIY

第二个例子:

<script type="text/javascript">

function employee(name,job,born)
{
this.name=name;
this.job=job;
this.born=born;
}

var bill=new employee("Bill Gates","Engineer",1985);

document.write(bill.constructor);

</script>

打印:

function employee(name, jobtitle, born)
{this.name = name; this.jobtitle = job; this.born = born;}

CART分类回归树算法

CART分类回归树算法

与上次文章中提到的ID3算法和C4.5算法类似,CART算法也是一种决策树分类算法。CART分类回归树算法的本质也是对数据进行分类的,最终数据的表现形式也是以树形的模式展现的,与ID3,C4.5算法不同的是,他的分类标准所采用的算法不同了。下面列出了其中的一些不同之处:

1、CART最后形成的树是一个二叉树,每个节点会分成2个节点,左孩子节点和右孩子节点,而在ID3和C4.5中是按照分类属性的值类型进行划分,于是这就要求CART算法在所选定的属性中又要划分出最佳的属性划分值,节点如果选定了划分属性名称还要确定里面按照那个值做一个二元的划分。

2、CART算法对于属性的值采用的是基于Gini系数值的方式做比较,gini某个属性的某次值的划分的gini指数的值为:

,pk就是分别为正负实例的概率,gini系数越小说明分类纯度越高,可以想象成与熵的定义一样。因此在最后计算的时候我们只取其中值最小的做出划分。最后做比较的时候用的是gini的增益做比较,要对分类号的数据做出一个带权重的gini指数的计算。举一个网上的一个例子:

比如体温为恒温时包含哺乳类5个、鸟类2个,则:

α表示的是每个非叶子节点的误差增益率,可以理解为误差代价,最后选出误差代价最小的一个节点进行剪枝。

里面变量的意思为:

Rid Age Income Student CreditRating BuysComputer 1 Youth High No Fair No 2 Youth High No Excellent No 3 MiddleAged High No Fair Yes 4 Senior Medium No Fair Yes 5 Senior Low Yes Fair Yes 6 Senior Low Yes Excellent No 7 MiddleAged Low Yes Excellent Yes 8 Youth Medium No Fair No 9 Youth Low Yes Fair Yes 10 Senior Medium Yes Fair Yes 11 Youth Medium Yes Excellent Yes 12 MiddleAged Medium No Excellent Yes 13 MiddleAged High Yes Fair Yes 14 Senior Medium No Excellent No下面是主程序,里面有具体的注释:

package DataMing_CART;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.Queue;

import javax.lang.model.element.NestingKind;
import javax.swing.text.DefaultEditorKit.CutAction;
import javax.swing.text.html.MinimalHTMLWriter;

/**
 * CART分类回归树算法工具类
 * 
 * @author lyq
 * 
 */
public class CARTTool {
	// 类标号的值类型
	private final String YES = "Yes";
	private final String NO = "No";

	// 所有属性的类型总数,在这里就是data源数据的列数
	private int attrNum;
	private String filePath;
	// 初始源数据,用一个二维字符数组存放模仿表格数据
	private String[][] data;
	// 数据的属性行的名字
	private String[] attrNames;
	// 每个属性的值所有类型
	private HashMap<String, ArrayList<String>> attrValue;

	public CARTTool(String filePath) {
		this.filePath = filePath;
		attrValue = new HashMap<>();
	}

	/**
	 * 从文件中读取数据
	 */
	public void readDataFile() {
		File file = new File(filePath);
		ArrayList<String[]> dataArray = new ArrayList<String[]>();

		try {
			BufferedReader in = new BufferedReader(new FileReader(file));
			String str;
			String[] tempArray;
			while ((str = in.readLine()) != null) {
				tempArray = str.split(" ");
				dataArray.add(tempArray);
			}
			in.close();
		} catch (IOException e) {
			e.getStackTrace();
		}

		data = new String[dataArray.size()][];
		dataArray.toArray(data);
		attrNum = data[0].length;
		attrNames = data[0];

		/*
		 * for (int i = 0; i < data.length; i++) { for (int j = 0; j <
		 * data[0].length; j++) { System.out.print(" " + data[i][j]); }
		 * System.out.print("\n"); }
		 */

	}

	/**
	 * 首先初始化每种属性的值的所有类型,用于后面的子类熵的计算时用
	 */
	public void initAttrValue() {
		ArrayList<String> tempValues;

		// 按照列的方式,从左往右找
		for (int j = 1; j < attrNum; j++) {
			// 从一列中的上往下开始寻找值
			tempValues = new ArrayList<>();
			for (int i = 1; i < data.length; i++) {
				if (!tempValues.contains(data[i][j])) {
					// 如果这个属性的值没有添加过,则添加
					tempValues.add(data[i][j]);
				}
			}

			// 一列属性的值已经遍历完毕,复制到map属性表中
			attrValue.put(data[0][j], tempValues);
		}

		/*
		 * for (Map.Entry entry : attrValue.entrySet()) {
		 * System.out.println("key:value " + entry.getKey() + ":" +
		 * entry.getValue()); }
		 */
	}

	/**
	 * 计算机基尼指数
	 * 
	 * @param remainData
	 *            剩余数据
	 * @param attrName
	 *            属性名称
	 * @param value
	 *            属性值
	 * @param beLongValue
	 *            分类是否属于此属性值
	 * @return
	 */
	public double computeGini(String[][] remainData, String attrName,
			String value, boolean beLongValue) {
		// 实例总数
		int total = 0;
		// 正实例数
		int posNum = 0;
		// 负实例数
		int negNum = 0;
		// 基尼指数
		double gini = 0;

		// 还是按列从左往右遍历属性
		for (int j = 1; j < attrNames.length; j++) {
			// 找到了指定的属性
			if (attrName.equals(attrNames[j])) {
				for (int i = 1; i < remainData.length; i++) {
					// 统计正负实例按照属于和不属于值类型进行划分
					if ((beLongValue && remainData[i][j].equals(value))
							|| (!beLongValue && !remainData[i][j].equals(value))) {
						if (remainData[i][attrNames.length - 1].equals(YES)) {
							// 判断此行数据是否为正实例
							posNum++;
						} else {
							negNum++;
						}
					}
				}
			}
		}

		total = posNum + negNum;
		double posProbobly = (double) posNum / total;
		double negProbobly = (double) negNum / total;
		gini = 1 - posProbobly * posProbobly - negProbobly * negProbobly;

		// 返回计算基尼指数
		return gini;
	}

	/**
	 * 计算属性划分的最小基尼指数,返回最小的属性值划分和最小的基尼指数,保存在一个数组中
	 * 
	 * @param remainData
	 *            剩余谁
	 * @param attrName
	 *            属性名称
	 * @return
	 */
	public String[] computeAttrGini(String[][] remainData, String attrName) {
		String[] str = new String[2];
		// 最终该属性的划分类型值
		String spiltValue = "";
		// 临时变量
		int tempNum = 0;
		// 保存属性的值划分时的最小的基尼指数
		double minGini = Integer.MAX_VALUE;
		ArrayList<String> valueTypes = attrValue.get(attrName);
		// 属于此属性值的实例数
		HashMap<String, Integer> belongNum = new HashMap<>();

		for (String string : valueTypes) {
			// 重新计数的时候,数字归0
			tempNum = 0;
			// 按列从左往右遍历属性
			for (int j = 1; j < attrNames.length; j++) {
				// 找到了指定的属性
				if (attrName.equals(attrNames[j])) {
					for (int i = 1; i < remainData.length; i++) {
						// 统计正负实例按照属于和不属于值类型进行划分
						if (remainData[i][j].equals(string)) {
							tempNum++;
						}
					}
				}
			}

			belongNum.put(string, tempNum);
		}

		double tempGini = 0;
		double posProbably = 1.0;
		double negProbably = 1.0;
		for (String string : valueTypes) {
			tempGini = 0;

			posProbably = 1.0 * belongNum.get(string) / (remainData.length - 1);
			negProbably = 1 - posProbably;

			tempGini += posProbably
					* computeGini(remainData, attrName, string, true);
			tempGini += negProbably
					* computeGini(remainData, attrName, string, false);

			if (tempGini < minGini) {
				minGini = tempGini;
				spiltValue = string;
			}
		}

		str[0] = spiltValue;
		str[1] = minGini + "";

		return str;
	}

	public void buildDecisionTree(AttrNode node, String parentAttrValue,
			String[][] remainData, ArrayList<String> remainAttr,
			boolean beLongParentValue) {
		// 属性划分值
		String valueType = "";
		// 划分属性名称
		String spiltAttrName = "";
		double minGini = Integer.MAX_VALUE;
		double tempGini = 0;
		// 基尼指数数组,保存了基尼指数和此基尼指数的划分属性值
		String[] giniArray;

		if (beLongParentValue) {
			node.setParentAttrValue(parentAttrValue);
		} else {
			node.setParentAttrValue("!" + parentAttrValue);
		}

		if (remainAttr.size() == 0) {
			if (remainData.length > 1) {
				ArrayList<String> indexArray = new ArrayList<>();
				for (int i = 1; i < remainData.length; i++) {
					indexArray.add(remainData[i][0]);
				}
				node.setDataIndex(indexArray);
			}
			System.out.println("attr remain null");
			return;
		}

		for (String str : remainAttr) {
			giniArray = computeAttrGini(remainData, str);
			tempGini = Double.parseDouble(giniArray[1]);

			if (tempGini < minGini) {
				spiltAttrName = str;
				minGini = tempGini;
				valueType = giniArray[0];
			}
		}
		// 移除划分属性
		remainAttr.remove(spiltAttrName);
		node.setAttrName(spiltAttrName);

		// 孩子节点,分类回归树中,每次二元划分,分出2个孩子节点
		AttrNode[] childNode = new AttrNode[2];
		String[][] rData;

		boolean[] bArray = new boolean[] { true, false };
		for (int i = 0; i < bArray.length; i++) {
			// 二元划分属于属性值的划分
			rData = removeData(remainData, spiltAttrName, valueType, bArray[i]);

			boolean sameClass = true;
			ArrayList<String> indexArray = new ArrayList<>();
			for (int k = 1; k < rData.length; k++) {
				indexArray.add(rData[k][0]);
				// 判断是否为同一类的
				if (!rData[k][attrNames.length - 1]
						.equals(rData[1][attrNames.length - 1])) {
					// 只要有1个不相等,就不是同类型的
					sameClass = false;
					break;
				}
			}

			childNode[i] = new AttrNode();
			if (!sameClass) {
				// 创建新的对象属性,对象的同个引用会出错
				ArrayList<String> rAttr = new ArrayList<>();
				for (String str : remainAttr) {
					rAttr.add(str);
				}
				buildDecisionTree(childNode[i], valueType, rData, rAttr,
						bArray[i]);
			} else {
				String pAtr = (bArray[i] ? valueType : "!" + valueType);
				childNode[i].setParentAttrValue(pAtr);
				childNode[i].setDataIndex(indexArray);
			}
		}

		node.setChildAttrNode(childNode);
	}

	/**
	 * 属性划分完毕,进行数据的移除
	 * 
	 * @param srcData
	 *            源数据
	 * @param attrName
	 *            划分的属性名称
	 * @param valueType
	 *            属性的值类型
	 * @parame beLongValue 分类是否属于此值类型
	 */
	private String[][] removeData(String[][] srcData, String attrName,
			String valueType, boolean beLongValue) {
		String[][] desDataArray;
		ArrayList<String[]> desData = new ArrayList<>();
		// 待删除数据
		ArrayList<String[]> selectData = new ArrayList<>();
		selectData.add(attrNames);

		// 数组数据转化到列表中,方便移除
		for (int i = 0; i < srcData.length; i++) {
			desData.add(srcData[i]);
		}

		// 还是从左往右一列列的查找
		for (int j = 1; j < attrNames.length; j++) {
			if (attrNames[j].equals(attrName)) {
				for (int i = 1; i < desData.size(); i++) {
					if (desData.get(i)[j].equals(valueType)) {
						// 如果匹配这个数据,则移除其他的数据
						selectData.add(desData.get(i));
					}
				}
			}
		}

		if (beLongValue) {
			desDataArray = new String[selectData.size()][];
			selectData.toArray(desDataArray);
		} else {
			// 属性名称行不移除
			selectData.remove(attrNames);
			// 如果是划分不属于此类型的数据时,进行移除
			desData.removeAll(selectData);
			desDataArray = new String[desData.size()][];
			desData.toArray(desDataArray);
		}

		return desDataArray;
	}

	public void startBuildingTree() {
		readDataFile();
		initAttrValue();

		ArrayList<String> remainAttr = new ArrayList<>();
		// 添加属性,除了最后一个类标号属性
		for (int i = 1; i < attrNames.length - 1; i++) {
			remainAttr.add(attrNames[i]);
		}

		AttrNode rootNode = new AttrNode();
		buildDecisionTree(rootNode, "", data, remainAttr, false);
		setIndexAndAlpah(rootNode, 0, false);
		System.out.println("剪枝前:");
		showDecisionTree(rootNode, 1);
		setIndexAndAlpah(rootNode, 0, true);
		System.out.println("\n剪枝后:");
		showDecisionTree(rootNode, 1);
	}

	/**
	 * 显示决策树
	 * 
	 * @param node
	 *            待显示的节点
	 * @param blankNum
	 *            行空格符,用于显示树型结构
	 */
	private void showDecisionTree(AttrNode node, int blankNum) {
		System.out.println();
		for (int i = 0; i < blankNum; i++) {
			System.out.print("    ");
		}
		System.out.print("--");
		// 显示分类的属性值
		if (node.getParentAttrValue() != null
				&& node.getParentAttrValue().length() > 0) {
			System.out.print(node.getParentAttrValue());
		} else {
			System.out.print("--");
		}
		System.out.print("--");

		if (node.getDataIndex() != null && node.getDataIndex().size() > 0) {
			String i = node.getDataIndex().get(0);
			System.out.print("【" + node.getNodeIndex() + "】类别:"
					+ data[Integer.parseInt(i)][attrNames.length - 1]);
			System.out.print("[");
			for (String index : node.getDataIndex()) {
				System.out.print(index + ", ");
			}
			System.out.print("]");
		} else {
			// 递归显示子节点
			System.out.print("【" + node.getNodeIndex() + ":"
					+ node.getAttrName() + "】");
			if (node.getChildAttrNode() != null) {
				for (AttrNode childNode : node.getChildAttrNode()) {
					showDecisionTree(childNode, 2 * blankNum);
				}
			} else {
				System.out.print("【  Child Null】");
			}
		}
	}

	/**
	 * 为节点设置序列号,并计算每个节点的误差率,用于后面剪枝
	 * 
	 * @param node
	 *            开始的时候传入的是根节点
	 * @param index
	 *            开始的索引号,从1开始
	 * @param ifCutNode
	 *            是否需要剪枝
	 */
	private void setIndexAndAlpah(AttrNode node, int index, boolean ifCutNode) {
		AttrNode tempNode;
		// 最小误差代价节点,即将被剪枝的节点
		AttrNode minAlphaNode = null;
		double minAlpah = Integer.MAX_VALUE;
		Queue<AttrNode> nodeQueue = new LinkedList<AttrNode>();

		nodeQueue.add(node);
		while (nodeQueue.size() > 0) {
			index++;
			// 从队列头部获取首个节点
			tempNode = nodeQueue.poll();
			tempNode.setNodeIndex(index);
			if (tempNode.getChildAttrNode() != null) {
				for (AttrNode childNode : tempNode.getChildAttrNode()) {
					nodeQueue.add(childNode);
				}
				computeAlpha(tempNode);
				if (tempNode.getAlpha() < minAlpah) {
					minAlphaNode = tempNode;
					minAlpah = tempNode.getAlpha();
				} else if (tempNode.getAlpha() == minAlpah) {
					// 如果误差代价值一样,比较包含的叶子节点个数,剪枝有多叶子节点数的节点
					if (tempNode.getLeafNum() > minAlphaNode.getLeafNum()) {
						minAlphaNode = tempNode;
					}
				}
			}
		}

		if (ifCutNode) {
			// 进行树的剪枝,让其左右孩子节点为null
			minAlphaNode.setChildAttrNode(null);
		}
	}

	/**
	 * 为非叶子节点计算误差代价,这里的后剪枝法用的是CCP代价复杂度剪枝
	 * 
	 * @param node
	 *            待计算的非叶子节点
	 */
	private void computeAlpha(AttrNode node) {
		double rt = 0;
		double Rt = 0;
		double alpha = 0;
		// 当前节点的数据总数
		int sumNum = 0;
		// 最少的偏差数
		int minNum = 0;

		ArrayList<String> dataIndex;
		ArrayList<AttrNode> leafNodes = new ArrayList<>();

		addLeafNode(node, leafNodes);
		node.setLeafNum(leafNodes.size());
		for (AttrNode attrNode : leafNodes) {
			dataIndex = attrNode.getDataIndex();

			int num = 0;
			sumNum += dataIndex.size();
			for (String s : dataIndex) {
				// 统计分类数据中的正负实例数
				if (data[Integer.parseInt(s)][attrNames.length - 1].equals(YES)) {
					num++;
				}
			}
			minNum += num;

			// 取小数量的值部分
			if (1.0 * num / dataIndex.size() > 0.5) {
				num = dataIndex.size() - num;
			}

			rt += (1.0 * num / (data.length - 1));
		}
		
		//同样取出少偏差的那部分
		if (1.0 * minNum / sumNum > 0.5) {
			minNum = sumNum - minNum;
		}

		Rt = 1.0 * minNum / (data.length - 1);
		alpha = 1.0 * (Rt - rt) / (leafNodes.size() - 1);
		node.setAlpha(alpha);
	}

	/**
	 * 筛选出节点所包含的叶子节点数
	 * 
	 * @param node
	 *            待筛选节点
	 * @param leafNode
	 *            叶子节点列表容器
	 */
	private void addLeafNode(AttrNode node, ArrayList<AttrNode> leafNode) {
		ArrayList<String> dataIndex;

		if (node.getChildAttrNode() != null) {
			for (AttrNode childNode : node.getChildAttrNode()) {
				dataIndex = childNode.getDataIndex();
				if (dataIndex != null && dataIndex.size() > 0) {
					// 说明此节点为叶子节点
					leafNode.add(childNode);
				} else {
					// 如果还是非叶子节点则继续递归调用
					addLeafNode(childNode, leafNode);
				}
			}
		}
	}

}

AttrNode节点的设计和属性:

/**
 * 回归分类树节点
 * 
 * @author lyq
 * 
 */
public class AttrNode {
	// 节点属性名字
	private String attrName;
	// 节点索引标号
	private int nodeIndex;
	//包含的叶子节点数
	private int leafNum;
	// 节点误差率
	private double alpha;
	// 父亲分类属性值
	private String parentAttrValue;
	// 孩子节点
	private AttrNode[] childAttrNode;
	// 数据记录索引
	private ArrayList<String> dataIndex;
	.....

get,set方法自行补上。客户端的场景调用:

package DataMing_CART;

public class Client {
	public static void main(String[] args){
		String filePath = "C:\\Users\\lyq\\Desktop\\icon\\input.txt";
		
		CARTTool tool = new CARTTool(filePath);
		
		tool.startBuildingTree();
	}
}

数据文件路径自行修改,否则会报错(特殊情况懒得处理了…..)。最后程序的输出结果,请自行从左往右看,从上往下,左边的是父亲节点,上面的是考前的子节点:

剪枝前:

    --!--【1:Age】
        --MiddleAged--【2】类别:Yes[3, 7, 12, 13, ]
        --!MiddleAged--【3:Student】
                --No--【4:Income】
                                --High--【6】类别:No[1, 2, ]
                                --!High--【7:CreditRating】
                                                                --Fair--【10】类别:Yes[4, 8, ]
                                                                --!Fair--【11】类别:No[14, ]
                --!No--【5:CreditRating】
                                --Fair--【8】类别:Yes[5, 9, 10, ]
                                --!Fair--【9:Income】
                                                                --Medium--【12】类别:Yes[11, ]
                                                                --!Medium--【13】类别:No[6, ]
剪枝后:

    --!--【1:Age】
        --MiddleAged--【2】类别:Yes[3, 7, 12, 13, ]
        --!MiddleAged--【3:Student】
                --No--【4:Income】【  Child Null】
                --!No--【5:CreditRating】
                                --Fair--【8】类别:Yes[5, 9, 10, ]
                                --!Fair--【9:Income】
                                                                --Medium--【12】类别:Yes[11, ]
                                                                --!Medium--【13】类别:No[6, ]

结果分析:

我在一开始的时候根据的是最后分类的数据是否为同一个类标识的,如果都为YES或者都为NO的,分类终止,一般情况下都说的通,但是如果最后属性划分完毕了,剩余的数据还有存在类标识不一样的情况就会偏差,比如说这里的7号CredaRating节点,下面Fair分支中的[4,8]就不是同类的。所以在后面的剪枝算法就被剪枝了。因为后面的4和7号节点的误差代价率为0,说明分类前后没有类偏差变化,这也见证了后剪枝算法的威力所在了。

在coding遇到的困难和改进的地方:

1、先说说在编码时遇到的困难,在对节点进行赋索引标号值的时候出了问题,因为是之前生成树的时候采用了DFS的思想,如果编号时也采用此方法就不对了,于是就用到了把节点取出放入队列这样的遍历方式,就是BFS的方式为节点标号。

2、程序的一个改进的地方在于算一个非叶子节点的时候需要计算他所包含的叶子节点数,采用了从当前节点开始从上往下递归计算,而且每个非叶子节点都计算一遍,显然这样做的效率是不高,后来想到了一种从叶子节点开始计算,从下往上直到根节点,对父亲节点的非叶子节点列表做更新操作,就只要计算一次,这有点dp的思想在里面了,由于时间关系,没有来得及实现。

3、第二个优化点就是后剪枝算法的多样化,我这里采用的是CCP代价复杂度算法,大家可以试着实现其他的诸如悲观误差算法进行剪枝,看看能不能把程序中4和7号节点识别出来,并且剪枝掉。

最后声明一下,用了里面的一点点的公式和例子,参考资料:http://blog.csdn.net/tianguokaka/article/details/9018933

java环境变量的简单配置

                                                                           java环境变量的配置
                               第一步:    JAVA_HOME:C:\Program Files\Java\jdk1.6.0_43
                                             (JDK的安装目录)
                               第二步:    PATH:%JAVA_HOME%\bin;
                                             (path是系统用来指定可执行文件的完整路径,java的各种操作命令是在其安装路径中的bin目录下)
                               第三步:   CLASSPATH:;%JAVA_HOME%\lib
                                            (当你要用的class,在某一个.jar下时,你需要在编译时引入import,jvm自动去找classpath环境变量引入下面的jar包)
                                             (path是执行javac和java命令时使用,而classpath是在你的程序执行时能找到java中的类)

Javascript最简单的把html字符串编码的方法

html字符串是指’<div id=”a”>aklsdjfklsjdfl</div>’这样的带html特殊符号的字符串,我们通常要对他进行处理再输出以免输出成了真正的html元素,也就是把<变成&lt;这样的html符号代码。

如果字符串很长,里面特殊符号很多,我们该怎么简单而高效的把他们全部转码呢?循环?正则表达式?都不用!且看:

2
3var a = ‘<div id=”a”>aklsdjfklsjdfl</div>‘;
alert(bian(a));

堆排序的实现

  堆排序也是一种重要的排序方式,时间复杂度能达到O(nlogn),是非常优越的排序,下面给出具体代码,原理和思路还是google一下吧,我语言欠缺描述不好,很多大神的博客里都有详细的讲解。

#include<iostream> 
using namespace std; 
void max_heapify(int data[],int i,int heapsize) 
{ 
    int l,r;
	int largest = i; 
    l = 2 * i + 1; 
    r = 2 * i + 2; 
    if(l < heapsize && data[l] > data[i]) 
        largest = l; 
    if(r < heapsize && data[r] > data[largest]) 
        largest = r; 
    if(largest != i) 
    { 
        int tmp = data[largest]; 
        data[largest] = data[i]; 
        data[i] = tmp; 
        max_heapify(data,largest,heapsize); 
    } 
} 
void set_max_heap(int data[],int heapsize) 
{ 
    for(int i = heapsize / 2 - 1;i >= 0;i --) 
        max_heapify(data,i,heapsize); 
} 
void heap_sort(int data[],int heapsize) 
{ 
    set_max_heap(data,heapsize); 
    for(int i = heapsize - 1;i > 0;i --) 
    { 
        int t = data[0]; 
        data[0] = data[i]; 
        data[i] = t; 
        max_heapify(data,0,i); 
    } 
} 
int main() 
{ 
    int data[10] = {3,6,1,9,4,5,2,7,0,8}; 
    heap_sort(data,10); 
    for(int i = 0;i < 10;i ++) 
        cout<<data[i]<<" "; 
    cout<<endl; 
} 

  输出结果:

高斯拟合c++实现

   高斯函数是数学上非常重要的函数,我们熟悉的正态分布的密度函数就是高斯函数,也称高斯分布。而正态分布无疑是概率论与数理统计里最重要的一个分布了。

   现在的问题是如果给出一些点集,如何找到一个高斯函数来拟合这些点集呢!

   当然,拟合方式还是最小二乘法,拟合函数形式为:

   y=a*exp(-((x-b)/c)^2);

 一共有三个参数,a、b、c.不过这种指数函数拟合比较难实现,所以利用对数变换将其化为二次函数,如下:

ln(y)=-(1/c^2)*x^2+(2b/c^2)*x +ln(a)-(b/c)^2;

这样,另z=ln(y),A=-(1/c^2),B=(2b/c^2),C=ln(a)-(b/c)^2;就可以利用最小二乘法拟合了,

这里我还是用奇异值分解法来解出最小二乘解,不过在这之前我先对点集做了处理,我并没有拟合所有的点集,因为这样效果很差,毕竟做了对数变换,

导致平坦点对曲线的影响太大,所以我事先判断出了峰值点,然后对峰值点做拟合,事实证明我的想法没错,拟合效果改善了很多!

VC实现效果如下:

核心程序实现如下:

//GaussFit.h

/*************************************************************************
	版本:     2014-1-06
	功能说明: 对平面上的一些列点给出最小二乘的高斯拟合,利用奇异值分解法
	           解得最小二乘解作为高斯参数。
	调用形式: gaussfit( arrayx, arrayy,int n,float box,miny );;    
	参数说明: arrayx: arrayx[n],每个值为x轴一个点
	           arrayx: arrayy[n],每个值为y轴一个点
			   n     : 点的个数
			   box   : box[3],高斯函数的3个参数,分别为a,b,c;
			   miny  :y方向上的平移,实际拟合的函数为y=a*exp(-((x-b)/c)^2)+miny
***************************************************************************/

#pragma once

struct GPOINT
{
	int x;
	int y;
	GPOINT(int x,int y):x(x),y(y){};
	friend bool operator <(GPOINT p1,GPOINT p2)
	{
		return p1.x>p2.x;
	}

};
class GaussFit
{
public:
	GaussFit(void);
	~GaussFit(void);
	void gaussfit( int *arrayx, int *arrayy,int n,float *box,int &miny );
private:
	int SVD(float *a,int m,int n,float b[],float x[],float esp);
	int gmiv(float a[],int m,int n,float b[],float x[],float aa[],float eps,float u[],float v[],int ka);
    int ginv(float a[],int m,int n,float aa[],float eps,float u[],float v[],int ka);
	int muav(float a[],int m,int n,float u[],float v[],float eps,int ka);
};

//GaussFit.cpp

#include "StdAfx.h"
#include "GaussFit.h"
#include <cmath>
#include<queue>
#include<vector>
using namespace std;
GaussFit::GaussFit(void)
{
}


GaussFit::~GaussFit(void)
{
}


void  GaussFit::gaussfit(  int *arrayx, int *arrayy,int n,float *box,int &miny )
{   
	float *A1=new float[n*3];
	float *B1=new float[n];
	float *Pointx=new float[n];
	float *Pointy=new float[n];
	int maxy=0,midx,minx;
	miny=INT_MAX;
    const double min_eps = 1e-10;
    int i;
	priority_queue<GPOINT> gp;            //用来对point.x排序
 	priority_queue<int>py;                //用来计算第m小的point.y
	int m=n/10;
	m=m>0?m:1;
	for(i=0;i<n;i++)
	{
		GPOINT tmp(arrayx[i],arrayy[i]);
		gp.push(tmp);
		if(py.size()<m)
		{
			py.push(arrayy[i]);
		}
		else if(py.top()>arrayy[i])
		{
			py.pop();
			py.push(arrayy[i]);
		}
	}
	miny=py.top();         //用第m小的y代替最小的y,防止异常点
	minx=gp.top().x;        
	for( i=0;i<n;i++)
	{
		GPOINT tmp=gp.top();
		gp.pop();
		Pointx[i]=(tmp.x-minx)*1.0;
		Pointy[i]=(tmp.y-miny)*1.0;
		if(Pointy[i]>maxy)
		{
			maxy=Pointy[i];
			midx=i;
		}
		else if (Pointy[i]<0)
		{
			Pointy[i]=0;
		}
	}
	float meany=0;
	for(int i=0;i<n;i++)
	{
		meany+=Pointy[i];
	}
	meany/=n;

	//统计峰值
	vector<GPOINT>VG,Vtmp;
	for(int i=0;i<n;i++)
	{
		if(Pointy[i]>meany)
		{
			GPOINT tmp(Pointx[i],Pointy[i]);
			Vtmp.push_back(tmp);
		}
		else
		{
			int s1=VG.size(),s2=Vtmp.size();
			if(s1<s2)
			{
				VG.clear();
				for(int j=0;j<Vtmp.size();j++)
				{
					GPOINT tmp1(Vtmp[j].x,Vtmp[j].y);
					VG.push_back(tmp1);
				}
			}
		    Vtmp.clear();
		}
	}
   int s1=VG.size(),s2=Vtmp.size();
	if(s1<s2)
	{
		VG.clear();
		for(int j=0;j<Vtmp.size();j++)
		{
			GPOINT tmp1(Vtmp[j].x,Vtmp[j].y);
			VG.push_back(tmp1);
		}
	}

	//对峰值进行高斯拟合
	int size=VG.size();
	if(size>0)
	{
		 for( i = 0; i < n; i++ )
		{
			int step=(i)*3;
			float px,py;
			px = VG[i%size].x;
			py = VG[i%size].y;
			B1[i] = log(py);
			A1[step] = 1.0;
			A1[step + 1] = px;
			A1[step + 2] = px * px;
		}
		float *x1=new float[3];

		SVD(A1,n,3,B1,x1,min_eps);
		if (x1[2]<0)
		{
			box[2]=sqrt(-1.0/x1[2]);
			box[1]=x1[1]*box[2]*box[2]*0.5;
			box[0]=exp(x1[0]+box[1]*box[1]/(box[2]*box[2]));
			box[1]+=minx;
		}
		else
		{
			box[0]=box[1]=box[2]=-1;
		}
		delete []x1;
	}
	else
	{
		box[0]=box[1]=box[2]=-1;
	}
	delete []A1;
    delete []B1;
	delete []Pointx;
	delete []Pointy;
}

int GaussFit::SVD(float *a,int m,int n,float b[],float x[],float esp)
{  
	float *aa;
	float *u;
	float *v;
    aa=new float[n*m];
	u=new  float[m*m];
    v=new  float[n*n];
   
   int ka;
   int  flag;
   if(m>n)
   { 
	ka=m+1;
   }else
   {
	   ka=n+1;
   }
   
   flag=gmiv(a,m,n,b,x,aa,esp,u,v,ka);
   
    
    
	delete []aa;
	delete []u;
	delete []v;
    
	return(flag);
}





int GaussFit::gmiv( float a[],int m,int n,float b[],float x[],float aa[],float eps,float u[],float v[],int ka)  
{ 
	int i,j;
    i=ginv(a,m,n,aa,eps,u,v,ka);

    if (i<0) return(-1);
    for (i=0; i<=n-1; i++)
      { x[i]=0.0;
        for (j=0; j<=m-1; j++)
          x[i]=x[i]+aa[i*m+j]*b[j];
      }
    return(1);
  }


int GaussFit::ginv(float a[],int m,int n,float aa[],float eps,float u[],float v[],int ka)
  { 

 //  int muav(float a[],int m,int n,float u[],float v[],float eps,int ka);
	
	int i,j,k,l,t,p,q,f;
    i=muav(a,m,n,u,v,eps,ka);
    if (i<0) return(-1);
    j=n;
    if (m<n) j=m;
    j=j-1;
    k=0;
    while ((k<=j)&&(a[k*n+k]!=0.0)) k=k+1;
    k=k-1;
    for (i=0; i<=n-1; i++)
    for (j=0; j<=m-1; j++)
      { t=i*m+j; aa[t]=0.0;
        for (l=0; l<=k; l++)
          { f=l*n+i; p=j*m+l; q=l*n+l;
            aa[t]=aa[t]+v[f]*u[p]/a[q];
          }
      }
    return(1);
  }






int GaussFit::muav(float a[],int m,int n,float u[],float v[],float eps,int ka)
  { int i,j,k,l,it,ll,kk,ix,iy,mm,nn,iz,m1,ks;
    float d,dd,t,sm,sm1,em1,sk,ek,b,c,shh,fg[2],cs[2];
    float *s,*e,*w;
    //void ppp();
   // void sss();
     void ppp(float a[],float e[],float s[],float v[],int m,int n);
     void sss(float fg[],float cs[]);

	s=(float *) malloc(ka*sizeof(float));
    e=(float *) malloc(ka*sizeof(float));
    w=(float *) malloc(ka*sizeof(float));
    it=60; k=n;
    if (m-1<n) k=m-1;
    l=m;
    if (n-2<m) l=n-2;
    if (l<0) l=0;
    ll=k;
    if (l>k) ll=l;
    if (ll>=1)
      { for (kk=1; kk<=ll; kk++)
          { if (kk<=k)
              { d=0.0;
                for (i=kk; i<=m; i++)
                  { ix=(i-1)*n+kk-1; d=d+a[ix]*a[ix];}
                s[kk-1]=(float)sqrt(d);
                if (s[kk-1]!=0.0)
                  { ix=(kk-1)*n+kk-1;
                    if (a[ix]!=0.0)
                      { s[kk-1]=(float)fabs(s[kk-1]);
                        if (a[ix]<0.0) s[kk-1]=-s[kk-1];
                      }
                    for (i=kk; i<=m; i++)
                      { iy=(i-1)*n+kk-1;
                        a[iy]=a[iy]/s[kk-1];
                      }
                    a[ix]=1.0f+a[ix];
                  }
                s[kk-1]=-s[kk-1];
              }
            if (n>=kk+1)
              { for (j=kk+1; j<=n; j++)
                  { if ((kk<=k)&&(s[kk-1]!=0.0))
                      { d=0.0;
                        for (i=kk; i<=m; i++)
                          { ix=(i-1)*n+kk-1;
                            iy=(i-1)*n+j-1;
                            d=d+a[ix]*a[iy];
                          }
                        d=-d/a[(kk-1)*n+kk-1];
                        for (i=kk; i<=m; i++)
                          { ix=(i-1)*n+j-1;
                            iy=(i-1)*n+kk-1;
                            a[ix]=a[ix]+d*a[iy];
                          }
                      }
                    e[j-1]=a[(kk-1)*n+j-1];
                  }
              }
            if (kk<=k)
              { for (i=kk; i<=m; i++)
                  { ix=(i-1)*m+kk-1; iy=(i-1)*n+kk-1;
                    u[ix]=a[iy];
                  }
              }
            if (kk<=l)
              { d=0.0;
                for (i=kk+1; i<=n; i++)
                  d=d+e[i-1]*e[i-1];
                e[kk-1]=(float)sqrt(d);
                if (e[kk-1]!=0.0)
                  { if (e[kk]!=0.0)
                      { e[kk-1]=(float)fabs(e[kk-1]);
                        if (e[kk]<0.0) e[kk-1]=-e[kk-1];
                      }
                    for (i=kk+1; i<=n; i++)
                      e[i-1]=e[i-1]/e[kk-1];
                    e[kk]=1.0f+e[kk];
                  }
                e[kk-1]=-e[kk-1];
                if ((kk+1<=m)&&(e[kk-1]!=0.0))
                  { for (i=kk+1; i<=m; i++) w[i-1]=0.0;
                    for (j=kk+1; j<=n; j++)
                      for (i=kk+1; i<=m; i++)
                        w[i-1]=w[i-1]+e[j-1]*a[(i-1)*n+j-1];
                    for (j=kk+1; j<=n; j++)
                      for (i=kk+1; i<=m; i++)
                        { ix=(i-1)*n+j-1;
                          a[ix]=a[ix]-w[i-1]*e[j-1]/e[kk];
                        }
                  }
                for (i=kk+1; i<=n; i++)
                  v[(i-1)*n+kk-1]=e[i-1];
              }
          }
      }
    mm=n;
    if (m+1<n) mm=m+1;
    if (k<n) s[k]=a[k*n+k];
    if (m<mm) s[mm-1]=0.0;
    if (l+1<mm) e[l]=a[l*n+mm-1];
    e[mm-1]=0.0;
    nn=m;
    if (m>n) nn=n;
    if (nn>=k+1)
      { for (j=k+1; j<=nn; j++)
          { for (i=1; i<=m; i++)
              u[(i-1)*m+j-1]=0.0;
            u[(j-1)*m+j-1]=1.0;
          }
      }
    if (k>=1)
      { for (ll=1; ll<=k; ll++)
          { kk=k-ll+1; iz=(kk-1)*m+kk-1;
            if (s[kk-1]!=0.0)
              { if (nn>=kk+1)
                  for (j=kk+1; j<=nn; j++)
                    { d=0.0;
                      for (i=kk; i<=m; i++)
                        { ix=(i-1)*m+kk-1;
                          iy=(i-1)*m+j-1;
                          d=d+u[ix]*u[iy]/u[iz];
                        }
                      d=-d;
                      for (i=kk; i<=m; i++)
                        { ix=(i-1)*m+j-1;
                          iy=(i-1)*m+kk-1;
                          u[ix]=u[ix]+d*u[iy];
                        }
                    }
                  for (i=kk; i<=m; i++)
                    { ix=(i-1)*m+kk-1; u[ix]=-u[ix];}
                  u[iz]=1.0f+u[iz];
                  if (kk-1>=1)
                    for (i=1; i<=kk-1; i++)
                      u[(i-1)*m+kk-1]=0.0;
              }
            else
              { for (i=1; i<=m; i++)
                  u[(i-1)*m+kk-1]=0.0;
                u[(kk-1)*m+kk-1]=1.0;
              }
          }
      }
    for (ll=1; ll<=n; ll++)
      { kk=n-ll+1; iz=kk*n+kk-1;
        if ((kk<=l)&&(e[kk-1]!=0.0))
          { for (j=kk+1; j<=n; j++)
              { d=0.0;
                for (i=kk+1; i<=n; i++)
                  { ix=(i-1)*n+kk-1; iy=(i-1)*n+j-1;
                    d=d+v[ix]*v[iy]/v[iz];
                  }
                d=-d;
                for (i=kk+1; i<=n; i++)
                  { ix=(i-1)*n+j-1; iy=(i-1)*n+kk-1;
                    v[ix]=v[ix]+d*v[iy];
                  }
              }
          }
        for (i=1; i<=n; i++)
          v[(i-1)*n+kk-1]=0.0;
        v[iz-n]=1.0;
      }
    for (i=1; i<=m; i++)
    for (j=1; j<=n; j++)
      a[(i-1)*n+j-1]=0.0;
    m1=mm; it=60;
    while (1==1)
      { if (mm==0)
          { ppp(a,e,s,v,m,n);
            free(s); free(e); free(w); return(1);
          }
        if (it==0)
          { ppp(a,e,s,v,m,n);
            free(s); free(e); free(w); return(-1);
          }
        kk=mm-1;
	while ((kk!=0)&&(fabs(e[kk-1])!=0.0))
          { d=(float)(fabs(s[kk-1])+fabs(s[kk]));
            dd=(float)fabs(e[kk-1]);
            if (dd>eps*d) kk=kk-1;
            else e[kk-1]=0.0;
          }
        if (kk==mm-1)
          { kk=kk+1;
            if (s[kk-1]<0.0)
              { s[kk-1]=-s[kk-1];
                for (i=1; i<=n; i++)
                  { ix=(i-1)*n+kk-1; v[ix]=-v[ix];}
              }
            while ((kk!=m1)&&(s[kk-1]<s[kk]))
              { d=s[kk-1]; s[kk-1]=s[kk]; s[kk]=d;
                if (kk<n)
                  for (i=1; i<=n; i++)
                    { ix=(i-1)*n+kk-1; iy=(i-1)*n+kk;
                      d=v[ix]; v[ix]=v[iy]; v[iy]=d;
                    }
                if (kk<m)
                  for (i=1; i<=m; i++)
                    { ix=(i-1)*m+kk-1; iy=(i-1)*m+kk;
                      d=u[ix]; u[ix]=u[iy]; u[iy]=d;
                    }
                kk=kk+1;
              }
            it=60;
            mm=mm-1;
          }
        else
          { ks=mm;
            while ((ks>kk)&&(fabs(s[ks-1])!=0.0))
              { d=0.0;
                if (ks!=mm) d=d+(float)fabs(e[ks-1]);
                if (ks!=kk+1) d=d+(float)fabs(e[ks-2]);
                dd=(float)fabs(s[ks-1]);
                if (dd>eps*d) ks=ks-1;
                else s[ks-1]=0.0;
              }
            if (ks==kk)
              { kk=kk+1;
                d=(float)fabs(s[mm-1]);
                t=(float)fabs(s[mm-2]);
                if (t>d) d=t;
                t=(float)fabs(e[mm-2]);
                if (t>d) d=t;
                t=(float)fabs(s[kk-1]);
                if (t>d) d=t;
                t=(float)fabs(e[kk-1]);
                if (t>d) d=t;
                sm=s[mm-1]/d; sm1=s[mm-2]/d;
                em1=e[mm-2]/d;
                sk=s[kk-1]/d; ek=e[kk-1]/d;
                b=((sm1+sm)*(sm1-sm)+em1*em1)/2.0f;
                c=sm*em1; c=c*c; shh=0.0;
                if ((b!=0.0)||(c!=0.0))
                  { shh=(float)sqrt(b*b+c);
                    if (b<0.0) shh=-shh;
                    shh=c/(b+shh);
                  }
                fg[0]=(sk+sm)*(sk-sm)-shh;
                fg[1]=sk*ek;
                for (i=kk; i<=mm-1; i++)
                  { sss(fg,cs);
                    if (i!=kk) e[i-2]=fg[0];
                    fg[0]=cs[0]*s[i-1]+cs[1]*e[i-1];
                    e[i-1]=cs[0]*e[i-1]-cs[1]*s[i-1];
                    fg[1]=cs[1]*s[i];
                    s[i]=cs[0]*s[i];
                    if ((cs[0]!=1.0)||(cs[1]!=0.0))
                      for (j=1; j<=n; j++)
                        { ix=(j-1)*n+i-1;
                          iy=(j-1)*n+i;
                          d=cs[0]*v[ix]+cs[1]*v[iy];
                          v[iy]=-cs[1]*v[ix]+cs[0]*v[iy];
                          v[ix]=d;
                        }
                    sss(fg,cs);
                    s[i-1]=fg[0];
                    fg[0]=cs[0]*e[i-1]+cs[1]*s[i];
                    s[i]=-cs[1]*e[i-1]+cs[0]*s[i];
                    fg[1]=cs[1]*e[i];
                    e[i]=cs[0]*e[i];
                    if (i<m)
                      if ((cs[0]!=1.0)||(cs[1]!=0.0))
                        for (j=1; j<=m; j++)
                          { ix=(j-1)*m+i-1;
                            iy=(j-1)*m+i;
                            d=cs[0]*u[ix]+cs[1]*u[iy];
                            u[iy]=-cs[1]*u[ix]+cs[0]*u[iy];
                            u[ix]=d;
                          }
                  }
                e[mm-2]=fg[0];
                it=it-1;
              }
            else
              { if (ks==mm)
                  { kk=kk+1;
                    fg[1]=e[mm-2]; e[mm-2]=0.0;
                    for (ll=kk; ll<=mm-1; ll++)
                      { i=mm+kk-ll-1;
                        fg[0]=s[i-1];
                        sss(fg,cs);
                        s[i-1]=fg[0];
                        if (i!=kk)
                          { fg[1]=-cs[1]*e[i-2];
                            e[i-2]=cs[0]*e[i-2];
                          }
                        if ((cs[0]!=1.0)||(cs[1]!=0.0))
                          for (j=1; j<=n; j++)
                            { ix=(j-1)*n+i-1;
                              iy=(j-1)*n+mm-1;
                              d=cs[0]*v[ix]+cs[1]*v[iy];
                              v[iy]=-cs[1]*v[ix]+cs[0]*v[iy];
                              v[ix]=d;
                            }
                      }
                  }
                else
                  { kk=ks+1;
                    fg[1]=e[kk-2];
                    e[kk-2]=0.0;
                    for (i=kk; i<=mm; i++)
                      { fg[0]=s[i-1];
                        sss(fg,cs);
                        s[i-1]=fg[0];
                        fg[1]=-cs[1]*e[i-1];
                        e[i-1]=cs[0]*e[i-1];
                        if ((cs[0]!=1.0)||(cs[1]!=0.0))
                          for (j=1; j<=m; j++)
                            { ix=(j-1)*m+i-1;
                              iy=(j-1)*m+kk-2;
                              d=cs[0]*u[ix]+cs[1]*u[iy];
                              u[iy]=-cs[1]*u[ix]+cs[0]*u[iy];
                              u[ix]=d;
                            }
                      }
                  }
              }
          }
      }
   
   	free(s);free(e);free(w); 
	  return(1);


  }

 
void ppp(float a[],float e[],float s[],float v[],int m,int n) 
{ int i,j,p,q;
    float d;
    if (m>=n) i=n;
    else i=m;
    for (j=1; j<=i-1; j++)
      { a[(j-1)*n+j-1]=s[j-1];
        a[(j-1)*n+j]=e[j-1];
      }
    a[(i-1)*n+i-1]=s[i-1];
    if (m<n) a[(i-1)*n+i]=e[i-1];
    for (i=1; i<=n-1; i++)
    for (j=i+1; j<=n; j++)
      { p=(i-1)*n+j-1; q=(j-1)*n+i-1;
        d=v[p]; v[p]=v[q]; v[q]=d;
      }
    return;
  }

 
  void sss(float fg[],float cs[])
 { float r,d;
    if ((fabs(fg[0])+fabs(fg[1]))==0.0)
      { cs[0]=1.0; cs[1]=0.0; d=0.0;}
    else 
      { d=(float)sqrt(fg[0]*fg[0]+fg[1]*fg[1]);
        if (fabs(fg[0])>fabs(fg[1]))
          { d=(float)fabs(d);
            if (fg[0]<0.0) d=-d;
          }
        if (fabs(fg[1])>=fabs(fg[0]))
          { d=(float)fabs(d);
            if (fg[1]<0.0) d=-d;
          }
        cs[0]=fg[0]/d; cs[1]=fg[1]/d;
      }
    r=1.0;
    if (fabs(fg[0])>fabs(fg[1])) r=cs[1];
    else
      if (cs[0]!=0.0) r=1.0f/cs[0];
    fg[0]=d; fg[1]=r;
    return;
  }

用Python写的简单脚本更新本地hosts

这两天Google墙得严重,于是就产生了做个一键更新hosts的脚本的想法。

由于正在学习Python,理所当然用Python来写这个脚本了。

接触比较多的就是urllib2这个库,习惯性的import进去了。还要import一个re的库,让Python支持正则表达式。关于正则表达式我研究不多,只会点简单的,如果想了解下正则表达式可以上这个网站http://deerchao.net/tutorials/regex/regex.htm。

Python比较简洁,这里就用到了个写入文件的语法。下面贴上代码。

# coding:utf-8
import re
import urllib2
r=urllib2.urlopen(‘http://googless.sinaapp.com‘).read()
link_list = re.findall(‘1\d+.\d+.\d+.\d+‘ ,r)
newlist = []
def unique(oldlist):
	for x in oldlist:
		if x not in newlist:
			newlist.append(x)
	return newlist
unique(link_list)
f = open(‘/private/etc/hosts‘,‘w‘)
for each_link in newlist:
	f.write(each_link+‘    ‘+‘www.google.com‘+‘\n‘)
f.close()

  后来我看到有个朋友在他的博客上一直在更新好用的hosts,索性不用正则表达式,一股脑把他的那个网页给爬下来了,嘿嘿

import urllib2
content = urllib2.urlopen(‘http://www.findspace.name/adds/hosts‘).read()
f = open(‘/private/etc/hosts‘,‘w‘)
f.write(content)
f.close()

  是不是超级简单,哈哈~

php关联数组和索引数组差别

    没有查到明确的php中定义关联数组/索引数组的解析,根据phpdocument及百度的一些资料和实际的代码测试,对关联数组/索引数据进行定义解析。这个问题主要在和手机端ios app产品提供api时遇到,用关联数组转换为json能更好的用oc解析转换为数组。

    关联数组:没有明确的索引键,默认从0开始作为索引键。

    $temp_arr = array (

     ‘已经在别处买到‘,

     ‘商品不符合需求‘,

    ‘价格太高‘,

    ‘不想买了‘,

    ‘卖家没有交易记录‘,

     ‘其他原因‘,

    );

    $temp_arr[0] = ‘已经在别处买到‘;

    $temp_arr[1] = ‘商品不符合需求‘;

    按print_f()打印数组:

    Array ( [0] => 已经在别处买到 [1] => 商品不符合需求 [2] => 价格太高 [3] => 不想买了 [4] => 卖家没有交易记录 [5] => 其他原因 )

    转换为json输出:

    [“\u5df2\u7ecf\u5728\u522b\u5904\u4e70\u5230″,”\u5546\u54c1\u4e0d\u7b26\u5408\u9700\u6c42″,”\u4ef7\u683c\u592a\u9ad8″,”\u4e0d\u60f3\u4e70\u4e86″,”\u5356\u5bb6\u6ca1\u6709\u4ea4\u6613\u8bb0\u5f55″,”\u5176\u4ed6\u539f\u56e0”]

    索引数组:有明确的索引键,形成键值对关系。

    $temp_arr = array (

    ‘1‘ => ‘已经在别处买到‘,

    ‘2‘ => ‘商品不符合需求‘,

    ‘3‘ => ‘价格太高‘,

    ‘4‘ => ‘不想买了‘,

    ‘5‘ => ‘卖家没有交易记录‘,

    ‘6‘ => ‘其他原因‘,

    );

    按print_f()打印数组:

    Array ( [1] => 已经在别处买到 [2] => 商品不符合需求 [3] => 价格太高 [4] => 不想买了 [5] => 卖家没有交易记录 [6] => 其他原因 )

    转换为json输出:

    {“1″:”\u5df2\u7ecf\u5728\u522b\u5904\u4e70\u5230″,”2″:”\u5546\u54c1\u4e0d\u7b26\u5408\u9700\u6c42″,”3″:”\u4ef7\u683c\u592a\u9ad8″,”4″:”\u4e0d\u60f3\u4e70\u4e86″,”5″:”\u5356\u5bb6\u6ca1\u6709\u4ea4\u6613\u8bb0\u5f55″,”6″:”\u5176\u4ed6\u539f\u56e0”}

CSS和JavaScript标签style属性对照表

CSS和JavaScript标签style属性对照表
一般情况是把”-“去掉,第二个字母用大写。

CSS语法 (不区分大小写) JavaScript语法 (区分大小写)

border border
border-bottom borderBottom
border-bottom-color borderBottomColor
border-bottom-style borderBottomStyle
border-bottom-width borderBottomWidth
border-color borderColor
border-left borderLeft
border-left-color borderLeftColor
border-left-style borderLeftStyle
border-left-width borderLeftWidth
border-right borderRight
border-right-color borderRightColor
border-right-style borderRightStyle
border-right-width borderRightWidth
border-style borderStyle
border-top borderTop
border-top-color borderTopColor
border-top-style borderTopStyle
border-top-width borderTopWidth
border-width borderWidth
clear clear
float float //styleFloat(IE)/cssFloat(FireFox)
margin margin
margin-bottom marginBottom
margin-left marginLeft
margin-right marginRight
margin-top marginTop
padding padding
padding-bottom paddingBottom
padding-left paddingLeft
padding-right paddingRight
padding-top paddingTop

颜色和背景标签和属性对照
background background
background-attachment backgroundAttachment
background-color backgroundColor
background-image backgroundImage
background-position backgroundPosition
background-repeat backgroundRepeat
color color

样式标签和属性对照
display display
list-style-type listStyleType
list-style-image listStyleImage
list-style-position listStylePosition
list-style listStyle
white-space whiteSpace

文字样式标签和属性对照
font font
font-family fontFamily
font-size fontSize
font-style fontStyle
font-variant fontVariant
font-weight fontWeight

文本标签和属性对照
letter-spacing letterSpacing
line-break lineBreak
line-height lineHeight
text-align textAlign
text-decoration textDecoration
text-indent textIndent
text-justify textJustify
text-transform textTransform
vertical-align verticalAlign

补充:
style 获取的是内联样式,即style属性中的值,可以用其改变元素显示样式
currentStyle 只有 IE 和 Opera 支持,获取 HTMLElement 的计算后的样式,其他浏览器中不支持
getComputedStyle 在标准浏览器中使用,IE9也支持getComputedStyle,但是支持不完全,比如border

var element = document.getElementById(“id”);
var currentStyle = element.currentStyle? element.currentStyle : window.getComputedStyle(element, null);
var float = currentStyle.getPropertyValue(“float”)//float 旧版浏览器保持兼容的简单方式

Java实现多线程并发

 1 import java.util.concurrent.ExecutorService;
 2 import java.util.concurrent.Executors;
 3 import java.util.concurrent.Semaphore;
 4 
 5 public class ThreadTest {
 6     private static int thread_num = 500;
 7     private static int client_num = 5000;
 8 
 9     public static void main(String[] args,String no) {
10         ExecutorService exec = Executors.newCachedThreadPool();
11 
12         final Semaphore semp = new Semaphore(thread_num);
13 
14         for (int index = 0; index < client_num; index++) {
15 
16             final int NO = index;
17 
18             Runnable run = new Runnable() {
19                 public void run() {
20                     try {
21                         semp.acquire();
22                         //HttpClientTest.postLogin();
23                         System.out.println("Thread:" + NO);
24                         semp.release();
25                     } catch (Exception e) {
26                         e.printStackTrace();
27                     }
28                 }
29             };
30             exec.execute(run);
31         }
32         exec.shutdown();
33     }
34 }

 

C++ vector常用法

在c++中,vector是一个十分有用的容器,下面对这个容器做一下总结。

1 基本操作

(1)头文件#include<vector>.

(2)创建vector对象,vector<int> vec;

(3)尾部插入数字:vec.push_back(a);

(4)使用下标访问元素,cout<<vec[0]<<endl;记住下标是从0开始的。

(5)使用迭代器访问元素.

vector<int>::iterator it;
for(it=vec.begin();it!=vec.end();it++)
    cout<<*it<<endl;

(6)插入元素:    vec.insert(vec.begin()+i,a);在第i+1个元素前面插入a;

(7)删除元素:    vec.erase(vec.begin()+2);删除第3个元素

vec.erase(vec.begin()+i,vec.end()+j);删除区间[i,j-1];区间从0开始

(8)向量大小:vec.size();

(9)清空:vec.clear();

2

vector的元素不仅仅可以使int,double,string,还可以是结构体,但是要注意:结构体要定义为全局的,否则会出错。下面是一段简短的程序代码:

#include<stdio.h>
#include<algorithm>
#include<vector>
#include<iostream>
using namespace std;

typedef struct rect
{
    int id;
    int length;
    int width;

  //对于向量元素是结构体的,可在结构体内部定义比较函数,下面按照id,length,width升序排序。
  bool operator< (const rect &a)  const
    {
        if(id!=a.id)
            return id<a.id;
        else
        {
            if(length!=a.length)
                return length<a.length;
            else
                return width<a.width;
        }
    } }Rect; int main() { vector<Rect> vec; Rect rect; rect.id=1; rect.length=2; rect.width=3; vec.push_back(rect); vector<Rect>::iterator it=vec.begin(); cout<<(*it).id<<‘ ‘<<(*it).length<<‘ ‘<<(*it).width<<endl; return 0; }

 3  算法

(1) 使用reverse将元素翻转:需要头文件#include<algorithm>

reverse(vec.begin(),vec.end());将元素翻转(在vector中,如果一个函数中需要两个迭代器,

一般后一个都不包含.)

(2)使用sort排序:需要头文件#include<algorithm>,

sort(vec.begin(),vec.end());(默认是按升序排列,即从小到大).

可以通过重写排序比较函数按照降序比较,如下:

定义排序比较函数:

bool Comp(const int &a,const int &b)
{
    return a>b;
}
调用时:sort(vec.begin(),vec.end(),Comp),这样就降序排序。

springmvc使用JSR-303进行校验

下面提供一种springmvc的校验方案,一般没有校验或者手动写validator的话都要写好多代码好多if判断,使用JSR-303规范校验只需要在Pojo字段上加上相应的注解就可以实现校验了

1.依赖的jar包,我直接贴pom了

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<spring.version>4.1.1.RELEASE</spring.version>
	</properties>
	
	<dependencies>
		<dependency>
			<groupId>com.fasterxml.jackson.core</groupId>
			<artifactId>jackson-core</artifactId>
			<version>2.4.0</version>
		</dependency>
		<dependency>
			<groupId>com.fasterxml.jackson.core</groupId>
			<artifactId>jackson-databind</artifactId>
			<version>2.4.0</version>
		</dependency>
		<dependency>
			<groupId>commons-fileupload</groupId>
			<artifactId>commons-fileupload</artifactId>
			<version>1.3.1</version>
		</dependency>
		<dependency>
			<groupId>javax.servlet</groupId>
			<artifactId>javax.servlet-api</artifactId>
			<version>3.1.0</version>
		</dependency>
		<dependency>
			<groupId>javax.validation</groupId>
			<artifactId>validation-api</artifactId>
			<version>1.1.0.Final</version>
		</dependency>
		<dependency>
			<groupId>org.hibernate</groupId>
			<artifactId>hibernate-validator</artifactId>
			<version>5.1.3.Final</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-orm</artifactId>
			<version>${spring.version}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-context</artifactId>
			<version>${spring.version}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-web</artifactId>
			<version>${spring.version}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-webmvc</artifactId>
			<version>${spring.version}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-test</artifactId>
			<version>${spring.version}</version>
		</dependency>
		<dependency>
			<groupId>org.slf4j</groupId>
			<artifactId>slf4j-log4j12</artifactId>
			<version>1.7.7</version>
		</dependency>
		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>4.11</version>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>org.aspectj</groupId>
			<artifactId>aspectjweaver</artifactId>
			<version>1.8.4</version>
		</dependency>
		<dependency>
			<groupId>javax.el</groupId>
			<artifactId>javax.el-api</artifactId>
			<version>3.0.0</version>
		</dependency>
	</dependencies>

2.springmvc配置

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tx="http://www.springframework.org/schema/tx"
	xmlns:context="http://www.springframework.org/schema/context"
	xmlns:mvc="http://www.springframework.org/schema/mvc"
	xmlns:aop="http://www.springframework.org/schema/aop"
	xsi:schemaLocation="http://www.springframework.org/schema/beans 
		http://www.springframework.org/schema/beans/spring-beans-3.0.xsd 
		http://www.springframework.org/schema/tx 
		http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
		http://www.springframework.org/schema/context
		http://www.springframework.org/schema/context/spring-context-3.0.xsd
		http://www.springframework.org/schema/mvc
		http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd
		http://www.springframework.org/schema/aop 
		http://www.springframework.org/schema/aop/spring-aop.xsd">

	<!-- 默认的注解映射的支持 -->
	<mvc:annotation-driven/>
	<context:annotation-config/>
	<aop:aspectj-autoproxy/>

	<!-- 自动扫描的包名 -->
	<context:component-scan base-package="spring.test.web.controller"/>
	
	<bean id="mappingJacksonHttpMessageConverter" class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter" />
	
</beans>

3.写java代码了

先建一个User类

public class User {
	@NotNull
	private String username;
	
	@NotNull(message = "密码不能为空")
	private String password;
	
	public String getUsername() {
		return username;
	}
	
	public void setUsername(String username) {
		this.username = username;
	}
	
	public String getPassword() {
		return password;
	}
	
	public void setPassword(String password) {
		this.password = password;
	}
	
}

然后建一个controller试试

@ResponseBody
@RequestMapping("/test1")
public AjaxResponse validateTest1(@Valid User user, BindingResult result){
	AjaxResponse ajaxResponse = new AjaxResponse();
	Map<String, Object> map = new HashMap<>();
	if(result.hasErrors()){
		ajaxResponse.setSuccess(false);
		ajaxResponse.setHasErrors(true);
		map.put("errors", result.getAllErrors());
	}
	else {
		map.put("now", new Date());
		map.put("user", user);
	}
	ajaxResponse.setData(map);
	return ajaxResponse;
}

这个方法第一个参数上加了@Valid标注,第二个参数是必须的而且必须紧跟着@Valid参数之后,校验结果都在这个result里面

方法逻辑比较简单,调用result.hasErrors()看校验有没有错误,有错误的话就把所有的错误放进结果返回给客户端,如果没有错误就返回当前时间跟user对象

然后写个测试方法试试

@Test
public void test1() throws Exception{
	this.mockMvc.perform(post("/validator/test1")).andDo(print());
	this.mockMvc.perform(post("/validator/test1").param("username", "testusername").param("password", "testpassword")).andDo(print());
}

第一个请求的结果是

{"success":false,"message":null,"hasErrors":true,"data":[{"name":"user","message":"may not be null"},{"name":"user","message":"密码不能为空"}]}

第二个请求结果是

{"success":true,"message":null,"hasErrors":false,"data":{"now":1420788232169,"user":{"username":"testusername","password":"testpassword"}}}

很明显第一次请求没有用户名和密码两个字段校验都没有通过,第二次是成功的

用起来还是很简单的,但是如果我不想每次都在controller方法里写if(result.hasErrors())怎么办呢,写个切面,把if写在切面里,如果有errors直接就返回了,不用再执行controller方法了

@Aspect
public class ValidAspect {
	
	@Autowired private Validator validator;
	
	@Around("@annotation(org.springframework.web.bind.annotation.ResponseBody)")
	public Object doTest(ProceedingJoinPoint pjp) throws Throwable{
		MethodSignature signature = (MethodSignature) pjp.getSignature();
		Method method = signature.getMethod();
		if(!AjaxResponse.class.equals(method.getReturnType())){
			pjp.proceed();
		}
		Object[] args = pjp.getArgs();
		Annotation[][] annotations = method.getParameterAnnotations();
		for(int i = 0; i < annotations.length; i++){
			if(!hasValidAnnotation(annotations[i])){
				continue;
			}
			if(!(i < annotations.length-1 && args[i+1] instanceof BindingResult)){
				//验证对象后面没有跟bindingResult,事实上如果没有应该到不了这一步
				continue;
			}
			BindingResult result = (BindingResult) args[i+1];
			if(result.hasErrors()){
				AjaxResponse ajaxResponse = new AjaxResponse();
				ajaxResponse.setSuccess(false);
				ajaxResponse.setHasErrors(true);
				ajaxResponse.setData(processErrors(result));
				return ajaxResponse;
			}
		}
		return pjp.proceed();
	}

	private boolean hasValidAnnotation(Annotation[] annotations){
		if(annotations == null){
			return false;
		}
		for(Annotation annotation : annotations){
			if(annotation instanceof Valid){
				return true;
			}
		}
		return false;
	}
	
	private List<BindingError> processErrors(BindingResult result){
		if(result != null && result.hasErrors()){
			List<BindingError> list = new ArrayList<BindingError>();
			for(ObjectError error : result.getAllErrors()){
				BindingError be = new BindingError();
				be.setMessage(error.getDefaultMessage());
				be.setName(error.getObjectName());
				list.add(be);
			}
			return list;
		}
		return null;
	}
}

注意要在springmvc配置文件加一行<bean class=”spring.test.web.aop.ValidAspect”/>

然后再写个方法逻辑跟上面的类似

@ResponseBody
@RequestMapping("/test2")
public AjaxResponse validateTest2(@Valid User user, BindingResult result){
	AjaxResponse ajaxResponse = new AjaxResponse();
	Map<String, Object> map = new HashMap<>();
	map.put("now", new Date());
	map.put("user", user);
	ajaxResponse.setData(map);
	return ajaxResponse;
}

这里没有再去判断hasErrors()了,然后测试一下

写测试方式试一下

@Test
public void test2() throws Exception{
	this.mockMvc.perform(post("/validator/test2")).andDo(print());
	this.mockMvc.perform(post("/validator/test2").param("username", "testusername").param("password", "testpassword")).andDo(print());
}

第一个请求结果是

{"success":false,"message":null,"hasErrors":true,"data":[{"name":"user","message":"密码不能为空"},{"name":"user","message":"may not be null"}]}

第二个请求结果是

{"success":true,"message":null,"hasErrors":false,"data":{"now":1420788479105,"user":{"username":"testusername","password":"testpassword"}}}

效果跟上面是一样的

当然我这个切面仅仅是个示范,我拦截的是带有ResponseBody注解的方法,我这些方法会返回一个统一的格式,如果是要返回别的视图就要自己定义注解加其他参数了

JSR-303原生支持的校验注解也是有限的,如果要实现其他的验证,可以自己拓展,拓展方法就下次再说了,我也才刚接触。

数组求最大,最小,和,平均 学习笔记

package com.ctgu.java.exer;

public class TestArray3 {
	public static void main(String[] args){
		int[] arr = new int[]{12,43,9,0,-65,-99,100,9};
		int max = arr[0];
		for(int i = 1; i < arr.length;i++ ){
			if(max < arr[i]){
				max = arr[i];
				
			}
		}
		System.out.println("数组最大值为:" + max);
		int min=arr[0];
		for(int i = 1;i< arr.length;i++){
			if(min >arr[i]){
				min = arr[i];
				
			}
			
		}
		System.out.println("数组最小值为:" + min);
		int sum = 0;
		for(int i = 0; i < arr.length; i++){
			sum +=arr[i];
			
		}
		System.out.println("总和为:" +sum);
		int avg = 0;
		avg = sum / arr.length;
		System.out.println("平均数为:" +avg );
		
		
	}

}

java的main函数为什么是public static void main(String[] args)

这个问题困扰我好久了,今天就一查究竟,毕竟我好奇心比较重

1. why “public”

   因为java程序是通过jvm虚拟机调用的,所以main()函数要是想被调用,必须是public

2.why “static”

   在java中,没有static的变量或函数,如果想被调用的话,是要先新建一个对象才可以。而main函数作为程序的入口,需要在其它函数实例化之前就启动,这也就是为什么要加一个static。main函数好比一个门,要探索其它函数要先从门进入程序。static提供了这样一个特性,无需建立对象,就可以启动。

3.why “(String[] args)”?

   (1)先来说说,为什么这个字符串数组要叫“args”,其实这个是约定俗成的一种命名,不叫”args”叫其它的也是ok的,不信可以自行测试。

   (2)再来看看为什么要是一个String的数组,java在运行的时候是这样的(如下),有点像命令行,a1,a2,a3就是这个String数组里的命令。

java main a1 a2 a3

下面我们在深入到jvm驱动里面看看,main()函数是怎样被启动的:

mainID = (*env)->GetStaticMethodID(env, mainClass, "main", "([Ljava/lang/String;)V");

这是jvm启动时的代码,直接去找static的,参数为String数组的main方法。

/********************************

* 本文来自博客  “李博Garvin“

* 转载请标明出处:

******************************************/

Spring文件名乱码的方法

 

 , 2,080 浏览  九 192012

 

以下是原创作品,转载请标明出处

直接上在Spring中下载Excel文件的例子,重点是包含有utf8的那一句,将ISO-8859-1编码格式改为utf8格式的。
对于IE浏览器必须使用URLEncoder转换,不然下载的时候会报错。
其中fileName = fileName.replaceAll(“\\+”, “%20″);可以修正URLEncoder将空格转换成+号的BUG。

	@RequestMapping(method = RequestMethod.GET, params="action=exportExcel")
	public void envExportExcel(HttpServletRequest request, HttpServletResponse response) throws IOException {
		response.setContentType("application/vnd.ms-excel");//设置输出格式 Excel 头信息.
		String fileName = "测试文件名";
		String agent = request.getHeader("USER-AGENT");
		if(agent != null && agent.indexOf("MSIE") != -1) {
			fileName = URLEncoder.encode(fileName, "UTF8");
			fileName = fileName.replaceAll("\\+", "%20");
		} else {
			fileName = new String(fileName.getBytes("UTF-8"), "ISO8859-1");
		}

		response.setHeader("Content-Disposition","attachment; filename=\"" + fileName + ".xls" + "\";target=_blank");

		OutputStream os = response.getOutputStream();
		HSSFWorkbook wb = service.exportExcelFile();
		wb.write(os);
		os.close();
		response.flushBuffer();
	}

  发表于 4:58 下午  标签:, ,

 

 

发现IE10以上if(agent != null && agent.indexOf(“MSIE”) != -1) 判断为火狐,兼容模式没有这个问题

Arcgis for JavaSctipt之常用Layer详解

Arcgis forJavaSctipt之常用Layer详解

概述:Arcgis for Javasctipt中常见的layer有动态图层(ArcGISDynamicMapServiceLayer

)、切片图层(ArcGISTiledMapServiceLayer)、特征图层(FeatureLayer)、图象图层(GraphicsLayer)、标注图层(LabelLayer)、wms图层(WMSLayer)和切片wms图层(WMTSLayer)等几种。本文结合SVG技术,详细介绍Arcgis for Javasctipt中常见的layer。

1、Arcgis for Javasctipt中常见的layer简介

1.1 ArcGISDynamicMapServiceLayer

       ArcGISDynamicMapServiceLayer为动态地图服务图层,可以理解为一个mxd的所有内容。


 b、A指令 

允许不闭合。可以想像成是椭圆的某一段,共七个参数:

ARX,RY,XROTATION,FLAG1,FLAG2,X,Y

RX,RY指所在椭圆的半轴大小

XROTATION指椭圆的X轴与水平方向顺时针方向夹角,可以想像成一个水平的椭圆绕中心点顺时针旋转XROTATION的角度。

FLAG1只有两个值,1表示大角度弧线,0为小角度弧线。

FLAG2只有两个值,确定从起点至终点的方向,1为顺时针,0为逆时针

X,Y为终点坐标

如:m200,250 a 150,30 0 1 0 0,70


⑧ 文本

<?xml version="1.0" standalone="no"?>

<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> 

<svg viewBox = "0 0 200 200" version = "1.1">

<text x = "10" y = "25" fill = "navy" font-size = "15">

        It was the best of times

    </text>

    <text x = "10" y = "25" dx = "5" dy = "15" fill = "navy" font-size = "15">

        It was the worst of times.

    </text>

</svg>

3、Arcgis for Javasctipt中常见layer详解

       上面简单的介绍了常见的layer和SVG中一些常见的标签,常见的layer中,ArcGISTiledMapServiceLayer、WMSLayer和WMTSLayer为栅格图层,ArcGISDynamicMapServiceLayer、FeatureLayer、GraphicsLayer、LabelLayer为矢量图层。栅格图层用栅格图片的方式展示,矢量图层用SVG(Scalable Vector Graphic)的方式展示,下面就结合SVG对常见的layer做一个详细的介绍与说明。

3.1 Arcgis for Javascript的页面组织

       打开一个地图,点击右键->审查元素,可查看生成完成后的地图的页面组织形式,如下:

如上图,Arcgis for Javascript的页面组织形式如下图:


下面就上面的组织形式做一个简单的介绍:

a、map_root

map_root是地图的显示容器,里面包含了地图、地图控制、Popup、缩放控件等。

b、esriControlsBR

esriControlsBR是地图控制控件。

c、  esriPopup

esriPopup是地图Popup,地图的InfoWindow是出现在这个div中。

d、 map_zoom_slider

map_zoom_slider是地图缩放控件。

e、map_tiled

       map_tiled是切片显示的div。

f、svg

       svg是一些矢量图层的展示的。

3.2栅格图层

3.2.1切片图层和WMTSLayer

如上图所示,切片图层和WMTSLayer就是通过上述形式在前段展示的。Arcgis切片的是将图片按照256*256的大小将图片切的。

3.2.2WMSLayer

       WMSLayer没有进行切片,直接将整个图片显示。

3.3矢量图层

       ArcGISDynamicMapServiceLayer、FeatureLayer、GraphicsLayer、LabelLayer等矢量图层通过svg实现的,其中:

PictureMarkerSymbol是通过<image>实现的。

SimpleMarkerSymbol是通过<circle>或者<path>等实现的。

线对象是通过<path>来实现的。

面对象是通过<circle>、闭合<path>等实现的。

LabelLayer是通过<text>实现的。


Javascript 获取链接(url)参数的方法

分解链接的方式:

 

[javascript]   

  1. <script type=”text/javascript”>  
  2. <!–  
  3. // 说明:Javascript 获取链接(url)参数的方法  
  4. // 整理:http://www.CodeBit.cn  
  5.    
  6. function getQueryString(name)  
  7. {  
  8.     // 如果链接没有参数,或者链接中不存在我们要获取的参数,直接返回空  
  9.     if(location.href.indexOf(“?”)==-1 || location.href.indexOf(name+‘=‘)==-1)  
  10.     {  
  11.         return ‘‘;  
  12.     }  
  13.    
  14.     // 获取链接中参数部分  
  15.     var queryString = location.href.substring(location.href.indexOf(“?”)+1);  
  16.    
  17.     // 分离参数对 ?key=value&key2=value2  
  18.     var parameters = queryString.split(“&”);  
  19.    
  20.     var pos, paraName, paraValue;  
  21.     for(var i=0; i<parameters.length; i++)  
  22.     {  
  23.         // 获取等号位置  
  24.         pos = parameters[i].indexOf(‘=‘);  
  25.         if(pos == -1) { continue; }  
  26.    
  27.         // 获取name 和 value  
  28.         paraName = parameters[i].substring(0, pos);  
  29.         paraValue = parameters[i].substring(pos + 1);  
  30.    
  31.         // 如果查询的name等于当前name,就返回当前值,同时,将链接中的+号还原成空格  
  32.         if(paraName == name)  
  33.         {  
  34.             return unescape(paraValue.replace(/\+/g, ” “));  
  35.         }  
  36.     }  
  37.     return ‘‘;  
  38. };  
  39.    
  40. //http://localhost/test.html?aa=bb&test=cc+dd&ee=ff  
  41. alert(getQueryString(‘test‘));  
  42. //–>  
  43. </script>  

正式表达式: 

 

[javascript]   

    1. <script type=”text/javascript”>  
    2. <!–  
    3. function getQueryStringRegExp(name)  
    4. {  
    5.     var reg = new RegExp(“(^|\\?|&)”+ name +”=([^&]*)(\\s|&|$)”, “i”);  
    6.     if (reg.test(location.href)) return unescape(RegExp.$2.replace(/\+/g, ” “)); return “”;  
    7. };  
    8.    
    9. //http://localhost/test.html?aa=bb&test=cc+dd&ee=ff  
    10. alert(getQueryStringRegExp(‘test‘));  
    11. //–>  
    12. </script> 

java 吞吐量

jvm中 ,执行用户的代码占 的时间/总时间 ,假如前者是99 分钟,后者一分钟,则吞吐量为99% ,吞吐量越大。系统越好,如何设计系统,导致系统吞吐量高,因为我们知道,垃圾回收,7种垃圾收集器,也不能保证系统不会停顿,因为系统在垃圾回收强制挂起所有的用户线程在用户不可见的情况。所以这个停顿时间是我们设计选用垃圾回收器的考虑。

Linux Shell数组常用操作详解

Linux Shell数组常用操作详解

1数组定义:

declare -a 数组名

数组名=(元素1 元素2 元素3 )

1 declare -a array
2 array=(1 2 3 4 5)

数组用小括号括起,数组元素之间用空格分开

2显示数组长度:

[@tc_132_227 dm_pid_day]$ echo ${#array[@]}
5
[@tc_132_227 dm_pid_day]$ echo ${#array[*]}
5

命令:

${#数组名[@或*]} 获取数组长度,若数组无元素,输出空

3读取数组元素:

[@tc_132_227 dm_pid_day]$ echo ${array[0]}
1
[@tc_132_227 dm_pid_day]$ echo ${array[1]}
2
[@tc_132_227 dm_pid_day]$ echo ${array[4]}
5
[@tc_132_227 dm_pid_day]$ echo ${array[*]}
1 2 3 4 5
[@tc_132_227 dm_pid_day]$ echo ${array[@]}
1 2 3 4 5

命令:
${数组名[下标]},数组下标从0开始,且下标为*或者@符号时,读取整个数组元素

4 对数组元素赋值:

[@tc_132_227 dm_pid_day]$ echo ${array[@]}
1 2 3 4 5
[@tc_132_227 dm_pid_day]$ array[1]=9
[@tc_132_227 dm_pid_day]$ echo ${array[@]}
1 9 3 4 5
[@tc_132_227 dm_pid_day]$ array[8]=7
[@tc_132_227 dm_pid_day]$ echo ${array[@]}
1 9 3 4 5 7
[@tc_132_227 dm_pid_day]$

命令:

数组名[下标]=元素 进行赋值  若下标不存在,则数组会自动增加一个新的元素

5删除数组元素:

[@tc_132_227 dm_pid_day]$ unset array[0]
[@tc_132_227 dm_pid_day]$ echo ${array[@]}
2 3 4 5
[@tc_132_227 dm_pid_day]$ echo ${#array[*]}
4
[@tc_132_227 dm_pid_day]$ unset array[1]
[@tc_132_227 dm_pid_day]$ echo ${array[*]}
3 4 5
[@tc_132_227 dm_pid_day]$ echo ${#array[*]}
3
[@tc_132_227 dm_pid_day]$ unset array
[@tc_132_227 dm_pid_day]$ echo ${array[*]}

[@tc_132_227 dm_pid_day]$

命令:

unset 数组名[下标]可以删除数组元素,  unset 数组名  删除整个数组

6数组分片:

[@tc_132_227 dm_pid_day]$ echo ${array[@]}
1 2 3 4 5
[@tc_132_227 dm_pid_day]$ echo ${array[@]:0:1}
1
[@tc_132_227 dm_pid_day]$ echo ${array[@]:1:1}
2
[@tc_132_227 dm_pid_day]$ echo ${array[@]:1:3}
2 3 4
[@tc_132_227 dm_pid_day]$ arr=(${array[@]:1:3})
[@tc_132_227 dm_pid_day]$ echo ${arr[@]}
2 3 4
[@tc_132_227 dm_pid_day]$ echo ${#arr[@]}
3
[@tc_132_227 dm_pid_day]$

命令: 

${数组名[@|*]:起始下标:长度} 会将原数组分片,并返回以“空格”为分隔符的一个字符串,若对该字符串外加”()“,则会得到一个新的分片数组

eclipse java ee 添加jrebel 工具

-noverify -javaagent:E:\work\jrebel.jar
-Drebel.dirs=E:\project\asset\asset-web\target\classes

 

公司用的eclipse j2ee 和tomcat 开发项目,以前用的myeclipse 是可以自动部署的。

编写完一个java 类需要重启项目才能测试,搞了几天累的不行了,baidu说用jrebel 可以实现热部署,jvm的插件。试着做成功了。

在网上找到jrebel 说是要破解的,正版收费。大家写的都一个样,找了破解的文件还是不行。网上转载太严重了。

最终在华为网盘找了一个面费的。具体步骤如下:

1,下载jrebel.jar,我把它放入百度网盘,本人找的很幸苦,不想新人也这样。

http://pan.baidu.com/s/1ntokf3b

2,把配置,包括jrebel.jar 地址(这个文件的目录)。

在eclipse服务中找到vm argument (有的在server/tomcat下)。写入

-noverify -javaagent:E:\work\jrebel.jar
-Drebel.dirs=E:\project\asset\asset-web\target\classes

还有其他配置,我没有配那么多。第一个指定jar路径;第二个制定tomcat下的class文件路径,配置完如下。

  

重启服务,成功会输出:

jetty命令行方式启动jetty-runner.jar 容器

 java -jar jetty-runner.jar test-clover-0.0.1-SNAPSHOT.war

 

C++查缺补漏之变量和基本类型

基本内置类型

1.块处理存储 一般来说计算机以位序列存储数据,每一位存储0或1,一段内存可能存储着0001010010101010…但是这样的存储是没有任何意义的,让存储器具有结构的方法是
块处理存储,那么什么是快处理存储呢,就是用2^n表示一个存储块的位数(一般以8位作为一个块),这样操作起来更加方便。 如下图:

123456 01110001
123457 10101001
123458 10100100

要操作一个块,需要什么东西呢?那就是所谓的地址,上面的123456就是这个块的地址,也相当于它的标示符,但是要让地址为123456的这个字节具有意义,那么还需要存储在该地址的值的类型,一旦知道了该地址的类型,那么就知道表示该类型需要多少位和如何解释这些位。 比如123456这个块如果是整形类型的(当然int需要占用四个字节)那么表示的就是112,如果表示的是ISO-Latin-1的一个字符,那么就表示小写字母q 下面来看一个例子:

#include <iostream>
#include <iomanip>
#include <stdio.h>
using namespace std;

int main(int argc, char const *argv[])
{
	unsigned int number = 16843009;//二进制为<span style="font-family: 'Comic Sans MS';">00000001</span><span style="font-family: 'Comic Sans MS';">00000001</span><span style="font-family: 'Comic Sans MS';">00000001</span><span style="font-family: 'Comic Sans MS';">00000001</span><span style="font-family: 'Comic Sans MS';">
</span>	unsigned int * pNumber = &number;
	cout<<reinterpret_cast<int>(pNumber)<<endl;//打印指针地址
	cout<<*pNumber<<endl;//unsigned类型时的值
	unsigned char * ptrChar = reinterpret_cast<unsigned char *>(pNumber);//unsigned int * 转为 unsigned char *
	cout<<static_cast<int>(*ptrChar)<<endl;//unsigned char类型以int类型输出
	ptrChar++;
	cout<<static_cast<int>(*ptrChar)<<endl;
	ptrChar++;
	cout<<static_cast<int>(*ptrChar)<<endl;
	ptrChar++;
	cout<<static_cast<int>(*ptrChar)<<endl;
	while(1);
	return 0;
}

最后输出结果为:

观察之前我们的那个整形number,不正是由四块值为00000001组成的么,只是解释为unsigned int 类型时为16843009,解释为unsigned char 类型时为1.

2.除了bool类型的整形才可以是unsigned 或者是signed ,如果在只写unsigned 不加上具体类型,那么就是unsigned int类型的 。

3.特殊的char类型 与其他整形不同,char类型有三种不同的类型:char , unsigned char , signed char。但是只有两种表示形式unsigned char 或者signed char。这句话是什么意思呢?我们知道(int,short,long)类型默认都是signed类型的,但是char类型时没有默认类型的,具体是signed还是unsigned类型,还要由编译器而定。要想确定编译器默认是signed还是unsigned,可以

char ch =-1;
printf("%d\n",ch);

如果打印的是-1,那么表明是signed类型的 若是255,那么表明是unsigned类型的 我在gcc上面测试以后发现是signed类型的,所以在编写char类型的时候最好还是加上signed或者unsigned吧,不然又会多了一些莫名奇妙的错误。

4.浮点型 都知道float表示的浮点型使用一个字来表示,且精度是单精度,只有6位有效数字 double类型占用两个字节,精度为双精度,至少保证了10位的精度 这里的精度到底是什么意思呢?我们来看一下float和double内部到底是如何存储的:

类型float大小为4字节,即32位,内存中的存储方式如下:

浮点类型是没有unsigned的

  • < jetty命令行方式启动jetty-runner.jar 容器

  • Python核心编程课后习题一 >

相关推荐

  • python如何导入模块
  • 用Python进行冒泡排序
  • 马拉车算法
  • 【python】 用来将对象持久化的 pickle 模块
  • 4-1 YAML配置文件 注入 JavaBean中
  • 《手把手教你》系列技巧篇(十)-java+ selenium自动化测试-元素定位大法之By class name(详细教程)
  • C语言常用函数-toupper()将字符转换为大写英文字母函数
  • 关于 .NET 与 JAVA 在 JIT 编译上的一些差异
  • SpringBoot|常用配置介绍
  • 基础的排序算法

电脑软件

  • You123浏览器 V2.0.18.2 官方安装版

    2023-05-31

  • nomacs(图片浏览器)V3.10.1 多国语言安装版

    2023-05-31

  • 知也浏览器 V1.0.2.20 免费安装版

    2023-05-23

  • 快转浏览器 V1.9 官方安装版

    2023-05-23

  • 源质浏览器 V1.0.0.132 官方安装版

    2023-05-19

  • K-Meleon(快速轻量可定制浏览器)V75.1.7 多国语言版

    2023-05-19

  • 汇聊 V2.6.0.0 官方安装版

    2023-05-15

  • 赞无止境 V1.8 绿色版

    2023-05-15

  • 畅谈通 V3.0.9.3 官方安装版

    2023-05-09

  • 光棍节QQ表情包

    2023-05-09

  • 才宝教育 V2.3.0 师生版

    2023-05-09

  • 春林文件批量改名系统 V7.1 绿色版

    2023-05-09

  • 快快浏览器(KChrome) V18.0.17 官方安装版

    2023-04-28

  • 红芯企业浏览器(红芯浏览器) V3.0.54 官方安装版

    2023-04-27

  • 小猪佩奇社会人表情包 V1.0 官方版

    2023-04-26

  • 手心拼音输入法(手心输入法) V2.7.0.1702 官方安装版

    2023-04-25

本类排行

  • 1C语言常用函数-isspace()判断字符是否为空白字符函数
  • 2Spring Cloud 从入门到精通(一)Nacos 服务中心初探
  • 3JavaScript:null是对象吗?
  • 4基础的排序算法
  • 5springboot jar包变成系统服务
  • 6JavaScript Style Guide JavaScript 代码风格指南
  • 7ThreadPoolExecutor添加线程源码解析——addWorker
  • 8python常见魔法函数
  • 9SpringBoot|常用配置介绍
  • 10关于 .NET 与 JAVA 在 JIT 编译上的一些差异

今日推荐

  • InterPals精简版

    版本:v2.1.15

    大小:16.87MB

    日期:2023-06-10

  • 中华老黄历精简版

    版本:v4.3.4

    大小:33.32MB

    日期:2023-06-10

  • 微信红包来了提示音精简版

    版本:v1.1.10

    大小:5.18MB

    日期:2023-06-10

  • 易车破解版

    版本:v10.90.0

    大小:56.57MB

    日期:2023-06-10

  • 杏花直播免费版

    版本:v1.32.01

    大小:168.7MB

    日期:2023-06-10

  • 樱花直播免费版

    版本:v1.31.01

    大小:161.1MB

    日期:2023-06-10

热门手游

  • 未变异者2免费版

    版本:v1.1.6

    大小:72.66MB

    日期:2023-06-10

  • 任我行极速版

    版本:v1.2

    大小:157.99MB

    日期:2023-06-10

  • 柏青哥布林经典版

    版本:v0.8.33

    大小:135.39MB

    日期:2023-06-10

  • 圣剑联盟经典版

    版本:v2.7.0

    大小:91.51MB

    日期:2023-06-10

  • 火柴人战争极速版

    版本:1.0.0

    大小:83.13MB

    日期:2023-06-10

  • 欢乐时光计划经典版

    版本:v0.0.3

    大小:104.34MB

    日期:2023-06-10

#include <iostream> #include <iomanip> #include <stdio.h> using namespace std; int main(int argc, char const *argv[]) { float f1 = 1.123456789123456789; double d1 = 1.123456789123456789; cout<<setprecision(20)<<“float:”<<f1<<endl; cout<<setprecision(20)<<“double:”<<d1<<endl; while(1); return 0; }可得到输出结果为: 我们可以明显的看到float从第7位(为什么不是第六位呢?因为8388608)有效数字开始就开始不准确了 double类型也就只显示了前16位,后面的都被截断了。所以以后再具体应用中需要辨别他们之间的区别,float的精度损失是不可忽视的,使用double类型基本上是不会出错的,双精度的计算代价相对于单精度乐意忽略,在某些机器上double类型比float类型要快得多(可能是硬件上做了优化了吧)。但是long double一般来说是没有必要的。。。 最后说一点,上面我们看到了浮点类型的结构,里面有一位固定符号位,所以!!!

layui.use(‘code’, function() {
layui.code({
elem: ‘pre’, //默认值为.layui-code
about: false,
skin: ‘notepad’,
title: ‘C++查缺补漏之变量和基本类型’,
encode: true //是否转义html标签。默认不开启
});
});

WebView注入Java对象注意事项

在android4.2以前,注入步骤如下:

 

[java]   

  1. webview.getSetting().setJavaScriptEnable(true);  
  2. class JsObject {  
  3.     public String toString() { return “injectedObject”; }  
  4.  }  
  5.  webView.addJavascriptInterface(new JsObject(), “injectedObject”);  

 

Android4.2及以后,注入步骤如下:

 

 

[java]   

  1.   

[java]   

  1. webview.getSetting().setJavaScriptEnable(true);  
  2. class JsObject {  
  3.     @JavascriptInterface  
  4.     public String toString() { return “injectedObject”; }  
  5.  }  
  6.  webView.addJavascriptInterface(new JsObject(), “injectedObject”);  

发现区别没?4.2之前向webview注入的对象所暴露的接口toString没有注释语句@JavascriptInterface,而4.2及以后的则多了注释语句@JavascriptInterface

 

经过查官方文档所知,因为这个接口允许JavaScript 控制宿主应用程序,这是个很强大的特性,但同时,在4.2的版本前存在重大安全隐患,因为JavaScript 可以使用反射访问注入webview的java对象的public fields,在一个包含不信任内容的WebView中使用这个方法,会允许攻击者去篡改宿主应用程序,使用宿主应用程序的权限执行java代码。因此4.2以后,任何为JS暴露的接口,都需要加

@JavascriptInterface

注释,这样,这个Java对象的fields 将不允许被JS访问。

 

官方文档说明:

 

 

From the Android 4.2 documentation:

Caution: If you‘ve set your targetSdkVersion to 17 or higher, you must add the @JavascriptInterface annotation to any method that you want available your web page code (the method must also be public). If you do not provide the annotation, then the method will not accessible by your web page when running on Android 4.2 or higher.

注:如果将targetSdkVersion 设置为17或者更高,但却没有给暴露的js接口加@JavascriptInterface注释,则logcat会报如下输出:

E/Web Console: Uncaught TypeError: Object [object Object] has no method ‘toString‘

 

public void addJavascriptInterface ( object,  name)

Added in 

Injects the supplied Java object into this WebView. The object is injected into the JavaScript context of the main frame, using the supplied name. This allows the Java object‘s methods to be accessed from JavaScript. For applications targeted to API level  and above, only public methods that are annotated with  can be accessed from JavaScript. For applications targeted to API level  or below, all public methods (including the inherited ones) can be accessed, see the important security note below for implications.

Note that injected objects will not appear in JavaScript until the page is next (re)loaded. For example:

 class JsObject {
    @JavascriptInterface
    public String toString() { return "injectedObject"; }
 }
 webView.addJavascriptInterface(new JsObject(), "injectedObject");
 webView.loadData("", "text/html", null);
 webView.loadUrl("javascript:alert(injectedObject.toString())");

IMPORTANT:

 

Parameters
object the Java object to inject into this WebView‘s JavaScript context. Null values are ignored.
name the name used to expose the object in JavaScript

 

GL(arui319)

http://blog.csdn.net/arui319

用python做UDP连接

写个客户端

#!/usr/bin/env python
from socket import *
HOST = '10.2.167.115'
PORT = 20001
BUFSIZE = 1024
ADDR = (HOST, PORT)
udpClientSock = socket(AF_INET, SOCK_DGRAM)
while True:
    data = raw_input('Enter the message you want to send >')
    if not data:
        break
    udpClientSock.sendto(data, ADDR)发送给服务端
    data, ADDR = udpClientSock.recvfrom(BUFSIZE)接收服务端信息
    if not data:
        break
    print data
udpClientSock.close()

写个服务端

#!/usr/bin/env python
# -*- coding:UTF-8 -*-


from socket import *
from time import ctime

HOST = ''
PORT = 20001监控端口
BUFSIZE = 1024缓冲区大小
ADDR = (HOST, PORT)
udpSerSock = socket(AF_INET, SOCK_DGRAM)
udpSerSock.bind(ADDR)
while True:
    print 'waiting for message...'
    data, addr = udpSerSock.recvfrom(BUFSIZE)
    udpSerSock.sendto('[%s] %s' % (ctime(), data), addr)给客户端发送信息
    print'received from %s >> %s' % (addr, data)
udpSerSock.close()

这个udp连接可以用于测速。在客户端记录开始和结束时间。再除以2可以估计网络耗时。忽略服务器响应时间

WP8 客户端C# UTF8 字符在转C++ 宽字符时失败问题。

有一段时间一直为编码格式的问题烦恼,某些编辑器在保存为UTF-8文件后,wp 客户端在从C#代码读取后转给C++保存为宽字符时,文本全部丢失,后查看官方文档: 发现是EILSEQ错误,即有“非法字节序列”,可某些编辑器处理后没有这个问题。

在仔细区别两种编辑工具最后生成的文件时发现有3字节的容量差,网上就开始搜索,发现还真有人总结出来是由于UTF8 带BOM 与 无BOM 的差别导致(原博客地址:)。

其中,当带BOM时WP宽字符转换会出现失败。因此当字节流开头为(EF BB BF)时去除(已验证), 或通过 new UTF8Encoding(false) 忽略BOM头即可解决。再次感谢原博主的帮助。

unity3d中脚本生命周期(MonoBehaviour lifecycle)

接下来,做出一下讲解:最先执行的方法是Awake,这是生命周期的开始,用于进行激活时的初始化代码,一般可以在这个地方将当前脚本禁用:this.enable=false,如果这样做了,则会直接跳转到OnDisable方法执行一次,然后其它的任何方法,都将不再被执行。

如果当前脚本处于可用状态,则正常的执行顺序是继续向下执行OnEnable,当然我们可以在另外一个脚本中实现这个脚本组件的启动:this.enab=true;

再向下执行,会进行一个判断,如果Start方法还没有被执行,则会被执行一次,如果已经被执行了,则不会再被执行。这是个什么意思呢?我们可以在某个脚本中将组件禁用this.enable=false,再启用时会转到OnEnable处执行,这时继续向下走,发现Start执行过了,将不再被执行。比如说:第一次启用时,将怪物的初始位置定在了(0,0,0)点,然后怪物可能会发生了位置的变换,后来被禁用了,再次启用时,不会让怪物又回到初始的(0,0,0)位置。

继续向后执行,就是Update了,然后是FixUpdate,再然后是LateUpdate,如果后面写了Reset,则会又回到Update,在这4个事件间可以进行循环流动。

再向后执行,就进入了渲染模块(Rendering),非常重要的一个方法就是OnGUI,用于绘制图形界面。当然,如果你使用了NGUI,这个生命周期的事情你就不用考虑了。

再向后,就是卸载模块(TearDown),这里主要有两个方法OnDisable与OnDestroy。当被禁用(enable=false)时,会执行OnDisable方法,但是这个时候,脚本并不会被销毁,在这个状态下,可以重新回到OnEnable状态(enable=true)。当手动销毁或附属的游戏对象被销毁时,OnDestroy才会被执行,当前脚本的生命周期结束。

特别要强调的是:这里虽然可以使用C#来写代码,但是这个类构造对象的生命周期,与MonoBehaviour的生命周期,是完全不同的。

可以通过如下示例:对脚本进行验证(两个脚本添加到同一个游戏对象上):

脚本1Monster1:

    void OnBecameInvisible()
    {
        Debug.Log(“invisible”);
        MonsterController mc1 = this.gameObject.GetComponent<MonsterController>();
        mc1.enabled = false;
    }

    void OnBecameVisible()
    {
        Debug.Log(“visible”);
        MonsterController mc1 = this.gameObject.GetComponent<MonsterController>();
        mc1.enabled = true;
    }

脚本2MonsterController:

    void Awake()
    {
        Debug.Log(“awake”);
        this.enabled = false;
    }

    void OnEnable()
    {
        Debug.Log(“enable”);
        this.enabled = true;
    }

    void Start()
    {
        Debug.Log(“start”);
        //this.gameObject.SetActive(false);
    }

    void Update()
    {
        Debug.Log(“update”);
    }

    void OnGUI()
    {
        Debug.Log(“gui”);
    }
    void OnDisable()
    {
        Debug.Log(“disable”);
    }

    void OnDestroy()
    {
        Debug.Log(“destroy”);
    }

JAVA数据源连接方式汇总

最近在研究JAVA的数据源连接方式,学习的时候发现了一位同行写的文章,转载过来,留作记录!

 

一、问题引入

在java程序中,需要访问数据库,做增删改查等相关操作。如何访问数据库,做数据库的相关操作呢?

 

二、Java连接数据库方法概述

java.sql提供了一些接口和类,用于支持数据库增删改查等相关的操作。该jar包定义了java访问各种不同数据库(mysql,oracle,sqlserver。。。。。)的统一接口和标准。同时,各个数据库厂商都提供了该jar包中定义的各个接口的实现类,用于具体实现本厂数据库的增删改查操作,即称之为“数据库驱动jdbc driver”。例如mysql的数据库驱动为:com.mysql.jdbc.driver;oracle的数据库驱动为:oracle.jdbc.driver.oracledriver。

在java程序中访问数据库,做数据库连接时,可以采用两种方式:

1、使用java.sql API

利用该包提供的各种接口和类直接访问数据库。

 

 

2、使用数据库连接池

目前存在多个开源的java数据库连接池,这些连接池都是在java.sql基础上编写而成。

Ø  该连接池的解决的问题是:

当使用java.sql中提供的api创 建数据库连接时候,需要耗费很大的资源,要进行用户名密码数据库连接验证等,即耗费资源也耗费时间。如果在程序中,每次需要访问数据库时候,都进行数据库 连接,那么势必会造成性能低下;同时,如果用户失误忘记释放数据库连接,会导致资源的浪费等。而数据库连接池就是解决该问题,通过管理连接池中的多个连接 对象(connection),实现connection重复利用。从而,大大提高了数据库连接方面的性能。

Ø  该连接池的功能是:

负责创建,管理,释放,分配数据库连接即(connection)。首先,负责创建相应数目的数据库连接对象(connection)对象,并存放到数据库连接池(connect pool)中。当用户请求数据库连接时,该连接池负责分配某个处于空闲状态的数据库连接对象;当用户发出释放该数据库连接时,该连接池负责将该连接对象重新设置为空闲状态,以便被别的请求重复利用。同时;数据库连接池负责检查(空闲时间>最大空闲时间)的数据库连接,并释放。

Ø  连接池主要参数介绍

最小连接数:初始化时,系统将负责创建该数目的connection放入连接池中。

最大连接数:系统允许创建connection的最大数值。当系统请求连接时候,且连接池中不存在空闲的连接:如果connection总数未超过最大连接数,那么连接池负责创建新的connection对象,并返回该对象;如果connection总数已经到达该最大连接数,那么连接池将用户请求转入等待队列。

 

 

三、常用的数据库连接池

1、 JNDI

2、 C3p0

3、 Apache 的Jakarta DBCP

4、 BoneCP

其中,sping框架依赖的第三方使用了c3p0和dbcp两种方式;而bonecp号称是速度最快的数据库连接池。JNDI方式创建实现的datasource是真正实现了javax.sql.datasource;其他的三种方式都不是。下面的列表,列出了几种方式的区别和不同:

序号

连接池名称

依赖的jar包

实现的datasource类

备注

1

JNDI

该数据源是由相应的web服务器(例如:tomcat,weblogic,websphere)负责初始化,创建,管理。程序中不需要引入特别的jar包。

Javax.sql.datasource

 

2

C3P0

c3p0-0.9.xxx.jar

com.mchange.v2.c3p0.ComboPooledDataSource

 

3

DBCP

commons-dbcp.jar,commons-pool.jar

org.apache.commons.dbcp.BasicDataSource

 

4

BoneCP

 

bonecp-0.6.5.jar

· google-collections-1.0.jar

· slf4j-api-1.5.11.jar

· slf4j-log4j12-1.5.11.jar

·log4j-1.2.15.jar

 

BoneCPDataSource

 

备注:以上几种方式的数据库连接池的配置参数大同小异,略有差别;其参数的配置,既可以通过配置文件的方式配置,也可以通过硬编码的方式配置。

 

四、分别列出几种连接池的编码例子

(所有的例子均可以参考D:\work\qsyworkspace2\jdbctest项目)

1、 使用java.sql API直接访问数据库

详细请参考javasql.java文件。

       

Class.forName(“com.mysql.jdbc.Driver”);

           String url=”jdbc:mysql://localhost:3306/editortest”;

           String user=”root”;

           String password=”123456″;

           Connection cn=DriverManager.getConnection(url, user, password);

           Statement st=cn.createStatement();

           String sql=”select * from artical where id=1″;

           ResultSet rs=st.executeQuery(sql);

           while(rs.next()){

              System.out.println(“1:”+rs.getString(1));

              System.out.println(“2:”+rs.getString(2));

              System.out.println(“3:”+rs.getString(3));

              System.out.println(“4:”+rs.getString(4));    

        }

2、 使用JNDI方式

这种方式,是由web服务器,实现了java.sql.datasource。由web服务器负责初始化数据源,创建connection,分配,管理connection。由于本身是由web服务器实现的功能,因此不需要在项目project中引入特别的jar包,但是需要在服务器的某些配置文件中增加相关的配置。下面,以tomcat服务器为例,讲述这种方式的使用。

    (1)、修改tomcat的conf下的context.xml文件,增加Resource的配置的支持。

    (2)、由于数据源是由tomcat负责创建,所以需要的jdbc驱动应该放到tomcat的lib路径下。

    (3)、编写使用java代码,并放在tomcat环境下使用,如下:

public void jnditest(){

       // TODO Auto-generated method stub

       try {

           Context initcontext=new InitialContext();

           Context context=(Context) initcontext.lookup(“java:comp/env”);

          

 

           DataSource datasource=(DataSource)context.lookup(“jdbc/editortest”);

          

           Connection cn=datasource.getConnection();

          

           Statement st=cn.createStatement();

           String sql=”select * from artical where id=1″;

           ResultSet rs=st.executeQuery(sql);

           while(rs.next()){

              System.out.println(“1:”+rs.getString(1));

              System.out.println(“2:”+rs.getString(2));

              System.out.println(“3:”+rs.getString(3));

              System.out.println(“4:”+rs.getString(4));    

           }

       } catch (NamingException e) {

           // TODO Auto-generated catch block

           e.printStackTrace();

       } catch (SQLException e) {

           // TODO Auto-generated catch block

           e.printStackTrace();

       }

    }

 

        (4)、详情参考jndisql。Java文件,以及index.jsp。

 

注意:该测试不能在main方法中测试;可以写一个jsp在tomcat环境中测试。因为:java单元的环境是jdk;而jsp的环境却是tomcat;数据连接池是在tomcat中配置的,所以能正常运行的,但java测试的环境只有jdk,所以在引用数据连接池时就时出现找不到环境的错误。

使用环境:当使用weblogic或者websphere等高级的web服务器的时候,可以考虑使用这种方式提高性能。

3、 使用C3p0方式

C3P0是开源的数据库连接组件,支持创建数据库连接池,管理connection等功能。使用该种方式做数据库连接时候,需要导入c3p0-0.9.1.2.jar。

同时,关于数据库连接的具体参数,例如:url,username,password,最小连接数,最大连接数。。。。。等信息既可以在xml配置文件中配置,也可以通过程序编码方式创建。Spring支持c3p0的数据库连接池方式,因此在spring环境中使用时,支持在applicationcontext.xml文件中配置。另外,由于数据库连接池在整个project中针对某个数据库而言是单例的,所以,即使通过编码的方式创建,那么要保证其单实例特性。如果存在多个,那么必然会导致性能低下。

下面,列出通过程序编码方式使用c3p0数据库连接池的方式。

ComboPooledDataSource ds = new ComboPooledDataSource();

       try {

           ds.setDriverClass(“com.mysql.jdbc.Driver”);

           ds.setJdbcUrl(“jdbc:mysql://localhost:3306/editortest”);

           ds.setUser(“root”);

           ds.setPassword(“123456”);

           ds.setMaxPoolSize(20);

           ds.setInitialPoolSize(10);

           ds.setMaxIdleTime(2000);

           Connection cn=ds.getConnection();

           Statement st=cn.createStatement();

           String sql=”select * from artical where id=1″;

           ResultSet rs=st.executeQuery(sql);

           while(rs.next()){

              System.out.println(“1:”+rs.getString(1));

               System.out.println(“2:”+rs.getString(2));

              System.out.println(“3:”+rs.getString(3));

              System.out.println(“4:”+rs.getString(4));    

           }

          

       } catch (PropertyVetoException e) {

           // TODO Auto-generated catch block

           e.printStackTrace();

       } catch (SQLException e) {

           // TODO Auto-generated catch block

           e.printStackTrace();

    }

    备注:通常使用方式,都是通过配置文件配置,几乎不会用到这种硬编码方式。在此,只是简单介绍C3P0的使用方式。详情,可以参考c3p0test.java。

 

4、 使用dbcp方式

DBCP方式,是apache提供的数据源连接池方式,支持数据库连接池创建,管理connection等功能。使用环境,需要导入commons-dbcp.jar 和 commons-pool.jar两个jar包。上面提到的JNDI方式,其实质实用的就是dbcp数据源;只是他是通过在web服务器上做配置,由web服务器负责创建该数据源。

同样的,dbcp数据源也支持xml配置文件和硬编码两种方式。通常使用方式,都是通过配置文件配置,几乎不会使用硬编码方式。下面简单介绍dbcp方式的编码:

BasicDataSource ds = new BasicDataSource();

       ds.setDriverClassName(“com.mysql.jdbc.Driver”);

       ds.setUrl(“jdbc:mysql://localhost:3306/editortest”);

       ds.setUsername(“root”);

       ds.setPassword(“123456”);

       ds.setMaxIdle(20);

       ds.setInitialSize(10);

       ds.setMaxActive(2000);

       try {

           Connection cn=ds.getConnection();

           Statement st=cn.createStatement();

           String sql=”select * from artical where id=1″;

           ResultSet rs=st.executeQuery(sql);

           while(rs.next()){

              System.out.println(“1:”+rs.getString(1));

              System.out.println(“2:”+rs.getString(2));

              System.out.println(“3:”+rs.getString(3));

              System.out.println(“4:”+rs.getString(4));    

           }

       } catch (SQLException e) {

           // TODO Auto-generated catch block

           e.printStackTrace();

    }

5、 使用BoneCP方式。

BoneCP是快速高效的数据库连接池组件,据说性能上目前是最好得,比C3P0和DBCP快25倍。使用该组件,需要导入bonecp-0.6.5.jar,google-collections-1.0.jar,slf4j-api-1.5.11.jar,slf4j-log4j12-1.5.11.jar,log4j-1.2.15.jar。

下面,简单列出编码方式的使用,做简单的了解。

BoneCPDataSource ds = new BoneCPDataSource();

       ds.setDriverClass(“com.mysql.jdbc.Driver”);

       ds.setJdbcUrl(“jdbc:mysql://localhost:3306/editortest”);

       ds.setUsername(“root”);

       ds.setPassword(“123456”);

      

       try {

           Connection cn = ds.getConnection();

           Statement st = cn.createStatement();

           String sql = “select * from artical where id=1”;

           ResultSet rs = st.executeQuery(sql);

           while (rs.next()) {

              System.out.println(“1:” + rs.getString(1));

              System.out.println(“2:” + rs.getString(2));

               System.out.println(“3:” + rs.getString(3));

              System.out.println(“4:” + rs.getString(4));

           }

       } catch (SQLException e) {

           // TODO Auto-generated catch block

           e.printStackTrace();

    }

 

总结:以上,介绍了几种常用的数据源连接池;这几种连接池在使用过程,即支持硬编码的方式,也支持配置文件的配置方式;在正式实用的时候,应该尽量使用配置的方式,便于维护和管理。硬编码的方式,可以做为测试使用。同时,spring框架,通过他自己的方式集成上述几种数据源,理论上来说,都支持。各个数据源连接池都有一些公有的属性,因为他们都是从javax.sql.datasource继 承而来,而且都有最大连接数,初始化连接数等概念。同时,他们又分别有各自不同的属性,做了扩展。这里只是简单的介绍,在实际使用中,想要实现高性能的数 据库连接池管理,还需要深入研究每种方式的连接属性配置;例如:根据实际需要,设置合适的最小连接数和最大连接数,等待时间等。

 

五、Java(x).sql直接操作数据库与各个开源数据源(datasource)关系

当使用JDK提供的java(x).sql包中的类访问数据库时候,基本上用到的就是drivermanager,connection,statement,resultset。其中drivermanger是类,他调用相应的驱动(即各个数据库厂商提供的驱动)中的方法生成connection对象。Connection是接口,在各个数据库厂商提供的数据库驱动中,都实现了该接口。例如:当使用com.mysql.jdbc.driver时候,生成的connection即为com.mysql.jdbc.Connection对象。

Javax.sql包中定义了接口datasource,统一规定了作为数据源连接池必须提供的方法和属性等。各个数据源组件中提供的datasource都实现了该接口。当通过数据源连接池的方式获取connnection的时候,同样的,各个数据源组件也都提供(实现了java.sql.connection)接口的类。

更为具体的细节,可以参考jdk文档中关于java(x).sql包中相关类和接口的描述;参考开源数据源连接池组件的相关源码(例如C3P0);参考相关的数据库驱动。

 

六、附录:Java开源的数据库连接池

        在Java中开源的数据库连接池有以下几种 :

  1, C3P0 C3P0是一个开放源代码的JDBC连接池,它在lib目录中与Hibernate一起发布,包括了实现jdbc3和jdbc2扩展规范说明的Connection 和Statement 池的DataSources 对象。

  2,Proxool 这是一个Java SQL Driver驱动程序,提供了对你选择的其它类型的驱动程序的连接池封装。可以非常简单的移植到现存的代码中。完全可配置。快速,成熟,健壮。可以透明地为你现存的JDBC驱动程序增加连接池功能。

  3,Jakarta DBCP DBCP是一个依赖Jakarta commons-pool对象池机制的数据库连接池.DBCP可以直接的在应用程序用使用。

  4,DDConnectionBroker DDConnectionBroker是一个简单,轻量级的数据库连接池。

  5,DBPool DBPool是一个高效的易配置的数据库连接池。它除了支持连接池应有的功能之外,还包括了一个对象池使你能够开发一个满足自已需求的数据库连接池。

  6,XAPool XAPool是一个XA数据库连接池。它实现了javax.sql.XADataSource并提供了连接池工具。

  7,Primrose Primrose是一个Java开发的数据库连接池。当前支持的容器包括Tomcat4&5,Resin3与JBoss3.它同样也有一个独立的版本可以在应用程序中使用而不必运行在容器中。Primrose通过一个web接口来控制SQL处理的追踪,配置,动态池管理。在重负荷的情况下可进行连接请求队列处理。

  8,SmartPool SmartPool是一个连接池组件,它模仿应用服务器对象池的特性。SmartPool能够解决一些临界问题如连接泄漏(connection leaks),连接阻塞,打开的JDBC对象如Statements,PreparedStatements等. SmartPool的特性包括支持多个pools,自动关闭相关联的JDBC对象, 在所设定time-outs之后察觉连接泄漏,追踪连接使用情况, 强制启用最近最少用到的连接,把SmartPool”包装”成现存的一个pool等。

  9,MiniConnectionPoolManager MiniConnectionPoolManager是一个轻量级JDBC数据库连接池。它只需要Java1.5(或更高)并且没有依赖第三方包。

10,BoneCP BoneCP是一个快速,开源的数据库连接池。帮你管理数据连接让你的应用程序能更快速地访问数据库。比C3P0/DBCP连接池快25倍。

Python菜鸟晋级04—-raw_input() 与 input()的区别

raw_input() 与 input()均是python 的内建函数,通过读取控制台的输入与用户实现交互。但他们的功能不尽相同。举两个小例子

>>> raw_input_A = raw_input("raw_input: ")
raw_input: abc
 >>> input_A = input("Input: ")
Input: abc

Traceback (most recent call last):
  File "<pyshell#1>", line 1, in <module>
    input_A = input("Input: ")
  File "<string>", line 1, in <module>
NameError: name 'abc' is not defined
 >>> input_A = input("Input: ")
Input: "abc"
 >>>

>>> raw_input_B = raw_input("raw_input: ")
raw_input: 123
 >>> type(raw_input_B)
 <type 'str'>
>>> input_B = input("input: ")
input: 123
>>> type(input_B)
<type 'int'>
>>>


值得注意的是从Python3.2开始,raw_input()已被移除。input的返回类型已经变为str


上一讲:

下一讲:

如果有什么疑问欢迎到我的微信公众号提问~

python html parse

  1. bs4:转换成unicode编码,http://www.crummy.com/software/BeautifulSoup/
    1. from bs4 import BeautifulSoup
      
      soup = BeautifulSoup(open("index.html"))
      
      soup = BeautifulSoup("<html>data</html>")
    2. Beautiful Soup将复杂HTML文档转换成一个复杂的树形结构,每个节点都是Python对象,所有对象可以归纳为4种: Tag , NavigableString ,BeautifulSoup , Comment .
    3. from bs4 import SoupStrainer
      
      only_a_tags = SoupStrainer("a")
      
      only_tags_with_id_link2 = SoupStrainer(id="link2")
      
      def is_short_string(string):
          return len(string) < 10
      
      only_short_strings = SoupStrainer(text=is_short_string)
    4. BeautifulSoup(html_doc, "html.parser", parse_only=only_a_tags)
  2. lxml: python 对 libxml 的包装
  3. html5lib:纯python实现

js随机生成一个数组中的随机字符串以及更新验证码

// 生成随机字符串
function randomMixed(n) {
    var chars = [‘0‘, ‘1‘, ‘2‘, ‘3‘, ‘4‘, ‘5‘, ‘6‘, ‘7‘, ‘8‘, ‘9‘, ‘A‘, ‘B‘, ‘C‘, ‘D‘, ‘E‘, ‘F‘, ‘G‘, ‘H‘, ‘I‘, ‘J‘, ‘K‘, ‘L‘, ‘M‘, ‘N‘, ‘O‘, ‘P‘, ‘Q‘, ‘R‘, ‘S‘, ‘T‘, ‘U‘, ‘V‘, ‘W‘, ‘X‘, ‘Y‘, ‘Z‘];
    var res = “”;
    for (var i = 0; i < n; i++) {
        var id = Math.ceil(Math.random() * 35);
        res += chars[id];
    }
    return res;
}

//alert(randomMixed(5))

// 更新验证码图片
function changeVerificationImg(imgId) {
    var newVerificationImg = app.url + ‘/Verifications/show?‘ + randomMixed(12);
    $(‘#‘ + imgId).attr(‘src‘, newVerificationImg);
}

[C++]LeetCode: 82 Fraction to Recurring Decimal

题目:

Given two integers representing the numerator and denominator of a fraction, return the fraction in string format.

If the fractional part is repeating, enclose the repeating part in parentheses.

For example,

MFC长按键盘:执行多次、执行一次

先说明一下你按下一个键盘不松开的时候发生了什么:产生了多个按下的消息,切消息的内容都一样,是你按下的那个键。

实现长按的时候执行多次:比如你长按“A”按钮,可以让界面上的某个控件的数值一直增大,那么你只要在实现这个按键的响应事件里增大一次就可以了,只要你长按,自动会多次调用你的这个函数。

实现长按的时候执行一次:这个按照网上的说法,可以添加一个静态变量,记录上次按了哪个键。主要的代码如下

(1)给类添加一个静态变量

private:
static WPARAM last_key;

(2)在源文件中初始化变量的值为一个非键盘的值以供第一次按键的时候响应

WPARAM CRobotClientDlg::last_key = -1;

(3)在响应按时按钮按下的时候判断是否第一次按下

if (WM_KEYDOWN == pMsg->message)

if(  pMsg->wParam == VK_LEFT && pMsg->wParam != last_key)

{

last_key = pMsg->wParam;

//doSomeThing

}

//松开控制键
if (WM_KEYUP == pMsg->message)

if( m_use_keyboard_contrl == true && pMsg->wParam == VK_LEFT )
{
last_key = -1;

//doSomeThing

}

注意,这里有几个BUG你可能会写到

(1)如果在松开按钮的时候没有回复last_key标记,那么你的要控制的键盘连续按多次的时候只能执行一次,因为这其间没有其他按键会改变last_key的值

(2)如果last_key设置为函数的静态变量,其实应该也可以,因为函数的静态变量只初始化一次,后面那句话就不执行了。

SDL2 中使用多线程绘图

   SDL的中文资料比较少,推荐一个英文的网站里面讲的非常详细  解决了我不少的疑惑。

  最近在移植ucGUI到Android上,ucGUI的windows Demo中可以实现在一个线程中绘图,在另一个线程中刷新。在Android中使用ucGUI也是可以实现的用SDL 当画布,在子线程中绘画 ,在主线程中刷新。代码如下:

/*This source code copyrighted by Lazy Foo' Productions (2004-2013)
 and may not be redistributed without written permission.*/

//Using SDL and standard IO
#include <SDL.h>
#include <stdio.h>
#include <stdbool.h>
#include "GUI.h"
//Screen dimension constants

extern int SCREEN_WIDTH;
extern int SCREEN_HEIGHT;
//Starts up SDL and creates window


//Loads media
bool loadMedia();//加载图片

//Frees media and shuts down SDL
void close();

//The window we'll be rendering to
extern SDL_Window* g_screen_window;

//The surface contained by the window
extern SDL_Surface* g_screen_surface;

//The image we will load and show on the screen
SDL_Surface* gXOut = NULL;

bool loadMedia() {
	//Loading success flag
	bool success = true;

	//Load splash image
	gXOut = SDL_LoadBMP("x.bmp");
	if (gXOut == NULL) {
		SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
				"Unable to load image %s! SDL Error: %s\n",
				"03_event_driven_programming/x.bmp", SDL_GetError());
		success = false;
	}

	return success;
}

void close() {
	//Deallocate surface
	SDL_FreeSurface(gXOut);
	gXOut = NULL;

	//Destroy window
	SDL_DestroyWindow(g_screen_window);
	g_screen_window = NULL;

	//Quit SDL subsystems
	SDL_Quit();
}
void Thread1(void *pdata) {

	MainTask(0);//子线程中的绘画函数都在这里

	SDL_Log("Thread runing");
}
int SDL_main(int argc, char* args[]) {

	//Start up SDL and create window
	GUI_Init();//初始化windows 以及surface

	//Load media
	if (!loadMedia()) {
		printf("Failed to load media!\n");
	} else {
		//Main loop flag
		bool quit = false;

		//Event handler
		SDL_Event e;

		SDL_Thread *thread = SDL_CreateThread(Thread1, "TestThread",
				(void *) NULL);

		//While application is running

		while (!quit) {
			//Handle events on queue
			while (SDL_PollEvent(&e) != 0) {
				//User requests quit
				if (e.type == SDL_QUIT) {
					quit = true;
				}
				if (e.type == SDL_FINGERUP) {
					quit = true;
				}
			}

			//Apply the image

			//Update the surface
			SDL_UpdateWindowSurface(g_screen_window);
			SDL_Log("Invalidate");
		}
	}

	//Free resources and close SDL
	close();

	return 0;
}

字符串匹配算法KMP Java实现

看了一些的kmp实现,依葫芦画瓢,很死板,前缀什么的完全没必要。

kmp算法的核心思想:先对搜索字串生成偏移对照表,匹配时从左向右依次比较(bm从右向左,号称比kmp更快),相等则文档和搜索字串的下标+1迭代,否则查表,定位最优的偏移位置(文档下标不变,搜索字串下标改变)。例外是,字符不匹配时,若搜索字串的下标为0,则文档的下标+1,继续迭代比较。

import java.util.Arrays;

public class KMPSearch {
	public static int[] table;
	public static void generateTab(String key){//查询字串生成偏移对照表,一次迭代就可以
		int len=key.length();
		table=new int[len];
		Arrays.fill(table, 0);
		
		for(int i=1;i<len;i++){
			if(key.charAt(i)==key.charAt(table[i-1])){
				table[i]=table[i-1]+1;
			}
		}
		for(int v : table){
			System.out.print(v);
		}
		System.out.println();
	}
	public static int KMPSearchs(String doc,String key){
		generateTab(key);
		int result=-1;
		int doc_size=doc.length(),
			key_size=key.length(),
			doc_iter=0,
			key_iter=0;
		while(doc_iter<doc_size){//遍历所查询的文档,同样,单层循环就可以实现→_→
			if(doc.charAt(doc_iter)==key.charAt(key_iter)){
				doc_iter++;
				key_iter++;
			}else{
				if(key_iter==0){
					doc_iter++;
					continue;
				}else{
					key_iter=table[key_iter-1];
					continue;
				}
			}
			if(key_iter==key_size){
				result=doc_iter-key_size;
				break;
			}
		}
		return result;
	}
	public static void main(String[] args){
		int i=KMPSearchs("bbc abcdab abcdabcdabde","abcdabd");
		System.out.println(i);
	}
}

算法讲解参考

HPU1291 小朋友排队 【逆序数+树状数组】

1291: 小朋友排队

时间限制: 1 Sec  内存限制: 128 MB

提交: 2  解决: 1

[][][] []

题目描述

n 个小朋友站成一排。现在要把他们按身高从低到高的顺序排列,但是每次只能交换位置相邻的两个小朋友。   每个小朋友都有一个不高兴的程度。开始的时候,所有小朋友的不高兴程度都是0。   如果某个小朋友第一次被要求交换,则他的不高兴程度增加1,如果第二次要求他交换,则他的不高兴程度增加2(即不高兴程度为3),依次类推。当要求某个小朋友第k次交换时,他的不高兴程度增加k。   请问,要让所有小朋友按从低到高排队,他们的不高兴程度之和最小是多少。   如果有两个小朋友身高一样,则他们谁站在谁前面是没有关系的。

输入

输入的第一行包含一个整数n,表示小朋友的个数。  第二行包含 n 个整数 H1 H2 … Hn,分别表示每个小朋友的身高。1<=n<=100000,0<=Hi<=1000000。

输出

输出一行,包含一个整数,表示小朋友的不高兴程度和的最小值。

样例输入

3 3 2 1

样例输出

9

提示

首先交换身高为3和2的小朋友,再交换身高为3和1的小朋友,再交换身高为2和1的小朋友,每个小朋友的不高兴程度都是3,总和为9。

先顺序将数组中的数读入到树状数组T1中求出每个当前点跟左边的点构成的逆序点对数,再逆序将数组读入到树状数组T2中求出每个当前点跟它右边的点构成的逆序点对数。

#include <stdio.h>
#include <string.h>

#define maxn 1000010
typedef long long LL;

LL T1[maxn], T2[maxn], unhappy[maxn];
LL A[maxn], B[maxn], N; // B[]存储交换次数

LL lowBit(LL x) { return x & -x; }

void update(LL x, LL *arr) {
	for ( ; x < maxn; x += lowBit(x))
		++arr[x];
}

LL getSum(LL x, LL *arr) {
	LL sum = 0;
	for ( ; x; x -= lowBit(x))
		sum += arr[x];
	return sum;
}

int main() {
	// freopen("stdin.txt", "r", stdin);
	LL i, j;
	LL ret = 0;

	for (i = 1; i < maxn; ++i)
		unhappy[i] = unhappy[i-1] + i;

	scanf("%lld%lld", &N, &A[0]);
	update(A[0] + 1, T1);
	for (i = 1; i < N; ++i) { // 当前点跟左边的点产生的逆序对数
		scanf("%lld", &A[i]);
		update(A[i] + 1, T1);
		B[i] = i+1 - getSum(A[i] + 1, T1);
	}
	// for (i = 0; i < N; ++i)
	// 	printf("%lld..", B[i]);
	// printf("\n");
	for (i = N - 1; i >= 0; --i) { // 当前点跟右边的点产生的逆序对数
		update(A[i] + 1, T2);
		ret += unhappy[B[i] + getSum(A[i], T2)];
	}

	printf("%lld\n", ret);

	return 0;
}

C#/JS 获取二维数组组合

C#获取二维数组组合

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace HF.SHOP.WebSite
{
    public partial class Demo : System.Web.UI.Page
    {

        List<List<string>> P_StringLL = new List<List<string>>();
        List<string> P_tmpStrL = new List<string>();

        protected void Page_Load(object sender, EventArgs e)
        {
            List<string> al = new List<string>();
            al.Add("红色");
            al.Add("黑色");
            al.Add("紫色");
            al.Add("白色");
            P_StringLL.Add(al);

            al = new List<string>();
            al.Add("小号");
            al.Add("中号");
            al.Add("大号");
            al.Add("加大号");
            P_StringLL.Add(al);

            al = new List<string>();
            al.Add("男同志");
            al.Add("女同志");

            P_StringLL.Add(al);

            GroupArray("", P_StringLL, 0);

            StringBuilder sb = new StringBuilder();

            foreach (string  str in P_tmpStrL)
            {
                sb.AppendLine(str + "<br/>");
            }

            Label1.Text = "总共有:" + P_tmpStrL.Count.ToString() + "种组合";
            Label2.Text = sb.ToString();
        }

        //组合计算
        void GroupArray(string tmpStr, List<List<string>> strLL, int index)
        {
            int count = strLL.Count;

            if (count <= index)
            {
                if (tmpStr.Length > 0)
                {
                    P_tmpStrL.Add(tmpStr);
                }

                return;
            }

            List<string> strL = strLL[index];

            foreach (string str in strL)
            {
                GroupArray(tmpStr + str, strLL, index + 1);
            }

        }
    }
}

 

 

JS代码:

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Demo.aspx.cs" Inherits="HF.SHOP.WebSite.Demo" %>

<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
    <title></title>
    <script src="UIResources/scripts/jquery.min.2.0.0.js"></script>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <asp:Label ID="Label1" runat="server" Text=""></asp:Label><br />
        <asp:Label ID="Label2" runat="server" Text=""></asp:Label>
        
        <div id="cou"></div>
        <ul id="list">

        </ul>
    </div>
    </form>

    <script type="text/javascript">

        var array = [[‘红色‘, ‘黑色‘, ‘白色‘], [‘小号‘, ‘中号‘, ‘加大‘], [‘男‘, ‘女‘]];
        var results = new Array(); //创建一个数组
        var len = array.length;
        var indexs = {};


        $(document).ready(function () {
            GetResults(-1);

            $("#cou").html("共有" + results.length + "种组合;<br/>");
            for (var a = 0; a < results.length; a++)
            {
                $("#list").append("<li>" + results[a] + "</li>");
            }
        });

        function GetResults(start) {
            start++;
            if (start > len - 1) {
                return;
            }
            if (!indexs[start]) {
                indexs[start] = 0;
            }
            if (!(array[start] instanceof Array)) {
                array[start] = [array[start]];
            }
            for (indexs[start] = 0; indexs[start] < array[start].length; indexs[start]++) {
                GetResults(start);
                if (start == len - 1) {
                    var temp = [];
                    for (var i = len - 1; i >= 0; i--) {
                        if (!(array[start - i] instanceof Array)) {
                            array[start - i] = [array[start - i]];
                        }
                        temp.push(array[start - i][indexs[start - i]]);
                    }
                    results.push(temp);
                }
            }

        }
    </script>
</body>
</html>

 

jsp和javabean的交叉使用

很多时候我们在jsp编程时,会出现大量重复的代码,这时候就需要使用javabean。下面我给大家介绍jsp文件使用javabean

目录结构

 

只需要在jsp文件的开始加上红色的一段代码即可

使用时直接使用

<%@ page language=“java”import=“java.util.*,java.sql.*” pageEncoding=“UTF-8”%>

<%

String path =request.getContextPath();

String basePath =request.getScheme()+”://”+request.getServerName()+”:”+request.getServerPort()+path+”/”;

%>

<jsp:useBean id=“DBJB” scope=“page”class=“lytjb.DB” />

<!DOCTYPE HTML PUBLIC “-//W3C//DTDHTML 4.01 Transitional//EN”>

<html>

  <head>

    <title>My JSP ‘index.jsp‘starting page</title>

  </head>

………

………..

……….

String sql=”selectbook_picture,book_name,book_out_price,book_stock from college_info wherecollege=‘政法 ‘”;

       ResultSet rs =DBJB.executeQuery(sql);// 返回SQL语句查询结果集(集合)

 

…………………….

 

 

附有DB.Java代码

package lytjb;

import java.sql.*;

//一个用于查找数据源的工具类。

public class DB {

    private Connection con = null;

    private Statement stmt = null;

    ResultSet rs = null;

 

    public ResultSet executeQuery(String sql) throws Exception {

          

           try

           Class.forName(“com.microsoft.sqlserver.jdbc.SQLServerDriver”); 

       con=DriverManager.getConnection(“jdbc:sqlserver://localhost:1433;DatabaseName=SHB”,”sa”,”1234567″); 

           //Class.forName(“sun.jdbc.odbc.JdbcOdbcDriver”); 

           //con=DriverManager.getConnection(“jdbc:odbc:JDBCSQLDemo_JSPTest”); 

           stmt=con.createStatement(); 

           rs=stmt.executeQuery(sql);

           }catch(Exception e){}

       return rs;

    }

 

    // 执行Insert,Update语句

    public void executeUpdate(String sql) throws Exception {

       try

           Class.forName(“com.microsoft.sqlserver.jdbc.SQLServerDriver”); 

       con=DriverManager.getConnection(“jdbc:sqlserver://localhost:1433;DatabaseName=SHB”,”sa”,”1234567″); 

           //Class.forName(“sun.jdbc.odbc.JdbcOdbcDriver”); 

           //con=DriverManager.getConnection(“jdbc:odbc:JDBCSQLDemo_JSPTest”); 

           stmt=con.createStatement(); 

           int rs=stmt.executeUpdate(sql);

          

           }catch(Exception e){}

    }

 

    // 关闭stmt和关闭连接

    public void close_all() {

       try {

           stmt.close();

           con.close();

       } catch (SQLException e) {

           e.printStackTrace();

       }

    }

 

}

 

VC++编程中获取系统时间

<span style="white-space:pre">	</span>总结了在程序中如何获得系统时间的方法
void CGetSystenTimeDlg::OnBnClickedGettimeButton()
{
	// TODO: 在此添加控件通知处理程序代码
	//方法一  使用MFC的CTime类
	CString str; //获取系统时间  
	CTime tm; tm=CTime::GetCurrentTime();   
	str=tm.Format("现在时间是%Y年%m月%d日 %X"); MessageBox(str,NULL,MB_OK); 

	////方法二  使用win32定义得结构体
	SYSTEMTIME time;
	CString str1,str2;
	GetLocalTime(&time);  //Windows API 函数,用来获取当地的当前系统日期和时间。
	str1.Format(L"%d-%d-%d",time.wYear,time.wMonth,time.wDay);
	str2.Format(L"%2d:%2d:%2d",time.wHour,time.wMinute,time.wSecond);
	MessageBox(str1,NULL,MB_OK);
	MessageBox(str2,NULL,MB_OK);

	//方法三:GetTickCount返回(retrieve)从操作系统启动所经过的毫秒数
	//,它的返回值是DWORD。 可以用它来测量程序的运行时间
	CString str3;
	long t1=GetTickCount();//程序段开始前取得系统运行时间(ms)   
	Sleep(500); 
	long t2=GetTickCount();//程序段结束后取得系统运行时间(ms)   
	str3.Format(L"time:%dms",t2-t1);//前后之差即 程序运行时间   
	AfxMessageBox(str3);//获取系统运行时间    即休眠的的时间 
	
	 
	 //从操作系统启动所经过的时间
	 long t=GetTickCount();
	 CString str4;
	 CString str5;
	 str4.Format(L"系统已运行 %d时",t/3600000); 
	 str5=str5+str4;
	// MessageBox(str4,NULL,MB_OK);
	 t%=3600000;
	 
	 str4.Format(L"系统已经运行 %d分",t/60000);
	 str5=str5+str4;
	 t%=60000;
	 str4.Format(L"系统已经运行 %d秒",t/1000);
	 str5=str5+str4;
	 
	 MessageBox(str5,NULL,MB_OK);


}

方法一:

方法二:

      

方法三:

    

php 数组 添加元素、删除元素

拆分数组

PHP数组添加一个元素的方式: push(), arr[],

 

 

Php代码

  1. $arr = array();
  2. array_push($arr, el1, el2 … eln);

 

但其实有一种更直接方便的做法:

 

Php代码

  1. $arr = array();
  2. $arr[] = el1;
  3. $arr[] = el2;
  4. $arr[] = eln;

 

而且有实验证明,第二种方法的效率比第一种方法高出将近一倍!

 

我们来看下面的例子:

 

Php代码

  1. $t = microtime(true);
  2. $array = array();
  3. for($i = 0; $i < 10000; $i++) {
  4. $array[] = $i;
  5. }
  6. print microtime(true) – $t;
  7. print ‘<br>‘;
  8. $t = microtime(true);
  9. $array = array();
  10. for($i = 0; $i < 10000; $i++) {
  11. array_push($array, $i);
  12. }
  13. print microtime(true) – $t;

运行脚本,结果为:

 

写道 Run 1 

0.0054171085357666 // array_push 

0.0028800964355469 // array[] 

Run 2 

0.0054559707641602 // array_push 

0.002892017364502 // array[] 

Run 3 

0.0055501461029053 // array_push 

0.0028610229492188 // array[]

 

其他方法:

1.在数组末尾添加一个或多个元素。
  array_push() 将 array 当成一个栈,并将传入的变量压入 array 的末尾。array 的长度将根据入栈变量的数目增加。
  php代码实例:
  <?php
    $arr1 = array(“a”, “b”);
    array_push($arr1, “c”, “d”);
    print_r($arr1);
  ?>
  运行结果:
   Array
  (
   [0] => a
   [1] => b
   [2] => c
   [3] => d
  )
  注:如果第一个参数不是数组,array_push() 将发出一条警告。
2.删除数组末尾的一个元素。
  array_pop() 弹出并返回 array 数组的最后一个单元,并将数组 array 的长度减一。如果 array 为空(或者不是数组)将返回 NULL。
  php代码实例:
  <?php
    $arr1 = array(“a”, “b”,”c”,”d”);
    array_pop($arr1);
    print_r($arr1);
  ?>
  运行结果:
   Array
  (
   [0] => a
   [1] => b
   [2] => c
  )  
3.在数组的开始添加一个或多个元素。
   array_unshift() 将传入的单元插入到 array 数组的开头。注意单元是作为整体被插入的,因此传入单元将保持同样的顺序。所有的数值键名将修改为从零开始重新计数,所有的文字键名保持不变。
   php代码实例:
  <?php
    $arr1 = array(“c”,”d”);
    array_unshift($arr1,”a”,”b”);
    print_r($arr1);
  ?>
  运行结果:
   Array
  (
   [0] => a
   [1] => b
   [2] => c
   [3] => d 
  )  
4.删除数组第一个元素,并将第一个元素返回。
   array_shift() 将 array 的第一个单元移出并作为结果返回,将 array 的长度减一并将所有其它单元向前移动一位。所有的数字键名将改为从零开始计数,文字键名将不变。如果 array 为空(或者不是数组),则返回 NULL。
   php代码实例:
  <?php
    $arr1 = array(“a”,”b”,”c”,”d”);
    echo (array_shift($arr1));
    print_r($arr1);
  ?>
  运行结果:
   a 
   Array
  (
   [0] => b
   [1] => c
   [2] => d 
  ) 

C++指定路径写入文件

 int iNum =9;
 CString str2;
 str2.Format(TEXT("e:\\%d.txt"),iNum);
 CFile file(  str2, CFile::modeRead | CFile::modeWrite | CFile::modeCreate );
 file.Write( "你好", strlen("你好") );
 file.Close();

用分治法实现大数乘法,加法,减法(java实现)

  大数乘法即多项式乘法问题,求A(x)与B(x)的乘积C(x),朴素解法的复杂度O(n^2),基本思想是把多项式A(x)与B(x)写成

A(x)=a*x^m+b
B(x)=c*x^m+d

其中a,b,c,d为x的多项式。
则A(x)*B(x)=(ac)*x^2m+(ad+bc)*x^m+bd
由ad+bc=(a+b)(c+d)-ac-bd
原来的4次乘法和1次加法由3次乘法和2次减法代替,减少了一次乘法操作。
用同样的方法应用到abcd的乘法上。

(以上内容摘自互联网)

以下为用java实现的代码:

 

package com.kyy.sf;

public class BigInteger {

    public BigInteger() {

    }

    // 基本思想是把多项式A(x)与B(x)写成
    // A(x)=a*x^m+b
    // B(x)=c*x^m+d
    // 其中a,b,c,d为x的多项式。
    // 则A(x)*B(x)=(ac)*x^2m+(ad+bc)*x^m+bd
    // 由ad+bc=(a+b)(c+d)-ac-bd
    // 字符串模拟乘法操作
    
    public static String mut(String x, String y) {
        // deep++;// Console.WriteLine("-" + deep + "-");
        String negative = "";
        // x,y同为正或者同为负
        if ((x.startsWith("-") && y.startsWith("-"))
                || (!x.startsWith("-") && !y.startsWith("-"))) {
            x = x.replaceAll("-", "");
            y = y.replaceAll("-", "");
            negative = "";
        }// x,y一正一负
        else if ((x.startsWith("-") && !y.startsWith("-"))
                || (!x.startsWith("-") && y.startsWith("-"))) {
            x = x.replace("-", "");
            y = y.replace("-", "");
            negative = "-";
        }

        // 如果长度都等于于9,直接相乘,返回就行了。
        if (x.length() == 1 && y.length() == 1) {
            // 计算乘积
            int tmp = (Integer.parseInt(x) * Integer.parseInt(y));

            if (tmp == 0) {
                return tmp + "";
            } else {
                return negative + tmp;
            }
        }

        // 公式里的abcd
        String a, b, c, d;
        if (x.length() == 1) {
            a = "0";
            b = x;
        } else {
            if (x.length() % 2 != 0) {
                x = "0" + x;
            }
            a = x.substring(0, x.length() / 2);
            b = x.substring(x.length() / 2);
        }
        if (y.length() == 1) {
            c = "0";
            d = y;
        } else {
            if (y.length() % 2 != 0) {
                y = "0" + y;
            }
            c = y.substring(0, y.length() / 2);
            d = y.substring(y.length() / 2);
        }
        // 按最大位数取值,以确定补零数目
        int n = x.length() >= y.length() ? x.length() : y.length();

        String t1, t2, t3;
        // 递归调用,根据公式计算出值。
        String ac = mut(a, c);
        String bd = mut(b, d);
        t1 = mut(sub(a, b), sub(d, c));
        t2 = add(add(t1, ac), bd);
        t3 = add(add(Power10(ac, n), Power10(t2, n / 2)), bd).replaceAll("^0+",
                "");

        if (t3 == "")
            return "0";
        return negative + t3;
    }

    private static String add(String x, String y) {

        if (x.startsWith("-") && !y.startsWith("-")) {
            return sub(y, x.replaceAll("^-", ""));
        } else if (!x.startsWith("-") && y.startsWith("-")) {
            return sub(x, y.replaceAll("^-", ""));
        } else if (x.startsWith("-") && y.startsWith("-")) {
            return "-" + add(x.replaceAll("^-", ""), y.replaceAll("^-", ""));
        }

        if (x.length() > y.length()) {
            y = format(y, x.length(), "0");
        } else {
            x = format(x, y.length(), "0");
        }
        int[] sum = new int[x.length() + 1];

        for (int i = x.length() - 1; i >= 0; i--) {
            int tmpsum = Integer.parseInt(x.charAt(i) + "")
                    + Integer.parseInt(y.charAt(i) + "") + sum[i + 1];
            if (tmpsum >= 10) {
                sum[i + 1] = tmpsum - 10;
                sum[i] = 1;// 表示进位
            } else {
                sum[i + 1] = tmpsum;
            }
        }

        StringBuilder returnvalue = new StringBuilder();

        for (int i : sum) {
            returnvalue.append(i);
        }

        if (sum[0] == 1) {

            return returnvalue.toString();

        } else {
            return returnvalue.replace(0, 1, "").toString();
        }

    }

    // 字符串模拟减法操作
    private static String sub(String x, String y) {

        // x是正数,y也是正数
        int flag = checkBigger(x, y);

        if (flag == 0) {
            return "0";
        } else if (flag == -1) {
            String tmp = y;
            y = x;
            x = tmp;
        }
        // 保证了x>=y
        y = format(y, x.length(), "0");// y补0与x对齐

        int[] difference = new int[x.length()];

        for (int i = x.length() - 1; i >= 0; i--) {

            int tmpdifference;

            tmpdifference = Integer.parseInt(x.charAt(i) + "")
                    - Integer.parseInt(y.charAt(i) + "") + difference[i];

            if (tmpdifference < 0) {

                tmpdifference += 10;
                difference[i - 1] = -1;// 表示进位
            }

            difference[i] = tmpdifference;
        }

        StringBuilder returnvalue = new StringBuilder();

        for (int i : difference) {
            returnvalue.append(i);
        }

        String rv = returnvalue.toString().replaceAll("^0+", "");

        if ("".equals(rv)) {
            return "0";
        }

        if (flag == -1) {
            rv = "-" + rv;
        }

        return rv;
    }

    // 比较大小
    private static int checkBigger(String x, String y) {

        if (x.length() > y.length()) {

            return 1;

        } else if (x.length() < y.length()) {

            return -1;

        } else {

            for (int i = 0; i < x.length(); i++) {

                if (x.charAt(i) > y.charAt(i)) {

                    return 1;

                } else if (x.charAt(i) < y.charAt(i)) {
                    return -1;
                }
            }

            return 0;
        }
    }

    //数据前补零
    private static String format(String str, int len, String fu) {

        len = len - str.length();

        for (int i = 0; i < len; i++) {

            str = fu + str;
        }

        return str;

    }

    // 模拟移位
    public static String Power10(String num, int n) {

        for (int i = 0; i < n; i++) {
            
            num += "0";
        
        }

        return num;
    }

    public static void main(String[] args) {

        String x = "93859048059849086850986804750894758903278473894578397598475984784857487584758094875890475984955624146039530798877974";
        String y = "224343444859408590475847538946";
        System.out.println(mut(x, y));
        
        System.out.println(mut("1111111111", "1111111111"));

    }
}

 

OpenCV图像匹配算法之sift

//utils.h
#ifndef _UTILS_H
#define _UTILS_H

#include <opencv2/opencv.hpp>
#include <opencv2/features2d/features2d.hpp>
#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2\nonfree\nonfree.hpp>
using namespace cv;

// ORB settings
const int ORB_MAX_KPTS = 1500;
const float ORB_SCALE_FACTOR = 1.5;
const int ORB_PYRAMID_LEVELS = 3;
const float ORB_EDGE_THRESHOLD = 31.0;
const int ORB_FIRST_PYRAMID_LEVEL = 0;
const int ORB_WTA_K = 2;
const int ORB_PATCH_SIZE = 31;

// BRISK settings
const float BRISK_HTHRES = 10.0;
const int BRISK_NOCTAVES = 6;


const float DRATIO = 0.8f;							// NNDR Matching value
const float MIN_H_ERROR = 2.50f;			// Maximum error in pixels to accept an inlier

void matches2points_nndr(const std::vector<cv::KeyPoint>& train,
                         const std::vector<cv::KeyPoint>& query,
                         const std::vector<std::vector<cv::DMatch> >& matches,
                         std::vector<cv::Point2f>& pmatches, float nndr);
void compute_inliers_ransac(const std::vector<cv::Point2f>& matches,
                            std::vector<cv::Point2f>& inliers,
                            float error, bool use_fund);
void draw_inliers(const cv::Mat& img1, const cv::Mat& img2, cv::Mat& img_com,
                  const std::vector<cv::Point2f>& ptpairs, int color);

typedef struct info
{
	double t;
	int n1;
	int n2;
	int m;
	int rm;
}INFO;

void sift(char* path1, char* path2, INFO& info, bool show);
void surf(char* path1, char* path2, INFO& info, bool show);
void orb(char* path1, char* path2, INFO& info, bool show);
void brisk(char* path1, char* path2, INFO& info, bool show);
void freak(char* path1, char* path2, INFO& info, bool show);
void showInfo(INFO info);

#endif
//utils.cpp
#include "stdafx.h"
#include "utils.h"
#include <iostream>
using namespace std;

/**
 * @brief This function converts matches to points using nearest neighbor distance
 * ratio matching strategy
 * @param train Vector of keypoints from the first image
 * @param query Vector of keypoints from the second image
 * @param matches Vector of nearest neighbors for each keypoint
 * @param pmatches Vector of putative matches
 * @param nndr Nearest neighbor distance ratio value
 */
void matches2points_nndr(const std::vector<cv::KeyPoint>& train,
                         const std::vector<cv::KeyPoint>& query,
                         const std::vector<std::vector<cv::DMatch> >& matches,
                         std::vector<cv::Point2f>& pmatches, float nndr) {

  float dist1 = 0.0, dist2 = 0.0;
  for (size_t i = 0; i < matches.size(); i++) {
    DMatch dmatch = matches[i][0];
    dist1 = matches[i][0].distance;
    dist2 = matches[i][1].distance;

    if (dist1 < nndr*dist2) {
      pmatches.push_back(train[dmatch.queryIdx].pt);
      pmatches.push_back(query[dmatch.trainIdx].pt);
    }
  }
}

/**
 * @brief This function computes the set of inliers estimating the fundamental matrix
 * or a planar homography in a RANSAC procedure
 * @param matches Vector of putative matches
 * @param inliers Vector of inliers
 * @param error The minimum pixelic error to accept an inlier
 * @param use_fund Set to true if you want to compute a fundamental matrix
 */
void compute_inliers_ransac(const std::vector<cv::Point2f>& matches,
                            std::vector<cv::Point2f>& inliers,
                            float error, bool use_fund) {

  vector<Point2f> points1, points2;
  Mat H = Mat::zeros(3,3,CV_32F);
  int npoints = matches.size()/2;
  Mat status = Mat::zeros(npoints,1,CV_8UC1);

  for (size_t i = 0; i < matches.size(); i+=2) {
    points1.push_back(matches[i]);
    points2.push_back(matches[i+1]);
  }

  if (use_fund == true){
    H = findFundamentalMat(points1,points2,CV_FM_RANSAC,error,0.99,status);
  }
  else {
    H = findHomography(points1,points2,CV_RANSAC,error,status);
  }

  for (int i = 0; i < npoints; i++) {
    if (status.at<unsigned char>(i) == 1) {
      inliers.push_back(points1[i]);
      inliers.push_back(points2[i]);
    }
  }
}

//*******************************************************************************
//*******************************************************************************

/**
 * @brief This function draws the set of the inliers between the two images
 * @param img1 First image
 * @param img2 Second image
 * @param img_com Image with the inliers
 * @param ptpairs Vector of point pairs with the set of inliers
 * @param color The color for each method
 */
void draw_inliers(const cv::Mat& img1, const cv::Mat& img2, cv::Mat& img_com,
                  const std::vector<cv::Point2f>& ptpairs, int color) {

  int x1 = 0, y1 = 0, x2 = 0, y2 = 0;
  float rows1 = 0.0, cols1 = 0.0;
  float rows2 = 0.0, cols2 = 0.0;
  float ufactor = 0.0, vfactor = 0.0;

  rows1 = img1.rows;
  cols1 = img1.cols;
  rows2 = img2.rows;
  cols2 = img2.cols;
  ufactor = (float)(cols1)/(float)(cols2);
  vfactor = (float)(rows1)/(float)(rows2);

  // This is in case the input images don't have the same resolution
  Mat img_aux = Mat(Size(img1.cols,img1.rows),CV_8UC3);
  resize(img2,img_aux,Size(img1.cols,img1.rows),0,0,CV_INTER_LINEAR);

  for (int i = 0; i < img_com.rows; i++) {
    for (int j = 0; j < img_com.cols; j++) {
      if (j < img1.cols) {
        *(img_com.ptr<unsigned char>(i)+3*j) = *(img1.ptr<unsigned char>(i)+3*j);
        *(img_com.ptr<unsigned char>(i)+3*j+1) = *(img1.ptr<unsigned char>(i)+3*j+1);
        *(img_com.ptr<unsigned char>(i)+3*j+2) = *(img1.ptr<unsigned char>(i)+3*j+2);
      }
      else {
        *(img_com.ptr<unsigned char>(i)+3*j) = *(img_aux.ptr<unsigned char>(i)+3*(j-img_aux.cols));
        *(img_com.ptr<unsigned char>(i)+3*j+1) = *(img_aux.ptr<unsigned char>(i)+3*(j-img_aux.cols)+1);
        *(img_com.ptr<unsigned char>(i)+3*j+2) = *(img_aux.ptr<unsigned char>(i)+3*(j-img_aux.cols)+2);
      }
    }
  }

  for (size_t i = 0; i < ptpairs.size(); i+= 2) {
    x1 = (int)(ptpairs[i].x+.5);
    y1 = (int)(ptpairs[i].y+.5);
    x2 = (int)(ptpairs[i+1].x*ufactor+img1.cols+.5);
    y2 = (int)(ptpairs[i+1].y*vfactor+.5);

    if (color == 0) {
      line(img_com,Point(x1,y1),Point(x2,y2),CV_RGB(255,255,0),1);
    }
    else if (color == 1) {
      line(img_com,Point(x1,y1),Point(x2,y2),CV_RGB(255,0,0),1);
    }
    else if (color == 2) {
      line(img_com,Point(x1,y1),Point(x2,y2),CV_RGB(0,0,255),1);
    }
  }
}


void showInfo(INFO info)
{
	printf("%-40s%d\n","The keypoints number of src image is :", info.n1);
	printf("%-40s%d\n","The keypoints number of dst image is : ", info.n2);
	printf("%-40s%d\n","The matching number is : ", info.m);
	printf("%-40s%d\n","The right result number is : ", info.rm);
	printf("%-40s%.2fs\n","The total time is : ", info.t);
	return ;
}
//sift.cpp
#include "stdafx.h"
#include <cv.hpp>
#include <highgui.h>
#include "utils.h"
#include <iostream>
using namespace std;

void sift(char* path1, char* path2, INFO& info, bool show)
{
	double t1,t2;
	t1=cvGetTickCount();

	initModule_nonfree();

	Mat img1, img2;	
	img1=imread(path1,0);
	img2=imread(path2,0);
	if(img1.data==NULL)
	{
		cout<<"The image can not been loaded: "<<path1<<endl;
		system("pause");
		exit(-1);
	}
	if(img2.data==NULL)
	{
		cout<<"The image can not been loaded: "<<path2<<endl;
		system("pause");
		exit(-1);
	}	

	Ptr<FeatureDetector> sift_detector = FeatureDetector::create( "SIFT" );
    Ptr<DescriptorExtractor> sift_descriptor = DescriptorExtractor::create( "SIFT" );  
    vector<KeyPoint> kpts1_sift, kpts2_sift;
	Mat desc1_sift, desc2_sift;
	Ptr<DescriptorMatcher> matcher_l2 = DescriptorMatcher::create("BruteForce");		//欧氏距离匹配
    vector<vector<DMatch> > dmatches_sift;
	vector<Point2f> matches_sift, inliers_sift;

	sift_detector->detect(img1,kpts1_sift);
	sift_detector->detect(img2,kpts2_sift);
	info.n1=kpts1_sift.size();
	info.n2=kpts2_sift.size();
	sift_descriptor->compute(img1,kpts1_sift,desc1_sift);
    sift_descriptor->compute(img2,kpts2_sift,desc2_sift);
	matcher_l2->knnMatch(desc1_sift,desc2_sift,dmatches_sift,2);										//匹配
	matches2points_nndr(kpts1_sift,kpts2_sift,dmatches_sift,matches_sift,DRATIO);
	info.m=matches_sift.size()/2;
	compute_inliers_ransac(matches_sift,inliers_sift,MIN_H_ERROR,false);
	info.rm=inliers_sift.size()/2;

	t2=cvGetTickCount();
	info.t=(t2-t1)/1000000.0/cvGetTickFrequency();

	Mat img1_rgb_sift = imread(path1,1);
	Mat img2_rgb_sift = imread(path2,1);
	Mat img_com_sift = Mat(Size(img1.cols*2,img1.rows),CV_8UC3);

	if(show == true)
	{
		draw_inliers(img1_rgb_sift,img2_rgb_sift,img_com_sift,inliers_sift,2);
		imshow("sift",img_com_sift);
		waitKey(0);
	}

	return;
}

使用

INFO sift_info;
sift(path1,path2,sift_info,true);
showInfo(sift_info);

java中io的详解

注:本文全篇转载于:http://blog.csdn.net/taxueyingmei/article/details/7697042,觉得讲的挺详细,就借过来看看,挺不错的文章。

 先贴一张图

                                           

Java 流在处理上分为字符流和字节流。字符流处理的单元为 2 个字节的 Unicode 字符,分别操作字符、字符数组或字符串,而字节流处理单元为 1 个字节,操作字节和字节数组。

Java 内用 Unicode 编码存储字符,字符流处理类负责将外部的其他编码的字符流和 java 内 Unicode 字符流之间的转换。而类 InputStreamReader 和 OutputStreamWriter 处理字符流和字节流的转换。字符流(一次可以处理一个缓冲区)一次操作比字节流(一次一个字节)效率高。 

 

( 一 )以字节为导向的 stream——InputStream/OutputStream

InputStream 和 OutputStream 是两个 abstact 类,对于字节为导向的 stream 都扩展这两个鸡肋(基类 ^_^ ) ;

1、 InputStream

 

 

1.1

ByteArrayInputStream — 把内存中的一个缓冲区作为 InputStream 使用 .

construct—

(A)ByteArrayInputStream(byte[]) 创建一个新字节数组输入流( ByteArrayInputStream ),它从指定字节数组中读取数据( 使用 byte 作为其缓冲区数组)

(B)—ByteArrayInputStream(byte[], int, int) 创建一个新字节数组输入流,它从指定字节数组中读取数据。

—mark:: 该字节数组未被复制。

1.2

StringBufferInputStream — 把一个 String 对象作为 InputStream .

construct—  

StringBufferInputStream(String) 据指定串创建一个读取数据的输入流串。

 

注释:不推荐使用 StringBufferInputStream 方法。 此类不能将字符正确的转换为字节。

同 JDK 1.1 版中的类似,从一个串创建一个流的最佳方法是采用 StringReader 类。

1.3

FileInputStream — 把一个文件作为 InputStream ,实现对文件的读取操作

construct—

(A)FileInputStream(File name) 创建一个输入文件流,从指定的 File 对象读取数据。

(B)FileInputStream(FileDescriptor) 创建一个输入文件流,从指定的文件描述器读取数据。

(C)-FileInputStream(String  name) 创建一个输入文件流,从指定名称的文件读取数据。

method —- read() 从当前输入流中读取一字节数据。

read(byte[]) 将当前输入流中 b.length 个字节数据读到一个字节数组中。

read(byte[], int, int) 将输入流中 len 个字节数据读入一个字节数组中。

1.4

PipedInputStream :实现了 pipe 的概念,主要在线程中使用 . 管道输入流是指一个通讯管道的接收端。

一个线程通过管道输出流发送数据,而另一个线程通过管道输入流读取数据,这样可实现两个线程间的通讯。

construct—

PipedInputStream() 创建一个管道输入流,它还未与一个管道输出流连接。

PipedInputStream(PipedOutputStream) 创建一个管道输入流 , 它已连接到一个管道输出流。

1.5

SequenceInputStream :把多个 InputStream 合并为一个 InputStream . “序列输入流”类允许应用程序把几个输入流连续地合并起来,

并且使它们像单个输入流一样出现。每个输入流依次被读取,直到到达该流的末尾。

然后“序列输入流”类关闭这个流并自动地切换到下一个输入流。

construct—

SequenceInputStream(Enumeration) 创建一个新的序列输入流,并用指定的输入流的枚举值初始化它。

SequenceInputStream(InputStream, InputStream) 创建一个新的序列输入流,初始化为首先 读输入流 s1, 然后读输入流 s2 。

 

2、 OutputSteam

 

2.1

ByteArrayOutputStream : 把信息存入内存中的一个缓冲区中 . 该类实现一个以字节数组形式写入数据的输出流。

当数据写入缓冲区时,它自动扩大。用 toByteArray() 和 toString() 能检索数据。

constructor

(A)— ByteArrayOutputStream() 创建一个新的字节数组输出流。

(B)— ByteArrayOutputStream() 创建一个新的字节数组输出流。

(C)— ByteArrayOutputStream(int) 创建一个新的字节数组输出流,并带有指定大小字节的缓冲区容量。

toString(String) 根据指定字符编码将缓冲区内容转换为字符串,并将字节转换为字符。

write(byte[], int, int) 将指定字节数组中从偏移量 off 开始的 len 个字节写入该字节数组输出流。

write(int) 将指定字节写入该字节数组输出流。

writeTo(OutputStream) 用 out.write(buf, 0, count) 调用输出流的写方法将该字节数组输出流的全部内容写入指定的输出流参数。

2.2  

FileOutputStream: 文件输出流是向 File 或 FileDescriptor 输出数据的一个输出流。

constructor

(A)FileOutputStream(File  name) 创建一个文件输出流,向指定的 File 对象输出数据。

(B)FileOutputStream(FileDescriptor) 创建一个文件输出流,向指定的文件描述器输出数据。

(C)FileOutputStream(String  name) 创建一个文件输出流,向指定名称的文件输出数据。

(D)FileOutputStream(String, boolean) 用指定系统的文件名,创建一个输出文件。

2.3

PipedOutputStream: 管道输出流是指一个通讯管道的发送端。 一个线程通过管道输出流发送数据,

而另一个线程通过管道输入流读取数据,这样可实现两个线程间的通讯。

constructor

(A)PipedOutputStream() 创建一个管道输出流,它还未与一个管道输入流连接。

(B)PipedOutputStream(PipedInputStream) 创建一个管道输出流,它已连接到一个管道输入流。

 

( 二 )以字符为导向的 stream Reader/Writer

以 Unicode 字符为导向的 stream ,表示以 Unicode 字符为单位从 stream 中读取或往 stream 中写入信息。

Reader/Writer 为 abstact 类

以 Unicode 字符为导向的 stream 包括下面几种类型:

1. Reader

 

 

1.1

  CharArrayReader :与 ByteArrayInputStream 对应此类实现一个可用作字符输入流的字符缓冲区

constructor

CharArrayReader(char[]) 用指定字符数组创建一个 CharArrayReader 。

CharArrayReader(char[], int, int) 用指定字符数组创建一个 CharArrayReader

1.2

StringReader : 与 StringBufferInputStream 对应其源为一个字符串的字符流。

StringReader(String) 创建一新的串读取者。

1.3

FileReader : 与 FileInputStream 对应

1.4

PipedReader :与 PipedInputStream 对应

 

2.  Writer

 

2.1    CharArrayWrite : 与 ByteArrayOutputStream 对应

2.2   StringWrite :无与之对应的以字节为导向的 stream

2.3  FileWrite : 与 FileOutputStream 对应

2.4  PipedWrite :与 PipedOutputStream 对应

 

3、两种不同导向的 stream 之间的转换  

3.1

InputStreamReader 和 OutputStreamReader :

把一个以字节为导向的 stream 转换成一个以字符为导向的 stream 。

InputStreamReader 类是从字节流到字符流的桥梁:它读入字节,并根据指定的编码方式,将之转换为字符流。

使用的编码方式可能由名称指定,或平台可接受的缺省编码方式。

InputStreamReader 的 read() 方法之一的每次调用,可能促使从基本字节输入流中读取一个或多个字节。

为了达到更高效率,考虑用 BufferedReader 封装 InputStreamReader ,

BufferedReader in = new BufferedReader(new InputStreamReader(System.in));

例如: // 实现从键盘输入一个整数

 1        String s = null;             
 2        InputStreamReader re = new InputStreamReader(System.in);  
 3         BufferedReader br = new BufferedReader(re);  
 4         try {  
 5            s = br.readLine();  
 6            System.out.println("s= " + Integer.parseInt(s));  
 7            br.close();  
 8         }  
 9         catch (IOException e)  
10         {  
11            e.printStackTrace();  
12         }  
13         catch (NumberFormatException e)// 当应用程序试图将字符串转换成一种数值类型,但该字符串不能转换为适当格式时,抛出该异常。  
14         {  
15            System.out.println(" 输入的不是数字 ");  
16         }  

InputStreamReader(InputStream) 用缺省的字符编码方式,创建一个 InputStreamReader 。

InputStreamReader(InputStream, String) 用已命名的字符编码方式,创建一个 InputStreamReader 。

 

OutputStreamWriter 将多个字符写入到一个输出流,根据指定的字符编码将多个字符转换为字节。

每个 OutputStreamWriter 合并它自己的 CharToByteConverter, 因而是从字符流到字节流的桥梁。

 

(三)Java IO 的一般使用原则 :  

一、按数据来源(去向)分类:

1 、是文件: FileInputStream, FileOutputStream, ( 字节流 )FileReader, FileWriter( 字符 )

2 、是 byte[] : ByteArrayInputStream, ByteArrayOutputStream( 字节流 )

3 、是 Char[]: CharArrayReader, CharArrayWriter( 字符流 )

4 、是 String: StringBufferInputStream, StringBufferOuputStream ( 字节流 )StringReader, StringWriter( 字符流 )

5 、网络数据流: InputStream, OutputStream,( 字节流 ) Reader, Writer( 字符流 )

二、按是否格式化输出分:

1 、要格式化输出: PrintStream, PrintWriter

三、按是否要缓冲分:

1 、要缓冲: BufferedInputStream, BufferedOutputStream,( 字节流 ) BufferedReader, BufferedWriter( 字符流 )

四、按数据格式分:

1 、二进制格式(只要不能确定是纯文本的) : InputStream, OutputStream 及其所有带 Stream 结束的子类

2 、纯文本格式(含纯英文与汉字或其他编码方式); Reader, Writer 及其所有带 Reader, Writer 的子类

五、按输入输出分:

1 、输入: Reader, InputStream 类型的子类

2 、输出: Writer, OutputStream 类型的子类

六、特殊需要:

1 、从 Stream 到 Reader,Writer 的转换类: InputStreamReader, OutputStreamWriter

2 、对象输入输出: ObjectInputStream, ObjectOutputStream

3 、进程间通信: PipeInputStream, PipeOutputStream, PipeReader, PipeWriter

4 、合并输入: SequenceInputStream

5 、更特殊的需要: PushbackInputStream, PushbackReader, LineNumberInputStream, LineNumberReader

决定使用哪个类以及它的构造进程的一般准则如下(不考虑特殊需要):

首先,考虑最原始的数据格式是什么: 原则四

第二,是输入还是输出:原则五

第三,是否需要转换流:原则六第 1 点

第四,数据来源(去向)是什么:原则一

第五,是否要缓冲:原则三 (特别注明:一定要注意的是 readLine() 是否有定义,有什么比 read, write 更特殊的输入或输出方法)

第六,是否要格式化输出:原则二

C++音视频通讯demo源码下载

以下为视频通讯系统中的部分回调函数源码:

具体可以在

http://download.csdn.net/detail/little_rui/7969285

下载

 可实现一对一、一对多、多对多的音视频通化要求,也满足文件传输,音视频文件录制等功能需求。

  1. /**  
  2.  *  视频数据回调函数  
  3.  */  
  4. void CALLBACK VideoData_CallBack(DWORD dwUserid, LPVOID lpBuf, DWORD dwLen, BITMAPINFOHEADER bmiHeader, LPVOID lpUserValue)   
  5. {   
  6.     CBRAnyChatSDKDemoDlg*   pDemoDlg = (CBRAnyChatSDKDemoDlg*)lpUserValue;   
  7.     if(pDemoDlg)   
  8.         pDemoDlg->DrawUserVideo(dwUserid,lpBuf,dwLen,bmiHeader);   
  9. }   
  10. /**  
  11.  *  音频数据回调函数  
  12.  */  
  13. void CALLBACK AudioData_CallBack(DWORD dwUserid, LPVOID lpBuf, DWORD dwLen, WAVEFORMATEX waveFormatEx, LPVOID lpUserValue)   
  14. {   
  15.     CBRAnyChatSDKDemoDlg*   pDemoDlg = (CBRAnyChatSDKDemoDlg*)lpUserValue;   
  16.     if(pDemoDlg)   
  17.     {   
  18.         // do something …   
  19.     }   
  20. }   
  21.   
  22. /**  
  23.  *  音量更改回调函数  
  24.  */  
  25. void CALLBACK VolumeChange_CallBack(BRAC_AudioDevice device, DWORD dwCurrentVolume, LPVOID lpUserValue)   
  26. {   
  27.     CBRAnyChatSDKDemoDlg*   pDemoDlg = (CBRAnyChatSDKDemoDlg*)lpUserValue;   
  28.     if(pDemoDlg)   
  29.     {   
  30.         if(device == BRAC_AD_WAVEIN)   
  31.             pDemoDlg->OnWaveInVolumeChange(dwCurrentVolume);   
  32.         else  
  33.             pDemoDlg->OnWaveOutVolumeChange(dwCurrentVolume);   
  34.     }   
  35. }   
  36. // 透明通道数据回调函数定义   
  37. void CALLBACK TransBuffer_CallBack(DWORD dwUserid, LPBYTE lpBuf, DWORD dwLen, LPVOID lpUserValue)   
  38. {   
  39.     CBRAnyChatSDKDemoDlg*   pDemoDlg = (CBRAnyChatSDKDemoDlg*)lpUserValue;   
  40.     if(pDemoDlg)   
  41.     {   
  42.         CString strNotify;   
  43.         strNotify.Format(“TransBuffer_CallBack:dwUserid-%d, bufSize-%d”,dwUserid,dwLen);   
  44.         pDemoDlg->AppendLogString(strNotify);   
  45.     }   
  46. }   
  47.   
  48. // 透明通道数据扩展回调函数定义   
  49. void CALLBACK TransBufferEx_CallBack(DWORD dwUserid, LPBYTE lpBuf, DWORD dwLen, DWORD wParam, DWORD lParam, DWORD dwTaskId, LPVOID lpUserValue)   
  50. {   
  51.     CBRAnyChatSDKDemoDlg*   pDemoDlg = (CBRAnyChatSDKDemoDlg*)lpUserValue;   
  52.     if(pDemoDlg)   
  53.     {   
  54.         BOOL bSuccess = FALSE;   
  55.         if(dwLen > 1000)   
  56.         {   
  57.             bSuccess = lpBuf[1000] == ‘Y‘ ? TRUE : FALSE;   
  58.         }   
  59.         CString strNotify;   
  60.         strNotify.Format(“TransBufferEx_CallBack:dwUserid-%d, bufSize-%d,verify:%s”,dwUserid,dwLen,bSuccess ? “Success” : “Fail”);   
  61.         pDemoDlg->AppendLogString(strNotify);   
  62.     }      
  63. }   
  64. // 文件传输回调函数定义   
  65. void CALLBACK TransFile_CallBack(DWORD dwUserid, LPCTSTR lpFileName, LPCTSTR lpTempFilePath, DWORD dwFileLength, DWORD wParam, DWORD lParam, DWORD dwTaskId, LPVOID lpUserValue)   
  66. {   
  67.     CBRAnyChatSDKDemoDlg*   pDemoDlg = (CBRAnyChatSDKDemoDlg*)lpUserValue;   
  68.     if(pDemoDlg)   
  69.     {   
  70.         CString strNotify;   
  71.         strNotify.Format(“TransFile_CallBack:dwUserid-%d, lpFileName-%s, lpTempFilePath-%s”,dwUserid,lpFileName,lpTempFilePath);   
  72.         pDemoDlg->AppendLogString(strNotify);   
  73.     }   
  74. }   
  75.   
  76. // 录像、快照任务完成回调函数定义   
  77. void CALLBACK RecordSnapShot_CallBack(DWORD dwUserid, LPCTSTR lpFileName, DWORD dwParam, BOOL bRecordType, LPVOID lpUserValue)   
  78. {   
  79.     CBRAnyChatSDKDemoDlg*   pDemoDlg = (CBRAnyChatSDKDemoDlg*)lpUserValue;   
  80.     if(pDemoDlg)   
  81.     {   
  82.         CString strNotify;   
  83.         strNotify.Format(“%s CallBack:dwUserid-%d, FilePathName-%s”,bRecordType?”Record”:”SnapShot”,(int)dwUserid,lpFileName);   
  84.         pDemoDlg->AppendLogString(strNotify);   
  85.     }   
  86. }   
  87.   
  88. // SDK Filter 通信数据回调函数定义   
  89. void CALLBACK SDKFilterData_CallBack(LPBYTE lpBuf, DWORD dwLen, LPVOID lpUserValue)   
  90. {   
  91.     CBRAnyChatSDKDemoDlg*   pDemoDlg = (CBRAnyChatSDKDemoDlg*)lpUserValue;   
  92.     if(pDemoDlg)   
  93.     {   
  94.         CString strNotify;   
  95.         strNotify.Format(“SDK Filter CallBack:%s”,lpBuf);   
  96.         pDemoDlg->AppendLogString(strNotify);   
  97.     }   
  98. }   
  99.   
  100. /**  
  101.  *  绘制用户视频  
  102.  *  数据来源于回调函数  
  103.  *  @param dwUserid 用户ID号  
  104.  *  @param lpBuf 视频数据缓冲区  
  105.  *  @param dwLen 缓冲区大小  
  106.  *  @param bmiHeader 视频缓冲区的头信息,包含了视频的大小、颜色位数等信息  
  107.  */  
  108. void CBRAnyChatSDKDemoDlg::DrawUserVideo(DWORD dwUserid, LPVOID lpBuf, DWORD dwLen, BITMAPINFOHEADER bmiHeader)   
  109. {   
  110.     // 根据用户ID号找到合适的显示区域   
  111.     DWORD dwSite = -1;   
  112.     for(INT i=0; i<DEMO_SHOW_USER_NUM; i++)   
  113.     {   
  114.         if(m_iUserID[i] == (INT)dwUserid)   
  115.         {   
  116.             dwSite = i;   
  117.             break;   
  118.         }   
  119.     }   
  120.     if(dwSite == -1)   
  121.         return;   
  122.   
  123.     CRect dispRect = m_UserRect[dwSite];   
  124.   
  125.     //构建Bitmap     
  126.     BITMAPINFOHEADER  *lpbmi = (BITMAPINFOHEADER*)malloc(sizeof(BITMAPINFOHEADER)+dwLen);     
  127.     ZeroMemory(lpbmi,sizeof(BITMAPINFOHEADER)+dwLen);   
  128.     memcpy((LPBYTE)lpbmi,&bmiHeader,sizeof(bmiHeader));   
  129.     memcpy(((LPBYTE)lpbmi+sizeof(BITMAPINFOHEADER)),lpBuf,dwLen);     
  130.   
  131.     HDC hdc = ::GetDC(m_hWnd);   
  132.     HDC dcMem = ::CreateCompatibleDC(hdc);     
  133.     HBITMAP hBitmap = CreateDIBitmap(hdc,lpbmi,CBM_INIT,(LPBYTE)lpbmi+sizeof(BITMAPINFOHEADER),(LPBITMAPINFO)lpbmi,DIB_RGB_COLORS);     
  134.     //绘图     
  135.     HBITMAP holdbm = (HBITMAP)SelectObject(dcMem,hBitmap);     
  136.     ::SetStretchBltMode(hdc,COLORONCOLOR);   
  137.     ::StretchBlt(hdc,dispRect.left,dispRect.top,dispRect.Width(),dispRect.Height(),dcMem,0,0,bmiHeader.biWidth,bmiHeader.biHeight,SRCCOPY);   
  138.   
  139.     SelectObject(dcMem,holdbm);     
  140.     ::DeleteDC(dcMem);   
  141.     ::DeleteObject(hBitmap);   
  142.     ::ReleaseDC(m_hWnd,hdc);   
  143.     free(lpbmi);   
  144.     lpbmi = NULL;   
  145. }   
  146.   
  147. BOOL CBRAnyChatSDKDemoDlg::OnInitDialog()   
  148. {   
  149.     CDialog::OnInitDialog();   
  150.   
  151.     // IDM_ABOUTBOX must be in the system command range.   
  152.     ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);   
  153.     ASSERT(IDM_ABOUTBOX < 0xF000);   
  154.   
  155.     CMenu* pSysMenu = GetSystemMenu(FALSE);   
  156.     if (pSysMenu != NULL)   
  157.     {   
  158.         CString strAboutMenu;   
  159.         strAboutMenu.LoadString(IDS_ABOUTBOX);   
  160.         if (!strAboutMenu.IsEmpty())   
  161.         {   
  162.             pSysMenu->AppendMenu(MF_SEPARATOR);   
  163.             pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);   
  164.         }   
  165.     }   
  166.   
  167.     SetIcon(m_hIcon, TRUE);         // Set big icon   
  168.     SetIcon(m_hIcon, FALSE);        // Set small icon   
  169.        
  170.     // 初始化每个视频显示位置信息   
  171.     GetDlgItem(IDC_STATIC_USER0)->ShowWindow(SW_HIDE);   
  172.     GetDlgItem(IDC_STATIC_USER0)->GetClientRect(m_UserRect[0]);     
  173.     GetDlgItem(IDC_STATIC_USER0)->ClientToScreen(m_UserRect[0]);   
  174.     ScreenToClient(m_UserRect[0]);   
  175.   
  176.     GetDlgItem(IDC_STATIC_USER1)->ShowWindow(SW_HIDE);   
  177.     GetDlgItem(IDC_STATIC_USER1)->GetClientRect(m_UserRect[1]);     
  178.     GetDlgItem(IDC_STATIC_USER1)->ClientToScreen(m_UserRect[1]);   
  179.     ScreenToClient(m_UserRect[1]);   
  180.   
  181.     GetDlgItem(IDC_STATIC_USER2)->ShowWindow(SW_HIDE);   
  182.     GetDlgItem(IDC_STATIC_USER2)->GetClientRect(m_UserRect[2]);     
  183.     GetDlgItem(IDC_STATIC_USER2)->ClientToScreen(m_UserRect[2]);   
  184.     ScreenToClient(m_UserRect[2]);   
  185.   
  186.     GetDlgItem(IDC_STATIC_USER3)->ShowWindow(SW_HIDE);   
  187.     GetDlgItem(IDC_STATIC_USER3)->GetClientRect(m_UserRect[3]);     
  188.     GetDlgItem(IDC_STATIC_USER3)->ClientToScreen(m_UserRect[3]);   
  189.     ScreenToClient(m_UserRect[3]);   
  190.   
  191.     // 隐藏录像状态标志   
  192.     GetDlgItem(IDC_STATIC_RECORDSTATE0)->ShowWindow(SW_HIDE);   
  193.     GetDlgItem(IDC_STATIC_RECORDSTATE1)->ShowWindow(SW_HIDE);   
  194.     GetDlgItem(IDC_STATIC_RECORDSTATE2)->ShowWindow(SW_HIDE);   
  195.     GetDlgItem(IDC_STATIC_RECORDSTATE3)->ShowWindow(SW_HIDE);   
  196.        
  197.     for(INT i=0; i<DEMO_SHOW_USER_NUM; i++)   
  198.     {   
  199.         m_iUserID[i] = -1;   
  200.     }   
  201.        
  202.     m_dwTransTaskId = -1;   
  203.   
  204.     m_iPort = 8906;   
  205.     m_iRoomId = 1;   
  206.     SetDlgItemText(IDC_IPADDRESS_SERVER,”demo.anychat.cn”);   
  207.     SetDlgItemText(IDC_EDIT_USERNAME,”AnyChat5″);   
  208.     UpdateData(FALSE);   
  209.     return TRUE;  // return TRUE  unless you set the focus to a control   
  210. }   
  211.   
  212. void CBRAnyChatSDKDemoDlg::OnSysCommand(UINT nID, LPARAM lParam)   
  213. {   
  214.     if ((nID & 0xFFF0) == IDM_ABOUTBOX)   
  215.     {   
  216.         CAboutDlg dlgAbout;   
  217.         dlgAbout.DoModal();   
  218.     }   
  219.     else  
  220.     {   
  221.         CDialog::OnSysCommand(nID, lParam);   
  222.     }   
  223. }   
  224.   
  225. // If you add a minimize button to your dialog, you will need the code below   
  226. //  to draw the icon.  For MFC applications using the document/view model,   
  227. //  this is automatically done for you by the framework.  

 

ant build.xml 打包应三方jar注意的问题与混淆R的写法

老规矩我们还是来看看说在前面的话:首先我们得分清楚android在打包成apk的过程中要经过哪几个步骤:

Android编译的具体流程如下:

1)  ndk-build编译native代码生成so文件

2)  aapt命令根据res资源文件生成R.java

3)  aidl命令解析.aidl文件生成对应java文件

4)  javac命令编译java文件为class文件

5)  dx命令将编译好的class文件打包为dex文件

6)  aapt命令根据res资源文件生成resources.ap_资源索引文件

7)  apkbuilder将resources.ap_与dex打包成未签名apk文件

8)  jarsinger签名apk文件

9)  zipalign工具优化apk读取速度

这里我想重点说的是javac这个阶段和aapt打包成res文件混淆的问题。

这里我们看看假如我们的工程里面如果应用到了第三方的jar应该怎么写,我们首先需要把我们的jar包复制到我们的libs下面然后修改一下编译这里的脚本,假如这个外部工程还有自己的R文件和res资源我们在写脚本的时候需要注意这几个问题.

1:编译成R文件的时候记得一起打包这个外部依赖项目的R文件。

2:编译class文件的时候记得加入lib依赖

3:打包res文件的时候记得外部文件的res也要添加

4:如果项目中有混淆记得不要混淆外部依赖文件的R文件,不然找不到外部的R

这里我来举个例子吧,我这里例如我用到了,PullrefreshListview这个外部依赖工程,需要注意的是这几个步骤,我贴出脚本吧。先看看编译R文件的时候的脚本。

<!-- 为该项目资源生成R.java文件 -->
	<target name="gen" depends="init">
		<echo>从资源文件生成R.java ...</echo>
		<exec executable="${aapt}" failonerror="true">
			<arg value="package" />
			<span style="color:#ff0000;"><arg value="--auto-add-overlay"></arg></span>
			<arg value="-m" />
			<arg value="-J" />
			<arg value="${gen}" />
			<arg value="-M" />
			<arg value="AndroidManifest.xml" />
			<arg value="-S" />
			<arg value="res" />
			<span style="color:#ff0000;"><arg value="-S" />
			<arg value="C:\Users\edsheng\Desktop\Android-PullToRefresh-master\library\res" />
			<arg value="--extra-packages" /> 
			<arg value="com.handmark.pulltorefresh.library"/></span>
			<arg value="-I" />
			<arg value="${androidjar}" />
		</exec>
		<echo>R.java文件生成成功</echo>
	</target>

看到我红色的部分了吗?这些就是外部依赖工程的时候需要添加的脚本是把我们外部依赖的res文件拿去生成R,文件。

然后需要注意的第二部分,编译class文件记得加入jar的依赖,我们要把用到的jar先复制到lib里面,由于Pullrefreshlistview这个工程是个lib工程,我们直接拷贝lib到我们的主工程的libs文件目录下边然后看看javac编译class文件的脚本。

<target name="compile" depends="aidl">
		<echo>开始编译.class文件...</echo>
		<javac fork="true" executable="${javac}" encoding="${encoding}" debug="true" extdirs="" source="1.5" target="1.5" destdir="${classes}"  bootclasspath="${androidjar}">
            <src path="${src}" />
            <src path="${gen}" />
      <span style="color:#ff0000;">      <classpath>
                <fileset dir="${root}/libs">
                    <include name="*.jar" />
                </fileset>
                </classpath></span>
        </javac>
		<echo>.class文件编译完成</echo>
	</target>

也是要理解这个编译过程要找libs下面的所有jar,同理然后我们来看看混淆代码的时候要注意的东西,一个就是lib文件一个就是混淆的时候lib的资源R文件不要去混淆了,这里我也贴出自己的混淆的脚本。

<target name="obfuscate" depends="compile">

        <echo>对打包的结果进行混淆...</echo>
        <java jar="${proguard_home}/lib/proguard.jar" fork="true" failonerror="true">

            <jvmarg value="-Dmaximum.inlined.code.length=32" />

            <arg value="-injars ${classes}" />

            <arg value="-outjars obfuscated.jar" />

            <arg value="-libraryjars ${androidjar}" />
 			
 	<span style="color:#ff0000;">    <arg value="-libraryjars C:\Users\edsheng\Desktop\wecheckscore\libs\library.jar" />
            <!-- 如果使用到外部库,请在这里指定 -->
               
			<arg value="@proguard.cfg" />
 			<arg value="-dump ${out}/dump.txt"/>
			<arg value="-printusage ${out}/usage.txt"/>
			<arg value="-printmapping ${out}/mapping.txt"/>
			<arg value="-printseeds ${out}/seeds.txt"/></span>

	 </java>
           <delete dir="${classes}"/>
           <mkdir dir="${classes}"/>
           <unzip src="obfuscated.jar" dest="${classes}"/>
            <delete file="obfuscated.jar"/> 
	 <echo>代码混淆结束</echo>
	 </target>

需要注意的地方也是我红色的地方,指定外部混淆的jar,还有的一个是proguard.cfg这个文件,就是我们配置的文件我们还是打开这个文件看看,该怎么配置呢?

#-optimizationpasses 7
#-optimizations !code/simplification/arithmetic,!field/*,!class/merging/*
-dontoptimize
-verbose
-dontskipnonpubliclibraryclasses
-dontskipnonpubliclibraryclassmembers 
#-overloadaggressively

<span style="color:#ff0000;">-keep public class  com.handmark.pulltorefresh.library.R*{*;}</span>

#------------------  下方是android平台自带的排除项,这里不要动         ----------------
-keep public class * extends android.app.Activity{
	public <fields>;
	public <methods>;
}
-keep public class * extends android.app.Application
{
	public <fields>;
	public <methods>;
}

-keep public class * extends android.app.Service
-keep public class * extends android.content.BroadcastReceiver
-keep public class * extends android.content.ContentProvider
-keep public class * extends android.app.backup.BackupAgentHelper
-keep public class * extends android.preference.Preference

-keepclassmembers enum * {
    public static **[] values();
    public static ** valueOf(java.lang.String);
}

-keepclasseswithmembers class * {
	public <init>(android.content.Context, android.util.AttributeSet);
}

-keepclasseswithmembers class * {
	public <init>(android.content.Context, android.util.AttributeSet, int);
}

-keepattributes *Annotation*

# 改成这样,因为发现部分SDK中没有被java代码显示调用过,SO文件中调用的native方法会被混淆掉

-keepclasseswithmembers class * {
	native <methods>;
}

-keep class * implements android.os.Parcelable {
  public static final android.os.Parcelable$Creator *;
}

#------------------  下方是共性的排除项目         ----------------
# 方法名中含有“JNI”字符的,认定是Java Native Interface方法,自动排除
# 方法名中含有“JRI”字符的,认定是Java Reflection Interface方法,自动排除

-keepclasseswithmembers class * {
    ... *JNI*(...);
}

-keepclasseswithmembernames class * {
	... *JRI*(...);
}

-keep class **JNI* {*;}

需要注意的也是红色的部分,提示了不要混淆lib的R文件,最后就是我们的res文件资源的打包了,然后我们也来看看我们的脚本应该是怎么写的吧。

!-- 打包项目的资源文件 -->
	<target name="package_res_with_assets">
		<echo>打包资源和资产文件...</echo>
		<exec executable="${aapt}" failonerror="true">
			<arg value="package" />
			<span style="color:#ff0000;">  <arg value="--auto-add-overlay"></arg></span>
			<arg value="-f" />
			<arg value="-M" />
			<arg value="AndroidManifest.xml" />
			<arg value="-S" />
			<arg value="res" />
			<span style="color:#ff0000;"><arg value="-S" />
			<arg value="C:\Users\edsheng\Desktop\Android-PullToRefresh-master\library\res" />
			 <arg value="--extra-packages" /> 
			 <arg value="com.handmark.pulltorefresh.library"/></span>
			<arg value="-A" />
			<arg value="assets" />
			<arg value="-I" />
			<arg value="${androidjar}" />
			<arg value="-F" />
			<arg value="${out}/${file_name}.ap_" />
		</exec>
		<echo>打包资源和资产文件完成</echo>
	</target>

还是红色的地方也是指定了外部的资源和lib这样外部工程的资源也被打包进来了。这样就可以不管是外部依赖一个还是多个都可以搞定了。那么大家就可以愉快的buld了。

MFC键盘长按执行多次与执行一次

先说明一下你按下一个键盘不松开的时候发生了什么:产生了多个按下的消息,切消息的内容都一样,是你按下的那个键。

实现长按的时候执行多次:比如你长按“A”按钮,可以让界面上的某个控件的数值一直增大,那么你只要在实现这个按键的响应事件里增大一次就可以了,只要你长按,自动会多次调用你的这个函数。

实现长按的时候执行一次:这个按照网上的说法,可以添加一个静态变量,记录上次按了哪个键。主要的代码如下

(1)给类添加一个静态变量

private:
static WPARAM last_key;

(2)在源文件中初始化变量的值为一个非键盘的值以供第一次按键的时候响应

WPARAM CRobotClientDlg::last_key = -1;

(3)在响应按时按钮按下的时候判断是否第一次按下

if (WM_KEYDOWN == pMsg->message)

if(  pMsg->wParam == VK_LEFT && pMsg->wParam != last_key)

{

last_key = pMsg->wParam;

//doSomeThing

}

//松开控制键
if (WM_KEYUP == pMsg->message)

if( m_use_keyboard_contrl == true && pMsg->wParam == VK_LEFT )
{
last_key = -1;

//doSomeThing

}

注意,这里有几个BUG你可能会写到

(1)如果在松开按钮的时候没有回复last_key标记,那么你的要控制的键盘连续按多次的时候只能执行一次,因为这其间没有其他按键会改变last_key的值

(2)如果last_key设置为函数的静态变量,其实应该也可以,因为函数的静态变量只初始化一次,后面那句话就不执行了。

python基础 – global关键字及全局变量的用法

python中global关键字主要作用是声明变量的作用域。
int a = 5; void test(void) { a = 1; // 没有先声明,所以用的是全局的变量a } void test1(void) { int a; a = 2; // 前面声明了,所以用的是局部变量a,对其所做的修改不会影响全局变量a } void main(void) { printf(“before: a = %d\n”, a); test(); printf(“after test: a = %d\n”, a); test1(); printf(“after test1: a = %d\n”, a); }

在python中,变量不需要先声明,直接使用即可,那我们怎么知道用的是局部变量还是全局变量呢? 首先:python使用的变量,在默认情况下一定是用局部变量。 其次:python如果想使用作用域之外的全局变量,则需要加global前缀。 举例说明,不用global的情况:

a = 5 
def test():
    a = 1
    print 'In test func: a = %d' % a
test()
print 'Global a = %d' % a

程序执行结果为:

In test func: a = 1
Global a = 5

可以看出,不加global的时候,在函数内部是改不了外面的全局变量的(list类型例外)。

下面是使用global前缀的情况:

a = 5

def test():
    global a
 #此处声明,告诉执行引擎:我要用全局变量a,不要整成局部的了!
    a = 1
    print 'In test func: a = %d' % a

test()
print 'Global a = %d' % a

执行结果:

In test func: a = 1
Global a = 1

可以看出,在函数内部成功的修改了全局变量的数值。

事实上,网络上很多文章推崇另外的一种方法来使用全局变量:使用单独的global文件。

方法如下: 1. 在同一个文件夹下,新建2个文件: myglobal.py test.py 2. myglobal.py中放置全局变量,内容示例如下:

a = 0
b = 1
c = 2
d = 3

3. test.py中是测试代码,其中可以使用全局变量

import myglobal

def test():
    myglobal.a = 100

print 'myglobal a = %d' % myglobal.a
test()
print 'after test, myglobal a = %d' % myglobal.a

执行test.py的结果如下:

myglobal a = 0
after test, myglobal a = 100

OK,同样成功修改了全局变量(这个说法不准确,但姑且就这么叫吧)。

在实际使用中,两种方法各有优势,通常我们大多数时候只是用python写小功能的脚本,此时用global关键字就够了。 如果写比较大的功能应用时,用后一种方法可以使得全局变量的管理更为方便。