2008年5月30日星期五

【转帖】五种提高SQL Server性能的方法

FROM: http://searchdatebase.techtarget.co...7/1923316.shtml
有时,为了让应用程序运行得更快,所做的全部工作就是在这里或那里做一些很小调整。啊,但关键在于确定如何进行调整!迟早您会遇到这种情况:应用程序中的 SQL 查询不能按照您想要的方式进行响应。它要么不返回数据,要么耗费的时间长得出奇。如果它降低了报告或您的企业应用程序的速度,用户必须等待的时间过长,他们就会很不满意。就像您的父母不想听您解释为什么在深更半夜才回来一样,用户也不会听你解释为什么查询 耗费这么长时间。(“对不起,妈妈,我使用了太多的 LEFT JOIN。”)用户希望应用程序响应迅速,他们的报告能够在瞬间之内返回分析数据。就我自己而言,如果在 Web 上冲浪时某个页面要耗费十多秒才能加载(好吧,五秒更实际一些),我也会很不耐烦。
为了解决这些问题,重要的是找到问题的根源。那么,从哪里开始呢?根本原因通常在于数据库设计和访问它的查询。在本月的专栏中,我将讲述四项技术,这些技术可用于提高基于 SQL Server? 的应用程序的性能或改善其可伸缩性。我将仔细说明 LEFT JOIN、CROSS JOIN 的使用以及 IDENTITY 值的检索。请记住,根本没有神奇的解决方案。调整您的数据库及其查询需要占用时间、进行分析,还需要大量的测试。这些技术都已被证明行之有效,但对您的应用程序而言,可能其中一些技术比另一些技术更适用。
从 INSERT 返回 IDENTITY
我决定从遇到许多问题的内容入手:如何在执行 SQL INSERT 后检索 IDENTITY 值。通常,问题不在于如何编写检索值的查询,而在于在哪里以及何时进行检索。在 SQL Server 中,下面的语句可用于检索由最新在活动数据库连接上运行的 SQL 语句所创建的 IDENTITY 值:
SELECT @@IDENTITY
这个 SQL 语句并不复杂,但需要记住的一点是:如果这个最新的 SQL 语句不是 INSERT,或者您针对非 INSERT SQL 的其他连接运行了此 SQL,则不会获得期望的值。您必须运行下列代码才能检索紧跟在 INSERT SQL 之后且位于同一连接上的 IDENTITY,如下所示:
INSERT INTO Products (ProductName) VALUES ('Chalk')
SELECT @@IDENTITY
在一个连接上针对 Northwind 数据库运行这些查询将返回一个名称为 Chalk 的新产品的 IDENTITY 值。所以,在使用 ADO 的 Visual Basic? 应用程序中,可以运行以下语句:
Set oRs = oCn.Execute("SET NOCOUNT ON;INSERT INTO Products _
(ProductName) VALUES ('Chalk');SELECT @@IDENTITY")
lProductID = oRs(0)
  此代码告诉 SQL Server 不要返回查询的行计数,然后执行 INSERT 语句,并返回刚刚为这个新行创建的 IDENTITY 值。SET NOCOUNT ON 语句表示返回的记录集有一行和一列,其中包含了这个新的 IDENTITY 值。如果没有此语句,则会首先返回一个空的记录集(因为 INSERT 语句不返回任何数据),然后会返回第二个记录集,第二个记录集中包含 IDENTITY 值。这可能有些令人困惑,尤其是因为您从来就没有希望过 INSERT 会返回记录集。之所以会发生此情况,是因为 SQL Server 看到了这个行计数(即一行受到影响)并将其解释为表示一个记录集。因此,真正的数据被推回到了第二个记录集。当然您可以使用 ADO 中的 NextRecordset 方法获取此第二个记录集,但如果总能够首先返回该记录集且只返回该记录集,则会更方便,也更有效率。
此方法虽然有效,但需要在 SQL 语句中额外添加一些代码。获得相同结果的另一方法是在 INSERT 之前使用 SET NOCOUNT ON 语句,并将 SELECT @@IDENTITY 语句放在表中的 FOR INSERT 触发器中,如下面的代码片段所示。这样,任何进入该表的 INSERT 语句都将自动返回 IDENTITY 值。
CREATE TRIGGER trProducts_Insert ON Products FOR INSERT AS
SELECT @@IDENTITY
GO
触发器只在 Products 表上发生 INSERT 时启动,所以它总是会在成功 INSERT 之后返回一个 IDENTITY。使用此技术,您可以始终以相同的方式在应用程序中检索 IDENTITY 值。
内嵌视图与临时表
某些时候,查询需要将数据与其他一些可能只能通过执行 GROUP BY 然后执行标准查询才能收集的数据进行联接。例如,如果要查询最新五个定单的有关信息,您首先需要知道是哪些定单。这可以使用返回定单 ID 的 SQL 查询来检索。此数据就会存储在临时表(这是一个常用技术)中,然后与 Products 表进行联接,以返回这些定单售出的产品数量:
CREATE TABLE #Temp1 (OrderID INT NOT NULL, _
OrderDate DATETIME NOT NULL)
INSERT INTO #Temp1 (OrderID, OrderDate)
SELECT TOP 5 o.OrderID, o.OrderDate
FROM Orders o ORDER BY o.OrderDate DESC
SELECT p.ProductName, SUM(od.Quantity) AS ProductQuantity
FROM #Temp1 t
INNER JOIN [Order Details] od ON t.OrderID = od.OrderID
INNER JOIN Products p ON od.ProductID = p.ProductID
GROUP BY p.ProductName
ORDER BY p.ProductName
DROP TABLE #Temp1
这些 SQL 语句会创建一个临时表,将数据插入该表中,将其他数据与该表进行联接,然后除去该临时表。这会导致此查询进行大量 I/O 操作,因此,可以重新编写查询,使用内嵌视图取代临时表。内嵌视图只是一个可以联接到 FROM 子句中的查询。所以,您不用在 tempdb 中的临时表上耗费大量 I/O 和磁盘访问,而可以使用内嵌视图得到同样的结果:
SELECT p.ProductName,
SUM(od.Quantity) AS ProductQuantity
FROM (
SELECT TOP 5 o.OrderID, o.OrderDate
FROM Orders o
ORDER BY o.OrderDate DESC
) t
INNER JOIN [Order Details] od ON t.OrderID = od.OrderID
INNER JOIN Products p ON od.ProductID = p.ProductID
GROUP BY
p.ProductName
ORDER BY
p.ProductName
此查询不仅比前面的查询效率更高,而且长度更短。临时表会消耗大量资源。如果只需要将数据联接到其他查询,则可以试试使用内嵌视图,以节省资源。
避免 LEFT JOIN 和 NULL
当然,有很多时候您需要执行 LEFT JOIN 和使用 NULL 值。但是,它们并不适用于所有情况。改变 SQL 查询的构建方式可能会产生将一个花几分钟运行的报告缩短到只花几秒钟这样的天壤之别的效果。有时,必须在查询中调整数据的形态,使之适应应用程序所要求的显示方式。虽然 TABLE 数据类型会减少大量占用资源的情况,但在查询中还有许多区域可以进行优化。SQL 的一个有价值的常用功能是 LEFT JOIN。它可以用于检索第一个表中的所有行、第二个表中所有匹配的行、以及第二个表中与第一个表不匹配的所有行。例如,如果希望返回每个客户及其定单,使用 LEFT JOIN 则可以显示有定单和没有定单的客户。
此工具可能会被过度使用。LEFT JOIN 消耗的资源非常之多,因为它们包含与 NULL(不存在)数据匹配的数据。在某些情况下,这是不可避免的,但是代价可能非常高。LEFT JOIN 比 INNER JOIN 消耗资源更多,所以如果您可以重新编写查询以使得该查询不使用任何 LEFT JOIN,则会得到非常可观的回报。
加快使用 LEFT JOIN 的查询速度的一项技术涉及创建一个 TABLE 数据类型,插入第一个表(LEFT JOIN 左侧的表)中的所有行,然后使用第二个表中的值更新 TABLE 数据类型。此技术是一个两步的过程,但与标准的 LEFT JOIN 相比,可以节省大量时间。一个很好的规则是尝试各种不同的技术并记录每种技术所需的时间,直到获得用于您的应用程序的执行性能最佳的查询。
测试查询的速度时,有必要多次运行此查询,然后取一个平均值。因为查询(或存储过程)可能会存储在 SQL Server 内存中的过程缓存中,因此第一次尝试耗费的时间好像稍长一些,而所有后续尝试耗费的时间都较短。另外,运行您的查询时,可能正在针对相同的表运行其他查询。当其他查询锁定和解锁这些表时,可能会导致您的查询要排队等待。例如,如果您进行查询时某人正在更新 此表中的数据,则在更新提交时您的查询可能需要耗费更长时间来执行。
避免使用 LEFT JOIN 时速度降低的最简单方法是尽可能多地围绕它们设计数据库。例如,假设某一产品可能具有类别也可能没有类别。如果 Products 表存储了其类别的 ID,而没有用于某个特定产品的类别,则您可以在字段中存储 NULL 值。然后您必须执行 LEFT JOIN 来获取所有产品及其类别。您可以创建一个值为“No Category”的类别,从而指定外键关系不允许 NULL 值。通过执行上述操作,现在您就可以使用 INNER JOIN 检索所有产品及其类别了。虽然这看起来好像是一个带有多余数据的变通方法,但可能是一个很有价值的技术,因为它可以消除 SQL 批处理语句中消耗资源较多的 LEFT JOIN。在数据库中全部使用此概念可以为您节省大量的处理时间。请记住,对于您的用户而言,即使几秒钟的时间也非常重要,因为当您有许多用户正在访问同一个联机数据库应用程序时,这几秒钟实际上的意义会非常重大。
灵活使用笛卡尔乘积
对于此技巧,我将进行非常详细的介绍,并提倡在某些情况下使用笛卡尔乘积。出于某些原因,笛卡尔乘积 (CROSS JOIN) 遭到了很多谴责,开发人员通常会被警告根本就不要使用它们。在许多情况下,它们消耗的资源太多,从而无法高效使用。但是像 SQL 中的任何工具一样,如果正确使用,它们也会很有价值。例如,如果您想运行一个返回每月数据(即使某一特定月份客户没有定单也要返回)的查询,您就可以很方便地使用笛卡尔乘积。
虽然这看起来好像没什么神奇的,但是请考虑一下,如果您从客户到定单(这些定单按月份进行分组并对销售额进行小计)进行了标准的 INNER JOIN,则只会获得客户有定单的月份。因此,对于客户未订购任何产品的月份,您不会获得 0 值。如果您想为每个客户都绘制一个图,以显示每个月和该月销售额,则可能希望此图包括月销售额为 0 的月份,以便直观标识出这些月份。如果使用 Figure 2(最后一页) 中的 SQL,数据则会跳过销售额为 0 美元的月份,因为在定单表中对于零销售额不会包含任何行(假设您只存储发生的事件)。
Figure 3(最后一页)中的代码虽然较长,但是可以达到获取所有销售数据(甚至包括没有销售额的月份)的目标。首先,它会提取去年所有月份的列表,然后将它们放入第一个 TABLE 数据类型表 (@tblMonths) 中。下一步,此代码会获取在该时间段内有销售额的所有客户公司的名称列表,然后将它们放入另一个 TABLE 数据类型表 (@tblCus-tomers) 中。这两个表存储了创建结果集所必需的所有基本数据,但实际销售数量除外。 第一个表中列出了所有月份(12 行),第二个表中列出了这个时间段内有销售额的所有客户(对于我是 81 个)。并非每个客户在过去 12 个月中的每个月都购买了产品,所以,执行 INNER JOIN 或 LEFT JOIN 不会返回每个月的每个客户。这些操作只会返回购买产品的客户和月份。
笛卡尔乘积则可以返回所有月份的所有客户。笛卡尔乘积基本上是将第一个表与第二个表相乘,生成一个行集合,其中包含第一个表中的行数与第二个表中的行数相乘的结果。因此,笛卡尔乘积会向表 @tblFinal 返回 972 行。最后的步骤是使用此日期范围内每个客户的月销售额总计更新 @tblFinal 表,以及选择最终的行集。
如果由于笛卡尔乘积占用的资源可能会很多,而不需要真正的笛卡尔乘积,则可以谨慎地使用 CROSS JOIN。例如,如果对产品和类别执行了 CROSS JOIN,然后使用 WHERE 子句、DISTINCT 或 GROUP BY 来筛选出大多数行,那么使用 INNER JOIN 会获得同样的结果,而且效率高得多。如果需要为所有的可能性都返回数据(例如在您希望使用每月销售日期填充一个图表时),则笛卡尔乘积可能会非常有帮助。但是,您不应该将它们用于其他用途,因为在大多数方案中 INNER JOIN 的效率要高得多。
拾遗补零
这里介绍其他一些可帮助提高 SQL 查询效率的常用技术。假设您将按区域对所有销售人员进行分组并将他们的销售额进行小计,但是您只想要那些数据库中标记为处于活动状态的销售人员。您可以按区域对销售人员分组,并使用 HAVING 子句消除那些未处于活动状态的销售人员,也可以在 WHERE 子句中执行此操作。在 WHERE 子句中执行此操作会减少需要分组的行数,所以比在 HAVING 子句中执行此操作效率更高。HAVING 子句中基于行的条件的筛选会强制查询对那些在 WHERE 子句中会被去除的数据进行分组。
另一个提高效率的技巧是使用 DISTINCT 关键字查找数据行的单独报表,来代替使用 GROUP BY 子句。在这种情况下,使用 DISTINCT 关键字的 SQL 效率更高。请在需要计算聚合函数(SUM、COUNT、MAX 等)的情况下再使用 GROUP BY。另外,如果您的查询总是自己返回一个唯一的行,则不要使用 DISTINCT 关键字。在这种情况下,DISTINCT 关键字只会增加系统开销。
您已经看到了,有大量技术都可用于优化查询和实现特定的业务规则,技巧就是进行一些尝试,然后比较它们的性能。最重要的是要测试、测试、再测试。在此专栏的将来各期内容中,我将继续深入讲述 SQL Server 概念,包括数据库设计、好的索引实践以及 SQL Server 安全范例。
Figure 2 Returning All Customers and Their Sales
set nocount on
DECLARE @dtStartDate DATETIME,
@dtEndDate DATETIME,
@dtDate DATETIME
SET @dtEndDate = '5/5/1997'
SET @dtEndDate = DATEADD(DD, -1, CAST(CAST((MONTH(@dtEndDate) + 1)
AS VARCHAR(2)) + '/01/' + CAST(YEAR(@dtEndDate) AS VARCHAR(4)) + '
23:59:59' AS DATETIME))
SET @dtStartDate = DATEADD(MM, -1 * 12, @dtEndDate)
SELECT CAST(YEAR(o.OrderDate) AS VARCHAR(4)) + '-' +
CASE
WHEN MONTH(o.OrderDate) < 10
THEN '0' + CAST(MONTH(o.OrderDate) AS VARCHAR(2))
ELSE CAST(MONTH(o.OrderDate) AS VARCHAR(2))
END AS sMonth,
c.CustomerID,
c.CompanyName,
c.ContactName,
SUM(od.Quantity * od.UnitPrice) AS mSales
FROM Customers c
INNER JOIN Orders o ON c.CustomerID = o.CustomerID
INNER JOIN [Order Details] od ON o.OrderID = od.OrderID
WHERE o.OrderDate BETWEEN @dtStartDate AND @dtEndDate
GROUP BY
CAST(YEAR(o.OrderDate) AS VARCHAR(4)) + '-' +
CASE
WHEN MONTH(o.OrderDate) < 10
THEN '0' + CAST(MONTH(o.OrderDate) AS VARCHAR(2))
ELSE CAST(MONTH(o.OrderDate) AS VARCHAR(2))
END,
c.CustomerID,
c.CompanyName,
c.ContactName
ORDER BY
c.CompanyName,
sMonth
___________________________________________________________________________
Figure 3 Cartesian Product at Work
DECLARE @tblMonths TABLE (sMonth VARCHAR(7))
DECLARE @tblCustomers TABLE ( CustomerID CHAR(10),
CompanyName VARCHAR(50),
ContactName VARCHAR(50))
DECLARE @tblFinal TABLE ( sMonth VARCHAR(7),
CustomerID CHAR(10),
CompanyName VARCHAR(50),
ContactName VARCHAR(50),
mSales MONEY)
DECLARE @dtStartDate DATETIME,
@dtEndDate DATETIME,
@dtDate DATETIME,
@i INTEGER
SET @dtEndDate = '5/5/1997'
SET @dtEndDate = DATEADD(DD, -1, CAST(CAST((MONTH(@dtEndDate) + 1) AS
VARCHAR(2)) + '/01/' + CAST(YEAR(@dtEndDate) AS VARCHAR(4)) + '
23:59:59' AS DATETIME))
SET @dtStartDate = DATEADD(MM, -1 * 12, @dtEndDate)
— Get all months into the first table
SET @i = 0
WHILE (@i < 12)
BEGIN
SET @dtDate = DATEADD(mm, -1 * @i, @dtEndDate)
INSERT INTO @tblMonths SELECT CAST(YEAR(@dtDate) AS VARCHAR(4)) + '-' +
CASE
WHEN MONTH(@dtDate) < 10
THEN '0' + CAST(MONTH(@dtDate) AS VARCHAR(2))
ELSE CAST(MONTH(@dtDate) AS VARCHAR(2))
END AS sMonth
SET @i = @i + 1
END
— Get all clients who had sales during that period into the "y" table
INSERT INTO @tblCustomers
SELECT DISTINCT
c.CustomerID,
c.CompanyName,
c.ContactName
FROM Customers c
INNER JOIN Orders o ON c.CustomerID = o.CustomerID
WHERE o.OrderDate BETWEEN @dtStartDate AND @dtEndDate
INSERT INTO @tblFinal
SELECT m.sMonth,
c.CustomerID,
c.CompanyName,
c.ContactName,
0
FROM @tblMonths m CROSS JOIN @tblCustomers c
UPDATE @tblFinal SET
mSales = mydata.mSales
FROM @tblFinal f INNER JOIN
(
SELECT c.CustomerID,
CAST(YEAR(o.OrderDate) AS VARCHAR(4)) + '-' +
CASE WHEN MONTH(o.OrderDate) < 10
THEN '0' + CAST(MONTH(o.OrderDate) AS VARCHAR(2))
ELSE CAST(MONTH(o.OrderDate) AS VARCHAR(2))
END AS sMonth,
SUM(od.Quantity * od.UnitPrice) AS mSales
FROM Customers c
INNER JOIN Orders o ON c.CustomerID = o.CustomerID
INNER JOIN [Order Details] od ON o.OrderID = od.OrderID
WHERE o.OrderDate BETWEEN @dtStartDate AND @dtEndDate
GROUP BY
c.CustomerID,
CAST(YEAR(o.OrderDate) AS VARCHAR(4)) + '-' +
CASE WHEN MONTH(o.OrderDate) < 10
THEN '0' + CAST(MONTH(o.OrderDate) AS VARCHAR(2))
ELSE CAST(MONTH(o.OrderDate) AS VARCHAR(2))
END
) mydata on f.CustomerID = mydata.CustomerID AND f.sMonth =
mydata.sMonth
SELECT f.sMonth,
f.CustomerID,
f.CompanyName,
f.ContactName,
f.mSales
FROM @tblFinal f
ORDER BY
f.CompanyName,
f.sMonth

2008年5月29日星期四

转:致工作者的一封信(阅读后必有收获)

     一天,一只兔子在山洞前写文章,一只狼走了过来, 问:“兔子啊,你在干什么?”
  答曰:“写文章。”
  问:“什么题目?”
  答曰:“《浅谈兔子是怎样吃掉狼的》。”
  狼哈哈大笑,表示不信,于是兔子把狼领进山洞。
  过了一会,兔子独自走出山洞,继续写文章。
  一只野猪走了过来,问:“兔子你在写什么?”
  答:“文 章。”
  问:“题目是什么?”
  答:“《浅谈兔子是如何把野猪吃掉的》。”
  野猪不信,于是同样的事情发生。
  最后,在山洞里,一只狮子在一堆白骨之间,满意的剔着牙读着兔子交给它的文章, 题目:“《一只动物,能力大小关键要看你的老板是谁》。”
  这只兔子有次不小心告诉了他的一个兔子朋友,这消息逐渐在森林中传播; 狮子知道后非常生气,他告诉兔子:“如果这个星期没有食物进洞,我就吃你。”
  于是兔子继续在洞口写文章
  一只小鹿走过来,
  “兔子,你在干什么啊?”
  “写文章”
  “什么题目”
  “《浅谈兔子是怎样吃掉狼的》”
  “哈哈,这个事情全森林都知道啊,你别胡弄我了,
  我是不会进洞的”
  “我马上要退休了,狮子说要找个人顶替我,难道你不想这篇文章的兔子变成小鹿么”
  小鹿想了想,终于忍不住诱惑,跟随兔子走进洞里。
  过了一会,兔子独自走出山洞,继续写文章
  一只小马走过来,同样是事情发生了。
  最后,在山洞里,一只狮子在一堆白骨之间,满意的剔着牙读着兔子交给它的文章
  题目是:《如何发展下线动物为老板提供食物》
  随着时间的推移,狮子越长越大,兔子的食物已远远不能填饱肚子。
  一日,他告诉兔子:“我的食物量要加倍,
  例如:原来4天一只小鹿,现在要2天一 只,如果一周之内改变不了局面 我就吃你。
  于是,兔子离开洞口,跑进森林深处,他见到一只狼
  “你相信兔子能轻松吃掉狼吗”
  狼哈哈大笑,表示不信,于是兔子把狼领进山洞。
  过了一会,兔子独自走出山洞,继续进入森林深处
  这回他碰到一只野猪----“你相信兔子能轻松吃掉野猪吗”
  野猪不信,于是同样的事情发生了。
  原来森林深处的动物并不知道兔子和狮子的故事
  最后,在山洞里,一只狮子在一堆白骨之间,满意的剔着牙读着兔子交给它的文章
  题目是:《如何实现由坐商到行商的转型为老板提供更多的食物》
  时间飞快,转眼之间,兔子在森林里的名气越来越大
  因为大家都知道它有一个很历害的老板
  这只小兔开始横行霸道,欺上欺下,没有动物敢惹
  它时时想起和乌龟赛跑的羞辱
  它找到乌龟说:“三天之内,见我老板!”扬长而去
  乌龟难过的哭了
  这时却碰到了一位猎人
  乌龟把这事告诉了他
  猎人哈哈大笑
  于是森林里发生了一件重大事情
  猎人披着狮子皮和乌龟一起在吃兔子火锅
  地下丢了半张纸片歪歪扭扭的写着:山外青山楼外楼,强中还有强中手啊!!
  在很长一段时间里森林里恢复了往日的宁静,兔子吃狼的故事似乎快要被大家忘记了
  不过一只年轻的老虎在听说了这个故事后,被激发了灵感
  于是他抓住了一只羚羊,对羚羊说,如果你可以象以前的兔子那样为我带来食物那我
  就不吃你。
  于是,羚羊无奈的答应了老虎,而老虎也悠然自得的进了山洞。
  可是三天过去了,也没有见羚羊领一只动物进洞。他实在憋不住了,想出来看看情
  况。
  羚羊早已不在了,他异常愤怒。正在他暴跳如雷的时候突然发现了羚羊写的一篇文章
  题目是:《想要做好老板先要懂得怎样留住员工》

【转帖】老外对T-sql的研究:一个问题多种方法.太精彩了!

原文地址:
http://www.winmag.com.cn/forum/itemdisplay.asp?boardid=11&id=503269
感谢这篇文章的作者带我们如此棒的文章
T-SQL允许你使用不同的方法解决一个问题.有的时候,尽管选择不是那么明显,但是却可以让你得到令人满意的和快乐的惊奇.下边让我们解读Dr. Tom Moreau对同一问题不同的可能性的探索.可能我们可以在那些不同的方法之中发现一些珍贵的东西.
让我们以我们的老朋友Northwind数据库为例,这里我们用到的是[order details]表,这个表是一个定单的明细表,和order表是多对一的关系.也就是一个定单对应多个订购的产品.假设你想得到每个定单订购的总价值,但是不包括59号产品.Listing 1给了我们第一种解法:
select
OrderID,sum (Quantity * UnitPrice) value
from
[Order Details] o1
where
ProductID <> 59
group by
OrderID
上边的语句很简单,它排除掉了59号产品的定单明细条目,然后进行分组统计.但是如果我们需要忽略掉订购59号产品的定单呢?也就是说我们要统计没有包含59号产品的定单的价值.你想到了WHERE, NOT EXIST(S)关键词了吗?Listing 2给了我们第二种方法:
select
o1.OrderID,sum (o1.Quantity * o1.UnitPrice) value
from
[Order Details] o1
where not exists
(
select
*
from
[Order Details] o2
where
o2.OrderID = o1.OrderID
and o2.ProductID = 59
)
group by
o1.OrderID
如果你不喜欢用exist的话,你可以转化成使用not in:
Listing 3
select
o1.OrderID,sum (o1.Quantity * o1.UnitPrice) value
from
[Order Details] o1
where 59 not in
(
select
ProductID
from
[Order Details] o2
where
o2.OrderID = o1.OrderID
)
group by
o1.OrderID
尽管Listing 1不满足我们现在的查询条件.但是从性能发面考虑,Listing 1还是最好的,因为它只用到了一次表的扫描.而后边的两个查询都是用到了相关子查询,如果你查看查询计划就回看到,他们都涉及到了两次表的扫描.如果你曾经在 T-SQL用过交叉表查询的话,你就不会对聚集函数里边的case结构陌生.现在我们就把这个非常有趣的方法应用到我们的问题中来:
Listing 4
select
OrderID,sum (Quantity * UnitPrice) value
from
[Order Details] o1
group by
OrderID
having
sum (case when ProductID = 59 then 1 else 0 end) = 0
HAVING子句起到了对分组的结果进行过滤的作用.如果没有包含59号产品,就会出现0=0,显然这是满足条件的.如果包含了59号产品的订购,就会出现n=0(n<>0),这样的定单就回被过滤掉.查看执行计划你就回发现是一次表的扫描,非常棒!
再来举一个例子:我们这回用到的表是order表,假设我们要统计只通过一个雇员雇员下定单的顾客.你可以想到用子查询not exist来实现:
select distinct
o1.CustomerID
from
Orders o1
where not exists
(
select
*
from
Orders o2
where
o2.CustomerID = o1.CustomerID
and o2.EmployeeID <> o1.EmployeeID
)
同样的,这个语句可以通过带有HAVING子句的分组来实现.
Listing 6
select
CustomerID
from
Orders
group by
CustomerID
having
min (EmployeeID) = max (EmployeeID)
另一种方法:
Listing 7
select
CustomerID
from
Orders
group by
CustomerID
having
count (distinct EmployeeID) = 1
Listing 6和Listing 7查询消耗都要小于Listing 5.相比Listing 5的两次表扫描,他们只进行一次表的扫描.而Listing 6的损耗还要稍微小于Listing 7.但是,Listing 7的一个显著的特点就是它可以适应到一个顾客对应两个雇员,三个雇员......
其实大家可能现在明白了这篇文章将的是什么?它就是教我们怎么用having 子句来达到过滤组的目的.可以达到避免两次表扫描的目的.可以达到更高的性能.我从这篇文章学到了很多的方法,你呢?

2008年5月28日星期三

[转]巧记 connect string

作者: DRL mikewolf

使用ADO的时候会需要创建ADOConnect,用来链接数据库
而创建一个Connect,必须要配置相关的参数
比如说:
Provide,server,database,uid/pwd...
不同以上参数不同,链接字符串不尽相同
比如说
[sqlserver]
Provider=SQLOLEDB.1;Integrated Security=SSPI;Persist Security Info=False;Initial Catalog=hrms;Data Source=(local)
[oracle]
Provider=MSDAORA.1;Password=fff;User ID=fff;Data Source=orcalserver;Persist Security Info=True
虽然对于一个应用,数据库连接部分写一次就可以了,也不同太记
但有时如果手边没有资料,也无法上google,就很难写对了
这里提供一个非常简单的,获得connect string的方法
1、创建一个txt
2、将扩展名改为.udl
3、双击这个改名后的文件,会出现一个连接配置向导
4、选择provide,以及其他参数
5、测试连接成功
6、确认
用notepad 打开这个文件
这时候就可以看到自动生成的connect string了。

2008年5月27日星期二

得到刚刚插入的记录的自动编号值一例

对于access和Sql server 数据库,下面方法可以得到刚刚插入记录的自动编号值。

'得到刚刚插入的记录的自动编号值

dim conn,rs
set conn=Server.CreateObject("ADODB.Connection")
conn.Open "Provider=Microsoft.Jet.OLEDB.4.0;Jet OLEDB:Database Password=;Data Source=" & Server.MapPath("51windows.mdb")
set rs=Server.CreateObject("ADODB.Recordset")
sql="select * from [table] where id is null" '自动编号字段为id
rs.open sql,conn,1,3
rs.addnew
rs("Name") = Request.form("Name")
rs("Name2") = Request.form("Name2")
rs.update
dim NewDbid
NewDbid = rs("id") '得刚刚插入记录的自动编号的值
rs.close
set rs = nothing
conn.close
set conn = nothing

更多方法和其它数据库请参考:How do I get the ID number of a just-inserted record

2008年5月26日星期一

[转]Java集合框架使用方法

前言:
    本文是对Java集合框架做了一个概括性的解说,目的是对Java集合框架体系有个总体认识,如果你想学习具体的接口和类的使用方法,请参看Java API文档。

    一、概述
    数据结构对程序设计有着深远的影响,在面向过程的C语言中,数据库结构用struct来描述,而在面向对象的编程中,数据结构是用类来描述的,并且包含有对该数据结构操作的方法。
    在Java语言中,Java语言的设计者对常用的数据结构和算法做了一些规范(接口)和实现(具体实现接口的类)。所有抽象出来的数据结构和操作(算法)统称为Java集合框架(Java Collection Framework)。
    Java程序员在具体应用时,不必考虑数据结构和算法实现细节,只需要用这些类创建出来一些对象,然后直接应用就可以了。这样就大大提高了编程效率。

    二、集合框架的层次结构
    Collection是集合接口
    |————Set子接口:无序,不允许重复。
    |————List子接口:有序,可以有重复元素。

    区别:Collections是集合类

    Set和List对比:
    Set:检索元素效率低下,删除和插入效率高,插入和删除不会引起元素位置改变。
    List:和数组类似,List可以动态增长,查找元素效率高,插入删除元素效率低,因为会引起其他元素位置改变。

    Set和List具体子类:
    Set
     |————HashSet:以哈希表的形式存放元素,插入删除速度很快。

    List
     |————ArrayList:动态数组
     |————LinkedList:链表、队列、堆栈。

    Array和java.util.Vector
    Vector是一种老的动态数组,是线程同步的,效率很低,一般不赞成使用。

    三、Iterator迭代器(接口)
    Iterator是获取集合中元素的过程,实际上帮助获取集合中的元素。
    迭代器代替了 Java Collections Framework 中的 Enumeration。迭代器与枚举有两点不同:
    迭代器允许调用方利用定义良好的语义在迭代期间从迭代器所指向的集合移除元素。
    方法名称得到了改进。

    Iterator仅有一个子接口ListIterator,是列表迭代器,允许程序员按任一方向遍历列表、迭代期间修改列表,并获得迭代器在列表中的当前位置。ListIterator 没有当前元素;它的光标位置 始终位于调用 previous() 所返回的元素和调用 next() 所返回的元素之间。在长度为 n 的列表中,有 n+1 个有效的索引值,从 0 到 n(包含)。

    四、集合框架之外的Map接口
    Map将键映射到值的对象。一个映射不能包含重复的键;每个键最多只能映射一个值。
    Map接口是Dictionary(字典)抽象类的替代品。
    Map 接口提供三种collection 视图,允许以键集、值集合或键-值映射关系集的形式查看某个映射的内容。映射的顺序 定义为迭代器在映射的 collection 视图中返回其元素的顺序。某些映射实现可明确保证其顺序,如 TreeMap 类;某些映射实现则不保证顺序,如 HashMap 类。

    有两个常见的已实现的子类:
    HashMap:基于哈希表的 Map 接口的实现。此实现提供所有可选的映射操作,并允许使用 null 值和 null 键。(除了不同步和允许使用 null 之外,HashMap 类与 Hashtable 大致相同。)此类不保证映射的顺序,特别是它不保证该顺序恒久不变。

    TreeMap:它实现SortedMap 接口的基于红黑树的实现。此类保证了映射按照升序顺序排列关键字,根据使用的构造方法不同,可能会按照键的类的自然顺序 进行排序(参见 Comparable),或者按照创建时所提供的比较器进行排序。

    Hashtable:此类实现一个哈希表,该哈希表将键映射到相应的值。任何非 null 对象都可以用作键或值。

    五、线程安全
    在集合框架中,有些类是线程安全的,这些都是JDK1.1中的出现的。在JDK1.2之后,就出现许许多多非线程安全的类。
    下面是这些线程安全的同步的类:
    Vector:就比ArrayList多了个同步化机制(线程安全)。
    Statck:堆栈类,先进后出。
    Hashtable:就比HashMap多了个线程安全。
    Enumeration:枚举,相当于迭代器。
    除了这些之外,其他的都是非线程安全的类和接口。
    线程安全的类其方法是同步的,每次只能一个访问。是重量级对象,效率较低。对于非线程安全的类和接口,在多线程中需要程序员自己处理线程安全问题。

    六、其他一些接口和类介绍
    Dictionary和Hashtable类:
    Dictionary提供键值映射的功能,是个抽象类。一般使用它的子类HashTable类。遍历Hashtable类要用到枚举。

    Properties类
    Properties 继承于 Hashtable,Properties 类表示了一个持久的属性集。Properties 可保存在流中或从流中加载。属性列表中每个键及其对应值都是一个字符串。一般可以通过读取properties配置文件来填充Properties对象。

2008年5月13日星期二

用Servlet生成验证码后session失效问题

在使用Servlet生成验证码时,在JSP中无法取得存放在session中的验证码的正确结果进行比对。经跟踪,发现在Servlet和JSP中的session id不同。百思不得其解,最后用HttpAnalyzer捕捉HTTP包进行分析,发现每个请求中的session ID不同,进而发现是由于准备在系统中使用JForum的单点登录SSO而设置了cookie的原因。

//JFroum SSO Cookie
Cookie cookie = new Cookie("jforumUserInfo",
                            (String) session.getAttribute("UserLogon"));
cookie.setMaxAge(-1);
response.addCookie(cookie);

删除上述的代码段,重启Web App。并清空浏览器的缓存和cookies,问题解决。

相册程序中的表单检查

<script language="JavaScript" type="text/javascript">
<!--
function checkForm(){
    var filename=document.getElementById("photos").value;
    if(filename==""){
        alert("请先选择要上传的照片后再确定上传!");
        return false;
    }else{
        if(!IsImageName(filename)){
            alert("只允许上传jpg、jpeg、png形式的图片!");
            return false;
        }
    }
    return true;
}
function IsImageName(filename){
    var regex=/^.*(\\|\/)[^\.]+.(jpg|png|jpeg)$/
    var splitted=filename.match(regex);
    if(splitted==null)
        return false;
    return true;
}
//-->
</script>

 

<form action="../servlet/PhotoUpload" method="post" enctype="multipart/form-data" name="form1" id="form1" onsubmit="return checkForm();">
  <table width="67%" border="0" cellspacing="0" cellpadding="0">
    <tr>
      <td>选择文件</td>
      <td><input type="file" name="photos" id="photos" /></td>
    </tr>
    <tr>
      <td valign="top">照片说明</td>
      <td><textarea name="description" id="description" cols="36" rows="3"></textarea></td>
    </tr>
    <tr>
      <td colspan="2"><center>
        <input name="btnSubmit" type="submit" id="btnSubmit" value="上传" />
      </center>
      </td>
      </tr>
  </table>
  </form>

Java中的简单图像处理

近日在制作一个网站(http://campus.cslg.cn)的过程中,对于校友录的相册部分需要生成缩略图。很多网站上为了改变图像的大小,往往直接指定<img>标记中的width和height属性,造成图像变形,效果惨不忍睹。

在网上搜索资料的过程中,找到了一篇比较好的讲 Java 2D图像处理的文章 Ultimate Java Image Manipulation (http://www.javalobby.org/articles/ultimate-image/#11)

在该作者提供的代码的基础上,增加了按照指定高度或者宽度缩放图像的方法,解决了图像显示的问题

import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;

import javax.imageio.ImageIO;

import com.sun.image.codec.jpeg.JPEGCodec;
import com.sun.image.codec.jpeg.JPEGEncodeParam;
import com.sun.image.codec.jpeg.JPEGImageEncoder;

public class ImageUtil {
    /**
     * 从磁盘文件装载图像
     *
     * @param ref
     * @return
     */
    public static BufferedImage loadImage(String ref) {
        BufferedImage bimg = null;
        try {

            bimg = ImageIO.read(new File(ref));
        } catch (Exception e) {
            e.printStackTrace();
        }
        return bimg;
    }

    /**
     * 将图像改为新的宽度和高度
     *
     * @param img
     * @param newWidth
     * @param newHeight
     * @return
     */
    public static BufferedImage resize(BufferedImage img, int newWidth,
            int newHeight) {
        int w = img.getWidth();
        int h = img.getHeight();
        BufferedImage dimg = new BufferedImage(newWidth, newHeight, img
                .getType());
        Graphics2D g = dimg.createGraphics();
        g.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
                RenderingHints.VALUE_INTERPOLATION_BILINEAR);
        g.drawImage(img, 0, 0, newWidth, newHeight, 0, 0, w, h, null);
        g.dispose();
        return dimg;
    }

    /**
     * 将图像改为新的宽度
     *
     * @param img
     * @param newWidth
     * @return
     */
    public static BufferedImage resizeWithNewWidth(BufferedImage img,
            int newWidth) {
        int w = img.getWidth();
        int h = img.getHeight();
        int newHeight = (int) ((double) h * newWidth / w);
        BufferedImage dimg = new BufferedImage(newWidth, newHeight, img
                .getType());
        Graphics2D g = dimg.createGraphics();
        g.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
                RenderingHints.VALUE_INTERPOLATION_BILINEAR);
        g.drawImage(img, 0, 0, newWidth, newHeight, 0, 0, w, h, null);
        g.dispose();
        return dimg;
    }

    /**
     * 将图像改为新的高度
     *
     * @param img
     * @param newHeight
     * @return
     */
    public static BufferedImage resizeWithNewHeight(BufferedImage img,
            int newHeight) {
        int w = img.getWidth();
        int h = img.getHeight();
        int newWidth = (int) ((double) w * newHeight / h);
        BufferedImage dimg = new BufferedImage(newWidth, newHeight, img
                .getType());
        Graphics2D g = dimg.createGraphics();
        g.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
                RenderingHints.VALUE_INTERPOLATION_BILINEAR);
        g.drawImage(img, 0, 0, newWidth, newHeight, 0, 0, w, h, null);
        g.dispose();
        return dimg;
    }

    public static void saveImage(String ref, BufferedImage img) {
        BufferedOutputStream out;
        try {
            out = new BufferedOutputStream(new FileOutputStream(ref));
            JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(out);
            JPEGEncodeParam param = encoder.getDefaultJPEGEncodeParam(img);
            int quality = 5;
            quality = Math.max(0, Math.min(quality, 100));
            param.setQuality((float) quality / 100.0f, false);
            encoder.setJPEGEncodeParam(param);
            encoder.encode(img);
            out.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * Saves a BufferedImage to the given file, pathname must not have any
     * periods "." in it except for the one before the format, i.e.
     * C:/images/fooimage.png
     *
     * @param img
     * @param saveFile
     */
    public static void saveImage(BufferedImage img, String ref) {
        try {
            String format = (ref.endsWith(".png")) ? "png" : "jpg";
            ImageIO.write(img, format, new File(ref));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static BufferedImage loadTranslucentImage(String url,
            float transperancy) {
        // Load the image
        BufferedImage loaded = loadImage(url);
        // Create the image using the
        BufferedImage aimg = new BufferedImage(loaded.getWidth(), loaded
                .getHeight(), BufferedImage.TRANSLUCENT);
        // Get the images graphics
        Graphics2D g = aimg.createGraphics();
        // Set the Graphics composite to Alpha
        g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
                transperancy));
        // Draw the LOADED img into the prepared reciver image
        g.drawImage(loaded, null, 0, 0);
        // let go of all system resources in this Graphics
        g.dispose();
        // Return the image
        return aimg;
    }

    public static BufferedImage makeColorTransparent(String ref, Color color) {
        BufferedImage image = loadImage(ref);
        BufferedImage dimg = new BufferedImage(image.getWidth(), image
                .getHeight(), BufferedImage.TYPE_INT_ARGB);
        Graphics2D g = dimg.createGraphics();
        g.setComposite(AlphaComposite.Src);
        g.drawImage(image, null, 0, 0);
        g.dispose();
        for (int i = 0; i < dimg.getHeight(); i++) {
            for (int j = 0; j < dimg.getWidth(); j++) {
                if (dimg.getRGB(j, i) == color.getRGB()) {
                    dimg.setRGB(j, i, 0x8F1C1C);
                }
            }
        }
        return dimg;
    }

    public static BufferedImage horizontalflip(BufferedImage img) {
        int w = img.getWidth();
        int h = img.getHeight();
        BufferedImage dimg = new BufferedImage(w, h, img.getType());
        Graphics2D g = dimg.createGraphics();
        g.drawImage(img, 0, 0, w, h, w, 0, 0, h, null);
        g.dispose();
        return dimg;
    }

    public static BufferedImage verticalflip(BufferedImage img) {
        int w = img.getWidth();
        int h = img.getHeight();
        BufferedImage dimg = new BufferedImage(w, h, img.getColorModel()
                .getTransparency());
        Graphics2D g = dimg.createGraphics();
        g.drawImage(img, 0, 0, w, h, 0, h, w, 0, null);
        g.dispose();
        return dimg;
    }

    public static BufferedImage rotate(BufferedImage img, int angle) {
        int w = img.getWidth();
        int h = img.getHeight();
        BufferedImage dimg = new BufferedImage(w, h, img.getType());
        Graphics2D g = dimg.createGraphics();
        g.rotate(Math.toRadians(angle), w / 2, h / 2);
        g.drawImage(img, null, 0, 0);
        return dimg;
    }

    public static BufferedImage[] splitImage(BufferedImage img, int cols,
            int rows) {
        int w = img.getWidth() / cols;
        int h = img.getHeight() / rows;
        int num = 0;
        BufferedImage imgs[] = new BufferedImage[w * h];
        for (int y = 0; y < rows; y++) {
            for (int x = 0; x < cols; x++) {
                imgs[num] = new BufferedImage(w, h, img.getType());
                // Tell the graphics to draw only one block of the image
                Graphics2D g = imgs[num].createGraphics();
                g.drawImage(img, 0, 0, w, h, w * x, h * y, w * x + w,
                        h * y + h, null);
                g.dispose();
                num++;
            }
        }
        return imgs;
    }

}

 

下面是用于改变图像大小的Servlet (使用格式: <img src="servlet/ImageResize?id=1&width=200&height=150"/>;可以指定高度、宽度或者宽度和高度)

import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

import javax.naming.NamingException;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import cn.cslg.campus.database.DBPoolException;
import cn.cslg.campus.database.JndiBean;

import com.sun.image.codec.jpeg.JPEGCodec;
import com.sun.image.codec.jpeg.JPEGImageEncoder;

public class ImageResize extends HttpServlet {

    private static final long serialVersionUID = -8774962472747611326L;

    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        response.setContentType("image/jpeg");
        int id = 0;
        int height = 0;
        int width = 0;
        try {
            try {
                id = Integer.parseInt(request.getParameter("id"));
                String h = request.getParameter("height");
                String w = request.getParameter("width");
                if (h != null && h.length() != 0)
                    height = Integer.parseInt(h);
                if (w != null && w.length() != 0)
                    width = Integer.parseInt(w);
            } catch (NumberFormatException e) {
                return;
            }
            BufferedImage image = loadImage(id, width, height);
            ServletOutputStream out = response.getOutputStream();
            JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(out);
            encoder.encode(image);
            out.close();
        } catch (Exception e) {
            throw new ServletException(e);
        }
    }

    public BufferedImage loadCachedImage(String fname, int width, int height)
            throws DBPoolException, NamingException, SQLException {
        BufferedImage image = null;
        ServletContext application = this.getServletContext();
        String folder = application.getRealPath("/photo/thumbnail");
        String ext = fname.substring(fname.lastIndexOf('.'));
        String pre = fname.substring(0, fname.lastIndexOf('.'));
        String cachedName = folder + "/" + pre + "-" + width + "-" + height
                + ext;
        File f = new File(cachedName);
        if (f.exists())
            image = ImageUtil.loadImage(cachedName);
        return image;
    }

    public void saveCachedImage(BufferedImage image, String fname, int width,
            int height) {
        ServletContext application = this.getServletContext();
        String folder = application.getRealPath("/photo/thumbnail");
        String ext = fname.substring(fname.lastIndexOf('.'));
        String pre = fname.substring(0, fname.lastIndexOf('.'));
        String cachedName = folder + "/" + pre + "-" + width + "-" + height
                + ext;
        ImageUtil.saveImage(image, cachedName);
    }

    public BufferedImage loadImage(int id, int width, int height)
            throws DBPoolException, NamingException, SQLException {
        ServletContext application = this.getServletContext();
        String uploadFolder = application.getRealPath("/photo");
        BufferedImage image = null;
        Connection conn = null;
        try {
            conn = JndiBean.getConnection();
            String sql = "select FileName from Album where PhotoId=" + id;
            Statement stmt = conn.createStatement();
            ResultSet rs = stmt.executeQuery(sql);
            if (!rs.next())
                return null;
            String fname = rs.getString("FileName").trim();
            String pname = uploadFolder + "/" + fname;

            image = loadCachedImage(fname, width, height);
            if (image == null) {
                if (height != 0 && width != 0) {
                    image = ImageUtil.resize(ImageUtil.loadImage(pname), width,
                            height);
                } else if (height != 0 && width == 0)
                    image = ImageUtil.resizeWithNewHeight(ImageUtil
                            .loadImage(pname), height);
                else if (height == 0 && width != 0)
                    image = ImageUtil.resizeWithNewWidth(ImageUtil
                            .loadImage(pname), width);
                saveCachedImage(image, fname, width, height);
            }
            return image;
        } finally {
            conn.close();
        }
    }
}

[转] jspSmartUpload上传下载全攻略

一、安装篇
jspSmartUpload是由www.jspsmart.com网站开发的一个可免费使用的全功能的文件上传下载组件,适于嵌入执行上传下载操作的JSP文件中。该组件有以下几个特点:
1、使用简单。在JSP文件中仅仅书写三五行JAVA代码就可以搞定文件的上传或下载,方便。
2、能全程控制上传。利用jspSmartUpload组件提供的对象及其操作方法,可以获得全部上传文件的信息(包括文件名,大小,类型,扩展名,文件数据等),方便存取。
3、能对上传的文件在大小、类型等方面做出限制。如此可以滤掉不符合要求的文件。
4、下载灵活。仅写两行代码,就能把Web服务器变成文件服务器。不管文件在Web服务器的目录下或在其它任何目录下,都可以利用jspSmartUpload进行下载。
5、能将文件上传到数据库中,也能将数据库中的数据下载下来。这种功能针对的是MYSQL数据库,因为不具有通用性,所以本文不准备举例介绍这种用法。
jspSmartUpload组件可以从www.jspsmart.com网站上自由下载,压缩包的名字是jspSmartUpload.zip。下载后,用WinZip或WinRAR将其解压到Tomcat的webapps目录下(本文以Tomcat服务器为例进行介绍)。解压后,将webapps/jspsmartupload目录下的子目录Web-inf名字改为全大写的WEB-INF,这样一改jspSmartUpload类才能使用。因为Tomcat对文件名大小写敏感,它要求Web应用程序相关的类所在目录为WEB-INF,且必须是大写。接着重新启动Tomcat,这样就可以在JSP文件中使用jspSmartUpload组件了。
注意,按上述方法安装后,只有webapps/jspsmartupload目录下的程序可以使用jspSmartUpload组件,如果想让Tomcat服务器的所有Web应用程序都能用它,必须做如下工作:
1.进入命令行状态,将目录切换到Tomcat的webapps/jspsmartupload/WEB-INF目录下。
2.运行JAR打包命令:jar cvf jspSmartUpload.jar com
(也可以打开资源管理器,切换到当前目录,用WinZip将com目录下的所有文件压缩成jspSmartUpload.zip,然后将jspSmartUpload.zip换名为jspSmartUpload.jar文件即可。)
3.将jspSmartUpload.jar拷贝到Tomcat的shared/lib目录下。
二、相关类说明篇
㈠ File类
这个类包装了一个上传文件的所有信息。通过它,可以得到上传文件的文件名、文件大小、扩展名、文件数据等信息。
File类主要提供以下方法:
1、saveAs作用:将文件换名另存。
原型:
public void saveAs(java.lang.String destFilePathName)

public void saveAs(java.lang.String destFilePathName, int optionSaveAs)
其中,destFilePathName是另存的文件名,optionSaveAs是另存的选项,该选项有三个值,分别是SAVEAS_PHYSICAL,SAVEAS_VIRTUAL,SAVEAS_AUTO。SAVEAS_PHYSICAL表明以操作系统的根目录为文件根目录另存文件,SAVEAS_VIRTUAL表明以Web应用程序的根目录为文件根目录另存文件,SAVEAS_AUTO则表示让组件决定,当Web应用程序的根目录存在另存文件的目录时,它会选择SAVEAS_VIRTUAL,否则会选择SAVEAS_PHYSICAL。
例如,saveAs("/upload/sample.zip",SAVEAS_PHYSICAL)执行后若Web服务器安装在C盘,则另存的文件名实际是c:\upload\sample.zip。而saveAs("/upload/sample.zip",SAVEAS_VIRTUAL)执行后若Web应用程序的根目录是webapps/jspsmartupload,则另存的文件名实际是webapps/jspsmartupload/upload/sample.zip。saveAs("/upload/sample.zip",SAVEAS_AUTO)执行时若Web应用程序根目录下存在upload目录,则其效果同saveAs("/upload/sample.zip",SAVEAS_VIRTUAL),否则同saveAs("/upload/sample.zip",SAVEAS_PHYSICAL)。
建议:对于Web程序的开发来说,最好使用SAVEAS_VIRTUAL,以便移植。
2、isMissing
作用:这个方法用于判断用户是否选择了文件,也即对应的表单项是否有值。选择了文件时,它返回false。未选文件时,它返回true。
原型:public boolean isMissing()
3、getFieldName
作用:取HTML表单中对应于此上传文件的表单项的名字。
原型:public String getFieldName()
4、getFileName
作用:取文件名(不含目录信息)
原型:public String getFileName()
5、getFilePathName
作用:取文件全名(带目录)
原型:public String getFilePathName
6、getFileExt
作用:取文件扩展名(后缀)
原型:public String getFileExt()
7、getSize
作用:取文件长度(以字节计)
原型:public int getSize()
8、getBinaryData
作用:取文件数据中指定位移处的一个字节,用于检测文件等处理。
原型:public byte getBinaryData(int index)。其中,index表示位移,其值在0到getSize()-1之间。
㈡ Files类
这个类表示所有上传文件的集合,通过它可以得到上传文件的数目、大小等信息。有以下方法:
1、getCount
作用:取得上传文件的数目。
原型:public int getCount()
2、getFile
作用:取得指定位移处的文件对象File(这是com.jspsmart.upload.File,不是java.io.File,注意区分)。
原型:public File getFile(int index)。其中,index为指定位移,其值在0到getCount()-1之间。
3、getSize
作用:取得上传文件的总长度,可用于限制一次性上传的数据量大小。
原型:public long getSize()
4、getCollection
作用:将所有上传文件对象以Collection的形式返回,以便其它应用程序引用,浏览上传文件信息。
原型:public Collection getCollection()
5、getEnumeration
作用:将所有上传文件对象以Enumeration(枚举)的形式返回,以便其它应用程序浏览上传文件信息。
原型:public Enumeration getEnumeration()
㈢ Request类
这个类的功能等同于JSP内置的对象request。只所以提供这个类,是因为对于文件上传表单,通过request对象无法获得表单项的值,必须通过jspSmartUpload组件提供的Request对象来获取。该类提供如下方法:
1、getParameter
作用:获取指定参数之值。当参数不存在时,返回值为null。
原型:public String getParameter(String name)。其中,name为参数的名字。
2、getParameterValues
作用:当一个参数可以有多个值时,用此方法来取其值。它返回的是一个字符串数组。当参数不存在时,返回值为null。
原型:public String[] getParameterValues(String name)。其中,name为参数的名字。
3、getParameterNames
作用:取得Request对象中所有参数的名字,用于遍历所有参数。它返回的是一个枚举型的对象。
原型:public Enumeration getParameterNames()
㈣ SmartUpload类这个类完成上传下载工作。
A.上传与下载共用的方法:
只有一个:initialize。
作用:执行上传下载的初始化工作,必须第一个执行。
原型:有多个,主要使用下面这个:
public final void initialize(javax.servlet.jsp.PageContext pageContext)
其中,pageContext为JSP页面内置对象(页面上下文)。
B.上传文件使用的方法:
1、upload
作用:上传文件数据。对于上传操作,第一步执行initialize方法,第二步就要执行这个方法。
原型:public void upload()
2、save
作用:将全部上传文件保存到指定目录下,并返回保存的文件个数。
原型:public int save(String destPathName)
和public int save(String destPathName,int option)
其中,destPathName为文件保存目录,option为保存选项,它有三个值,分别是SAVE_PHYSICAL,SAVE_VIRTUAL和SAVE_AUTO。(同File类的saveAs方法的选项之值类似)SAVE_PHYSICAL指示组件将文件保存到以操作系统根目录为文件根目录的目录下,SAVE_VIRTUAL指示组件将文件保存到以Web应用程序根目录为文件根目录的目录下,而SAVE_AUTO则表示由组件自动选择。
注:save(destPathName)作用等同于save(destPathName,SAVE_AUTO)。
3、getSize
作用:取上传文件数据的总长度
原型:public int getSize()
4、getFiles
作用:取全部上传文件,以Files对象形式返回,可以利用Files类的操作方法来获得上传文件的数目等信息。
原型:public Files getFiles()
5、getRequest
作用:取得Request对象,以便由此对象获得上传表单参数之值。
原型:public Request getRequest()
6、setAllowedFilesList
作用:设定允许上传带有指定扩展名的文件,当上传过程中有文件名不允许时,组件将抛出异常。
原型:public void setAllowedFilesList(String allowedFilesList)
其中,allowedFilesList为允许上传的文件扩展名列表,各个扩展名之间以逗号分隔。如果想允许上传那些没有扩展名的文件,可以用两个逗号表示。例如:setAllowedFilesList("doc,txt,,")将允许上传带doc和txt扩展名的文件以及没有扩展名的文件。
7、setDeniedFilesList
作用:用于限制上传那些带有指定扩展名的文件。若有文件扩展名被限制,则上传时组件将抛出异常。
原型:public void setDeniedFilesList(String deniedFilesList)
其中,deniedFilesList为禁止上传的文件扩展名列表,各个扩展名之间以逗号分隔。如果想禁止上传那些没有扩展名的文件,可以用两个逗号来表示。例如:setDeniedFilesList("exe,bat,,")将禁止上传带exe和bat扩展名的文件以及没有扩展名的文件。
8、setMaxFileSize
作用:设定每个文件允许上传的最大长度。
原型:public void setMaxFileSize(long maxFileSize)
其中,maxFileSize为为每个文件允许上传的最大长度,当文件超出此长度时,将不被上传。
9、setTotalMaxFileSize
作用:设定允许上传的文件的总长度,用于限制一次性上传的数据量大小。
原型:public void setTotalMaxFileSize(long totalMaxFileSize)
其中,totalMaxFileSize为允许上传的文件的总长度。
C.下载文件常用的方法
1、setContentDisposition
作用:将数据追加到MIME文件头的CONTENT-DISPOSITION域。jspSmartUpload组件会在返回下载的信息时自动填写MIME文件头的CONTENT-DISPOSITION域,如果用户需要添加额外信息,请用此方法。
原型:public void setContentDisposition(String contentDisposition)
其中,contentDisposition为要添加的数据。如果contentDisposition为null,则组件将自动添加"attachment;",以表明将下载的文件作为附件,结果是IE浏览器将会提示另存文件,而不是自动打开这个文件(IE浏览器一般根据下载的文件扩展名决定执行什么操作,扩展名为doc的将用word程序打开,扩展名为pdf的将用acrobat程序打开,等等)。
2、downloadFile
作用:下载文件。
原型:共有以下三个原型可用,第一个最常用,后两个用于特殊情况下的文件下载(如更改内容类型,更改另存的文件名)。
① public void downloadFile(String sourceFilePathName)
其中,sourceFilePathName为要下载的文件名(带目录的文件全名)
② public void downloadFile(String sourceFilePathName,String contentType)
其中,sourceFilePathName为要下载的文件名(带目录的文件全名),contentType为内容类型(MIME格式的文件类型信息,可被浏览器识别)。
③ public void downloadFile(String sourceFilePathName,String contentType,String destFileName)
其中,sourceFilePathName为要下载的文件名(带目录的文件全名),contentType为内容类型(MIME格式的文件类型信息,可被浏览器识别),destFileName为下载后默认的另存文件名。
三、文件上传篇
㈠ 表单要求
对于上传文件的FORM表单,有两个要求:
1、METHOD应用POST,即METHOD="POST"。
2、增加属性:ENCTYPE="multipart/form-data"
下面是一个用于上传文件的FORM表单的例子:
<FORM METHOD="POST" ENCTYPE="multipart/form-data" 
ACTION="/jspSmartUpload/upload.jsp">
<INPUT TYPE="FILE" NAME="MYFILE">
<INPUT TYPE="SUBMIT">
</FORM>

㈡ 上传的例子

1、上传页面upload.html

本页面提供表单,让用户选择要上传的文件,点击"上传"按钮执行上传操作。

页面源码如下:
<!-- 
文件名:upload.html
作 者:纵横软件制作中心雨亦奇(zhsoft88@sohu.com)
-->
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>文件上传</title>
<meta http-equiv="Content-Type" content="text/html; charset=gb2312">
</head>

<body>
<p> </p>
<p align="center">上传文件选择</p>
<FORM METHOD="POST" ACTION="jsp/do_upload.jsp"
ENCTYPE="multipart/form-data">
<input type="hidden" name="TEST" value="good">
<table width="75%" border="1" align="center">
<tr>
<td><div align="center">1、
<input type="FILE" name="FILE1" size="30">
</div></td>
</tr>
<tr>
<td><div align="center">2、
<input type="FILE" name="FILE2" size="30">
</div></td>
</tr>
<tr>
<td><div align="center">3、
<input type="FILE" name="FILE3" size="30">
</div></td>
</tr>
<tr>
<td><div align="center">4、
<input type="FILE" name="FILE4" size="30">
</div></td>
</tr>
<tr>
<td><div align="center">
<input type="submit" name="Submit" value="上传它!">
</div></td>
</tr>
</table>
</FORM>
</body>
</html>

2、上传处理页面do_upload.jsp

本页面执行文件上传操作。页面源码中详细介绍了上传方法的用法,在此不赘述了。

页面源码如下:
<%-- 
文件名:do_upload.jsp
作 者:纵横软件制作中心雨亦奇(zhsoft88@sohu.com)
--%>
<%@ page contentType="text/html; charset=gb2312" language="java"
import="java.util.*,com.jspsmart.upload.*" errorPage="" %>
<html>
<head>
<title>文件上传处理页面</title>
<meta http-equiv="Content-Type" content="text/html; charset=gb2312">
</head>

<body>
<%
// 新建一个SmartUpload对象
SmartUpload su = new SmartUpload();
// 上传初始化
su.initialize(pageContext);
// 设定上传限制
// 1.限制每个上传文件的最大长度。
// su.setMaxFileSize(10000);
// 2.限制总上传数据的长度。
// su.setTotalMaxFileSize(20000);
// 3.设定允许上传的文件(通过扩展名限制),仅允许doc,txt文件。
// su.setAllowedFilesList("doc,txt");
// 4.设定禁止上传的文件(通过扩展名限制),禁止上传带有exe,bat,
jsp,htm,html扩展名的文件和没有扩展名的文件。
// su.setDeniedFilesList("exe,bat,jsp,htm,html,,");
// 上传文件
su.upload();
// 将上传文件全部保存到指定目录
int count = su.save("/upload");
out.println(count+"个文件上传成功!<br>");

// 利用Request对象获取参数之值
out.println("TEST="+su.getRequest().getParameter("TEST")
+"<BR><BR>");

// 逐一提取上传文件信息,同时可保存文件。
for (int i=0;i<su.getFiles().getCount();i++)
{
com.jspsmart.upload.File file = su.getFiles().getFile(i);

// 若文件不存在则继续
if (file.isMissing()) continue;

// 显示当前文件信息
out.println("<TABLE BORDER=1>");
out.println("<TR><TD>表单项名(FieldName)</TD><TD>"
+ file.getFieldName() + "</TD></TR>");
out.println("<TR><TD>文件长度(Size)</TD><TD>" +
file.getSize() + "</TD></TR>");
out.println("<TR><TD>文件名(FileName)</TD><TD>"
+ file.getFileName() + "</TD></TR>");
out.println("<TR><TD>文件扩展名(FileExt)</TD><TD>"
+ file.getFileExt() + "</TD></TR>");
out.println("<TR><TD>文件全名(FilePathName)</TD><TD>"
+ file.getFilePathName() + "</TD></TR>");
out.println("</TABLE><BR>");

// 将文件另存
// file.saveAs("/upload/" + myFile.getFileName());
// 另存到以WEB应用程序的根目录为文件根目录的目录下
// file.saveAs("/upload/" + myFile.getFileName(),
su.SAVE_VIRTUAL);
// 另存到操作系统的根目录为文件根目录的目录下
// file.saveAs("c:\\temp\\" + myFile.getFileName(),
su.SAVE_PHYSICAL);

}
%>
</body>
</html>

四、文件下载篇

1、下载链接页面download.html

页面源码如下:
<!-- 
文件名:download.html
作 者:纵横软件制作中心雨亦奇(zhsoft88@sohu.com)
-->
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>下载</title>
<meta http-equiv="Content-Type" content="text/html; charset=gb2312">
</head>
<body>
<a href="jsp/do_download.jsp" mce_href="jsp/do_download.jsp">点击下载</a>
</body>
</html>

2、下载处理页面do_download.jsp do_download.jsp展示了如何利用jspSmartUpload组件来下载文件,从下面的源码中就可以看到,下载何其简单。

源码如下:
<%@ page contentType="text/html;charset=gb2312" 
import="com.jspsmart.upload.*" %><%
// 新建一个SmartUpload对象
SmartUpload su = new SmartUpload();
// 初始化
su.initialize(pageContext);
// 设定contentDisposition为null以禁止浏览器自动打开文件,
//保证点击链接后是下载文件。若不设定,则下载的文件扩展名为
//doc时,浏览器将自动用word打开它。扩展名为pdf时,
//浏览器将用acrobat打开。
su.setContentDisposition(null);
// 下载文件
su.downloadFile("/upload/如何赚取我的第一桶金.doc");
%>

注意,执行下载的页面,在Java脚本范围外(即<% ... %>之外),不要包含HTML代码、空格、回车或换行等字符,有的话将不能正确下载。不信的话,可以在上述源码中%><%之间加入一个换行符,再下载一下,保证出错。因为它影响了返回给浏览器的数据流,导致解析出错。

3、如何下载中文文件

jspSmartUpload虽然能下载文件,但对中文支持不足。若下载的文件名中有汉字,则浏览器在提示另存的文件名时,显示的是一堆乱码,很扫人兴。上面的例子就是这样。(这个问题也是众多下载组件所存在的问题,很少有人解决,搜索不到相关资料,可叹!)

为了给jspSmartUpload组件增加下载中文文件的支持,我对该组件进行了研究,发现对返回给浏览器的另存文件名进行UTF-8编码后,浏览器便能正确显示中文名字了。这是一个令人高兴的发现。于是我对jspSmartUpload组件的SmartUpload类做了升级处理,增加了toUtf8String这个方法,改动部分源码如下:
public void downloadFile(String s, String s1, String s2, int i) 
throws ServletException, IOException, SmartUploadException
{
if(s == null)
throw new IllegalArgumentException("File '" + s +
"' not found (1040).");
if(s.equals(""))
throw new IllegalArgumentException("File '" + s +
"' not found (1040).");
if(!isVirtual(s) && m_denyPhysicalPath)
throw new SecurityException("Physical path is
denied (1035).");
if(isVirtual(s))
s = m_application.getRealPath(s);
java.io.File file = new java.io.File(s);
FileInputStream fileinputstream = new FileInputStream(file);
long l = file.length();
boolean flag = false;
int k = 0;
byte abyte0[] = new byte[i];
if(s1 == null)
m_response.setContentType("application/x-msdownload");
else
if(s1.length() == 0)
m_response.setContentType("application/x-msdownload");
else
m_response.setContentType(s1);
m_response.setContentLength((int)l);
m_contentDisposition = m_contentDisposition != null ?
m_contentDisposition : "attachment;";
if(s2 == null)
m_response.setHeader("Content-Disposition",
m_contentDisposition + " filename=" +
toUtf8String(getFileName(s)));
else
if(s2.length() == 0)
m_response.setHeader("Content-Disposition",
m_contentDisposition);
else
m_response.setHeader("Content-Disposition",
m_contentDisposition + " filename=" + toUtf8String(s2));
while((long)k < l)
{
int j = fileinputstream.read(abyte0, 0, i);
k += j;
m_response.getOutputStream().write(abyte0, 0, j);
}
fileinputstream.close();
}

/**
* 将文件名中的汉字转为UTF8编码的串,以便下载时能正确显示另存的文件名.
* 纵横软件制作中心雨亦奇2003.08.01
* @param s 原文件名
* @return 重新编码后的文件名
*/
public static String toUtf8String(String s) {
StringBuffer sb = new StringBuffer();
for (int i=0;i<s.length();i++) {
char c = s.charAt(i);
if (c >= 0 && c <= 255) {
sb.append(c);
} else {
byte[] b;
try {
b = Character.toString(c).getBytes("utf-8");
} catch (Exception ex) {
System.out.println(ex);
b = new byte[0];
}
for (int j = 0; j < b.length; j++) {
int k = b[j];
if (k < 0) k += 256;
sb.append("%" + Integer.toHexString(k).
toUpperCase());
}
}
}
return sb.toString();
}

注意源码中粗体部分,原jspSmartUpload组件对返回的文件未作任何处理,现在做了编码的转换工作,将文件名转换为UTF-8形式的编码形式。UTF-8编码对英文未作任何处理,对中文则需要转换为%XX的形式。toUtf8String方法中,直接利用Java语言提供的编码转换方法获得汉字字符的UTF-8编码,之后将其转换为%XX的形式。

将源码编译后打包成jspSmartUpload.jar,拷贝到Tomcat的common/lib目录下(可为所有WEB应用程序所共享),然后重启Tomcat服务器就可以正常下载含有中文名字的文件了。另,toUtf8String方法也可用于转换含有中文的超级链接,以保证链接的有效,因为有的WEB服务器不支持中文链接。

小结:jspSmartUpload组件是应用JSP进行B/S程序开发过程中经常使用的上传下载组件,它使用简单,方便。现在我又为其加上了下载中文名字的文件的支持,真个是如虎添翼,必将赢得更多开发者的青睐。

经过jimshen修改JspSmartUpload组件

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

一个不错的正则表达式教程

http://www.unibetter.com/deerchao/zhengzhe-biaodashi-jiaocheng-se.htm 

版本:v2.3 (2008-4-13)

作者:deerchao

关于MD5破解网站的感想

前几天在网上看到一个国内的MD5在线查询破解网站 http://www.cmd5.com/ 。下面是该网站的简介:

本站4T硬盘已经上线,共有md5记录457,354,352,282条,宇宙第一,且还在不断增长中,已包含12位及12位以下数字、8位字母、全部7位及以下字母加数字等组合,并针对国内用户做了大量优化,例如已经包含所有手机号码、全国部分大中城市固定电话号码、百家姓、常用拼音等大量组合,另加入了大型网站真实会员密码数据100万条。本站数据量大,查询速度快,同时支持16位及32位密码查询。通过对10万会员的真实动网论坛样本数据的测试,本站对于动网论坛密码的命中率达到83%。全国独此一家。另感谢一热心网友给本站奉上数百万条数据库原始密码样本,大大提高了命中率!

令我诧异的是有人会送上原始密码样本,这样很多系统的安全性就得不到保证了。

看来在系统中如果不能保证绝对没有bug,绝对没有SQL诸如漏洞的话,我要考虑密码保存的方式了。至少换一个非公开加密算法的程序来实现密码的加密存储。

正则表达式介绍及其在EmEditor的应用

转自:http://bbs.et8.net/bbs/showthread.php?t=652159

作者:nb590@ccf

应lyh728之约, 也算是对命令行和正则表达式专题的支持, 随便写点东西介绍下正则表达式的基础概念. 由于本版偏重应用, 故只取EmEditor的正则子集来作介绍. Perl 和 CLR 的 Regex 的内容远比这类编辑器所支持的功能多.
正则表达式实在包含的内容太多, 仅仅用一篇文章来涵盖是没可能的了, 所以我只是简要的做些介绍和基本的模式应用举例. 即使这样也需要多次分章节的来连载了~~~ 闲话少说, 以下正文:
正则表达式, 英文 Regular expression, 简写Regexes或Regex.
应用概述: 提供与预期的搜索结果匹配的确切文本来进行字符串的搜索和替换操作, 这种技术不仅仅用于开发领域, 更被集成到一些常见的文本扩展编辑器, 如UltraEdit, Emeditor等. 历史上第一个实用应用程序是Unix 中的qed 编辑器。
举一个简单的类比: 我们对DOS中的通配符"*"和"?"应该很熟悉, 如命令"dir *.exe" 将列出所有后缀名为exe的文件名. 正则表达式提供的方法与其类似, 而且远比通配符强大的多.
从某种意义上说, 正则表达式是一种语言, 通过及其简短的一行代码即可以高效, 精确的描述要匹配的复杂文本, 当然, 它最大的优点也是他最大的缺点: 语法复杂, 创建困难. (熟悉之后就可以忽略后半句了 )
主要应用:

  • 数据验证; 这是正则表达式在开发中最常见的应用, 通过测试字符串内的模式。来验证输入的字符串是否为邮政编码, 电话号码, 电子邮件地址, 信用卡号码等等。
  • 搜索和替换文本; 用正则表达式来搜索文档中的特定文本块, 根据需要用其他指定的文本块进行替换。这也是文本编辑中的一个常见应用, 如将网页中的HTML代码转化为UBB代码.

既然发在『软件使用』板, 正则表达式的开发中的应用就不介绍了, 以下仅以EmEditor中的正则表达式来作介绍:
1. 启用正则表达式
菜单: Search - Find (Replace) - 选中 Use Regular Expressions
2. Emeditor 正则语法
正则表达式是普通字符和元字符组合的一种模式. 它的结构与算术表达式的结构类似, 各种元字符和运算符可以将小的表达式组合起来,创建大的表达式。通过在一对分隔符之间放置表达式模式的各种组件,就可以构建正则表达式。
2.1 普通字符
普通字符是指除了 ".", "*", "?", "+", "(", ")", "{", "}", "[", "]", "^", "$" 和 "\" 这些特殊字符之外的所有其他字符. 而这些特殊字符也可以通过前面加上"\"前缀而变为普通字符. 比如, 搜索"CCF"即为在文本中匹配所有的"CCF"字符串, 搜索"\[CCF\]"则是在文本中匹配所有的"[CCF]"字符串.
简而言之, 普通字符即为只匹配自身的字符.
2.2 元字符
元字符不匹配其自身,它用特殊方式来解析从而实现更多的逻辑功能。正则表达式通过元字符在模式中包含选择和循环
2.2.1 特殊字符
  • . 匹配除换行符 \n 之外的任何单个字符。
  • ( ) 分组捕获(子表达式)的开始和结束。可以捕获子表达式以供以后使用。
  • [ ] 中括号表达式的开始。
    中括号表达式是在方括号内包含一个或多个字符构成的列表的表达式。普通字符在中括号内表示本身,大多数特殊字符在中括号表达式内出现时失去它们的意义。除了转义字符'\', (要包含'\', 需要使用'\\') 如: 正则表达式 No [1234] 匹配 No 1, No 2, No 3 和 No 4.
    如果想在中括号中使用一个范围作为列表来匹配字符,可以用连字符 '-' 将范围中的开始字符和结束字符分开。单个字符的字符值确定范围内的相对顺序。如: 正则表达式 No [1-4] = No [1234]
    注意 1. 开始值的Unicode值必须在结束值Unicode值的前面。
    注意 2. [\-]匹配连字符'-', 放在中括号列表的开始或结尾也可起到同样的效果, 如 [-c-f] 匹配 c 至 f 的字符和连字符
    如果需要匹配不属于列表或范围内的任何字符,可以在列表开头加上'^'前缀。如: 正则表达式 No [^1-4] 匹配 No 5 和更大的编号.
    中括号表达式还可进行组合, 如 [A-Za-z0-9] 匹配A-Z, a-z, 0-9 的字符
  • \ 将下一字符标记为特殊字符、文本、反向引用或八进制转义符。例如,
    字符 n 匹配字符 n
    \n 匹配换行符
    序列 \\ 匹配 \
    序列 \( 匹配 (
  • | 替换字符, 对|左右的两个项分别匹配进行选择。或者说, 就是逻辑的OR的概念
  • { } 标记限定符表达式的开始。
    (数量)限定字符
    限定字符能够指定正则表达式的某个部分必须出现的次数
    • * 零次或多次匹配前面的字符或子表达式。如,c*f 可以匹配 f 和 ccf。* = {0,}
    • + 一次或多次匹配前面的字符或子表达式。如,c+f 可以匹配 cf 和 ccf,但不匹配 f。+ = {1,}
    • ? 零次或一次匹配前面的字符或子表达式。如,cc?f 可以匹配 cf 或 ccf。? = {0,1}
    • {n} n 是非负整数。正好匹配 n 次。如,c{2}f 可以匹配 ccf。
    • {n,} n 是非负整数。至少匹配 n 次。如,c{2,}f 不匹配 cf,而可以匹配 ccccccf。c{1,} = c+。c{0,} = c*
    • {n,m} m 和 n 是非负整数,其中 n <= m。至少匹配 n 次,至多匹配 m 次。如,c{1,3} 可以匹配 ccf 中的cc。c{0,1} 等效于 c?。

2.2.2 控制字符
  • \a Bell 字符。= 0x07
  • \f 换页符匹配。= 0x0C
  • \n 换行符匹配。= 0x0A
  • \r 匹配一个回车符。= 0x0D
  • \t 制表符匹配。= 0x09
  • \v 垂直制表符匹配。= 0x0B
  • \e ASCII 换码字符。= 0x1B
  • \0dd 八进制换码字符, dd代表八进制数字。
  • \xXXXX或\x{XXXX} 4位十六进制Unicode字符, XXXX代表十六进制数字。
  • \cZ Z-'@' 控制字符Control-Z, Z为大于等与"@"的ASCII字符
2.2.3 换码字符
  • \w 任一单词字符, 如A-Z, a-z, 0-9, _等, 如 \w\w\w可以匹配 U_4 但不匹配 %^e
  • \W 任一非单词字符, 如 \W\W 可以匹配 *& 但不匹配 7#
  • \s 任一空白字符,包括空格、制表符、换页符、回车符和垂直制表符。= [ \f\n\r\t\v]
  • \S 任一非空白字符. = [^ \f\n\r\t\v]
  • \d 0-9的任一数字字符, 如 \d\d可以匹配 54 但不匹配 a4
  • \D 任一非数字字符. 如 \D\D可以匹配 a4 但不匹配 54
  • \l a-z 之间的任一小写字符, 如 \l\l\l可以匹配 ccf 但不匹配 ccF
  • \L 任一非小写字符, 如 \L\L\L可以匹配 CCF 但不匹配 cCF
  • \u a-z 之间的任一大写字符, 如 \u\u\u可以匹配 CCF 但不匹配 CCf
  • \U 任一非大写字符, 如 \U\U\U可以匹配 ccf 但不匹配 ccF
  • \C 任一字符, = '.'
  • \Q 前置引号符, 其后的任意字符均被认为普通字符直至出现后置引号符\E. 同时匹配单引号和双引号
  • \E 后置引号符
2.2.4 转义字符串
表示为[:classname:], 如"[[:space:]]"表示所有的空格字符
  • alnum 任一单词字符和数字字符. = [\w\d]
  • alpha 任何一个单词字符, 如A-Z, a-z, 0-9
  • blank 任一空白字符,包括空格、制表符、换页符、回车符和垂直制表符。= [ \f\n\r\t\v] = \s
  • cntrl 任一控制字符.
  • digit 0-9的任一数字字符, = \d
  • graph 任一图形字符.
  • lower a-z 之间的任一小写字符 =\l
  • print 任一可打印字符 = '.' = \C
  • punct 任一标点符号
  • space 任一空格字符
  • upper a-z 之间的任一大写字符 = \u
  • xdigit 4位十六进制Unicode字符, = \xXXXX
  • word 任何一个单词字符, 如A-Z, a-z, 0-9, _等, = \w
  • unicode 任何一个ASCII值大于255的字符
2.2.5 定位字符
定位字符可以把正则表达式固定到行首或行尾。在Perl正则全集中还能使正则表达式出现在一个单词内、在一个单词的开头或者一个单词的结尾, emeditor只是一个子集, 不包含这个功能。
  • ^ 匹配输入字符串开始的位置。如果设置customize中的"regular expressions can match new line characters",那么 ^ 还匹配 \n 或 \r 后面的位置。 但在中括号表达式中使用的情况除外,在那种情况下它对字符集求反。
  • $ 匹配输入字符串结尾的位置。如果设置customize中的"regular expressions can match new line characters",那么 $ 还匹配 \n 或 \r 前面的位置。
3. 分组捕获和替换
分组通常用来捕获特定模式的一组文本, 并用与之后的替换操作, 这也就是将分组和替换结合起来讲解的原因.
最基本的分组构造方式就是(),在左右括号中括起来的部分,就是一个分组;在正则全集中还有如(? )的命名分组方式,这种方式组合了模式在就是对分组的部分进行了命名,这样就可以通过该组的命名来获取信息, 但这种方式在emeditor中不被支持. 以下分别来介绍各种不同的分组:
  • () 组捕获. 这种分组对模式在括号内所捕获的字符进行组合, 并且每个分组捕获的匹配结果都将保存为一个实体以备其后的操作所引用. 甚至在正则全集中还可对前面的分组进行反向引用(这是题外话, emeditor不支持). 举例说明:
    源文本:

    代码:

    site status- online members: 65, online guests: 12



    使用正则表达式:



    代码:



    (members|guests): 其后是冒号和一个空格, 最后匹配至少一个数字. 匹配模式结果如下:


    代码:

    members: 65
    guests: 12

    其中members和guests在两次匹配中被捕捉, 可以在随后的操作中引用.

  • (?:) 非组捕获. 这种分组仅仅对模式在括号内所匹配的字符进行组合, 模式所匹配的字符将不会作为一个组来捕获. 虽然他也同样成为最终的匹配结果的一部分, 但无法为其后的操作所引用. 同样以上例继续:
    使用正则表达式:

    代码:

    (?:members|guests): \d+

    匹配模式结果同样为:

    代码:

    members: 65
    guests: 12

    但是members和guests仅仅在两次匹配中被分组, 并不被捕获, 也不可以在随后的操作中引用.
    使用非捕获组有其原因和场合. 其一, 从效率上说, 捕获一个分组需要消耗额外的资源和处理时间, 所以不应该捕获不需要使用的数据. 其二, 对模式中有多个捕获组的情况, 对不需要处理的分组进行捕获只会对分组信息造成混乱. 其三, 避免不需要贪婪匹配的场合发生贪婪匹配, 贪婪匹配是正则引擎的一个重要特性, 要说清楚其机理可能还需要另外开一个专题了. 对这一点, 还以上例说明一下:
    使用不带分组的正则表达式:

    代码:

    members|guests: \d+

    匹配模式为:

    代码:

    members
    guests: 12

    这个正则表达式的问题在于, 他匹配的是"members" 或 "guests: \d+", 这是模式中贪婪"消费"字符引起的. 而通过增加括号进行分组, 使正则引擎将两个匹配选项作为一个组处理, 从而正确匹配其中的一个匹配项.

  • (?=) 正声明组, 非捕获. 此分组中的模式必须出现在声明的右侧, 并且, 这个模式不构成匹配结果的一部分. 举例:
    源文本:

    代码:

    site status- online members: 65, online guests: 12

    使用正则表达式:

    代码:

    \S+(?=\s\d+)

    此模式中规定了\s\d+必须出现在\S+声明的右侧. 也就是说, 在至少一个非空格字符(声明)的右侧必须出现一个空格字符和至少一个数字, 而且只有这个声明构成匹配结果. 匹配模式结果如下:

    代码:

    members:
    guests:

    这两次匹配中不被捕捉.

  • (?!) 负声明组, 非捕获. 此分组中的模式不得出现在声明的右侧, 并且, 这个模式不构成匹配结果的一部分. 还是用上面的例子:
    使用正则表达式:

    代码:

    \d{2}(?!,)

    此模式中规定了","不得出现在\d{2}声明的右侧. 也就是说, 在连续两个数字(声明)的右侧不得出现逗号才能被匹配. 匹配模式结果如下:

    代码:

    12

    这两次匹配中不被捕捉.





严格的说, 后面两个分组不能称之为分组, 他们只是模式声明, 他们不能成为匹配结果, 也不能被捕获. 在正则全集中, 还有反向声明分组(?<=)(?), 在emeditor中不被支持.


说到括号的功能, 本来正则中的一个重要指令-条件指令和分组内联设定是不得不说的, 可惜的是... emeditor也同样不支持~~~~


在前面的例子中一直提到匹配之后的操作, 而这个进一步的操作最常见的就是替换. 先继续上面的例子:


源文本:

代码:



site status- online members: 65, online guests: 12



使用搜索正则表达式:



代码:



(members|guests)



和替换正则表达式:



代码:



ccf-\1



匹配模式结果如下:



代码:



members
guests



替换后的文本为:



代码:



site status- online ccf-members: 65, online ccf-guests: 12



其中members和guests在两次匹配中被捕捉, 在随后被引用, 并添加ccf-前缀后替换源文本中的匹配字符.

在匹配模式中的分组匹配结果将按前后顺序被正则引擎分别赋予内部组号, 在替换操作中就可以用\加上这个组号来引用相应的匹配结果. 继续上例:


使用搜索正则表达式:



代码:



(members|guests): (\d{2})



和替换正则表达式:



代码:



ccf-\1 = \2



匹配模式结果如下:



代码:



members: 65
guests: 12



替换后的文本为:



代码:



site status- online ccf-members = 65, online ccf-guests = 12



在emeditor的正则子集中增加了一个特殊的引用: \0 , \0 将引用上次的匹配结果, 继续把:

使用搜索正则表达式:



代码:



\d{2}



和替换正则表达式:



代码:



*\0*



匹配模式结果如下:



代码:



65
12



替换后的文本为:



代码:



site status- online ccf-members: *65*, online ccf-guests: *12*



作为一个编辑软件, emeditor的正则子集中增加了一些替换修饰符:





  • \U 大写修饰. 将其后的所有的字符替换为大写


  • \L 小写修饰. 将其后的所有的字符替换为小写


  • \H 半角修饰. 将其后的所有的字符替换为半角字符. 写到这里, 不得不称许一下emeditor对中文的良好支持, 这个\H至少我是很常用的, 不喜欢看到文本里面都是些123abc之类的全角字符...


  • \F 全角修饰. 将其后的所有的字符替换为全角字符


  • \E 关闭之前的\U, \L, \H, \F修饰.



4. 常见模式举例 .... 待续

JSP文件下载及出现getOutputStream() has already been called for this response的解决方法

[转自 http://iamin.blogdriver.com/iamin/1072546.html]

一、采用RequestDispatcher的方式进行
1、web.xml文件中增加
  <mime-mapping>
    <extension>doc</extension>
    <mime-type>application/vnd.ms-word</mime-type>
  </mime-mapping>
2、程序如下:
<%@page language="java" import="java.net.*" pageEncoding="gb2312"%>
<%
    response.setContentType("application/x-download");//设置为下载application/x-download
    String filenamedownload = "/系统解决方案.doc";//即将下载的文件的相对路径
    String filenamedisplay = "系统解决方案.doc";//下载文件时显示的文件保存名称
    filenamedisplay = URLEncoder.encode(filenamedisplay,"UTF-8");
    response.addHeader("Content-Disposition","attachment;filename=" + filenamedisplay);
    try
    {
        RequestDispatcher dispatcher = application.getRequestDispatcher(filenamedownload);
        if(dispatcher != null)
        {
            dispatcher.forward(request,response);
        }
        response.flushBuffer();
    }
    catch(Exception e)
    {
        e.printStackTrace();
    }
    finally
    {
    }
%>
二、采用文件流输出的方式下载
1、web.xml文件中增加
  <mime-mapping>
    <extension>doc</extension>
    <mime-type>application/vnd.ms-word</mime-type>
  </mime-mapping>
2、程序如下:
<%@page language="java" contentType="application/x-msdownload" import="java.io.*,java.net.*" pageEncoding="gb2312"%><%
    //关于文件下载时采用文件流输出的方式处理:
    //加上response.reset(),并且所有的%>后面不要换行,包括最后一个;
    //因为Application Server在处理编译jsp时对于%>和<%之间的内容一般是原样输出,而且默认是PrintWriter,
    //而你却要进行流输出:ServletOutputStream,这样做相当于试图在Servlet中使用两种输出机制,
    //就会发生:getOutputStream() has already been called for this response的错误
    //详细请见《More Java Pitfill》一书的第二部分 Web层Item 33:试图在Servlet中使用两种输出机制 270
    //而且如果有换行,对于文本文件没有什么问题,但是对于其它格式,比如AutoCAD、Word、Excel等文件
    //下载下来的文件中就会多出一些换行符0x0d和0x0a,这样可能导致某些格式的文件无法打开,有些也可以正常打开。
    response.reset();//可以加也可以不加
    response.setContentType("application/x-download");//设置为下载application/x-download
    // /../../退WEB-INF/classes两级到应用的根目录下去,注意Tomcat与WebLogic下面这一句得到的路径不同,WebLogic中路径最后没有/
    System.out.println(this.getClass().getClassLoader().getResource("/").getPath());
    String filenamedownload = this.getClass().getClassLoader().getResource("/").getPath() + "/../../系统解决方案.doc";
    String filenamedisplay = "系统解决方案.doc";//系统解决方案.txt
    filenamedisplay = URLEncoder.encode(filenamedisplay,"UTF-8");
    response.addHeader("Content-Disposition","attachment;filename=" + filenamedisplay);
    OutputStream output = null;
    FileInputStream fis = null;
    try
    {
        output  = response.getOutputStream();
        fis = new FileInputStream(filenamedownload);
        byte[] b = new byte[1024];
        int i = 0;
        while((i = fis.read(b)) > 0)
        {
            output.write(b, 0, i);
        }
        output.flush();
    }
    catch(Exception e)
    {
        System.out.println("Error!");
        e.printStackTrace();
    }
    finally
    {
        if(fis != null)
        {
            fis.close();
            fis = null;
        }
        if(output != null)
        {
            output.close();
            output = null;
        }
    }
%>

经过本人修改的JspSmartUpload组件 JspSmartUpload组件

使用javascript显示页面装载过程进度条

<script language="javascript" type="text/javascript">
var percent=0;          //百分比进度,显示在滚动条后面
var element="||";      //滚动条单元竖线
var elements="||";    //滚动条当前竖线
count();                //开始调用循环

function count(){
    percent=percent+10;    //每次百分比加10
    elements =elements + element;  //滚动条当前竖线增加一个滚动条单元竖线
    document.loading.bar.value=elements; //设置窗体loading表单中bar元素的当前值
    document.loading.percentage.value=percent+"%"; //设置窗体loading表单中percentage元素的当前值
    if (percent<99){                    //percent小于99则继续循环
        setTimeout("count()",500);        //每500ms进行一次count()
    }
    else{
         window.location = "http://campus.cslg.cn/";    //percent达到100时跳转
    }
}
</script>

2008年5月10日星期六

Linux下使用JNI控制squid代理服务器

利用JNI实现Linux下squid代理服务器的控制
1、Java文件

public class MyJni{
        public native void exec();
        static{
System.loadLibrary("MyJni");
        }
}
public class Test{
        public static void main(String[]  args){
                new MyJni().exec();
        }

2、编译,并用javah产生头文件

javac MyJni.java
javac Test.java
javah -jni MyJni 

3、编写C程序,注意包含上面产生的头文件

#include <jni.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include "MyJni.h"
JNIEXPORT void JNICALL Java_MyJni_exec(JNIEnv *env,jobject obj){
uid_t uid ,euid;
//      char cmd[1024];
uid = getuid() ;
euid = geteuid();
printf("my uid :%u\n",getuid());  //这里显示的是当前的uid 可以注释掉.
printf("my euid :%u\n",geteuid()); //这里显示的是当前的euid
if(setreuid(euid, uid))  //交换这两个id
perror("setreuid");
printf("after setreuid uid :%u\n",getuid());
printf("afer sertreuid euid :%u\n",geteuid());
system("service squid restart"); //重新启动squid服务
}
int main(){
Java_MyJni_exec(NULL,NULL);
        return 0;

4、编译

gcc -o libMyJni.so -shared MyJni.c -static -lc 

5、添加动态链接库所在的路径到LD_LIBRARY_PATH环境变量

export LD_LIBRARY_PATH=/root; 

6、运行Java程序

java Test 

通过之后,也可以在Web应用程序中调用此功能

Tomcat 5.5以上版本中数据库连接池的设置

在WebRoot下-INF目录中新建文件context.xml,内容如下

<Context path="/doclib" docBase="doclib" debug="5" reloadable="true" crossContext="true" >
<Resource name="jdbc/doclib" auth="Container"
            type="javax.sql.DataSource" username="" password=""
            driverClassName="sun.jdbc.odbc.JdbcOdbcDriver" url="jdbc:odbc:doclib"
            maxActive="8" maxIdle="4"/>
</Context>

用于取得数据库连接的Java Bean

import java.sql.Connection;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.sql.DataSource;

/**
* 从系统配置的JNDI数据连接池中获得数据库连接
*
* @author jimshen
*
*/
public class JndiBean {
/**
  * 从系统配置的JNDI数据连接池中获得数据库连接
  *
  * @return 获得的数据库连接,该连接在使用之后应该释放(close)
  * @throws Exception
  */
public static Connection getConnection() throws Exception {
  Connection conn = null;
  Context initCtx = new InitialContext();
  Context envCtx = (Context) initCtx.lookup("java:comp/env");
  DataSource ds = (DataSource) envCtx.lookup("jdbc/doclib");
  if (ds != null) {
   conn = ds.getConnection();
  } else
   throw new Exception("Can't find JNDI data source!");
  if (conn == null)
   throw new Exception("Can't establish connection!");
  return conn;
}
}

使用示例
Connection conn=JndiBean.getConnection();
....
conn.close();

Apache 2.0 on CentOS

 

在CentOS上启动Apache 2时遇到下面的问题

Permission denied: make_sock: could not bind to address *:80

解决方法

禁用selinux

# setenforce 0
然后尝试再次启动httpd服务

使用myEclipse开发JSP时遇到的奇怪问题

在MyEclipse中启动Tomcat,并将项目部署之后,在浏览器地址栏中输入 http://localhost/doclib/inex.jsp 进行测试。MyEclipse自动会跳出来一个对话框
This kind of launch is configured to open the Debug perspective when it suspends.Do you want to open this perspective now?

确认以后进入程序调试视图,并且自动跳出来一个叫getNextToken的页面,上面用红色字体写着:
Source not found for Scanner.getNextToken()line:899
在Debug视图中可以复制Exception Stack的内容如下:
Thread [http-8080-Processor24] (Suspended (exception ArrayIndexOutOfBoundsException))
Scanner.getNextToken() line: 899
Parser.parse() line: 8460
Parser.parse(ICompilationUnit, CompilationResult, int, int) line: 8657
Parser.parse(ICompilationUnit, CompilationResult) line: 8622
Parser.dietParse(ICompilationUnit, CompilationResult) line: 7420
Compiler.beginToCompile(ICompilationUnit[]) line: 289
Compiler.compile(ICompilationUnit[]) line: 315
JDTCompiler.generateClass(String[]) line: 404
JDTCompiler(Compiler).compile(boolean, boolean) line: 297
JDTCompiler(Compiler).compile(boolean) line: 276
JDTCompiler(Compiler).compile() line: 264
JspCompilationContext.compile() line: 563
JspServletWrapper.service(HttpServletRequest, HttpServletResponse, boolean) line: 303
JspServlet.serviceJspFile(HttpServletRequest, HttpServletResponse, String, Throwable, boolean) line: 314
JspServlet.service(HttpServletRequest, HttpServletResponse) line: 264
JspServlet(HttpServlet).service(ServletRequest, ServletResponse) line: 802
ApplicationFilterChain.internalDoFilter(ServletRequest, ServletResponse) line: 252
ApplicationFilterChain.doFilter(ServletRequest, ServletResponse) line: 173
StandardWrapperValve.invoke(Request, Response) line: 213
StandardContextValve.invoke(Request, Response) line: 178
StandardHostValve.invoke(Request, Response) line: 126
ErrorReportValve.invoke(Request, Response) line: 105
StandardEngineValve.invoke(Request, Response) line: 107
CoyoteAdapter.service(Request, Response) line: 148
Http11Processor.process(InputStream, OutputStream) line: 869
Http11Protocol$JmxHttp11ConnectionHandler(Http11BaseProtocol$Http11ConnectionHandler).processConnection(TcpConnection, Object[]) line: 664
PoolTcpEndpoint.processSocket(Socket, TcpConnection, Object[]) line: 527
LeaderFollowerWorkerThread.runIt(Object[]) line: 80
ThreadPool$ControlRunnable.run() line: 684
ThreadWithAttributes(Thread).run() line: 595

此时,按下F8继续运行(Resume)的话程序可以继续运行,但是这样比较麻烦。

在网络上也找到了类似的问题:

http://www.it130.net/JAVA/tomcat-jsp-12563.htm

http://www.51log.net/dev/5409/4717455.htm

他们解决的方法是降低Tomcat的版本,从5.5降到5.0就可以了。但是我的项目是基于5.5的,一部分工作已经完成。经过研究,发现这其实不是Tomcat的问题,因为如果单独启动Tomcat的话网站运行是正常的。

在MyEclipse网站上发现了这篇文章

http://www.myeclipseide.com/index.php?name=PNphpBB2&file=viewtopic&p=39901

其中的解决方法是

This is the ghost breakpoint issue we have seen before, normally using -clean fixes it, but as you mentioned it didn't help. The solution is to create a new workspace and reimport your projects into it. We have been unable to track down why breakpoints are getting set or where they come from, and it happens very infrequently. I've had it happen once to me, and I know 2 other users that had it happen. In all cases a new workspace fixed it and it never came back.

而且这种情况是非常罕见的。

具体来说,解决的方法是:

在磁盘上新建一个目录作为新的工作区(workspace),然后在Eclipse中选择菜单项 File|Switch workspace 切换到新的工作区,将原来的项目导入。再重新启动Tomcat,问题解决。

也可以先将workspace中的全部项目导出,退出eclipse后,删除该workspace对应目录下的所有内容;然后启动eclipse并导入刚才导出的项目。

个人认为:该错误可能是由workspace目录下 .data\.plugins 中的插件出现混乱引起的

JSP Web应用程序中使用XML文件存储系统配置参数

在使用J2EE编写Web应用程序时,经常把参数设置在一个XML文件中,在Web应用程序启动时来读取。下面就是一个实际应用中的例子。

XML文件存放在WEB-INF目录下,文件名是SysConfig.xml,其内容如下:

<?xml version="1.0" encoding="utf-8"?>
<DocRoot>
<SysConfig>
  <BaseFolder>D:/DocLib</BaseFolder>
  <TempUploadFolder>D:/DocLib/temp</TempUploadFolder>
  <DocFolder>D:/DocLib/docs</DocFolder>
  <IndexFolder>D:/DocLib/index</IndexFolder>
  <LiusConfig>D:/DocLib/Config/liusConfig.xml</LiusConfig>
  <Log4jConfig>D:/DocLib/Config/log4j/log4j.properties</Log4jConfig>
  <Database>D:/DocLib/database/database.mdb</Database>
</SysConfig>
</DocRoot>

为了在Web应用程序启动时读取这些配置值,编写了一个Servlet如下

package bootstrap;

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import util.XMLHelper;
import util.XMLHelperException;

/**
* LoadConfig is a sevlet which load config properties from XML files named SysConfig.xml
* when the web application start.
* All these properties are stored in servlet context. The key is the same as the node name
* in XML file.
* @author jimshen
*
*/

public class LoadConfig extends HttpServlet {

public void init(ServletConfig config) throws ServletException {
  super.init(config);
  String configFile = getServletContext().getRealPath("/")
    + "WEB-INF\\SysConfig.xml";
  Document doc = null;
  ServletContext application = getServletContext();
  try {
   doc = XMLHelper.parseXMLFromFile(configFile);
   Element root = doc.getDocumentElement();
   NodeList nodes = root.getElementsByTagName("SysConfig");
   NodeList children = nodes.item(0).getChildNodes();
   for (int i = 0; i < children.getLength(); i++) {
    Node node = children.item(i);
if (node.getNodeType() != Node.ELEMENT_NODE)
     continue;
    String key=node.getNodeName();
    String = node.getFirstChild().getNode().trim();
    application.setAttribute(key,);
   }
  } catch (XMLHelperException e) {
   throw new ServletException(e.toString());
  }
}

public void doGet(HttpServletRequest request, HttpServletResponse response)
   throws ServletException, IOException {
  PrintWriter out = response.getWriter();
  out.println("This Servlet does not service requests!");
}

public void doPost(HttpServletRequest request, HttpServletResponse response)
   throws ServletException, IOException {
  PrintWriter out = response.getWriter();
  out.println("This Servlet does not service requests!");
}

}

web.xml文件中将servlet配置为启动时加载

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.4"
xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
  <servlet>
    <deion>Load Configuration Information From SysConfig.xml</deion>
    <display-name>LoadConfig</display-name>
    <servlet-name>LoadConfig</servlet-name>
    <servlet-class>bootstrap.LoadConfig</servlet-class>
<load-on-startup>1</load-on-startup>
  </servlet>

  <servlet-mapping>
    <servlet-name>LoadConfig</servlet-name>
    <url-pattern>/servlet/LoadConfig</url-pattern>
  </servlet-mapping>

</web-app>

【转帖】你有几个朋友?

兄弟我最近缺钱????
上个月,我的一个朋友魏某因为生意上出了点意外,急需要一笔钱,当他打电话给我时,我感觉有一点奇怪,因为我们的关系仅仅只限于一般朋友,故此,就有了一点点犹豫。我说:一会儿我给你电话吧。
我考虑了十分钟,决定把这钱借给他。
上个星期,他把钱还给了我,之后请我喝茶。
他说:你答应借钱给我还真出乎我的意料之外!
我问:为什么?
他回答:打你电话之前我已经打过9通电话,你是第10个。当你说“一会儿给你电话”时,我认为我需要打第11通电话了。我是按照亲疏关系打的这10通电话,越打到后面越没有信心,所以,打你电话已经是死马当成活马医的 心态了。
之后,就这个话题我们谈论了许多,他总结性地说了一句话:如果不是这次借钱,我还以为我有很多朋友,现在我才明白,原来我是这么孤独。
过后的几天我都在想这件事,然后,我决定了解一下自己到底是否也如我那个朋友一 样那么孤独。在做这件事之前,我把想法打电话告诉了他,他笑了:我劝你还是别做这 种游戏,这会让你感觉从天堂落入了地狱!
我把现在身边自认为的好朋友的名字挑了出来,这些人都在本地,外地的暂不列入,他们和我从来没有金钱上的借贷关系,也和我的工作没有任何牵连,我们经常在一起,要不吃饭,要不喝茶,要不泡酒吧,相互之间我帮他们的一点小忙的时候居多,属于纯粹意义上的朋友,有9个人,而且他们的经济实力借个几万块肯定是没很大的问题的。
我给他们每人发了一条内容差不多的短信:我现在遇到点麻烦,需要问你借X万块钱, 一个月之内归还。如行的话给我电话,不行就发个信息吧,我等你答复 。
我是昨天下午发的,晚饭时间未到,收到了7条信息,2通电话。
信息基本回的都很快,全没超过一个小时,其中一通电话是信息发出后20分钟左右来的,还有一通是信息发出后2个半小时左右打来的。7条信息内容如下:(除了我的名字用xx代替,别人的名字用Y代替,信息内容一字未改)
陈:真对不起!我目前有点困难,真的,要不然你的事情肯定没话说的,你问问YYY吧,不好意思!(注:YYY是我们一起认识的朋友)
乔:XX,上个星期我小舅子刚问我借了20万,下个月还有点可能,真对不起!
唐:这段时间我自己都很困难,前一段赌球输了好多钱,XX,不好意思,我要情况好的话绝对没问题的。
王:真不好意思了,我的钱都在股票里,对不起!
陈:XX,你怎么会要借钱哦,我昨天才借给人家10万,是放息的,你又不早说,不好意思,你再想想别的办法咯。
陆:对不起,最近我的股票都套牢了,手里没有现金,不好意思呵!
章:XX,我儿子开学就要转到浦东的YY中学去(这里 校名省略),那是寄宿学校,开学就要交5万,真的没办法帮你,请原谅!
电话是姓王和姓张的朋友打来的。
第一通电话:
王:喂,XX吗?
我:你好,是我!
王:搞什么搞,怎么这么点钱还要借啊?你出什么事儿了?
我:没出什么事儿,我钱放在市场里,一时半会儿出不来,我弟弟有点事儿,急用的。
王:没出事儿就好。你在公司啊?
我:啊,是啊!
王:我儿子上学被人家自行车给撞了,小腿骨折,我几天都没出门了。
我:啊?你儿子骨折怎么没听你说啊?要帮忙吗?
王:我请了一星期假,我老公那个该死的公司又请不到假,我准备下个星期让我妈过来帮着照顾,你别管了!你把银行卡的卡号告诉我,我让我老公明天上班给你打过去。
第二通电话:
张:喂,XX啊,我是张Y哦,你现在在哪里?
我:我在公司啊!
张:哦,我刚到店里,钱已经准备好了,是我送过去还是你过来拿?
我:怎么好意思让你送过来啊,这样吧,我一会儿去你那儿给你写张借条 ,钱到时就打到我卡上吧。
张:那你把卡号给我,我现在就帮你打过去,什么行啊?工行还是建行?
我:随便,哪个行你方便?
张:我店对面就是工行,你把工行卡号给我吧。
昨晚,魏某和我又一起去了咖吧,又各自发了一番感慨。我告诉他,借钱给我的这2个 朋友平时从来没有麻烦过我替他们解决任何事情,其它的朋友倒是时不时地要麻烦我, 一会儿是电脑的问题,一会儿是股票的问题,一会儿是投资的问题??
他问我:借你钱的朋友你准备告诉他们实情吗?
我说:除非我今晚开始神经了!
他笑了,很落寞地说:从现在开始,你只有这2个朋友了。
我不记得曾经看过一本什么书,似乎有过这样一句话:帮助过你的人永远都会帮助你,但你帮过的人就不一定。我也笑了,看着眼前袅袅升起的烟雾,淡淡地说:求之不得 !
回家后,我在台历上下个月的昨天做了个记号,提醒自己千万别忘了还朋友钱。

在Java程序中读取HTTPS页面的类

import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;

import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;

public class HttpsReader {
public String getHTML(String url) throws IOException {
  // Create a trust manager that does not validate certificate chains
  TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() {
   public java.security.cert.X509Certificate[] getAcceptedIssuers() {
    return null;
   }

   public void checkClientTrusted(java.security.cert.X509Certificate[] certs, String authType) {
   }

   public void checkServerTrusted(java.security.cert.X509Certificate[] certs, String authType) {
   }
  } };

  // Install the all-trusting trust manager
  try {
   SSLContext sc = SSLContext.getInstance("SSL");
   sc.init(null, trustAllCerts, new java.security.SecureRandom());
   HttpsURLConnection
     .setDefaultSSLSocketFactory(sc.getSocketFactory());
  } catch (Exception e) {
  }
  URL sslUrl = new url(/blog/url);
  URLConnection inConnection = sslUrl.openConnection();
  InputStream is = inConnection.getInputStream();

  byte[] buffer = new byte[16384];
  StringBuffer sb = new StringBuffer();

  while (true) {
   int bytesRead = is.read(buffer, 0, buffer.length);
   if (bytesRead < 0)
    break;
   String s = new String(buffer, 0, bytesRead, "GB2312");
   sb.append(s);
  }
  is.close();

  String outContent = sb.toString();
  return outContent;
}

}