说说IO(一)

Posted on

说说IO(一)- IO的分层

IO性能对于一个系统的影响是至关重要的。一个系统经过多项优化以后,瓶颈往往落在数据库;而数据库经过多种优化以后,瓶颈最终会落到IO。而IO性能的发展,明显落后于CPU的发展。Memchached也好,NoSql也好,这些流行技术的背后都在直接或者间接地回避IO瓶颈,从而提高系统性能。

IO**系统的分层:**

  1. 三层结构

上图层次比较多,但总的就是三部分。磁盘(存储)、VM(卷管理)和文件系统。专有名词不好理解,打个比方说:磁盘就相当于一块待用的空地;LVM相当于空地上的围墙(把空地划分成多个部分);文件系统则相当于每块空地上建的楼房(决定了有多少房间、房屋编号如何,能容纳多少人住);而房子里面住的人,则相当于系统里面存的数据。

  • 文件系统—数据如何存放?

对应了上图的File System和Buffer Cache。

File System**(文件系统):解决了空间管理的问题**,即:数据如何存放、读取。

Buffer Cache:解决数据缓冲的问题。对读,进行cache,即:缓存经常要用到的数据;对写,进行buffer,缓冲一定数据以后,一次性进行写入。

  • VM—磁盘空间不足了怎么办?

对应上图的Vol Mgmt。

VM其实跟IO没有必然联系。他是处于文件系统和磁盘(存储)中间的一层。VM**屏蔽了底层磁盘对上层文件系统的影响**。当没有VM的时候,文件系统直接使用存储上的地址空间,因此文件系统直接受限于物理硬盘,这时如果发生磁盘空间不足的情况,对应用而言将是一场噩梦,不得不新增硬盘,然后重新进行数据复制。而VM则可以实现动态扩展,而对文件系统没有影响。另外,VM也可以把多个磁盘合并成一个磁盘,对文件系统呈现统一的地址空间,这个特性的杀伤力不言而喻。

  • 存储—数据放在哪儿?如何访问?如何提高IO速度?

对应上图的Device Driver、IO Channel和Disk Device

数据最终会放在这里,因此,效率、数据安全、容灾是这里需要考虑的问题。而提高存储的性能,则可以直接提高物理IO的性能

2. Logical IO vs Physical IO

逻辑IO是操作系统发起的IO,这个数据可能会放在磁盘上,也可能会放在内存(文件系统的Cache)里。

物理IO是设备驱动发起的IO,这个数据最终会落在磁盘上。

  逻辑IO和物理IO不是一一对应的。

Java使用”指针”快速比较字节

Posted on

Java使用”指针”快速比较字节

如何才能快速比较两个字节数组呢?我将问题描述成下面的接口:

public int compareTo(byte[] b1, int s1, int l1, byte[] b2, int s2,int l2);

最直观的做法是同时遍历两个数组,两两比较。

public int compareTo(byte[] buffer1, int offset1, int length1, byte[] buffer2, int offset2, int length2) {

// Short circuit equal case
if (buffer1 == buffer2 && offset1 == offset2

    && length1 == length2) {
    return 0;

}
// Bring WritableComparator code local

int end1 = offset1 + length1;
int end2 = offset2 + length2;

for (int i = offset1, j = offset2; i < end1 && j < end2; i++, j++) {
    int a = (buffer1[i] & 0xff);

    int b = (buffer2[j] & 0xff);
    if (a != b) {

return a - b; }

}
return length1 - length2;

}

如果事情这么简单就结束了,就没有意思了。

如果要提升性能,可以做循环展开等等优化,但这些优化应该依赖JVM来做,新的JVM可以做的很好。那还有什么办法可以提高性能呢? 可以将字节数组合并!!上面的例子中,每个byte被迫转型成了int,再比较。其实我们可以将8个byte转换成一个long,在比较long,这样效果会不会好些?用什么方法转换才是最优的?

long sun.misc.Unsafe.getLong(Object o,int offset)

Java提供了一个本地方法,可以最快最好转换byte与long。该函数是直接访问一个对象的内存,内存地址是对象指针加偏移量,返回该地址指向的值。有人说Java很安全,不可以操作指针,所以有的时候性能也不高。其实不对,有了这个Unsafe类,Java一样也不安全。所以Unsafe类中的方法都不是public的,不过没关系,我们有反射。言归正传,下面是使用这种技术手段的实现代码。

public int compareTo(byte[] buffer1, int offset1, int length1, byte[] buffer2, int offset2, int length2) {

// Short circuit equal case
if (buffer1 == buffer2 && offset1 == offset2

        && length1 == length2) {
    return 0;

}
int minLength = Math.min(length1, length2);

int minWords = minLength / Longs.BYTES;
int offset1Adj = offset1 + BYTE_ARRAY_BASE_OFFSET;

int offset2Adj = offset2 + BYTE_ARRAY_BASE_OFFSET;


//*
 /* Compare 8 bytes at a time. Benchmarking shows comparing 8

 /* bytes at a time is no slower than comparing 4 bytes at a time
 /* even on 32-bit. On the other hand, it is substantially faster

 /* on 64-bit.
 /*/

for (int i = 0; i < minWords /* Longs.BYTES; i += Longs.BYTES) {
    long lw = theUnsafe.getLong(buffer1, offset1Adj + (long) i);

    long rw = theUnsafe.getLong(buffer2, offset2Adj + (long) i);
    long diff = lw ^ rw;


    if (diff != 0) {

        if (!littleEndian) {
            return (lw + Long.MIN_VALUE) < (rw + Long.MIN_VALUE) ? -1

                    : 1;
        }


        // Use binary search,一下省略若干代码

        .....
        return (int) (((lw >>> n) & 0xFFL) - ((rw >>> n) & 0xFFL));

    }
}


// The epilogue to cover the last (minLength % 8) elements.

for (int i = minWords /* Longs.BYTES; i < minLength; i++) {
    int result = UnsignedBytes.compare(buffer1[offset1 + i],

            buffer2[offset2 + i]);
    if (result != 0) {

        return result;
    }

}
return length1 - length2;

}

实现比原来复杂了一些。但这次一次可以比较8个字节了。这种getLong函数和系统的字节序是紧紧相关的,如果是小端序操作起来有点麻烦,代码先省略掉。这样操作实际效果如何?我们需要对比测试下。对比两个1M的字节数组,如果使用第一个版本,每次比较平均需要2.5499ms,如果使用第二个版本,需要0.8359ms,提升了3倍。对应这种CPU密集型的操作,这样的提升可是很可观的。

如果要提升性能,使用Unsafe直接访问内存也是不错的选择。 Share the post "Java使用"指针"快速比较字节"

JSTL EL 详解

Posted on

JSTL EL 详解

概述**:** JavaWind.net Document 在JSP页面中,使用标签库代替传统的Java片段语言来实现页面的显示逻辑已经不是新技术了,然而,由自定义标签很容易造成重复定义和非标准的实现。鉴于此,出现了JSTL(JSP Standard Tag Library),为大多数JSP页面逻辑提供了实现的JSTL技术,该技术本身就是一个标签库。 Sun公司Java规范标准的JSTL由apache jakarta组织负责维护。作为开源的标准技术,它一直在不断地完善。JSTL的发布包有两个版本:Standard-1.0 Taglib、Standard-1.1 Taglib,它们在使用时是不同的。 Standard-1.0 Taglib(JSTL1.0)支持Servlet2.3和JSP1.2规范,Web应用服务器Tomcat4支持这些规范,而它的发布也在Tomcat 4.1.24测试通过了。 Standard-1.1 Taglib(JSTL1.1)支持Servlet2.4和JSP2.0规范,Web应用服务器Tomcat5支持这些规范,它的发布在Tomcat 5.0.3测试通过了。 在本章的介绍中,将以由Sun发布的Standard-1.1 Taglib标签库为主,而apache jakarta组织发布的开源标签库,可以从http://jakarta.apache.org/taglibs/找到所需要的帮助。Sun发布的标准JSTL1.1标签库有以下几个标签: 核心标签库:包含Web应用的常见工作,比如:循环、表达式赋值、基本输入输出等。 国际化标签库:用来格式化显示数据的工作,比如:对不同区域的日期格式化等。 数据库标签库:可以做访问数据库的工作。 XML标签库:用来访问XML文件的工作,这是JSTL标签库的一个特点。 函数标签库:用来读取已经定义的某个函数。

  此外,JSTL还提供了EL表达式语言(Expression Language)来进行辅助的工作。
  JSTL标签库由标签库和EL表达式语言两个部分组成。EL在JSTL 1.0规范中被引入,当时用来作为Java表达式来工作,而该表达式必须配合JSTL的标签库才能得到需要的结果。
  说明:在JSTL 1.1规范中,JSP2.0容器已经能够独立的理解任何EL表达式。EL可以独立出现在JSP页面的任何角落。本文随后的内容将以JSTL 1.1规范作为介绍的重点。

9.2.1 JSTL EL**表达式语言简介** JavaWind.net Document EL是从JavaScript脚本语言得到启发的一种表达式语言,它借鉴了JavaScript多类型转换无关性的特点。在使用EL从scope中得到参数时可以自动转换类型,因此对于类型的限制更加宽松。Web服务器对于request请求参数通常会以String类型来发送,在得到时使用的Java语言脚本就应该是request.getParameter(“XXX”),这样的话,对于实际应用还必须进行强制类型转换。而EL就将用户从这种类型转换的繁琐工作脱离出来,允许用户直接使用EL表达式取得的值,而不用关心它是什么类型。 下面的示例就是一个EL表达式,见例9.1。 例9.1:简单EL表达式

  <%@ page contentType="text/html; charset=UTF-8"%>
  <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
  <html>
   <body> ${sampleValue + 1} <br> </body>
  </html>

  这个示例将在JSP页面显示为“1”,EL表达式必须以“${XXX}”来表示,其中“XXX”部分就是具体表达式内容,“${}”将这个表达式内容包含在其中作为EL表达式的定义。本示例可以在满足JSP2.0规范的任何Web应用服务器中使用。

9.2.2 EL**表达式的默认变量** JavaWind.net Document 一个EL表达式包含变量和操作符两个内容。任何存在于JSP作用范围的JavaBean都可以被转化成EL表达式来使用,它所包含的默认变量如下:

1**.默认变量pageScope、requestScope、sessionScope、applicationScope** 这4个默认变量包含Scope作用范围的参数集合,相当于被保存在java.util.Map中的某个参数。下面看简单的示例9.2:

例9.2:使用sessionScope变量的EL表达式

<%request.getSession().setAttribute("sampleValue", new Integer(10));%> ${sessionScope.sampleValue}

取得保存在Session中参数的sessionScope变量的EL表达式,“.”是property访问操作符,在这里表示从Session中取得“键”为“sampleValue”的参数,并显示出来。显示结果为“10”。

2**.默认变量param、paramValues** 这两个默认变量包含请求参数的集合,param表明请求包含的参数为单一控件,paramValues表明请求包含的参数为控件数组。下面看一个简单示例9.3:

例9.3:提交请求的页面和接受的页面

\<%@ page contentType="text/html; charset=UTF-8"%> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">

  在这个页面中定义了两组控件,控件名为“sampleValue”的是一套控件数组,控件名为“sampleSingleValue”的是单一控件,通过递交将请求参数传送到SampleJsp.jsp。

<%@ page contentType="text/html; charset=UTF-8"%> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">

${paramValues.sampleValue[2]}
${param.sampleSingleValue}
  这是请求转发到的页面,通过EL表达式的paramValues变量得到控件数组中最后一个控件的递交参数,通过EL表达式的param变量得到单一控件的递交参数。控件数组参数的EL表达式使用“[]”来指定数组下标。本示例将显示控件数组中最后一个控件的值“12”和单一控件的值“SingleValue”。

3**.默认变量header、headerValues** 这两个默认变量包含请求参数头部信息的集合,header变量表示单一头部信息,headerValues则表示数组型的头部信息。

4**.默认变量cookie** 包含所有请求的cookie集合,集合中的每个对象对应javax.servlet.http.Cookie。

5**.默认变量initParam** 包含所有应用程序初始化参数的集合。

6**.默认变量pageContext** 等价于page环境类javax.servlet.jsp.PageContext的实例,用来提供访问不同的请求参数。 11个默认变量几乎包含了Web应用的所有基本操作,若一个表达式不使用这些变量而直接使用参数名,那么就采用就近原则。该表达式将使用最近取得的参数值。

**标签库介绍** JavaWind.net Document

表达式的操作符 EL表达式中还有许多操作符可以帮助完成各种所需的操作,之前的示例中“.”、“[]”就是其中的两个,下面将用表9.1来展示所有操作符及它们各自的功能。

表9.1 EL表达式的操作符 操作符

功能和作用 .

访问一个bean属性或者Map entry []

访问一个数组或者链表元素 ()

对子表达式分组,用来改变赋值顺序 ? :

条件语句,比如:条件?ifTrue:ifFalse

如果条件为真,表达式值为前者,反之为后者 +

数学运算符,加操作 -

数学运算符,减操作或者对一个值取反 /*

数学运算符,乘操作 / 或div

数学运算符,除操作 % 或mod

数学运算符,模操作(取余) == 或eq

逻辑运算符,判断符号左右两端是否相等,如果相等返回true,否则返回false != 或ne

逻辑运算符,判断符号左右两端是否不相等,如果不相等返回true,否则返回false < 或lt

逻辑运算符,判断符号左边是否小于右边,如果小于返回true,否则返回false > 或gt

逻辑运算符,判断符号左边是否大于右边,如果大于返回true,否则返回false <= 或le

逻辑运算符,判断符号左边是否小于或者等于右边,如果小于或者等于返回true,否则返回false >= 或ge

逻辑运算符,判断符号左边是否大于或者等于右边,如果大于或者等于返回true,否则返回false && 或and

逻辑运算符,与操作赋。如果左右两边同为true返回true,否则返回false || 或or

逻辑运算符,或操作赋。如果左右两边有任何一边为true返回true,否则返回false ! 或not

逻辑运算符,非操作赋。如果对true取运算返回false,否则返回true empty

用来对一个空变量值进行判断: null、一个空String、空数组、空Map、没有条目的Collection集合 func(args)

调用方法, func是方法名,args是参数,可以没有,或者有一个、多个参数.参数间用逗号隔开

这些操作符都是极其有用的,下面通过几个示例来演示它们的使用方法:

例9.4:几组操作符的示例

${pageScope.sampleValue + 12}
//显示12 ${(pageScope.sampleValue + 12)/3}
//显示4.0 ${(pageScope.sampleValue + 12) /3==4}
//显示true ${(pageScope.sampleValue + 12) /3>=5}
//显示false

//显示值为10的Text控件

可以看到,对于这些示例,程序设计者完全无需管理它们的类型转换,在表达式内部都已经处理了。有了EL表达式,在JSP页面的编程变得更灵活,也更容易。

**标签库介绍** 在JSTL1.1中有以下这些标签库是被支持的:Core标签库、XML processing标签库、I18N formatting标签库、Database access标签库、Functions标签库。 对应的标识符见表9.2所示:

表9.2 标签库的标识符 标签库

URI

前缀 Core

http://java.sun.com/jsp/jstl/core

c XML processing

http://java.sun.com/jsp/jstl/xml

x I18N formatting

http://java.sun.com/jsp/jstl/fmt

fmt Database access

http://java.sun.com/jsp/jstl/sql

sql Functions

http://java.sun.com/jsp/jstl/functions

fn

下面看例9.5,简单使用标签库的示例。

例9.5:简单JSTL标签库示例

<%@ page contentType="text/html; charset=UTF-8"%> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">

${i}

在该示例的JSP页面中声明了将使用Core标签库,它的URI为“http://java.sun.com/jsp/jstl/core”,前缀为“c”。之后,页面中标签就是使用了JSTL的标签进行了工作。对于该标签的功能,这里暂时不作具体讲解,只是让读者能够有个简单的概念,了解怎样定义和使用标签库。 标签库 Core标签库,又被称为核心标签库,该标签库的工作是对于JSP页面一般处理的封装。在该标签库中的标签一共有14个,被分为了四类,分别是:

     多用途核心标签:<c:out>、<c:set>、<c:remove>、<c:catch>。
     条件控制标签:<c:if>、<c:choose>、<c:when>、<c:otherwise>。
     循环控制标签:<c:forEach>、<c:forTokens>。
     URL相关标签:<c:import>、<c:url>、<c:redirect>、<c:param>。

以下是各个标签的用途和属性以及简单示例。

用于显示的****标签 JavaWind.net Document 标签是一个最常用的标签,用于在JSP中显示数据。它的属性和描述如表9.3所示:

表9.3 标签属性和说明 属性

描述 value

输出到页面的数据,可以是EL表达式或常量(必须) default

当value为null时显示的数据(可选) escapeXml

当设置为true时会主动更换特殊字符,比如“<,>,&”(可选,默认为true)

在JSTL1.0的时候,在页面显示数据必须使用来进行。然而,在JSTL1.1中,由于JSP2.0规范已经默认支持了EL表达式,因此可以直接在JSP页面使用表达式。下面看一个示例。

该示例将从Session查找名为“anyValue”的参数,并显示在页面,若没有找到则显示“no value”。

标签 JavaWind.net Document

用于赋值的标签 标签用于为变量或JavaBean中的变量属性赋值的工作。它的属性和描述如表9.4所示:

表9.4 标签属性和说明 属性

描述 value

值的信息,可以是EL表达式或常量 target

被赋值的JavaBean实例的名称,若存在该属性则必须 存在property属性(可选) property

JavaBean实例的变量属性名称(可选) var

被赋值的变量名(可选) scope

变量的作用范围,若没有指定,默认为page(可选)

当不存在value的属性时,将以包含在标签内的实体数据作为赋值的内容。下面看一个示例:

${oneString}

该示例将为名为“oneString”的变量赋值为“this is andy”,其作用范围为page。

9.3.3 **用于删除的 标签 ** 标签用于删除存在于scope中的变量。它的属性和描述如表9.5所示:

表9.5 标签属性和说明 属性

描述 var

需要被删除的变量名 scope

变量的作用范围,若没有指定,默认为全部查找(可选)

下面看一个示例:

${sessionScope.sampleValue}

该示例将存在于Session中名为“sampleValue”的变量删除。下一句EL表达式显示该变量时,该变量已经不存在了。

9.3.4 **用于异常捕获的 标签 ** 标签允许在JSP页面中捕捉异常。它包含一个var属性,是一个描述异常的变量,改变量可选。若没有var属性的定义,那么仅仅捕捉异常而不做任何事情,若定义了var属性,则可以利用var所定义的异常变量进行判断转发到其他页面或提示报错信息。看一个示例。

${param.sampleSingleValue[9] == 3} ${err}

当“${param.sampleSingleValue[9] == 3}”表达式有异常时,可以从var属性“err”得到异常的内容,通常判断“err”是否为null来决定错误信息的提示。

9.3.5 **用于判断的 标签 ** 标签用于简单的条件语句。它的属性和描述如表9.6所示:

表9.6 标签属性和说明 属性

描述 test

需要判断的条件 var

保存判断结果true或false的变量名,该变量可供之后的工作使用(可选) scope

变量的作用范围,若没有指定,默认为保存于page范围中的变量(可选)

下面看一个示例:

It is 12
${visits}

该示例将判断request请求提交的传入控件数组参数中,下标为“2”的控件内容是否为“12”,若为12则显示“It is 12”。判断结果被保存在page范围中的“visits”变量中。

9.3.6 **用于复杂判断的 标签 ** 这三个标签用于实现复杂条件判断语句,类似“if,elseif”的条件语句。

     <c:choose>标签没有属性,可以被认为是父标签,<c:when>、<c:otherwise>将作为其子标签来使用。
     <c:when>标签等价于“if”语句,它包含一个test属性,该属性表示需要判断的条件。
     <c:otherwise>标签没有属性,它等价于“else”语句。

下面看一个复杂条件语句的示例。

not 12 not 13,it is 11 not 11 not 13,it is 12 not 11 not 12,it is 13 not 11 、12、13

该示例将判断request请求提交的传入控件数组参数中,下标为“2”控件内容是否为“11”或“12”或“13”,并根据判断结果显示各自的语句,若都不是则显示“not 11 、12、13”。

9.3.7 **用于循环的 标签** 为循环控制标签。它的属性和描述如表9.7所示:

表9.7 标签属性和说明 属性

描述 items

进行循环的集合(可选) begin

开始条件(可选) end

结束条件(可选) step

循环的步长,默认为1(可选) var

做循环的对象变量名,若存在items属性,则表示循环集合中对象的变量名(可选) varStatus

显示循环状态的变量(可选)

下面看一个集合循环的示例。

<%ArrayList arrayList = new ArrayList(); arrayList.add("aa"); arrayList.add("bb"); arrayList.add("cc"); %> <%request.getSession().setAttribute("arrayList", arrayList);%>

${arrayListI}

该示例将保存在Session中的名为“arrayList”的ArrayList类型集合参数中的对象依次读取出来,items属性指向了ArrayList类型集合参数,var属性定义了一个新的变量来接收集合中的对象。最后直接通过EL表达式显示在页面上。下面看一个简单循环的示例。

${i}

该示例从“1”循环到“10”,并将循环中变量“i”显示在页面上。

9.3.8 **用于分隔字符的 标签 ** 标签可以根据某个分隔符分隔指定字符串,相当于java.util.StringTokenizer类。它的属性和描述如表9.8所示:

表9.8 标签属性和说明 属性

描述 items

进行分隔的EL表达式或常量 delims

分隔符 begin

开始条件(可选) end

结束条件(可选) step

循环的步长,默认为1(可选) var

做循环的对象变量名(可选) varStatus

显示循环状态的变量(可选)

下面看一个示例。

${aValue}

需要分隔的字符串为“aa,bb,cc,dd”,分隔符为“,”。begin属性指定从第一个“,”开始分隔,end属性指定分隔到第三个“,”,并将做循环的变量名指定为“aValue”。由于步长为“2”,使用EL表达式${aValue}只能显示“aa

标签

9.3.9 **用于包含页面的 ** 标签允许包含另一个JSP页面到本页面来。它的属性和描述如表9.9所示:

表9.9 标签属性和说明 属性

描述 rl

需要导入页面的URL context

Web Context该属性用于在不同的Context下导入页面,当出现context属性时,必须以“/”开头,此时也需要url属性以“/”开头(可选) charEncoding

导入页面的字符集(可选) var

可以定义导入文本的变量名(可选) scope

导入文本的变量名作用范围(可选) varReader

接受文本的java.io.Reader类变量名(可选)

下面看一个示例。

该示例演示了三种不同的导入方法,第一种是在同一 Context 下的导入,第二种是在不同的 Context 下导入,第三种是导入任意一个 URL 。

9.3.10 **用于得到URL地址的标签** 标签用于得到一个 URL 地址。它的属性和描述如表 9.10 所示:

表9.10 标签属性和说明 属性

描述 value

页面的URL地址 context

Web Context该属性用于得到不同Context下的URL地址,当出现context属性时,必须以“/”开头,此时也需要url属性以“/”开头(可选) charEncoding

URL的字符集(可选) var

存储URL的变量名(可选) scope

变量名作用范围(可选)

下面看一个示例:

link

得到了一个 URL 后,以 EL 表达式放入 标签的 href 属性,达到链接的目的。

9.3.11 **用于页面重定向的标签** 用于页面的重定向,该标签的作用相当于 response.setRedirect 方法的工作。它包含 url 和 context 两个属性,属性含义和 标签相同。下面看一个示例。

该示例若出现在 JSP 中,则将重定向到当前 Web Context 下的“ MyHtml.html ”页面,一般会与 等标签一起使用。

9.3.12 **用于包含传递参数的标签** 用来为包含或重定向的页面传递参数。它的属性和描述如表 9.11 所示:

表9.11 标签属性和说明 属性

描述 name

传递的参数名 value

传递的参数值(可选)

下面是一个示例:

该示例将为重定向的“ MyHtml.jsp ”传递指定参数“ userName=’RW’ ”。

9.4 JSTL XML processing**标签库** JavaWind.net Document 在企业级应用越来越依赖 XML 的今天, XML 格式的数据被作为信息交换的优先选择。 XML processing 标签库为程序设计者提供了基本的对 XML 格式文件的操作。在该标签库中的标签一共有 10 个,被分为了三类,分别是:

     XML核心标签:<x:parse>、<x:out>、<x:set>。
     XML流控制标签:<x:if>、<x:choose>、<x:when>、<x:otherwise>、<x:forEach>。
     XML转换标签:<x:transform>、<x:param>。

由于该组标签库专注于对某一特定领域的实现,因此本书将只选择其中常见的一些标签和属性进行介绍。

9.4.1 **用于解析XML文件的标签** 标签是该组标签库的核心,从其标签名就可以知道,它是作为解析 XML 文件而存在的。它的属性和描述如表 9.12 所示:

表9.12 标签属性和说明 属性

描述 doc

源XML的内容,该属性的内容应该为String类型或者java.io.Reader的实例,可以用xml属性来替代,但是不被推荐 var

将解析后的XML保存在该属性所指定的变量中,之后XML processing标签库中的其他标签若要取XML中的内容就可以从该变量中得到(可选) scope

变量的作用范围(可选) varDom

指定保存的变量为org.w3c.dom.Document接口类型(可选) scopeDom

org.w3c.dom.Document的接口类型变量作用范围(可选) systemId

定义一个URI,该URI将被使用到XML文件中以接入其他资源文件(可选) filter

该属性必须为org.xml.sax.XMLFilter类的一个实例,可以使用EL表达式传入,将对XML文件做过滤得到自身需要的部分(可选)

其中, var 、 scope 和 varDom 、 scopeDom 不应该同时出现,而应该被视为两个版本来使用,二者的变量都可以被 XML processing 标签库的其他标签来使用。

标签单独使用的情况很少,一般会结合 XML processing 标签库中的其他标签来一起工作。下面看一个示例。

首先给出一个简单的 XML 文件,将对该 XML 文件做解析,该 XML 文件名为 SampleXml.xml 。

<?xml version="1.0" encoding="UTF-8"?>

RW 123456 28 book1 book2 book3

标签库的工作:

标签 JavaWind.net Document

看到I18N就应该想到知识“国际化”,I18N formatting标签库就是用于在JSP页面中做国际化的动作。在该标签库中的标签一共有12个,被分为了两类,分别是: 国际化核心标签:。 格式化标签:

下面只选择其中常见的一些标签和属性进行介绍。

9.5.1 **用于设置本地化环境的标签** 标签用于设置Locale环境。它的属性和描述如表9.17所示:

表9.17 标签属性和说明 属性

描述 value

Locale环境的指定,可以是java.util.Locale或String类型的实例 scope

Locale环境变量的作用范围(可选)

下面看一个示例:

表示设置本地环境为繁体中文。

**9.5.2 **用于资源文件绑定的标签 这两组标签用于资源配置文件的绑定,唯一不同的是标签将资源配置文件绑定于它标签体中的显示,标签则允许将资源配置文件保存为一个变量,在之后的工作可以根据该变量来进行。

    根据Locale环境的不同将查找不同后缀的资源配置文件,这点在国际化的任何技术上都是一致的,通常来说,这两种标签单独使用是没有意义的,它们都会与I18N formatting标签库中的其他标签配合使用。它们的属性和描述如表9.18所示:

表9.18 标签属性和说明 属性

描述 basename

资源配置文件的指定,只需要指定文件名而无须扩展名,二组标签共有的属性 var

独有的属性,用于保存资源配置文件为一个变量 scope

变量的作用范围

下面看一个示例

该示例将会查找一个名为applicationMessage_zh_CN.properties的资源配置文件,来作为显示的Resource绑定。

**9.5.3 **用于显示资源配置文件信息的标签 用于信息显示的标签,将显示资源配置文件中定义的信息。它的属性和描述如表9.19所示:

表9.19 标签属性和说明 属性

描述 key

资源配置文件的“键”指定 bundle

若使用保存了资源配置文件,该属性就可以从保存的资源配置文件中进行查找 var

将显示信息保存为一个变量 scope

变量的作用范围

下面看一个示例:

   该示例使用了两种资源配置文件的绑定的做法,“ applicationMessage ”资源配置文件利用<fmt:setBundle>标签被赋于了变量“ applicationBundle ”,而作为<fmt:bundle>标签定义的“ applicationAllMessage ”资源配置文件作用于其标签体内的显示。

     第一个<fmt:message>标签将使用“ applicationAllMessage ”资源配置文件中“键”为“ userName ”的信息显示。
     第二个<fmt:message>标签虽然被定义在<fmt:bundle>标签体内,但是它使用了bundle属性,因此将指定之前由<fmt:setBundle>标签保存的“ applicationMessage ”资源配置文件,该“键”为“ passWord ”的信息显示。

9.5.4 **用于参数传递的标签**

标签应该位于标签内,将为该消息标签提供参数值。它只有一个属性value。

标签有两种使用版本,一种是直接将参数值写在value属性中,另一种是将参数值写在标签体内。

9.5.6 **用于为请求设置字符编码的标签**

标签用于为请求设置字符编码。它只有一个属性value,在该属性中可以定义字符编码。

9.5.7 **用于设定时区的标签** 这两组标签都用于设定一个时区。唯一不同的是标签将使得在其标签体内的工作可以使用该时区设置,标签则允许将时区设置保存为一个变量,在之后的工作可以根据该变量来进行。它们的属性和描述如表9.20所示:

表9.20 标签属性和说明 属性

描述 value

时区的设置 var

独有的属性,用于保存时区为一个变量 scope

变量的作用范围

**9.5.8 **用于格式化数字的标签

标签用于格式化数字。它的属性和描述如表9.21所示:

表9.21 标签属性和说明 属性

描述 value

格式化的数字,该数值可以是String类型或java.lang.Number类型的实例 type

格式化的类型 pattern

格式化模式 var

结果保存变量 scope

变量的作用范围 maxIntegerDigits

指定格式化结果的最大值 minIntegerDigits

指定格式化结果的最小值 maxFractionDigits

指定格式化结果的最大值,带小数 minFractionDigits

指定格式化结果的最小值,带小数

标签实际是对应java.util.NumberFormat类,type属性的可能值包括currency(货币)、number(数字)和percent(百分比)。

下面看一个示例。

该结果将被保存在“ money ”变量中,将根据Locale环境显示当地的货币格式。

9.5.9 **用于解析数字的标签** 标签用于解析一个数字,并将结果作为java.lang.Number类的实例返回。标签看起来和标签的作用正好相反。它的属性和描述如表9.22所示:

表9.22 标签属性和说明 属性

描述 value

将被解析的字符串 type

解析格式化的类型 pattern

解析格式化模式 var

结果保存变量,类型为java.lang.Number scope

变量的作用范围 parseLocale

以本地化的形式来解析字符串,该属性的内容应为String或java.util.Locale类型的实例

下面看一个示例。

解析之后的结果为“ 0.15 ”。

9.5.10 **用于格式化日期的标签**

标签用于格式化日期。它的属性和描述如表9.23所示:

表9.23 标签属性和说明 属性

描述 value

格式化的日期,该属性的内容应该是java.util.Date类型的实例 type

格式化的类型 pattern

格式化模式 var

结果保存变量 scope

变量的作用范围 timeZone

指定格式化日期的时区

标签与两组标签的关系密切。若没有指定 timeZone属性,也可以通过两组标签设定的时区来格式化最后的结果。

9.5.11 **用于解析日期的标签** 标签用于解析一个日期,并将结果作为java.lang.Date类型的实例返回。标签看起来和标签的作用正好相反。它的属性和描述如表9.24所示:

表9.24 标签属性和说明 属性

描述 value

将被解析的字符串 type

解析格式化的类型 pattern

解析格式化模式 var

结果保存变量,类型为java.lang.Date scope

变量的作用范围 parseLocale

以本地化的形式来解析字符串,该属性的内容为String或java.util.Locale类型的实例 timeZone

指定解析格式化日期的时区

两组标签都实现解析字符串为一个具体对象实例的工作,因此,这两组解析标签对var属性的字符串参数要求非常严格。就JSP页面的表示层前段来说,处理这种解析本不属于份内之事,因此两组标签应该尽量少用,替代工作的地方应该在服务器端表示层的后段,比如在Servlet中。

标签 JavaWind.net Document

9.6 Database access**标签库** Database access标签库中的标签用来提供在JSP页面中可以与数据库进行交互的功能,虽然它的存在对于早期纯JSP开发的应用以及小型的开发有着意义重大的贡献,但是对于MVC模型来说,它却是违反规范的。因为与数据库交互的工作本身就属于业务逻辑层的工作,所以不应该在JSP页面中出现,而是应该在模型层中进行。

   对于Database access标签库本书不作重点介绍,只给出几个简单示例让读者略微了解它们的功能。

   Database access标签库有以下6组标签来进行工作:<sql:setDataSource>、<sql:query>、<sql:update>、<sql:transaction>、<sql:setDataSource>、<sql:param>、<sql:dateParam>。

9.6.1 **用于设置数据源的 标签** 标签用于设置数据源,下面看一个示例:

该示例定义一个数据源并保存在“ dataSrc ”变量内。

9.6.2 **用于查询的 标签** 标签用于查询数据库,它标签体内可以是一句查询SQL。下面看一个示例:

select /* from table1

该示例将返回查询的结果到变量“ queryResults ”中,保存的结果是javax.servlet.jsp.jstl.sql.Result类型的实例。要取得结果集中的数据可以使用循环来进行。下面看一个示例。

${row.userName} ${row.passWord}

“ rows ”是javax.servlet.jsp.jstl.sql.Result实例的变量属性之一,用来表示数据库表中的“列”集合,循环时,通过“ ${row.XXX} ”表达式可以取得每一列的数据,“ XXX ”是表中的列名。

9.6.3 **用于更新的 标签**

标签用于更新数据库,它的标签体内可以是一句更新的SQL语句。其使用和标签没有什么不同。

9.6.4 用于事务处理的 标签 标签用于数据库的事务处理,在该标签体内可以使用标签和标签,而标签的事务管理将作用于它们之上。 标签对于事务处理定义了read_committed、read_uncommitted、repeatable_read、serializable4个隔离级别。

9.6.5 用于事务处理的 标签 这两个标签用于向SQL语句提供参数,就好像程序中预处理SQL的“ ? ”一样。标签传递除java.util.Date类型以外的所有相融参数,标签则指定必须传递java.util.Date类型的参数。

标签 JavaWind.net Document

9.7 Functions**标签库** 称呼Functions标签库为标签库,倒不如称呼其为函数库来得更容易理解些。因为Functions标签库并没有提供传统的标签来为JSP页面的工作服务,而是被用于EL表达式语句中。在JSP2.0规范下出现的Functions标签库为EL表达式语句提供了许多更为有用的功能。Functions标签库分为两大类,共16个函数。

   长度函数:fn:length
   字符串处理函数:fn:contains、fn:containsIgnoreCase、fn:endsWith、fn:escapeXml、fn:indexOf、fn:join、fn:replace、fn:split、fn:startsWith、fn:substring、fn:substringAfter、fn:substringBefore、fn:toLowerCase、fn:toUpperCase、fn:trim

以下是各个函数的用途和属性以及简单示例。

9.7.1 **长度函数 fn:length 函数** 长度函数fn:length的出现有重要的意义。在JSTL1.0中,有一个功能被忽略了,那就是对集合的长度取值。虽然java.util.Collection接口定义了size方法,但是该方法不是一个标准的JavaBean属性方法(没有get,set方法),因此,无法通过EL表达式“ ${collection.size} ”来轻松取得。

fn:length函数正是为了解决这个问题而被设计出来的。它的参数为input,将计算通过该属性传入的对象长度。该对象应该为集合类型或String类型。其返回结果是一个int类型的值。下面看一个示例。

<%ArrayList arrayList1 = new ArrayList(); arrayList1.add("aa"); arrayList1.add("bb"); arrayList1.add("cc");

%> <%request.getSession().setAttribute("arrayList1", arrayList1);%> ${fn:length(sessionScope.arrayList1)}

假设一个ArrayList类型的实例“ arrayList1 ”,并为其添加三个字符串对象,使用fn:length函数后就可以取得返回结果为“ 3 ”。

9.7.2 **判断函数 fn:contains 函数** fn:contains函数用来判断源字符串是否包含子字符串。它包括string和substring两个参数,它们都是String类型,分布表示源字符串和子字符串。其返回结果为一个boolean类型的值。下面看一个示例。

${fn:contains("ABC", "a")}
${fn:contains("ABC", "A")}

前者返回“ false ”,后者返回“ true ”。

9.7.3 fn:containsIgnoreCase**函数** fn:containsIgnoreCase函数与fn:contains函数的功能差不多,唯一的区别是fn:containsIgnoreCase函数对于子字符串的包含比较将忽略大小写。它与fn:contains函数相同,包括string和substring两个参数,并返回一个boolean类型的值。下面看一个示例。

${fn:containsIgnoreCase("ABC", "a")}
${fn:containsIgnoreCase("ABC", "A")}

前者和后者都会返回“ true ”。

9.7.4 **词头判断函数 fn:startsWith 函数** fn:startsWith函数用来判断源字符串是否符合一连串的特定词头。它除了包含一个string参数外,还包含一个subffx参数,表示词头字符串,同样是String类型。该函数返回一个boolean类型的值。下面看一个示例。

${fn:startsWith ("ABC", "ab")}
${fn:startsWith ("ABC", "AB")}

前者返回“ false ”,后者返回“ true ”。

9.7.5 **词尾判断函数 fn:endsWith 函数** fn:endsWith函数用来判断源字符串是否符合一连串的特定词尾。它与fn:startsWith函数相同,包括string和subffx两个参数,并返回一个boolean类型的值。下面看一个示例。

${fn:endsWith("ABC", "bc")}
${fn:endsWith("ABC", "BC")}

前者返回“ false ”,后者返回“ true ”。

9.7.6 **字符实体转换函数 fn:escapeXml 函数** fn:escapeXml函数用于将所有特殊字符转化为字符实体码。它只包含一个string参数,返回一个String类型的值。

9.7.8 **字符匹配函数 fn:indexOf 函数** fn:indexOf函数用于取得子字符串与源字符串匹配的开始位置,若子字符串与源字符串中的内容没有匹配成功将返回“ -1 ”。它包括string和substring两个参数,返回结果为int类型。下面看一个示例。

${fn:indexOf("ABCD","aBC")}
${fn:indexOf("ABCD","BC")}

前者由于没有匹配成功,所以返回-1,后者匹配成功将返回位置的下标,为1。

9.7.9 **分隔符函数 fn:join 函数** fn:join函数允许为一个字符串数组中的每一个字符串加上分隔符,并连接起来。它的参数、返回结果和描述如表9.25所示:

表9.25 fn:join函数 参数

描述 array

字符串数组。其类型必须为String[]类型 separator

分隔符。其类型必须为String类型 返回结果

返回一个String类型的值

下面看一个示例。

<% String[] stringArray = {"a","b","c"}; %> <%request.getSession().setAttribute("stringArray", stringArray);%> ${fn:join(sessionScope.stringArray,";")}

定义数组并放置到Session中,然后通过Session得到该字符串数组,使用fn:join函数并传入分隔符“ ; ”,得到的结果为“ a;b;c ”。

9.7.10 **替换函数 fn:replace 函数** fn:replace函数允许为源字符串做替换的工作。它的参数、返回结果和描述如表9.26所示:

表9.26 fn:replace函数 参数

描述 inputString

源字符串。其类型必须为String类型 beforeSubstring

指定被替换字符串。其类型必须为String类型 afterSubstring

指定替换字符串。其类型必须为String类型 返回结果

返回一个String类型的值

下面看一个示例。

${fn:replace("ABC","A","B")}

将“ ABC ”字符串替换为“ BBC ”,在“ ABC ”字符串中用“ B ”替换了“ A ”。

9.7.11 **分隔符转换数组函数 fn:split 函数** fn:split函数用于将一组由分隔符分隔的字符串转换成字符串数组。它的参数、返回结果和描述如表9.27所示:

表9.27 fn:split函数 参数

描述 string

源字符串。其类型必须为String类型 delimiters

指定分隔符。其类型必须为String类型 返回结果

返回一个String[]类型的值

下面看一个示例。

${fn:split("A,B,C",",")}

将“ A,B,C ”字符串转换为数组{A,B,C}。

9.7.12 **字符串截取函数 fn:substring 函数** fn:substring函数用于截取字符串。它的参数、返回结果和描述如表9.28所示:

表9.28 fn:substring函数 参数

描述 string

源字符串。其类型必须为String类型 beginIndex

指定起始下标(值从0开始)。其类型必须为int类型 endIndex

指定结束下标(值从0开始)。其类型必须为int类型 返回结果

返回一个String类型的值

下面看一个示例。

${fn:substring("ABC","1","2")}

截取结果为“ B ”。

9.7.14 **起始到定位截取字符串函数 fn:substringBefore 函数** fn:substringBefore函数允许截取源字符从开始到某个字符串。它的参数和fn:substringAfter函数相同,不同的是substring表示的是结束字符串。下面看一个示例。

${fn:substringBefore("ABCD","BC")}

截取的结果为“ A ”。

9.7.15 **小写转换函数 fn:toLowerCase 函数** fn:toLowerCase函数允许将源字符串中的字符全部转换成小写字符。它只有一个表示源字符串的参数string,函数返回一个String类型的值。下面看一个示例。

${fn:toLowerCase("ABCD")}

转换的结果为“ abcd ”。

9.7.16**大写转换函数 fn:toUpperCase 函数** fn:toUpperCase函数允许将源字符串中的字符全部转换成大写字符。它与fn:toLowerCase函数相同,也只有一个String参数,并返回一个String类型的值。下面看一个示例。

${fn:toUpperCase("abcd")}

转换的结果为“ ABCD ”。

9.7.17**空格删除函数 fn:trim 函数** fn:trim函数将删除源字符串中结尾部分的“空格”以产生一个新的字符串。它与fn:toLowerCase函数相同,只有一个String参数,并返回一个String类型的值。下面看一个示例。

${fn:trim("AB C ")}D

转换的结果为“ AB CD ”,注意,它将只删除词尾的空格而不是全部,因此“ B ”和“ C ”之间仍然留有一个空格。

单向链表

Posted on

单向链表

单向链表**

博客分类:

【链表】 是一种线性表,但是并不会按线性的顺序存储数据,而是在每一个节点里存到下一个节点的指针(Pointer)。 由于不必须按顺序存储,链表在插入的时候可以达到O(1)的复杂度,比另一种线性表顺序表快得多, 但是查找一个节点或者访问特定编号的节点则需要O(n)的时间, 而顺序表相应的时间复杂度分别是O(㏒ n)和O(1)。 【单向链表】 是链表的一种,其特点是链表的链接方向是单向的,对链表的访问要通过顺序读取从头部开始 一个节点包含两个域,一个信息域和一个指针域,指针域指向下一个节点,最后一个节点指向一个空值 单向链表有一个头节点,可以不存储任何信息,包含一个指向第一个节点的指针 特点: 1.访问节点速度慢,需要从头节点一次遍历链表 2.更新节点快,节点不需要移动,更改相应指针即可

单向链表节点类定义如下:

Java代码 收藏代码

  1. public class Node{
  2. AnyType element;
  3. Node next;
  4. }

要实现单向列表的相关操作,可定义类SingleLinkedList如下: Java代码 收藏代码

  1. public class SingleLinkedList{
  2. private static class Node{
  3. AnyType element;
  4. Node next;
  5. public Node(AnyType element){
  6. this.element = element;
  7. }
  8. }
  9. private Node header = new Node(null);
  10. private int size = 0;
  11. public boolean isEmpty(){
  12. return size==0;
  13. }
  14. public void makeEmpty(){
  15. header = null;
  16. }
  17. public int size(){
  18. return size;
  19. }
  20. ///
  21. /* Get element by index
  22. / //
  23. public AnyType get(int index){
  24. return getNode(index).element;
  25. }
  26. ///
  27. /* Add the element to the end of this list
  28. / //
  29. public void add(AnyType element){
  30. add(new Node(element));
  31. }
  32. ///
  33. /* Inserts the specified element at the specified position in this list
  34. /* 插入逻辑:
  35. /* 1.创建一个新节点
  36. /* 2.将原index节点的前一个节点的指针指向新节点
  37. /* 3.将新节点的指针指向index节点
  38. /* 4.插入后,新节点的位置为index
  39. / //
  40. public void add(int index,AnyType element){
  41. Node newNode = new Node(element);
  42. Node previous = getNode(index-1);
  43. //index节点
  44. Node node = previous.next;
  45. //将原index节点的前一个节点的指针指向newNode
  46. previous.next = newNode;
  47. //将newNode的指针指向index节点
  48. newNode.next = node;
  49. size++;
  50. }
  51. ///
  52. /* Inserts the specified element at the specified position in this list
  53. /* 删除逻辑:
  54. /* 1.获得index节点的前一个节点previousNode
  55. /* 2.获得index节点的后一个节点nextNode
  56. /* 3.将previousNode的指针指向nextNode
  57. / //
  58. public void remove(int index){
  59. Node previous = getNode(index-1);
  60. Node next = previous.next.next;
  61. previous.next = next;
  62. size--;
  63. }
  64. ///
  65. /* 定义此方法是为了便于测试
  66. / //
  67. public List getElements(){
  68. if(isEmpty()){
  69. return null;
  70. }else{
  71. List elements = new ArrayList();
  72. Node node = (Node) header;
  73. while(node.next!=null){
  74. node = node.next;
  75. elements.add(((Element)node.element).getValue());
  76. }
  77. return elements;
  78. }
  79. }
  80. //private methods
  81. private Node getNode(int index){
  82. if(isEmpty()){
  83. throw new RuntimeException("List is empty");
  84. }
  85. int i = 0;
  86. Node node = header;
  87. while(i<=index){
  88. node = node.next;
  89. i++;
  90. }
  91. return node;
  92. }
  93. private void add(Node newNode){
  94. Node node = header;
  95. while(node.next!=null){
  96. node = node.next;
  97. }
  98. node.next = newNode;
  99. size++;
  100. }
  101. }

如何实现链表反向: Java代码 收藏代码

  1. ///
  2. /* 链表反向
  3. / //
  4. public void reverse(){
  5. if(!isEmpty()){
  6. reverse(header.next,header.next.next);
  7. }
  8. }

Java代码 收藏代码

  1. private void reverse(Node node,Node nextNode){
  2. if(nextNode.next!=null){
  3. reverse(nextNode,nextNode.next);
  4. }else{
  5. //如果该节点是表尾,那么用头节点指向此节点
  6. header.next = nextNode;
  7. }
  8. //该节点的指针指向前一个节点P,并将节点P的指针设置为空
  9. nextNode.next = node;
  10. node.next = null;
  11. }

来源: [http://tangyanbo.iteye.com/blog/1472811](http://tangyanbo.iteye.com/blog/1472811)

常用排序算法分析与实现(一)(Java版)

Posted on

常用排序算法分析与实现(一)(Java版) - 数据结构 - Tech - ITeye论坛

您还未登录 ! 登录 注册

ITeye-最棒的软件开发交流社区

论坛首页综合技术论坛

常用排序算法分析与实现(一)(Java版)

全部 Linux 数据库 敏捷编程 数据结构 软件测试 项目管理 编程综合 Oracle Erlang 互联网 MySQL Java开发新方式:专注UI,快速开发

« 上一页 1 2 下一页 » 浏览 20153 次 锁定老帖子 主题:常用排序算法分析与实现(一)(Java版)

精华帖 (14) :: 良好帖 (6) :: 新手帖 (0) :: 隐藏帖 (0) 作者 正文 * junJZ_2008

  • 等级: 一星会员
  • junJZ_2008的博客
  • 性别:
  • 文章: 54
  • 积分: 160
  • 来自: 湖南澧縣
  • 发表时间:2009-12-13 最后修改:2010-01-21

< > 猎头职位: 上海: Junior Product Manager

相关文章:

这篇排序文章从思想 理解 到实现,然后到整理,花了我几天的时间,现把它记录于此,希望对大家有一定的帮助,写的不好的请不要见笑,写错了的,请指出来我更正。最后如果对你有一定的帮助,请回贴支持一下哦^_^ !

申明: 排序算法思想来自互联网,代码自己实现,仅供参考。

  • 插入排序

直接插入排序、希尔排序

  • 选择排序

简单选择排序、堆排序

  • 交换排序

冒泡排序、快速排序

  • 归并排序

  • 基数排序

排序基类

Java代码 收藏代码

  1. package sort;
  2. import java.util.Arrays;
  3. import java.util.Comparator;
  4. import java.util.Random;
  5. ///
  6. /* 排序接口,所有的排序算法都要继承该抽象类,并且要求数组中的
  7. /* 元素要具有比较能力,即数组元素已实现了Comparable接口
  8. /*
  9. /* @author jzj
  10. /* @date 2009-12-5
  11. /*
  12. /* @param
  13. /*/
  14. public abstract class Sort> {
  15. public final Comparator DEFAULT_ORDER = new DefaultComparator();
  16. public final Comparator REVERSE_ORDER = new ReverseComparator();
  17. ///
  18. /* 排序算法,需实现,对数组中指定的元素进行排序
  19. /* @param array 待排序数组
  20. /* @param from 从哪里
  21. /* @param end 排到哪里
  22. /* @param c
  23. /*/
  24. public abstract void sort(E[] array, int from, int end, Comparator c);
  25. ///
  26. /* 对数组中指定部分进行排序
  27. /* @param from 从哪里
  28. /* @param len 排到哪里
  29. /* @param array 待排序数组
  30. /* @param c 比较器
  31. /*/
  32. public void sort(int from, int len, E[] array, Comparator c) {
  33. sort(array, 0, array.length - 1, c);
  34. }
  35. ///
  36. /* 对整个数组进行排序,可以使用自己的排序比较器,也可使用该类提供的两个比较器
  37. /* @param array 待排序数组
  38. /* @param c 比较器
  39. /*/
  40. public final void sort(E[] array, Comparator c) {
  41. sort(0, array.length, array, c);
  42. }
  43. ///
  44. /* 对整个数组进行排序,采用默认排序比较器
  45. /* @param array 待排序数组
  46. /*/
  47. public final void sort(E[] array) {
  48. sort(0, array.length, array, this.DEFAULT_ORDER);
  49. }
  50. //默认比较器(一般为升序,但是否真真是升序还得看E是怎样实现Comparable接口的)
  51. private class DefaultComparator implements Comparator {
  52. public int compare(E o1, E o2) {
  53. return o1.compareTo(o2);
  54. }
  55. }
  56. //反序比较器,排序刚好与默认比较器相反
  57. private class ReverseComparator implements Comparator {
  58. public int compare(E o1, E o2) {
  59. return o2.compareTo(o1);
  60. }
  61. }
  62. ///
  63. /* 交换数组中的两个元素的位置
  64. /* @param array 待交换的数组
  65. /* @param i 第一个元素
  66. /* @param j 第二个元素
  67. /*/
  68. protected final void swap(E[] array, int i, int j) {
  69. if (i != j) {//只有不是同一位置时才需交换
  70. E tmp = array[i];
  71. array[i] = array[j];
  72. array[j] = tmp;
  73. }
  74. }
  75. ///
  76. /* 数组元素后移
  77. /* @param array 待移动的数组
  78. /* @param startIndex 从哪个开始移
  79. /* @param endIndex 到哪个元素止
  80. /*/
  81. protected final void move(E[] array, int startIndex, int endIndex) {
  82. for (int i = endIndex; i >= startIndex; i--) {
  83. array[i + 1] = array[i];
  84. }
  85. }
  86. ///
  87. /* 以指定的步长将数组元素后移,步长指定每个元素间的间隔
  88. /* @param array 待排序数组
  89. /* @param startIndex 从哪里开始移
  90. /* @param endIndex 到哪个元素止
  91. /* @param step 步长
  92. /*/
  93. protected final void move(E[] array, int startIndex, int endIndex, int step) {
  94. for (int i = endIndex; i >= startIndex; i -= step) {
  95. array[i + step] = array[i];
  96. }
  97. }
  98. //测试方法
  99. @SuppressWarnings("unchecked")
  100. public static final > void testSort(Sort sorter, E[] array) {
  101. if (array == null) {
  102. array = randomArray();
  103. }
  104. //为了第二次排序,需拷贝一份
  105. E[] tmpArr = (E[]) new Comparable[array.length];
  106. System.arraycopy(array, 0, tmpArr, 0, array.length);
  107. System.out.println("源 - " + Arrays.toString(tmpArr));
  108. sorter.sort(array, sorter.REVERSE_ORDER);
  109. System.out.println("降 - " + Arrays.toString(array));
  110. sorter.sort(tmpArr, sorter.DEFAULT_ORDER);
  111. System.out.println("升 - " + Arrays.toString(tmpArr));
  112. }
  113. //生成随机数组
  114. @SuppressWarnings("unchecked")
  115. private static > E[] randomArray() {
  116. Random r = new Random(System.currentTimeMillis());
  117. Integer[] a = new Integer[r.nextInt(30)];
  118. for (int i = 0; i < a.length; i++) {
  119. a[i] = new Integer(r.nextInt(100));
  120. }
  121. return (E[]) a;
  122. }
  123. }
    package sort; import java.util.Arrays; import java.util.Comparator; import java.util.Random; /// / 排序接口,所有的排序算法都要继承该抽象类,并且要求数组中的 / 元素要具有比较能力,即数组元素已实现了Comparable接口 / / @author jzj / @date 2009-12-5 / / @param // public abstract class Sort> { public final Comparator DEFAULT_ORDER = new DefaultComparator(); public final Comparator REVERSE_ORDER = new ReverseComparator(); /// / 排序算法,需实现,对数组中指定的元素进行排序 / @param array 待排序数组 / @param from 从哪里 / @param end 排到哪里 / @param c // public abstract void sort(E[] array, int from, int end, Comparator c); /// / 对数组中指定部分进行排序 / @param from 从哪里 / @param len 排到哪里 / @param array 待排序数组 / @param c 比较器 // public void sort(int from, int len, E[] array, Comparator c) { sort(array, 0, array.length - 1, c); } /// / 对整个数组进行排序,可以使用自己的排序比较器,也可使用该类提供的两个比较器 / @param array 待排序数组 / @param c 比较器 // public final void sort(E[] array, Comparator c) { sort(0, array.length, array, c); } /// / 对整个数组进行排序,采用默认排序比较器 / @param array 待排序数组 // public final void sort(E[] array) { sort(0, array.length, array, this.DEFAULT_ORDER); } //默认比较器(一般为升序,但是否真真是升序还得看E是怎样实现Comparable接口的) private class DefaultComparator implements Comparator { public int compare(E o1, E o2) { return o1.compareTo(o2); } } //反序比较器,排序刚好与默认比较器相反 private class ReverseComparator implements Comparator { public int compare(E o1, E o2) { return o2.compareTo(o1); } } /// / 交换数组中的两个元素的位置 / @param array 待交换的数组 / @param i 第一个元素 / @param j 第二个元素 // protected final void swap(E[] array, int i, int j) { if (i != j) {//只有不是同一位置时才需交换 E tmp = array[i]; array[i] = array[j]; array[j] = tmp; } } /// / 数组元素后移 / @param array 待移动的数组 / @param startIndex 从哪个开始移 / @param endIndex 到哪个元素止 // protected final void move(E[] array, int startIndex, int endIndex) { for (int i = endIndex; i >= startIndex; i--) { array[i + 1] = array[i]; } } /// / 以指定的步长将数组元素后移,步长指定每个元素间的间隔 / @param array 待排序数组 / @param startIndex 从哪里开始移 / @param endIndex 到哪个元素止 / @param step 步长 /*/ protected final void move(E[] array, int startIndex, int endIndex, int step) { for (int i = endIndex; i >= startIndex; i -= step) { array[i + step] = array[i]; } } //测试方法 @SuppressWarnings("unchecked") public static final > void testSort(Sort sorter, E[] array) { if (array == null) { array = randomArray(); } //为了第二次排序,需拷贝一份 E[] tmpArr = (E[]) new Comparable[array.length]; System.arraycopy(array, 0, tmpArr, 0, array.length); System.out.println("源 - " + Arrays.toString(tmpArr)); sorter.sort(array, sorter.REVERSE_ORDER); System.out.println("降 - " + Arrays.toString(array)); sorter.sort(tmpArr, sorter.DEFAULT_ORDER); System.out.println("升 - " + Arrays.toString(tmpArr)); } //生成随机数组 @SuppressWarnings("unchecked") private static > E[] randomArray() { Random r = new Random(System.currentTimeMillis()); Integer[] a = new Integer[r.nextInt(30)]; for (int i = 0; i < a.length; i++) { a[i] = new Integer(r.nextInt(100)); } return (E[]) a; } }

插入排序

直接插入排序

一般直接插入排序的时间复杂度为O ( n^2 ) ,但是当数列基本有序时,如果按照有数列顺序排时,时间复杂度将改善到O( n ),另外,因直接插入排序算法简单,如果待排序列规模不很大时效率也较高。

在已经排好序的序列中查找待插入的元素的插入位置,并将待插入元素插入到有序列表中的过程。

将数组分成两部分,初始化时,前部分数组为只有第一个元素,用来存储已排序元素,我们这里叫 arr1 ;后部分数组的元素为除第一个元素的所有元素,为待排序或待插入元素,我们这里叫 arr2 。 排序时使用二层循环:第一层对 arr2 进行循环,每次取后部分数组(待排序数组)里的第一个元素(我们称为待排序元素或称待插入元素) e1 ,然后在第二层循环中对 arr1 (已排好序的数组)从第一个元素往后进行循环,查到第一个大于待插入元素(如果是升序排列)或第一个小于待插入元素(如果是降序排列) e2 ,然后对 arr1 从 e2 元素开始往后的所有元素向后移,最后把 e1 插入到原来 e2 所在的位置。这样反复地对 arr2 进行循环,直到 arr2 中所有的待插入的元素都插入到 arr1 中。 Java代码 收藏代码

  1. package sort;
  2. import java.util.Comparator;
  3. ///
  4. /* 直接插入排序算法
  5. /* @author jzj
  6. /* @date 2009-12-5
  7. /*
  8. /* @param
  9. /*/
  10. public class InsertSort> extends Sort {
  11. ///
  12. /* 排序算法的实现,对数组中指定的元素进行排序
  13. /* @param array 待排序的数组
  14. /* @param from 从哪里开始排序
  15. /* @param end 排到哪里
  16. /* @param c 比较器
  17. /*/
  18. public void sort(E[] array, int from, int end, Comparator c) {
  19. //*
  20. /* 第一层循环:对待插入(排序)的元素进行循环
  21. /* 从待排序数组断的第二个元素开始循环,到最后一个元素(包括)止
  22. /*/
  23. for (int i = from + 1; i <= end; i++) {
  24. //*
  25. /* 第二层循环:对有序数组进行循环,且从有序数组最第一个元素开始向后循环
  26. /* 找到第一个大于待插入的元素
  27. /* 有序数组初始元素只有一个,且为源数组的第一个元素,一个元素数组总是有序的
  28. /*/
  29. for (int j = 0; j < i; j++) {
  30. E insertedElem = array[i];//待插入到有序数组的元素
  31. //从有序数组中最一个元素开始查找第一个大于待插入的元素
  32. if (c.compare(array[j], insertedElem) > 0) {
  33. //找到插入点后,从插入点开始向后所有元素后移一位
  34. move(array, j, i - 1);
  35. //将待排序元素插入到有序数组中
  36. array[j] = insertedElem;
  37. break;
  38. }
  39. }
  40. }
  41. //=======以下是java.util.Arrays的插入排序算法的实现
  42. //*
  43. /* 该算法看起来比较简洁一j点,有点像冒泡算法。
  44. /* 将数组逻辑上分成前后两个集合,前面的集合是已经排序好序的元素,而后面集合为待排序的
  45. /* 集合,每次内层循从后面集合中拿出一个元素,通过冒泡的形式,从前面集合最后一个元素开
  46. /* 始往前比较,如果发现前面元素大于后面元素,则交换,否则循环退出
  47. /*
  48. /* 总感觉这种算术有点怪怪,既然是插入排序,应该是先找到插入点,而后再将待排序的元素插
  49. /* 入到的插入点上,那么其他元素就必然向后移,感觉算法与排序名称不匹,但返过来与上面实
  50. /* 现比,其实是一样的,只是上面先找插入点,待找到后一次性将大的元素向后移,而该算法却
  51. /* 是走一步看一步,一步一步将待排序元素往前移
  52. /*/
  53. //*
  54. for (int i = from; i <= end; i++) {
  55. for (int j = i; j > from && c.compare(array[j - 1], array[j]) > 0; j--) {
  56. swap(array, j, j - 1);
  57. }
  58. }
  59. /*/
  60. }
  61. ///
  62. /* 测试
  63. /* @param args
  64. /*/
  65. public static void main(String[] args) {
  66. Integer[] intgArr = { 5, 9, 1, 4, 1, 2, 6, 3, 8, 0, 7 };
  67. InsertSort insertSort = new InsertSort();
  68. Sort.testSort(insertSort, intgArr);
  69. Sort.testSort(insertSort, null);
  70. }
  71. }
    package sort; import java.util.Comparator; /// / 直接插入排序算法 / @author jzj / @date 2009-12-5 / / @param // public class InsertSort> extends Sort { /// / 排序算法的实现,对数组中指定的元素进行排序 / @param array 待排序的数组 / @param from 从哪里开始排序 / @param end 排到哪里 / @param c 比较器 // public void sort(E[] array, int from, int end, Comparator c) { // / 第一层循环:对待插入(排序)的元素进行循环 / 从待排序数组断的第二个元素开始循环,到最后一个元素(包括)止 // for (int i = from + 1; i <= end; i++) { // / 第二层循环:对有序数组进行循环,且从有序数组最第一个元素开始向后循环 / 找到第一个大于待插入的元素 / 有序数组初始元素只有一个,且为源数组的第一个元素,一个元素数组总是有序的 // for (int j = 0; j < i; j++) { E insertedElem = array[i];//待插入到有序数组的元素 //从有序数组中最一个元素开始查找第一个大于待插入的元素 if (c.compare(array[j], insertedElem) > 0) { //找到插入点后,从插入点开始向后所有元素后移一位 move(array, j, i - 1); //将待排序元素插入到有序数组中 array[j] = insertedElem; break; } } } //=======以下是java.util.Arrays的插入排序算法的实现 // / 该算法看起来比较简洁一j点,有点像冒泡算法。 / 将数组逻辑上分成前后两个集合,前面的集合是已经排序好序的元素,而后面集合为待排序的 / 集合,每次内层循从后面集合中拿出一个元素,通过冒泡的形式,从前面集合最后一个元素开 / 始往前比较,如果发现前面元素大于后面元素,则交换,否则循环退出 / / 总感觉这种算术有点怪怪,既然是插入排序,应该是先找到插入点,而后再将待排序的元素插 / 入到的插入点上,那么其他元素就必然向后移,感觉算法与排序名称不匹,但返过来与上面实 / 现比,其实是一样的,只是上面先找插入点,待找到后一次性将大的元素向后移,而该算法却 / 是走一步看一步,一步一步将待排序元素往前移 // // for (int i = from; i <= end; i++) { for (int j = i; j > from && c.compare(array[j - 1], array[j]) > 0; j--) { swap(array, j, j - 1); } } // } /// / 测试 / @param args /*/ public static void main(String[] args) { Integer[] intgArr = { 5, 9, 1, 4, 1, 2, 6, 3, 8, 0, 7 }; InsertSort insertSort = new InsertSort(); Sort.testSort(insertSort, intgArr); Sort.testSort(insertSort, null); } }

插入排序算法对于大数组,这种算法非常慢。但是对于小数组,它比其他算法快。其他算法因为待的数组元素很少,反而使得效率降低。在Java集合框架中,排序都是借助于java.util.Arrays来完成的,其中排序算法用到了插入排序、快速排序、归并排序。插入排序用于元素个数小于7的子数组排序,通常比插入排序快的其他排序方法,由于它们强大的算法是针对大数量数组设计的,所以元素个数少时速度反而慢。

希尔排序

希尔思想介绍

希尔算法的本质是缩小增量排序,是对直接插入排序算法的改进。一般直接插入排序的时间复杂度为O ( n^2 ) ,但是当数列基本有序时,如果按照有数列顺序排时,时间复杂度将改善到O( n ),另外,因直接插入排序算法简单,如果待排序列规模不很大时效率也较高,Shell 根据这两点分析结果进行了改进,将待排记录序列以一定的增量间隔h 分割成多个子序列,对每个子序列分别进行一趟直接插入排序, 然后逐步减小分组的步长h ,对于每一个步长h 下的各个子序列进行同样方法的排序,直到步长为1 时再进行一次整体排序。 因为不管记录序列多么庞大,关键字多么混乱,在先前较大的分组步长h 下每个子序列的规模都不大,用直接插入排序效率都较高。 尽管在随后的步长h 递减分组中子序列越来越大,但由于整个序列的有序性也越来越明显,则排序效率依然较高。这种改进抓住了直接插入排序的两点本质,大大提高了它的时间效率。

希尔增量研究

综上所述:

(1) 希尔排序的核心是以某个增量h 为步长跳跃分组进行插入排序,由于分组的步长h 逐步缩小,所以也叫“缩小增量排序”插入排序。其关键是如何选取分组的步长序列ht ,. . . , hk ,. . . , h1 , h0 才能使得希尔方法的时间效率最高;

(2) 待排序列记录的个数n 、跳跃分组步长逐步减小直到为1时所进行的扫描次数T、增量的和、记录关键字比较的次数以及记录移动的次数或各子序列中的反序数等因素都影响希尔算法的时间复杂度:其中记录关键字比较的次数是重要因素,它主要取决于分组步长序列的选择;

(3) 希尔方法是一种不稳定排序算法,因为其排序过程中各趟的步长不同,在第k 遍用hk作为步长排序之后,第k +1 遍排序时可能会遇到多个逆序存在,影响排序的稳定性。

试验结果表明,SHELL 算法的时间复杂度受增量序列的影响明显大于其他因素,选取恰当的增量序列能明显提高排序的时间效率,我们认为第k 趟排序扫描的增量步长为 2^k - 1 ,即增量序列为. . . 2^k - 1 ,. . . ,15 ,7 ,3 ,1时较为理想,但它并不是唯一的最佳增量序列,这与其关联函数目前尚无确定解的理论结果是一致的。

Java代码 收藏代码

  1. package sort;
  2. import java.util.Comparator;
  3. ///
  4. /* 希尔排序算法
  5. /* @author jzj
  6. /* @date 2009-12-5
  7. /*
  8. /* @param
  9. /*/
  10. public class ShelltSort> extends Sort {
  11. ///
  12. /* 排序算法的实现,对数组中指定的元素进行排序
  13. /* @param array 待排序的数组
  14. /* @param from 从哪里开始排序
  15. /* @param end 排到哪里
  16. /* @param c 比较器
  17. /*/
  18. public void sort(E[] array, int from, int end, Comparator c) {
  19. //初始步长,实质为每轮的分组数
  20. int step = initialStep(end - from + 1);
  21. //第一层循环是对排序轮次进行循环。(step + 1) / 2 - 1 为下一轮步长值
  22. for (; step >= 1; step = (step + 1) / 2 - 1) {
  23. //对每轮里的每个分组进行循环
  24. for (int groupIndex = 0; groupIndex < step; groupIndex++) {
  25. //对每组进行直接插入排序
  26. insertSort(array, groupIndex, step, end, c);
  27. }
  28. }
  29. }
  30. ///
  31. /* 直接插入排序实现
  32. /* @param array 待排序数组
  33. /* @param groupIndex 对每轮的哪一组进行排序
  34. /* @param step 步长
  35. /* @param end 整个数组要排哪个元素止
  36. /* @param c 比较器
  37. /*/
  38. private void insertSort(E[] array, int groupIndex, int step, int end, Comparator c) {
  39. int startIndex = groupIndex;//从哪里开始排序
  40. int endIndex = startIndex;//排到哪里
  41. //*
  42. /* 排到哪里需要计算得到,从开始排序元素开始,以step步长,可求得下元素是否在数组范围内,
  43. /* 如果在数组范围内,则继续循环,直到索引超现数组范围
  44. /*/
  45. while ((endIndex + step) <= end) {
  46. endIndex += step;
  47. }
  48. // i为每小组里的第二个元素开始
  49. for (int i = groupIndex + step; i <= end; i += step) {
  50. for (int j = groupIndex; j < i; j += step) {
  51. E insertedElem = array[i];
  52. //从有序数组中最一个元素开始查找第一个大于待插入的元素
  53. if (c.compare(array[j], insertedElem) >= 0) {
  54. //找到插入点后,从插入点开始向后所有元素后移一位
  55. move(array, j, i - step, step);
  56. array[j] = insertedElem;
  57. break;
  58. }
  59. }
  60. }
  61. }
  62. ///
  63. /* 根据数组长度求初始步长
  64. /*
  65. /* 我们选择步长的公式为:2^k-1,2^(k-1)-1,...,15,7,3,1 ,其中2^k 减一即为该步长序列,k
  66. /* 为排序轮次
  67. /*
  68. /* 初始步长:step = 2^k-1
  69. /* 初始步长约束条件:step < len - 1 初始步长的值要小于数组长度还要减一的值(因
  70. /* 为第一轮分组时尽量不要分为一组,除非数组本身的长度就小于等于4)
  71. /*
  72. /* 由上面两个关系试可以得知:2^k - 1 < len - 1 关系式,其中k为轮次,如果把 2^k 表 达式
  73. / 转换成 step 表达式,则 2^k-1 可使用 (step + 1)/2-1 替换(因为 step+1 相当于第k-1
  74. /* 轮的步长,所以在 step+1 基础上乘以 2 就相当于 2^k 了),即步长与数组长度的关系不等式为
  75. / (step + 1)/2 - 1 < len -1
  76. /*
  77. /* @param len 数组长度
  78. /* @return
  79. /*/
  80. private static int initialStep(int len) {
  81. //*
  82. /* 初始值设置为步长公式中的最小步长,从最小步长推导出最长初始步长值,即按照以下公式来推:
  83. /* 1,3,7,15,...,2^(k-1)-1,2^k-1
  84. /* 如果数组长度小于等于4时,步长为1,即长度小于等于4的数组不且分组,此时直接退化为直接插
  85. /* 入排序
  86. /*/
  87. int step = 1;
  88. //试探下一个步长是否满足条件,如果满足条件,则步长置为下一步长
  89. while ((step + 1) /* 2 - 1 < len - 1) {
  90. step = (step + 1) /* 2 - 1;
  91. }
  92. System.out.println("初始步长 - " + step);
  93. return step;
  94. }
  95. ///
  96. /* 测试
  97. /* @param args
  98. /*/
  99. public static void main(String[] args) {
  100. Integer[] intgArr = { 5, 9, 1, 4, 8, 2, 6, 3, 7, 10 };
  101. ShelltSort shellSort = new ShelltSort();
  102. Sort.testSort(shellSort, intgArr);
  103. Sort.testSort(shellSort, null);
  104. }
  105. }
    package sort; import java.util.Comparator; /// / 希尔排序算法 / @author jzj / @date 2009-12-5 / / @param // public class ShelltSort> extends Sort { /// / 排序算法的实现,对数组中指定的元素进行排序 / @param array 待排序的数组 / @param from 从哪里开始排序 / @param end 排到哪里 / @param c 比较器 // public void sort(E[] array, int from, int end, Comparator c) { //初始步长,实质为每轮的分组数 int step = initialStep(end - from + 1); //第一层循环是对排序轮次进行循环。(step + 1) / 2 - 1 为下一轮步长值 for (; step >= 1; step = (step + 1) / 2 - 1) { //对每轮里的每个分组进行循环 for (int groupIndex = 0; groupIndex < step; groupIndex++) { //对每组进行直接插入排序 insertSort(array, groupIndex, step, end, c); } } } /// / 直接插入排序实现 / @param array 待排序数组 / @param groupIndex 对每轮的哪一组进行排序 / @param step 步长 / @param end 整个数组要排哪个元素止 / @param c 比较器 // private void insertSort(E[] array, int groupIndex, int step, int end, Comparator c) { int startIndex = groupIndex;//从哪里开始排序 int endIndex = startIndex;//排到哪里 // / 排到哪里需要计算得到,从开始排序元素开始,以step步长,可求得下元素是否在数组范围内, / 如果在数组范围内,则继续循环,直到索引超现数组范围 // while ((endIndex + step) <= end) { endIndex += step; } // i为每小组里的第二个元素开始 for (int i = groupIndex + step; i <= end; i += step) { for (int j = groupIndex; j < i; j += step) { E insertedElem = array[i]; //从有序数组中最一个元素开始查找第一个大于待插入的元素 if (c.compare(array[j], insertedElem) >= 0) { //找到插入点后,从插入点开始向后所有元素后移一位 move(array, j, i - step, step); array[j] = insertedElem; break; } } } } /// / 根据数组长度求初始步长 / / 我们选择步长的公式为:2^k-1,2^(k-1)-1,...,15,7,3,1 ,其中2^k 减一即为该步长序列,k / 为排序轮次 / / 初始步长:step = 2^k-1 / 初始步长约束条件:step < len - 1 初始步长的值要小于数组长度还要减一的值(因 / 为第一轮分组时尽量不要分为一组,除非数组本身的长度就小于等于4) / / 由上面两个关系试可以得知:2^k - 1 < len - 1 关系式,其中k为轮次,如果把 2^k 表 达式 / 转换成 step 表达式,则 2^k-1 可使用 (step + 1)/2-1 替换(因为 step+1 相当于第k-1 / 轮的步长,所以在 step+1 基础上乘以 2 就相当于 2^k 了),即步长与数组长度的关系不等式为 / (step + 1)/2 - 1 < len -1 / / @param len 数组长度 / @return // private static int initialStep(int len) { // / 初始值设置为步长公式中的最小步长,从最小步长推导出最长初始步长值,即按照以下公式来推: / 1,3,7,15,...,2^(k-1)-1,2^k-1 / 如果数组长度小于等于4时,步长为1,即长度小于等于4的数组不且分组,此时直接退化为直接插 / 入排序 // int step = 1; //试探下一个步长是否满足条件,如果满足条件,则步长置为下一步长 while ((step + 1) / 2 - 1 < len - 1) { step = (step + 1) / 2 - 1; } System.out.println("初始步长 - " + step); return step; } /// / 测试 / @param args /*/ public static void main(String[] args) { Integer[] intgArr = { 5, 9, 1, 4, 8, 2, 6, 3, 7, 10 }; ShelltSort shellSort = new ShelltSort(); Sort.testSort(shellSort, intgArr); Sort.testSort(shellSort, null); } }

选择排序

简单选择排序

每一趟从待排序的数据元素中选出最小(或最大)的一个元素,顺序放在已排好序的数列的最后,直到全部待排序的数据元素排完。 选择排序不像冒泡排序算法那样先并不急于调换位置,第一轮(k=1)先从array[k]开始逐个检查,看哪个数最小就记下该数所在的位置于minlIndex中,等一轮扫描完毕,如果找到比array[k-1]更小的元素,则把array[minlIndex]和a[k-1]对调,这时array[k]到最后一个元素中最小的元素就换到了array[k-1]的位置。 如此反复进行第二轮、第三轮…直到循环至最后一元素 Java代码 收藏代码

  1. package sort;
  2. import java.util.Comparator;
  3. ///
  4. /* 简单选择排序算法
  5. /* @author jzj
  6. /* @date 2009-12-5
  7. /*
  8. /* @param
  9. /*/
  10. public class SelectSort> extends Sort {
  11. ///
  12. /* 排序算法的实现,对数组中指定的元素进行排序
  13. /* @param array 待排序的数组
  14. /* @param from 从哪里开始排序
  15. /* @param end 排到哪里
  16. /* @param c 比较器
  17. /*/
  18. public void sort(E[] array, int from, int end, Comparator c) {
  19. int minlIndex;//最小索引
  20. //*
  21. /* 循环整个数组(其实这里的上界为 array.length - 1 即可,因为当 i= array.length-1
  22. /* 时,最后一个元素就已是最大的了,如果为array.length时,内层循环将不再循环),每轮假设
  23. /* 第一个元素为最小元素,如果从第一元素后能选出比第一个元素更小元素,则让让最小元素与第一
  24. /* 个元素交换
  25. /*/
  26. for (int i = from; i <= end; i++) {
  27. minlIndex = i;//假设每轮第一个元素为最小元素
  28. //从假设的最小元素的下一元素开始循环
  29. for (int j = i + 1; j <= end; j++) {
  30. //如果发现有比当前array[smallIndex]更小元素,则记下该元素的索引于smallIndex中
  31. if (c.compare(array[j], array[minlIndex]) < 0) {
  32. minlIndex = j;
  33. }
  34. }
  35. //先前只是记录最小元素索引,当最小元素索引确定后,再与每轮的第一个元素交换
  36. swap(array, i, minlIndex);
  37. }
  38. }
  39. ///
  40. /* 测试
  41. /* @param args
  42. /*/
  43. public static void main(String[] args) {
  44. Integer[] intgArr = { 5, 9, 1, 4, 1, 2, 6, 3, 8, 0, 7 };
  45. SelectSort insertSort = new SelectSort();
  46. Sort.testSort(insertSort, intgArr);
  47. Sort.testSort(insertSort, null);
  48. }
  49. }
    package sort; import java.util.Comparator; /// / 简单选择排序算法 / @author jzj / @date 2009-12-5 / / @param // public class SelectSort> extends Sort { /// / 排序算法的实现,对数组中指定的元素进行排序 / @param array 待排序的数组 / @param from 从哪里开始排序 / @param end 排到哪里 / @param c 比较器 // public void sort(E[] array, int from, int end, Comparator c) { int minlIndex;//最小索引 // / 循环整个数组(其实这里的上界为 array.length - 1 即可,因为当 i= array.length-1 / 时,最后一个元素就已是最大的了,如果为array.length时,内层循环将不再循环),每轮假设 / 第一个元素为最小元素,如果从第一元素后能选出比第一个元素更小元素,则让让最小元素与第一 / 个元素交换 // for (int i = from; i <= end; i++) { minlIndex = i;//假设每轮第一个元素为最小元素 //从假设的最小元素的下一元素开始循环 for (int j = i + 1; j <= end; j++) { //如果发现有比当前array[smallIndex]更小元素,则记下该元素的索引于smallIndex中 if (c.compare(array[j], array[minlIndex]) < 0) { minlIndex = j; } } //先前只是记录最小元素索引,当最小元素索引确定后,再与每轮的第一个元素交换 swap(array, i, minlIndex); } } /// / 测试 / @param args /*/ public static void main(String[] args) { Integer[] intgArr = { 5, 9, 1, 4, 1, 2, 6, 3, 8, 0, 7 }; SelectSort insertSort = new SelectSort(); Sort.testSort(insertSort, intgArr); Sort.testSort(insertSort, null); } }

堆排序

堆实质上是满足如下性质的完全二叉树:树中任一非叶结点的关键字均不大于(或不小于)其左右孩子(若存在)结点的关键字。 【例】关键字序列(10,15,56,25,30,70)和(70,56,30,25,15,10)分别满足堆性质(1)和(2),故它们均是堆,其对应的完全二叉树分别如小根堆示例和大根堆示例所示:

根结点(亦称为堆顶)的关键字是堆里所有结点关键字中最小者的堆称为小顶堆。 根结点(亦称为堆顶)的关键字是堆里所有结点关键字中最大者,称为大顶堆。

堆是一种完全二叉树,一般使用数组来实现。堆排序也是一种选择性的排序,每次选择第i大的元素。

另外排序过程中借助了堆结构,堆就是一种完全二叉树,所以这里先要熟悉要用的二叉树几个性质:

N(N>1)个节点的的完全二叉树从层次从左自右编号,最后一个分枝节点(非叶子节点)的编号为 N/2 取整。

且对于编号 i(1<=i<=N)有:父节点为 i/2 向下取整;若2i>N,则节点i没有左孩子,否则其左孩子为2i;若2i+1>N,则没有右孩子,否则其右孩子为2i+1。

注,这里使用完全二叉树只是为了好描述算法,它只是一种逻辑结构,真真在实现时我们还是使用数组来存储这棵二叉树的,因为完全二叉树完全可以使用数组来存储。

算法描述:

堆排序其实最主要的两个过程:第一步,创建初始堆;第二步,交换根节点与最后一个非叶子节

第一步实现 :从最后一个非叶子节点为开始向前循环每个会支节点,比较每个分支节点与他左右子节点,如果其中某个子节点比父节点大,则与父节点交换,交换后原父节点可能还小于原子节点的子节点,所以还需对原父节点进行调整,使用原父节点继续下沉,直到没有子节点或比左右子节点都大为止,调用过程可通过递归完成。当某个非叶子节点调整完毕后,再处理下一个非叶子节点,直到根节点也调整完成,这里初始堆就创建好了,这里我们创建的是大顶堆,即大的元素向树的根浮,这样排序最后得到的结果为升序,因为最大的从树中去掉,并从数组最后往前存放。

第二步实现 :将树中的最后一个元素与堆顶元素进行交换,并从树中去掉最后叶子节点。交换后再按创建初始堆的算法调整根节点,如此下去直到树中只有一个节点为止。

Java代码 收藏代码

  1. package sort;
  2. import java.util.Comparator;
  3. public class HeapSort> extends Sort {
  4. ///
  5. /* 排序算法的实现,对数组中指定的元素进行排序
  6. /* @param array 待排序的数组
  7. /* @param from 从哪里开始排序
  8. /* @param end 排到哪里
  9. /* @param c 比较器
  10. /*/
  11. public void sort(E[] array, int from, int end, Comparator c) {
  12. //创建初始堆
  13. initialHeap(array, from, end, c);
  14. //*
  15. /* 对初始堆进行循环,且从最后一个节点开始,直接树只有两个节点止
  16. /* 每轮循环后丢弃最后一个叶子节点,再看作一个新的树
  17. /*/
  18. for (int i = end - from + 1; i >= 2; i--) {
  19. //根节点与最后一个叶子节点交换位置,即数组中的第一个元素与最后一个元素互换
  20. swap(array, from, i - 1);
  21. //交换后需要重新调整堆
  22. adjustNote(array, 1, i - 1, c);
  23. }
  24. }
  25. ///
  26. /* 初始化堆
  27. /* 比如原序列为:7,2,4,3,12,1,9,6,8,5,10,11
  28. /* 则初始堆为:1,2,4,3,5,7,9,6,8,12,10,11
  29. /* @param arr 排序数组
  30. /* @param from 从哪
  31. /* @param end 到哪
  32. /* @param c 比较器
  33. /*/
  34. private void initialHeap(E[] arr, int from, int end, Comparator c) {
  35. int lastBranchIndex = (end - from + 1) / 2;//最后一个非叶子节点
  36. //对所有的非叶子节点进行循环 ,且从最一个非叶子节点开始
  37. for (int i = lastBranchIndex; i >= 1; i--) {
  38. adjustNote(arr, i, end - from + 1, c);
  39. }
  40. }
  41. ///
  42. /* 调整节点顺序,从父、左右子节点三个节点中选择一个最大节点与父节点转换
  43. /* @param arr 待排序数组
  44. /* @param parentNodeIndex 要调整的节点,与它的子节点一起进行调整
  45. /* @param len 树的节点数
  46. /* @param c 比较器
  47. /*/
  48. private void adjustNote(E[] arr, int parentNodeIndex, int len, Comparator c) {
  49. int minNodeIndex = parentNodeIndex;
  50. //如果有左子树,i /* 2为左子节点索引
  51. if (parentNodeIndex /* 2 <= len) {
  52. //如果父节点小于左子树时
  53. if (c.compare(arr[parentNodeIndex - 1], arr[parentNodeIndex /* 2 - 1]) < 0) {
  54. minNodeIndex = parentNodeIndex /* 2;//记录最大索引为左子节点索引
  55. }
  56. // 只有在有或子树的前提下才可能有右子树,再进一步断判是否有右子树
  57. if (parentNodeIndex /* 2 + 1 <= len) {
  58. //如果右子树比最大节点更大
  59. if (c.compare(arr[minNodeIndex - 1], arr[(parentNodeIndex /* 2 + 1) - 1]) < 0) {
  60. minNodeIndex = parentNodeIndex /* 2 + 1;//记录最大索引为右子节点索引
  61. }
  62. }
  63. }
  64. //如果在父节点、左、右子节点三都中,最大节点不是父节点时需交换,把最大的与父节点交换,创建大顶堆
  65. if (minNodeIndex != parentNodeIndex) {
  66. swap(arr, parentNodeIndex - 1, minNodeIndex - 1);
  67. //交换后可能需要重建堆,原父节点可能需要继续下沉
  68. if (minNodeIndex /* 2 <= len) {//是否有子节点,注,只需判断是否有左子树即可知道
  69. adjustNote(arr, minNodeIndex, len, c);
  70. }
  71. }
  72. }
  73. ///
  74. /* 测试
  75. /* @param args
  76. /*/
  77. public static void main(String[] args) {
  78. Integer[] intgArr = { 7, 2, 4, 3, 12, 1, 9, 6, 8, 5, 10, 11 };
  79. HeapSort sort = new HeapSort();
  80. HeapSort.testSort(sort, intgArr);
  81. HeapSort.testSort(sort, null);
  82. }
  83. }
    package sort; import java.util.Comparator; public class HeapSort> extends Sort { /// / 排序算法的实现,对数组中指定的元素进行排序 / @param array 待排序的数组 / @param from 从哪里开始排序 / @param end 排到哪里 / @param c 比较器 // public void sort(E[] array, int from, int end, Comparator c) { //创建初始堆 initialHeap(array, from, end, c); // / 对初始堆进行循环,且从最后一个节点开始,直接树只有两个节点止 / 每轮循环后丢弃最后一个叶子节点,再看作一个新的树 // for (int i = end - from + 1; i >= 2; i--) { //根节点与最后一个叶子节点交换位置,即数组中的第一个元素与最后一个元素互换 swap(array, from, i - 1); //交换后需要重新调整堆 adjustNote(array, 1, i - 1, c); } } /// / 初始化堆 / 比如原序列为:7,2,4,3,12,1,9,6,8,5,10,11 / 则初始堆为:1,2,4,3,5,7,9,6,8,12,10,11 / @param arr 排序数组 / @param from 从哪 / @param end 到哪 / @param c 比较器 // private void initialHeap(E[] arr, int from, int end, Comparator c) { int lastBranchIndex = (end - from + 1) / 2;//最后一个非叶子节点 //对所有的非叶子节点进行循环 ,且从最一个非叶子节点开始 for (int i = lastBranchIndex; i >= 1; i--) { adjustNote(arr, i, end - from + 1, c); } } /// / 调整节点顺序,从父、左右子节点三个节点中选择一个最大节点与父节点转换 / @param arr 待排序数组 / @param parentNodeIndex 要调整的节点,与它的子节点一起进行调整 / @param len 树的节点数 / @param c 比较器 // private void adjustNote(E[] arr, int parentNodeIndex, int len, Comparator c) { int minNodeIndex = parentNodeIndex; //如果有左子树,i / 2为左子节点索引 if (parentNodeIndex / 2 <= len) { //如果父节点小于左子树时 if (c.compare(arr[parentNodeIndex - 1], arr[parentNodeIndex / 2 - 1]) < 0) { minNodeIndex = parentNodeIndex / 2;//记录最大索引为左子节点索引 } // 只有在有或子树的前提下才可能有右子树,再进一步断判是否有右子树 if (parentNodeIndex / 2 + 1 <= len) { //如果右子树比最大节点更大 if (c.compare(arr[minNodeIndex - 1], arr[(parentNodeIndex / 2 + 1) - 1]) < 0) { minNodeIndex = parentNodeIndex / 2 + 1;//记录最大索引为右子节点索引 } } } //如果在父节点、左、右子节点三都中,最大节点不是父节点时需交换,把最大的与父节点交换,创建大顶堆 if (minNodeIndex != parentNodeIndex) { swap(arr, parentNodeIndex - 1, minNodeIndex - 1); //交换后可能需要重建堆,原父节点可能需要继续下沉 if (minNodeIndex / 2 <= len) {//是否有子节点,注,只需判断是否有左子树即可知道 adjustNote(arr, minNodeIndex, len, c); } } } /// / 测试 / @param args /*/ public static void main(String[] args) { Integer[] intgArr = { 7, 2, 4, 3, 12, 1, 9, 6, 8, 5, 10, 11 }; HeapSort sort = new HeapSort(); HeapSort.testSort(sort, intgArr); HeapSort.testSort(sort, null); } }
  • 大小: 32.8 KB

  • 大小: 81.4 KB

  • 大小: 35 KB

  • 大小: 14.9 KB

  • 大小: 139.3 KB

  • 查看图片附件

声明:ITeye文章版权属于作者,受法律保护。没有作者书面许可不得转载。 推荐链接

不错,挺详细的.只是有点老生常谈了 返回顶楼 回帖地址

0 0 请登录后投票 * junJZ_2008

  • 等级: 一星会员
  • junJZ_2008的博客
  • 性别:
  • 文章: 54
  • 积分: 160
  • 来自: 湖南澧縣
  • 发表时间:2009-12-18

呵呵,以前只是知道这些理论知识罢了,从大学里就开始学这些理论,一直没有去实现它,直到现在才下点狠心看能否实现!@ 返回顶楼 回帖地址

0 0 请登录后投票 * aixinnature

  • 等级: 初级会员
  • aixinnature的博客
  • 性别:
  • 文章: 55
  • 积分: 40
  • 来自: 北京
  • 发表时间:2009-12-23

写的非常好,原理说的很清楚了,代码也注释的详尽! 返回顶楼 回帖地址

0 0 请登录后投票 * dapp66

  • 等级: 初级会员
  • dapp66的博客
  • 性别:
  • 文章: 6
  • 积分: 40
  • 来自: 深圳
  • 发表时间:2010-01-04

真是不错,正好看看。写的很详细的 返回顶楼 回帖地址

0 0 请登录后投票 * JavaLet

  • 等级: 初级会员
  • JavaLet的博客
  • 文章: 6
  • 积分: 62
  • 发表时间:2010-03-17

我认为这是我看到的最详细的文章,有图有代码 返回顶楼 回帖地址

0 0 请登录后投票 * 我不说话

  • 等级: 初级会员
  • 我不说话的博客
  • 性别:
  • 文章: 13
  • 积分: 30
  • 来自: 深圳
  • 发表时间:2010-04-27

没久没看这些东西了,都忘了(当然当初也没啃透,不然怎么会忘了返回顶楼 回帖地址

0 0 请登录后投票 * Heart.X.Raid

  • 等级: 五星会员
  • Heart.X.Raid的博客
  • 性别:
  • 文章: 72
  • 积分: 530
  • 来自: 武汉
  • 发表时间:2010-04-28

算法不要用Java写,不能自由的控制内存,有的时候很难在效率上改进。 返回顶楼 回帖地址

0 0 请登录后投票 * lixia0417

  • 等级: 初级会员
  • lixia0417的博客
  • 性别:
  • 文章: 24
  • 积分: 30
  • 来自: 广州
  • 发表时间:2010-07-03

楼主,写得实在是太详细了,对我这种数据结构知识不好的人帮助很大,呵呵 返回顶楼 回帖地址

0 0 请登录后投票 * rmn190

  • 等级: 一星会员
  • rmn190的博客
  • 性别:
  • 文章: 354
  • 积分: 120
  • 来自: 北京
  • 发表时间:2010-08-22

楼主写的很详细啊, 谢谢分享! 返回顶楼 回帖地址

0 0 请登录后投票

« 上一页 1 2 下一页 » 论坛首页综合技术版 跳转论坛:移动开发技术 Web前端技术 Java企业应用 编程语言技术 综合技术 入门技术 招聘求职 海阔天空

© 2003-2012 ITeye.com. [ 京ICP证110151号 京公网安备110105010620 ] 百联优力(北京)投资有限公司 版权所有