存储 (Storage)存储是用于保有数据的技术。目前计算机存储采用的技术方式主要有以下几种：磁介质（硬盘，磁带），半导体（内存，固态硬盘，闪存卡等），光介质（光盘）等。不同的存储方式在各方面具有不同的特点，如易失性（断电是否失去数据），容量大小，读写支持（读写，只读，快读慢写，一次性写入等），数据存取方式（连续存取，随机存取），性能（如延迟，吞吐量等），能耗等。下文所说的存储指二级存储，又称外存，或辅存。与此对应的主要存储是指内存，即"运行内存"。事实上只有这一种内存，把移动设备的持久化存储称为内存在技术上是错误的，这些其实是外存。外存与内存的区别在于是否能直接与 CPU 连接。常见的外存设备包括硬盘，固态硬盘等。硬盘 (HDD, Hard Disk Drive)硬盘是一种电子机械设备，使用转动的磁盘和可平面移动的磁头来存取数据。硬盘的存取延迟一般在几毫秒左右。它支持随机存取，每秒大概几十次到二百次。硬盘适用于连续存取较多的场景如多媒体数据或备份等，其吞吐量可达到 100 ~ 200 MB/s.硬盘驱动器中可以有多张磁碟，互相平行重叠，碟间和两侧有读写磁头(Head)。每张碟被划分为若干个同心圆，称为磁道(Track)，每个磁道划分为多个扇区(Sector)；所有碟相同位置的磁道构成一个柱面(Cylinder)。硬盘可以通过 CHS 方式对每个扇区进行寻址：Cylinde 编号(0 ~ 1023)，Head 编号(0~254)，Sector(1 ~ 63)。这种方式在常见的 512 字节扇区大小下，能处理的最大硬盘容量是 7844 GB。历史上这些值直接对应硬盘的内部的物理器件，但随着存储密度的增大，硬盘向 BIOS 报告的参数也变为了逻辑数据。为解决更大容量的寻址问题，同时提升部分场景的性能，AF (Advanced Formatting) 技术将扇区大小调整为 4 KB。较新的存储系统现在主要采用 LBA(Logical Block Addressing) 寻址方式，直接使用 32 位或 64 位的扇区编号。硬盘根据磁极与盘面的方向关系可分为 LMR 和 PMR 两种。LMR(Longitudinal Magnetic Recording, 纵向磁性记录)仅用于早期的硬盘，磁极与盘面平行。PMR(Perpendicular Magnetic Recording, 垂直磁性记录)是目前所有硬盘都采用的技术，磁极与盘面垂直，这样能做到更高的数据密度。PMR 又分为两种：CMR 和 SMR。CMR(Conventional PMR) 中磁道之间留有空白区域，以避免存取时与相邻磁道相互影响。SMR(Shingled Magnetic Recording , 叠瓦磁性记录) 中磁道间没有间隔，修改数据时需要特殊处理，以免影响相邻磁道。SMR 提升了数据存储密度，在性能和稳定性上做了一定的妥协。硬盘的"硬"字是相对于软盘而言的。软盘（Floppy disk）是一种可移动的磁存储媒体，塑料硬壳里边的盘片使用较软的聚酯薄膜制成。硬盘的盘片则由铝或玻璃制成。固态硬盘 (SDD, Solid-state Drive/Solid-state Disk)固态硬盘使用闪存芯片存储数据。固态硬盘的存取延迟一般在几十到几百微秒之间。随机存取每秒可以达到千次以上，并且可在内部使用类似 RAID 0 的技术进一步提升性能。吞吐量一般大于硬盘，高端的产品甚至经常被物理接口(SATA, PCIE等)所限制。由于 NAND(Not AND) 闪存的特性，固态硬盘长期不通电会损失数据（正常室温情况下若干年）。闪存芯片可按字节读取，但是写入时则只能在特定情况下进行：代表 1 的位(bit)可被改写为 0, 反之则不行；只有整块(一般单块在 16KB 到 512KB 之间)擦写才能将 0 变为 1。闪存具有有限的 P/E 周期(Program-Eraspe)，数据写入量过大会造成写入耗尽(wear out)。由于这些特性，针对固态硬盘，固件及软件系统需要进行相关的处理，才能将保持高性能和保证设备状态。文件系统可以发送 TRIM 命令，通知 SSD 块已经不再使用，可以擦除；固件可能会将数据分布到不同的位置，以平衡不同块的写入量。固态硬盘里边没有"盘"。它不包含可运动的机械装置，因此称为"固态"。它也不包含磁性组件，但是由于习惯和翻译的原因，通常还是把它和硬盘等可按块随机访问的二级存储设备统称为磁盘(disk)。S.M.A.R.T (Self-Monitoring, Analysis and Reporting Technology)磁盘可能因为机械磨损，材料降级等原因发生故障。为增强数据安全性，现代的磁盘基本都支持通过 S.M.A.R.T. 向软件系统报告自身健康状态。S.M.A.R.T. 标准仅定义了软硬件间通信的接口协议，并未规定具体的监控指标（Attribute, 特性），因此不同厂商的产品或不同的产品系列报告的数据意义并不完全相同。S.M.A.R.T 报告的数据中，有一个最基本的状态，代表是否已超过健康阈值。另外还有一系列的指标，每个指标包含以下几个值：ID，整数，指标的标识，由厂商定义，但有部分是通用的原始值(Raw value)，整数，其意义由厂商定义正规值(Normalized value)，1 到 253 之间，通常默认为100，数字越小代表磁盘健康情况越差最差值(Worst value)，代表历史记录到的最差的正规值阈值(Thresholdvalue)，当正规值小于或等于该值时，磁盘的健康程度已经到达了临界点，必须进行处理了部分较关键的通用指标包括 5(0x05, Reallocated Sectors Count，重映射扇区数), 10(0x0A, Spin Retry Count，旋转重试次数), 187(0xBB, Reported Uncorrectable Errors, 无法使用硬件 ECC 恢复的错误次数), 196(0xC4, Reallocation Event Count, 重映射事件次数)，197(0xC5, Current Pending Sector Count，待重映射扇区数) 等。独立磁盘冗余阵列 (RAID, Redundant Array of Independent Disks) {#raid-redundant-array-of-independent-disks}RAID 技术用于将多个二级存储设备（下称磁盘）虚拟为一个，以提升性能和/或安全性。RAID 有不同的等级，分别达到不同的整合效果。常用的等级有以下几种：RAID 0 将两个以上的磁盘并联，提供多倍容量，多倍性能。数据无冗余，无容错，单个磁盘损坏时会丢失所有数据。RAID 1 使用两个以上的磁盘互为镜像，提供单盘的容量，多倍读取性能，略低于单盘的写入性能。只有所有磁盘同时损坏才会丢失数据。RAID 5 使用三个以上的磁盘分布式存储数据和容错信息。读写性能稍低于 RAID 0， 单个磁盘损坏时不会丢失数据。RAID 6 类似 RAID 5，存储两份容错信息，使用四个以上磁盘。读写性能类似 RAID 5。两个以内磁盘损坏时不会丢失数据。RAID 10 为 RAID 1 和 RAID 0 的结合，以更高的代价同时提供性能和安全性。文件系统 (FS, File System) {#fs-file-system}文件系统是用于在存储介质上组织和存取数据的软件。它提供了数据分组的抽象机制（文件，目录等），以及在此抽象机制上的操作接口。文件系统有很多种，以适应不同的底层存储机制（如硬盘，光盘，网络等），达到不同的设计目标（如速度，灵活性，安全等）。磁盘文件系统 (Disk file system)磁盘文件系统借助于磁盘的随机读写能力，提供方便而高效的文件服务。它通常把存储介质抽象为卷（Volume），即逻辑磁盘，一个卷可能对应一个或多个物理磁盘或磁盘分区。常见的磁盘文件系统包括以下几种：FAT(File Allocation Table) 家族是支持最广泛的文件系统。FAT 把卷的空间分为三个部分：目录表，文件分配表和数据区。其中数据区被分为不同的簇(cluster)，每个簇包含 N （N 可以为 1, 2, 4, 8, 16, 32, 64, 128）个物理连续的扇区。文件内容存储在这些簇中，单个文件至少占用一个簇。文件分配表中为记录了每个簇的使用情况：未使用，文件末尾，同文件下个簇的编号。这样，文件内容所使用的簇就形成了一个单链表。磁盘上存有两份文件分配表，以便在数据损坏或硬件故障时进行恢复。目录表中记录每个文件/目录的名称，属性，存取日期等元数据，以及内容起始簇的编号等。FAT 中文件名最长为 8 个字符加上 3 个字符的扩展名（称为 8.3 文件名格式），Windows 95 以后对此进行了扩展，最多可达 255 个 UCS-2 字符。目前最常用的 FAT32 中单个文件不能超过 4 GB，单卷（逻辑磁盘）不能超过 2 TB(格式化为 4KB 扇区时为 16 TB)。exFAT 是在 FAT32 的基础上，专门为闪存设备设计的文件系统，通常用于移动存储，移动设备或嵌入式系统。它放宽了 FAT32 对文件和卷尺寸的限制。exFAT 中只有一份文件分配表，它支持对连续的文件内容使用扩展（extend），而忽略文件分配表中的数据，从而减少了文件修改时对闪存的写入量。Windows 10 仅允许 32 GB 以上的分区格式化为 exFAT, 更小的分区会被推荐使用 FAT32 格式。NTFS (New Technology File System) 是 Windows 的默认文件系统，在 macOS 和 Linux 中也能作为只读装载。NTFS 中，文件/目录的所有信息都是属性（Attribute），包括文件内容（称为流，一个文件可以有多个流，其中至少有一个是默认流，其它的需要单独命名）。这些属性都保存在主文件表(Master File Table, MFT)的记录里，每条记录大小为 1 KB。如果空间足够，一个文件仅需要这么一条记录即可，无需另外分配磁盘空间；如果空间不足以容纳所有的属性，则会把部分属性存储在同卷的其它地方，在MFT中记录这些位置。NTFS 使用了日志系统，保证文件系统元数据不被意外破坏。ext系列(ext2/ext3/ext4)是 Linux 下常用的文件系统。ext2 中每个文件或目录对应一个索引节点(inode, index node)，其中记录了文件的尺寸，权限等信息，以及数据块的位置（指针）。单个 inode 中可以有 15 个指针：其中前 12 个直接指向数据块；第 13 个是间接指针，它指向一个指针块，指针块中包含若干指向数据块的指针；第 14 个是双重间接指针，指向指向指针块的指针块；第 15 个是三重间接指针。ext3 在 ext2的基础上增加了日志功能，提升了系统故障时的容错性。ext4 支持更大的卷，为元数据和日志添加了校验码，同时在其它方面也有更多的改进。这些文件系统也可以通过第三方软件在 Windows/macOS 中受限使用（部分特性不受支持）。ZFS 是一种独特的先进文件系统，在 Solaris 操作系统中原生可用，也可通过第三方软件在 Linux/macOS 中使用。ZFS 为长期大量数据存储而设计，有多种安全措施来防止软硬件错误带来的数据丢失：所有数据都存有多层级校验码，数据和校验码可存储多份，支持写入日志，提供软 RAID。功能上它支持快照，写时复制(copy-on-write)；性能上，ZFS 支持多级缓存设备，如内存和固态硬盘。与大部分其它文件系统不同的是，ZFS 还提供了虚拟设备的功能，在虚拟设备上可以使用其它文件系统，这样可以用 ZFS 提供优异的性能和数据安全性，同时使用其它文件系统的功能。ZFS 是新一代文件系统中较为成熟和完善的，但是潜在的版权法律问题，限制了它在 Linux 各发行版中的采用率。Btrfs(B-tree file system) 基于写时复制(copy-on-write)的原则，解决传统文件系统中欠缺存储池，快照，校验码，多设备整合等功能的问题。功能上它和 ZFS 类似，经过完善和成熟后，有希望成为未来 Linux 的默认文件系统。ReFS(Resilient File System) 是微软设计开发的新一代文件系统，类似 ZFS 和 Btrfs，尚未大规模普及，目前仅在 Windows Server 和 Windows 10 企业版本可用。网络文件系统 (Network File System)网络文件系统作为远程文件访问协议的客户端，提供服务器上文件的存取服务。常见的网络文件系统(及类似系统）包括以下几种：NFS(Network File System) 是一套分布式文件系统协议的开放标准。目前主要在 *nix 系统上应用，在 macOS 和 Windows 下使用较少。目前主流使用的 NFS v3 以上版本，可选使用 TCP 或 UDP 作为底层传输协议。NFS v4 及以后的版本只使用一个端口 2049。SMB(Server Message Block) 也叫 CIFS(Common Internet File System)，是 Windows 自带的网络协议，用于共享文件，打印机等。macOS 自带了 SMB 支持, Linux 可使用包 cifs-utils 和 samba 来使用该协议。SMB v2.0 及以后的版本基于 TCP, 使用端口 445。9P(Plan 9 File System Protocol) 是一套轻量级的远程文件系统协议，经常用于虚拟机与外界的文件共享。WSL2(Windows Subsystem for Linux 2) 就使用了该协议与 Linux 文件系统交互。 9P 通常使用 TCP 端口 564。WebDAV(Web Distributed Authoring and Versioning) 是 HTTP 的扩展，允许客户端执行远程文件编辑操作。它在标准的 HTTP 方法(GET, POST, PUT, DELETE 等)外，添加了新的方法：COPY, LOCK, UNLOCK, MKCOL, MOVE, PROPFIND, PROPPATCH。主流 HTTP 服务器(Nginx, Apache, IIS 等)都有内置或第三方的 WebDAV 支持，部分私人云软件(ownCloud, Nextcloud等)也支持 WebDAV 访问方式。 Windows 集成了 WebDAV 客户端。WebDAV 和 HTTP/HTTPS 使用相同的端口，默认为 80 和 443。FTP(File Transfer Protocol) 是用于文件传输的标准网络协议，应用十分广泛。FTP 的控制流和数据流使用不同的端口（通常是21 和 20）。数据连接可选主动或被动模式，被动模式下服务器侦听客户端的请求，可解决客户端由于 NAT 或防火墙等原因无公开端口的问题。 FTP 可设置为匿名连接，也可使用明文传输的用户名/密码进行身份认证。FTPS(FTP Secure) 在 FTP 的基础上增加了数据加密的功能，以提升安全性，它通常使用 990(控制)和 989 端口。SFTP(SSH File Transfer Protocol) 用于在安全数据连接上管理和传输文件。它是 SSH(Secure Shell)协议的扩展，通常由 SSH 服务器提供支持，但是也可以作为独立的文件服务使用。一般 SFTP 和 SSH 使用同一端口 22。
字符集和编码字符集(Character Set)是字符的集合，定义系统能处理哪些字符；编码(Encoding)则规定这些字符在计算机内部的表示方式。这里字符是抽象的概念，编码将其与二进制数据进行映射。由于编码通常依赖于字符集，实践中两者经常是绑定或互指的。常见的汉字编码方案GB2312，其全名为《信息交换用汉字编码字符集·基本集》；而 HTML 中的 标签也混用了字符集（charset）和编码的概念。既然是集合，字符集就会有超集和子集。如 1995 年发布的GBK(《汉字内码扩展规范》)，就是 GB2312 （发布于 1980 年）的超集；而2000 年发布的GB18030（《信息技术 中文编码字符集》）又是 GBK的超集。代码页代码页就是字符集加上编码。Windows提供了很多代码页选项，用于支持不同的语言文字。每个代码页有一个编号，简体中文对应的编号为936，其对应的字符集为GBK。你可以在命令行中执行命令reg query HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Nls\CodePage /f ACP，查看系统当前设置的代码页编号：C:\>reg query HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Nls\CodePage /f ACP HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Nls\CodePage ACP REG_SZ 936 搜索结束: 找到 1 匹配。 C:> 代码页是 Windows 系统的术语。在 Linux 系统中，类似的概念是locale，不过当前Linux 一般默认编码方式都是 utf-8，对应的字符集是 Unicode。ANSI 编码ANSI 编码代表 Windows 系统中当前代码页对应的编码，亦即系统默认编码。这个术语经常在 Windows平台使用，但实际上是被误用的。理论上，它应该等同于ANSI 在 1986 年发布的US-ASCII。现实中，它可以代表任意编码，甚至可以与ASCII 完全不兼容，只要设置为系统默认编码。UnicodeUnicode是一套标准，包含多语言统一的字符集及其相关编码，以及在这个字符集上进行文本处理的相关规则。Unicode 当前版本(12.1)共规定了 137,929个字符。它们不仅囊括了当前全球使用的主要语言文字（如拉丁字母，阿拉伯文字，简繁汉字等），还包含了很多符号（货币符号，标点符号，数学符号，几何图形，emoji等），甚至还有仅在史料上使用的文字（如楔形文字，埃及象形文字等）。在 Unicode中，每个字符被分配了一个数值(Code Point，代码点)和一个名称。比如字母A的名称是LATIN CAPITAL LETTER A(大写拉丁字母A)。它对应的数值是65，通常写作 U+0041(41是十六进制数，等于10进制的65)。除此之外，Unicode还定义了各个字符的一系列属性，比如是否是大写字母，是否代表数字，书写方向（左到右还是右到左），宽度（半角还是全角）等。基于这些属性，Unicode提供了大小写转换，文本换行，双向书写显示等相关算法。Unicode 字符集被分为十七个子集(Plane，平面或位面)，每个子集最多可包含65536 个字符，因此总共可以有 1,114,112 个字符。其中第一个子集(Plane0)包含最常用的字符，被称为 BMP(Basic Multilingual Plane,基本多文种平面)。 BMP 中为 UTF-16 中的代理对(Surrogate Pair)保留了2048 个位置，只剩下 63488 个有效字符空间（因此 Unicode中实际最多有1,112,064个字符）。BMP中的字符可以用四位十六进制数(U+xxxx)表示，其它的字符需要五位或更多。UTF-8, UTF-16, UTF-32这些是 Unicode 标准中规定的几种编码方式。UTF 是 Unicode Transformation Format 的缩写。UTF-8 是一种变长编码，以字节为基本单位，单个字符占用的字节数可能是 1（U+0000 ~ U+007F，128个位置，所有的 ASCII 字符），2（U+0080 ~ U+07FF, 1920个位置，主要是各种字母和符号），3（U+0800 ~ U+FFFF，63488个位置，BMP中所有其它字符，包括绝大部分常用汉字）或4（U+10000 ~ U+10FFFF，1048576个位置，所有其他字符）。 UTF-8 的主要优势在于兼容 ASCII，面向字节因而无需考虑字节顺序。现在 UTF-8 是互联网上使用率最高的编码方式。UTF-16 是另一种变长编码，以 16 位(bit)为基本单位，单个字符可占用单位数可能为 1 （BMP 中的所有字符）或 2（所有其它字符，这两个单位被称为代理对）。相对于另外两种编码，它最大的优势是存储大量东亚文本（中日韩）时占用空间较少。由于基本单位不是字节，而是 16 位，不同的系统通信时需要考虑字节序(Byte Order)问题。Windows, JavaScript, Java 等内部使用了 UTF-16。UTF-32 (又称 UCS-4) 是一种定长编码，每个字符使用 32 位来表示。它的优势在于实现和处理简单，劣势在于空间效率低。它同样需要考虑字节序问题。在 Unicode 标准早期版本中，唯一的编码方式是 16 位定长编码(UCS-2)，Windows 和 JavaScript 等采用了这种简单高效的标准。后来 Unicode 字符集扩充，16 位空间不足以容纳所有的字符，不得不产生了 UTF-16 的代理对机制，定长编码尴尬地转变成了变长编码。而 Linux 由于反应较慢，躲过一劫，后来逐渐转向了 UTF-8 编码。字节序 (Byte Order) {#byte-order}不同系统中处理数值时可能采用的大小端序(Endianness)不同。比如在小端序（Little-endian）的机器上，32位数字 1 在内存中表示为 4个字节：01 00 00 00；而在大端序(Big-endian)的机器上则表示为00 00 00 01。常见的 x86/x64, ARM 等处理器架构使用小端序，OpenRISC,SPARC等则使用大端序。不同系统间通信时，或读取其它系统存储的文件时，需要考虑字节顺序问题。Unicode 编码中可以使用字符 U+FEFF来作为字节序标记(Byte Order Mark)，需要时在字符序列前添加该字符。该字符本身不被视为文本的一部分，但通过分析它的表示方式，处理程序可以判断编码时的字节序，进而做出相应的处理。U+FEFF名称为ZERO WIDTH NO-BREAK SPACE(零宽度非间断空白)，原本是有实际意义的。后来这个意义被新字符U+2060 (WORD JOINER) 取代，U+FEFF 现在仅用于标明字节序。编码 大端字节序标记 小端字节序标记 -------- ---------------- ---------------- UTF-8 EF BB BF EF BB BF UTF-16 FE FF FF FE UTF-32 00 00 FE FF FF FE 00 00UTF-8 编码的基本单位是字节，无需考虑字节序问题。尽管可以添加字节序标记EF BB BF(字符 U+FEFF 在 UTF-8中的表示方式)，但标准推荐仅在特殊情况下使用。Windows 下记事本保存为UTF-8 格式时，会自动添加 BOM，用以与系统默认编码（即记事本所称的ANSI编码）区分。而 Linux/macOS等其它操作系统上的软件，一般只支持无字节序标记的 UTF-8编码文件，这样导致文件交换出现不少问题。 从 Windows 10 v1903起，记事本也在保存为 UTF-8 编码时默认不附加字节序标记了。字素(Grapheme)与字素簇(Grapheme Cluster) {#graphemegrapheme-cluster}字素是文本在书写时最小的单位，可以被理解为单独的"字"。在 Unicode 标准中，字符（Character）一般指代码点（Code Point）。通常，一个字素就是一个字符。但是，也有些字素是由多个字符序列组合而成的，这样的字符序列被称为字素簇。比如字母 é 可以用字母 e (U+0065) 加上重音符(U+0301)组合而成。像重音符这样用于修饰前一个字符的字符，被称为组合字符（Combining Character）。可以使用多个组合字符来修饰同一个字符，这就是有一段时间内各个社区很流行的越界文字的技术根源。超̷̪͓̫͕̳̝̔͐̋͌͑͗́̒̕͟͞越͓̻̗̙̙̠͖̔̆̌͑͐̽̊代̷͉̘̲̺̤͈̀͑̒͗̄͘̕͜码̵̨̟͖͎̉̿͌͜͞͞͞，越界文字̗̗̗̗̗̗̗̗̗̗̗̗̗̗̗̗̗̗̗̗̗̗̗̗̗̗̗̗̗̗̗̗̗̗̗̗̗̗̗̗̗̗̗̗̗̗̗̗̗̗̗̗̗̗̗̗̗̗̗̗̗̗̗̗̗̗̗̗̗̗̗̗̗̗̗̗̗̗̗̗̗̗̗̗̗̗̗̗̗̗̗̗̗̗̗̗如同前面的例子所示，含重音符的拉丁字母可以使用基本字符加上重音修饰字符来表示。为保持与旧软件系统的兼容性，这种情况下Unicode 中实际还包含了预先组合好的单个字符。即，某些字素可以有多个表示方式。上面的 é 既可以用字符序列 U+0065 U+0301 表示，也可以用单个 U+00E9 表示。这样也带来了新的问题，在字符串比较，排序等操作前需要首先进行正规化(Normalization)。正规化即把所有可用单个字符表示的字符序列替换为对应的单个字符。CollationCollation 是字符间的排序规则。在一个 Collation 中，对应字符集里所有的字符都有确定的排序先后关系，因而构成了全序关系。文本排序的规则可以有很多，如字母表顺序（Alphabetical order），数值顺序等；针对汉字还有部首笔画顺序等。同一字符，在不同的语言中，所处的排序位置也有可能不同。排序时，还要考虑是否忽略大小写，是否忽略重音/音调等，因此排序规则是很复杂的。Unicode 标准中提供了所有字符的默认的排序规则（Default Unicode Collation Element Table, DUCET），该规则也可根据不同的情况进行定制。开源项目ICU(International Components for Unicode)里提供了各种语言的各种排序规则。MySQL 与 UTF-8 {#mysql-utf-8}在 MySQL 数据库中，存在多个与 Unicode 相关的字符集和排序规则。一般情况下应该使用 utf8mb4 字符集，这才是 MySQL 中真正的UTF-8编码(如第一节所言，这里字符集和编码又一次互指了)。相应的所谓 utf8 字符集是非标准的，其中单个字符最多只能编码为 3 个字节，因此很多字符无法保存。MySQL 中排序规则应该使用 utf8mb4_unicode_ci，而不是 utf8mb4_general_ci。后者性能稍微高于前者，但算法不符合 Unicode 标准，可能在处理某些语言文字时出现错误的结果。utf8mb4_unicode_ci对应 Unicode v4.0，较新版本的 MySQL 还同时支持v5.2(utf8mb4_unicode_520_ci)，v9.0(utf8mb4_0900_ai_ci)。
维度phonegap代表Smobiler备注跨平台性通用android/ios劣势不明显人员技术依赖js + html5 + css + (java + oc/swift等等)C#/VB + ( ? )技术平台不同，不太好作对比，不过js dever覆盖面更广, 因此以phonegap为代表的技术更易推广，括号里面是定制化开发使用到的技术集成开发工具不一致Visual Studio完爆发布直接编译成目标平台的安装文件依赖client + Server考虑到使用体验和推广问题，劣势很明显
System.currentTimeMillis()的调用比new一个普通对象要耗时的多（具体耗时高出多少我还没测试过，有人说是100倍左右）System.currentTimeMillis()之所以慢是因为去跟系统打了一次交道后台定时更新时钟，JVM退出时，线程自动回收，得到的数据10亿：43410,206,210.72815533980582%1亿：4699,29,162.0344827586207%1000万：480,12,40.0%100万：50,10,5.0%参考代码如下：import java.util.Date; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; public class SystemClock { private final long period; private volatile long now; private static final SystemClock instance = new SystemClock(1); private SystemClock(long period) { this.period = period; // this.now = new AtomicLong(System.currentTimeMillis()); this.now = System.currentTimeMillis(); scheduleClockUpdating(); } public static long now() { return instance.currentTimeMillis(); } public static Date nowDate() { return new Date(instance.currentTimeMillis()); } private void scheduleClockUpdating() { ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(new ThreadFactory() { public Thread newThread(Runnable runnable) { Thread thread = new Thread(runnable, "SystemClock"); thread.setDaemon(true); return thread; } }); scheduler.scheduleAtFixedRate(new Runnable() { public void run() { now = System.currentTimeMillis(); } }, period, period, TimeUnit.MILLISECONDS); } private long currentTimeMillis() { return now; } }
关于验证和授权，很多计算机程序员都分不清楚，举个例子。美女许天仙在上网聊天。慕名已久的王二狗邀她见面，为了表示诚意，二狗开通了视频。天仙看到二狗的真面目后说：“我只和帅哥见面。”张三风听闻此事，也去约天仙见面，他发了一张照片过去。天仙还是拒绝了。三风不解：“你不是喜欢帅哥吗？”天仙答曰：“你竟然想用我的男神竹野内丰（读者如认为他不是帅哥可自行换成金秀贤，笔者十分好奇“美男”、“帅哥”之类的概念外延有多大，长相差异如此大的两人怎么能被划定为一类人）的照片骗我？”上面的场景用计算机技术的术语来解读就是：王二狗验证成功，授权失败。张三风授权没问题，如果验证不失败。在对用户名和密码的讨论里，我们已经谈到身份验证。不过一个用户通过验证并不表示他就可以进行系统里所有的操作，对很多有限制的操作，验证还仅仅是整个安全审核过程的第一步。我们在使用软件和浏览网站时，经常会遇到一个词——权限。游戏里很多装备要付费之后才能使用，论坛里不同级别的用户有不等的阅读和发帖权限，公司里的软件系统各个账户具备不同的功能，甚至你电脑上运行的不同软件本身也有不同的权限。“您没有权限进行以下操作”是一个令人懊恼的提示。何谓权限？一个系统里注册了很多账号，有很多操作、功能和资源，有一部分功能只有一部分账号能使用，一部分资源只有一部分账户能访问，这种账号和功能、资源间关系的差异就是权限的来源，换句话说，只要不是所有账户都能无差别地执行系统的所有操作，就存在权限的概念。而系统对账户的权限进行分配和验证的过程就称为授权。授权（authorization）和验证（authentication）在计算机技术里含义相差很远，英文拼写却有些相似，在实际的软件系统中，这两个过程也总是相继发生，合作构成系统的安全机制。验证首先确认某个用户是否具有他所声称的身份（identity），也就是排除冒名顶替。用户通过验证后，系统就明确了他是谁。但是哪些事情他能做，哪些不能做就是授权所解决的问题。
场景:前端需要展示一个列表（如：首页的轮播图），后台需要能够控制列表数据的排序。需求：自动规则 + 运营干预，干预包括：降低排名，提升排名，设定位置和新增item实现：在Web后台管理系统中，列表形式的数据排序功能是很常见的需求。要实现这类功能，给数据表增加一个排序字段order，越大排名越靠前，数字相同时按照自动规则（如：id）排序，这样，就可以实现了：idnameorder1tom22jack33bob1实际的排序显示为：jack tom bob 既然是后台，那么主要的用户是公司的运营人员，本着提高使用效率的原则，在草稿纸上画了个原型让运营对比，然后来得出哪个更符合他的使用习惯，再做确定，结果沟通下来运营理想中操作是想要在后台直接拖动排序。现在问题来了，应该怎么设计这个运营干预的功能：方案1：填写数字方案2：上移、下移方案3：手动控制加分or减分（+1/-1 +10/-10）方案4：如运运营所想，提供拖动排序功能方案1的技术实现最简单，对开发来说最方便，但对运营来说却比较麻烦。方案2的技术实现其实较复杂，比如上移一位，需要先查询当前项的前一项的order, 然后让当前项的order+1并保存, 对运营来说操作较简单。方案3的技术实现比较简单，比如点击-10，只需更新当前项的order-10并保存，但有可能在order-10后当前项的顺序还没有变（如：前一项：12，当前项11，后一项：0），对运营来说操作比较简单。方案4的技术实现比较复杂，需要使用js实现拖拽数据项的效果等，在拖动结束或点击保存后判断当前的所有数据顺序并保存，对运营来说操作简单。综合比较技术实现复杂度：方案1<方案3<方案2<方案4操作便捷性：方案4<方案3<方案2<方案1分析一下大多数的场景，看到列表的数据是有限的，一般都是5条、10条、20条左右，由于我所在的是创业公司，开发资源有限，选择方案3在当前情况下是合适的实现方式。经过了一番沟通，在许诺了下午茶后，说服了产品、运营接收这中方案。图中就是我用方案3实现的一个界面，单个箭头的是+1 -1，俩个箭头的分别是+10 -10，分别用于微调和较大位置距离的调整，在管理大多数少量数据列表的场景下对于运营人员是够用了。
JSP与MySQL交互的中文乱码解决方案及总结
首先实现了一个StringConvert bean（GBtoISO()和ISOtoGB()两个方法），解决了与MySQL数据库交互的时候的部分中文乱码问题：在JSP程序中读取MySQL的中文内容，用这两个方法可以解决乱码问题。 但是从JSP写入到MySQL的中文内容都成了乱码，并且再读出来的时候也显示为“？？”，在这里应该出现了编码转换过程中的字符信息丢失。郁闷的是，我在命令行窗口中登陆到MySQL后，执行如INSERT INTO customer VALUES(''字符'',...)这样的语句时，写入到数据表中的中文内容又是显示正常的！！！数据库使用的字符集是utf8。 碰壁多次，终于发现一条解决问题的路径：查看MySQL手册的时候，看到一条这样的语句：To allow multiple character sets to be sent from the client, the "UTF-8" encoding should be used, either by configuring "utf8" as the default server character set, or by configuring the JDBC driver to use "UTF-8" through the characterEncoding property. 此外，在查阅《MySQL权威指南》时，发现在查询语句中可以使用这样的语法将字符串转换到一个给定的字符集：_charset str。 其中charset必须是服务器支持的某个字符集。在本例中，shopdb数据库使用的默认字符集是utf8，于是开始测试： 先输入INSERT INTO publish Values(''8'',_gb2312 ''高等教育出版社'') 写入后中文变成“？？” 再试INSERT INTO publish Values(''8'',_gbk ''高等教育出版社'') 结果同上 INSERT INTO publish Values(''8'',_utf8 ''高等教育出版社'') 这下更干脆，什么都没有！！ 快疯了!!没办法，用show character set;命令查看MySQL支持的字符集，心想我都试一遍总有一个能成功吧。浏览了一下，发现没有几个熟悉的字符集，就只剩下一个latin1(ISO-8859-1)比较常见了，不会是它吧，一试之下果然便是。 INSERT INTO publish Values(''8'',_latin1 ''高等教育出版社'') 输入中文能够正确显示。 这下总算找到方法了，把Tomcat下配置的数据库连接池的url改为＂...characterEncoding=UTF-8＂，然后把写入数据库的中文内容用 String s2 = new String(s1.getBytes("gb2312"),"ISO-8859-1")进行转码，其中s1为中文字符串．然后再写入到数据库一切显示正常。 为解决这个问题查看了n多资料，现作一个总结：由于字符集和字符编码方式的不同，在OS以及程序之间传递数据（尤其是multiple character sets中的数据）时便会产生乱码以及字符信息的丢失．解决这个问题的关键便是了解数据输出端和接收端使用的字符集和字符编码方式，如果这两种编码方式不同，便需要在数据出口或入口处进行 转码。一般的说，在编写代码,编译，以及运行期间都会字符数据的传递，因此需要特别小心。 在编写代码的时候，你可能会使用某种开发工具，例如我正在使用的Eclipse．或许在写的时候一切正常，可是一旦保存后再次打开文档，所有的中文字符都变成了乱码。这是因为在编写的时候，这些字符数据都在内存的某个stream中，ok，这没问题，可是保存的时候这个stream中的数据会被写入到硬盘，使用的就是你的开发工具默认的编码方式，如果很不幸你的开发工具默认编码方式是ISO-8859-1，中文字符信息就不能正确地存储。Eclipse中可以这样查看并修改默认字符编码方式：Project->Properties->info，这里有＂default encoding for text file＂。如果设置为GBK，那么编写代码并保存这关就过了。 对于JSP程序而言，编写完代码后就交给Container，首先它们会被转成.java文件，然后编译成.class才能提交给服务器执行．这个过程也存在字符编码问题．java编译器(javac)使用操作系统的语言环境作为默认的字符编码方式，JRE(Java Runtime Environment)也是这样。只有当编译和运行环境的字符编码方式与存储源文件的编码方式相同时，中文字符才能正确地显示。否则就需要在运行时进行转码，使它们使用兼容的编码。这里的设置可以分为几个层次：操作系统层支持的语言，这是最重要的，因为它会影响ＪＶＭ的默认字符编码方式，同时对字符的显示，如字体等有直接影响；J2EE服务器层，大多数服务器都可以对字符编码进行自定义的配置，例如Tomcat就可以通过web.xml中设置javaEncoding参数设置字符编码，默认是UTF-8． IE也可以设置成总是使用UTF-8编码来发送请求．应用程序层，每个配置在服务器下的程序都可以设置自己的编码方式，这个我目前还没有用到，以后再学习。 运行时的转码，运行时期，应用程序很可能需要与外部系统进行交互，例如对数据库进行读写，对外部文件进行读写．在这些情况下，应用程序免不了要和外部系统进行数据交换。那么对于中文字符， 数据出入口的编码方式就显得特别重要了。一般外部系统都有自己的字符编码方式，我的例子中配置的MySQL就是使用的UTF-8编码。JSP页面通过设定＂charset=gb2312＂, 使用gb2312编码，在它与数据库交互的时候就需要进行显式的转码才能正确处理中文字符。
Catalina.bat是tomcat所有脚本中最重要的脚本，完成几乎所有的tomcat操作。如启动，关闭等等,都是由catalina.bat脚本来完成的。接下来，我将对Tomcat catalina.bat脚本进行分析。 首先省去catalina.bat开头诸多注解，这些注解主要是讲解各个变量是干什么的。需要的话，自己看下英文就可以了。这里就不翻译了。 rem Guess CATALINA_HOME if not defined 查看是否在tomcat目录下，与startup.bat里相同，不解释了。 set CURRENT_DIR=%cd% if not "%CATALINA_HOME%" == "" goto gotHome set CATALINA_HOME=%CURRENT_DIR% if exist "%CATALINA_HOME%\bin\catalina.bat" goto okHome cd .. set CATALINA_HOME=%cd% cd %CURRENT_DIR% :gotHome if exist "%CATALINA_HOME%\bin\catalina.bat" goto okHome echo The CATALINA_HOME environment variable is not defined correctly echo This environment variable is needed to run this program goto end :okHome rem Get standard environment variables if exist "%CATALINA_HOME%\bin\setenv.bat" call "%CATALINA_HOME%\bin\setenv.bat" 如果存在setenv.bat脚本，调用它，我的tomcat 没有这个脚本 rem Get standard Java environment variables if exist "%CATALINA_HOME%\bin\setclasspath.bat" goto okSetclasspath 查看是否存在setclasspath.bat脚本，如果存在，转到okSetclasspath位置 echo Cannot find %CATALINA_HOME%\bin\setclasspath.bat 否则输出下面两行，并退出 echo This file is needed to run this program goto end :okSetclasspath okSetclasspath位置 set BASEDIR=%CATALINA_HOME% 设定BASEDIR变量与CATALINA_HOME变量值相同 call "%CATALINA_HOME%\bin\setclasspath.bat" %1 调用setclasspath.bat脚本并加上参数 if errorlevel 1 goto end 如果存在错误 退出 rem Add on extra jar files to CLASSPATH 设定JSSE_HOME变量，如果存在加入CLASSPATH，不存在跳过 if "%JSSE_HOME%" == "" goto noJsse 检查是否存在JSSE_HOME变量 set CLASSPATH=%CLASSPATH%;%JSSE_HOME%\lib\jcert.jar;%JSSE_HOME%\lib\jnet.jar;%JSSE_HOME%\lib\jsse.jar 如果有加入到CLASSPATH变量后面 :noJsse set CLASSPATH=%CLASSPATH%;%CATALINA_HOME%\bin\bootstrap.jar 将bootstrap.jar加入到CLASSPATH里 if not "%CATALINA_BASE%" == "" goto gotBase 如果CATALINA_BASE变量不为空，跳过，转到gotBase位置 set CATALINA_BASE=%CATALINA_HOME% 如果为空，将CATALINA_BASE设为CATALINA_HOME变量的值 :gotBase if not "%CATALINA_TMPDIR%" == "" goto gotTmpdir CATALINA_TMPDIR不为空，跳过，转到gotTmpdir位置 set CATALINA_TMPDIR=%CATALINA_BASE%\temp 如果为空，将 CATALINA_TMPDIR设为%CATALINA_BASE%\temp变量的值（即tomcat\temp） :gotTmpdir if not exist "%CATALINA_HOME%\bin\tomcat-juli.jar" goto noJuli 如果不存在tomcat-juli.jar这个类，转到noJuli位置 set JAVA_OPTS=%JAVA_OPTS% -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager - Djava.util.logging.config.file="%CATALINA_BASE%\conf \logging.properties" 如果存在，将变量加入到JAVA_OPTS里 :noJuli set JAVA_OPTS=%JAVA_OPTS% -Xms128m -Xmx512m -Dfile .encoding=UTF8 -Duser.timezone=GMT -Djava.security.auth.login.config=%CATALINA_HOME%/conf/jaas.config 设定JAVA_OPTS变量 echo Using CATALINA_BASE: %CATALINA_BASE% 输出CATALINA_BASE变量值 echo Using CATALINA_HOME: %CATALINA_HOME% 输出CATALINA_HOME变量值 echo Using CATALINA_TMPDIR: %CATALINA_TMPDIR% 输出CATALINA_TMPDIR变量值 if ""%1"" == ""debug"" goto use_jdk 如果变量%1里存在debug ，转到use_jdk位置 echo Using JRE_HOME: %JRE_HOME% 输出JRE_HOME变量值 goto java_dir_displayed 转到java_dir_displayed :use_jdk echo Using JAVA_HOME: %JAVA_HOME% 输出JAVA_HOME变量值 :java_dir_displayed 下面几行设定相应变量 set _EXECJAVA=%_RUNJAVA% set MAINCLASS=org.apache.catalina.startup.Bootstrap set ACTION=start set SECURITY_POLICY_FILE= set DEBUG_OPTS= set JPDA= if not ""%1"" == ""jpda"" goto noJpda set JPDA=jpda if not "%JPDA_TRANSPORT%" == "" goto gotJpdaTransport set JPDA_TRANSPORT=dt_shmem :gotJpdaTransport if not "%JPDA_ADDRESS%" == "" goto gotJpdaAddress set JPDA_ADDRESS=jdbconn :gotJpdaAddress if not "%JPDA_SUSPEND%" == "" goto gotJpdaSuspend set JPDA_SUSPEND=n :gotJpdaSuspend if not "%JPDA_OPTS%" == "" goto gotJpdaOpts set JPDA_OPTS=-Xdebug -Xrunjdwp:transport=%JPDA_TRANSPORT%,address=%JPDA_ADDRESS%,server=y,suspend=%JPDA_SUSPEND% :gotJpdaOpts shift :noJpda if ""%1"" == ""debug"" goto doDebug 如果%1为debug，转到doDebug，运行debug模式 if ""%1"" == ""run"" goto doRun 如果%1为run，转到doRun，运行正常模式 if ""%1"" == ""start"" goto doStart 如果%1为start，转到doStart，启动tomcat if ""%1"" == ""stop"" goto doStop 如果%1为stop，转到doStop，关闭tocmat if ""%1"" == ""version"" goto doVersion 如果%1为version，转到doVersion，显示tomcat的版本号 echo Usage: catalina ( commands ... ) 如果%1没有上述内容，输出下面几行，并结束 echo commands: echo debug Start Catalina in a debugger echo debug -security Debug Catalina with a security manager echo jpda start Start Catalina under JPDA debugger echo run Start Catalina in the current window echo run -security Start in the current window with security manager echo start Start Catalina in a separate window echo start -security Start in a separate window with security manager echo stop Stop Catalina echo version What version of tomcat are you running? goto end :doDebug shift 将%2里的值转到%1 set _EXECJAVA=%_RUNJDB% 将变量 _EXECJAVA设为_RUNJDB变量的值 set DEBUG_OPTS=-sourcepath "%CATALINA_HOME%\..\..\jakarta-tomcat-catalina\catalina\src\share" 设定DEBUG_OPTS变量 if not ""%1"" == ""-security"" goto execCmd 如果%1不为-security，转到execCmd位置 shift 将%2里的值转到%1 echo Using Security Manager 输出该行 set SECURITY_POLICY_FILE=%CATALINA_BASE%\conf\catalina.policy 设定SECURITY_POLICY_FILE变量的值 goto execCmd 转到execCmd位置 :doRun shift 将%2里的值转到%1 if not ""%1"" == ""-security"" goto execCmd 如果%1不为-security，转到execCmd位置 shift 将%2里的值转到%1 echo Using Security Manager 输出该行 set SECURITY_POLICY_FILE=%CATALINA_BASE%\conf\catalina.policy 设定SECURITY_POLICY_FILE变量的值 goto execCmd 转到execCmd位置 :doStart shift 将%2里的值转到%1 if not "%OS%" == "Windows_NT" goto noTitle 如果OS变量不为Windows_NT，转到noTitle set _EXECJAVA=start "Tomcat" %_RUNJAVA% 设定_EXECJAVA变量的值 goto gotTitle 转到gotTitle位置 :noTitle set _EXECJAVA=start %_RUNJAVA% 设定 _EXECJAVA 变量的值 :gotTitle if not ""%1"" == ""-security"" goto execCmd 如果%1不为-security，转到execCmd位置 shift 将%2里的值转到%1 echo Using Security Manager 输出该行 set SECURITY_POLICY_FILE=%CATALINA_BASE%\conf\catalina.policy 设定SECURITY_POLICY_FILE变量的值 goto execCmd 转到execCmd位置 :doStop shift 将%2里的值转到%1 set ACTION=stop 将ACTION的变量设为stop set CATALINA_OPTS= 设CATALINA_OPTS为空 goto execCmd 转到execCmd位置 :doVersion 显示tomcat版本号 %_EXECJAVA% -classpath "%CATALINA_HOME%\server\lib\catalina.jar" org.apache.catalina.util.ServerInfo 执行该命令 goto end 结束该程序 :execCmd rem Get remaining unshifted command line arguments and save them in the 以下几行将命令参数存入CMD_LINE_ARGS变量中 set CMD_LINE_ARGS= :setArgs if ""%1""=="""" goto doneSetArgs set CMD_LINE_ARGS=%CMD_LINE_ARGS% %1 shift goto setArgs :doneSetArgs rem Execute Java with the applicable properties if not "%JPDA%" == "" goto doJpda 如果JPDA变量不为空，转到doJpda位置 if not "%SECURITY_POLICY_FILE%" == "" goto doSecurity 如果SECURITY_POLICY_FILE变量不为空，转到doSecurity位置 如果都没有执行下面命令，并结束该程序 %_EXECJAVA% %JAVA_OPTS% %CATALINA_OPTS% %DEBUG_OPTS% -Djava.endorsed.dirs="%JAVA_ENDORSED_DIRS%" -classpath "%CLASSPATH%" -Dcatalina.base="%CATALINA_BASE%" -Dcatalina.home="%CATALINA_HOME%" -Djava.io.tmpdir="%CATALINA_TMPDIR%" %MAINCLASS% %CMD_LINE_ARGS% %ACTION% goto end :doSecurity 执行下面命令，并结束该程序 %_EXECJAVA% %JAVA_OPTS% %CATALINA_OPTS% %DEBUG_OPTS% -Djava.endorsed.dirs="%JAVA_ENDORSED_DIRS%" -classpath "%CLASSPATH%" -Djava.security.manager -Djava.security.policy=="%SECURITY_POLICY_FILE%" -Dcatalina.base="%CATALINA_BASE%" -Dcatalina.home="%CATALINA_HOME%" -Djava.io.tmpdir="%CATALINA_TMPDIR%" %MAINCLASS% %CMD_LINE_ARGS% %ACTION% goto end :doJpda 如果SECURITY_POLICY_FILE变量不为空，转到doSecurityJpda位置，为空执行下面命令，并结束该程序 if not "%SECURITY_POLICY_FILE%" == "" goto doSecurityJpda %_EXECJAVA% %JAVA_OPTS% %CATALINA_OPTS% %JPDA_OPTS% %DEBUG_OPTS% -Djava.endorsed.dirs="%JAVA_ENDORSED_DIRS%" -classpath "%CLASSPATH%" -Dcatalina.base="%CATALINA_BASE%" -Dcatalina.home="%CATALINA_HOME%" -Djava.io.tmpdir="%CATALINA_TMPDIR%" %MAINCLASS% %CMD_LINE_ARGS% %ACTION% goto end :doSecurityJpda 执行下面命令，并结束该程序 %_EXECJAVA% %JAVA_OPTS% %CATALINA_OPTS% %JPDA_OPTS% %DEBUG_OPTS% -Djava.endorsed.dirs="%JAVA_ENDORSED_DIRS%" -classpath "%CLASSPATH%" -Djava.security.manager -Djava.security.policy=="%SECURITY_POLICY_FILE%" -Dcatalina.base="%CATALINA_BASE%" -Dcatalina.home="%CATALINA_HOME%" -Djava.io.tmpdir="%CATALINA_TMPDIR%" %MAINCLASS% %CMD_LINE_ARGS% %ACTION% goto end :end catalina.bat咋一看还以为是什么windows程序，可以双击运行，实际上就是调用java命令运行Bootstrap类。从上面代码可以看 出tomcat确实是一个纯java的程序，脚本最后都变成直接使用java命令执行程序，与我们普通写的java程序，没有什么不同。只不过由于 tomcat可以使用各种众多的模式（如debug,Security等），以及各种需要各种参数所以不得不使用脚本来执行。 如果你想看看你到底使用了什么命令可以在“if not "%SECURITY_POLICY_FILE%" == "" goto doSecurity”这行下面添加两行。 echo %_EXECJAVA% %JAVA_OPTS% %CATALINA_OPTS% %DEBUG_OPTS% -Djava.endorsed.dir ....（下面那段，是一行但太长，折行了） pause 第一行的命令是打印这具话，系统会将% %里面的变量提换成找到的值并输出。第二行是暂停程序，你可以通过任意键来恢复运行。下面是我的程序打印的结果： start "Tomcat" "C:\Program Files\Java\jdk1.6.0_01\bin\java" -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager -Djava.util.logging.config.file="C:\work\tomcat\conf\logging.properties" -Xms128m -Xmx512m -Dfile.encoding=UTF8 -Duser.timezone=GMT -Djava.security.auth.login.config=C:\work\tomcat/conf/jaas.config -Djava.endorsed.dirs="C:\work\tomcat\common\endorsed" -classpath "C:\Program Files\Java\jdk1.6.0_01\lib\tools.jar;C:\work\tomcat\bin\bootstrap.jar" -Dcatalina.base="C:\work\tomcat" -Dcatalina.home="C:\work\tomcat" -Djava.io.tmpdir="C:\work\tomcat\temp" org.apache.catalina.startup.Bootstrap start start "tomcat"是另开一个窗口，窗口名是tomcat的意思，你可以去掉这部分，完将你程序打印的拷贝到命令行下，点回车，看是不是一样运行 tomcat了。Catalina.bat startup实际时就将各种系统变量加以总结，输出成这个命令。 呵呵!在安装版的Tomcat中没有catalina.bat，实际上这些catalina.bat的设置都是对注册表的修改，对应的注册表键值如下HKEY_LOCAL_MACHINE\SOFTWARE\Apache Software Foundation\Procrun 2.0\Tomcat6\Parameters\Java\Options 在使用MyEclipse+Tomcat发布项目的时候通常会给如下提示信息：[org.apache.catalina.core.AprLifecycleListener]-[INFO] The Apache Tomcat Native library which allows optimal performance in production environments was not found on the java.library.path: （...你的系统环境PATH路径...） 在停止时发现出现如下信息 [org.apache.catalina.core.AprLifecycleListener]-[INFO] Failed shutdown of Apache Portable Runtime 解决：Windows下的安装配置与使用 先去 http://tomcat.heanet.ie/native/ 下载编译好的 tcnative-1.dll 文件，也可以下载tomcat-native.zip自己进行编译 修改一下 catalina.bat，加上set CATALINA_OPTS="-Djava.library.path=../../APR/lib"，因为我把这个DLL放在与Tomcat目录同级的APR/lib目录下，所以采用了相对路径。或者把dll加到你的系统变量PATH里之类的，最简单的方法应当就是把tcnative-1.dll文件放在Tomcat的bin目录下。这样设置后，启动可以看到信息如下： [org.apache.coyote.http11.Http11AprProtocol]-[INFO] Initializing Coyote HTTP/1.1 on http-9091 在停止时发现出现如下信息 [org.apache.coyote.http11.Http11AprProtocol]-[INFO] Pausing Coyote HTTP/1.1 on http-9091 ...... [org.apache.coyote.http11.Http11AprProtocol]-[INFO] Stopping Coyote HTTP/1.1 on http-9091 这样就配置成功了。
一、Tomcat的JVM提示内存溢出查看%TOMCAT_HOME%\logs文件夹下，日志文件是否有内存溢出错误二、修改Tomcat的JVM1、错误提示：java.lang.OutOfMemoryError: Java heap spaceTomcat默认可以使用的内存为128MB，在较大型的应用项目中，这点内存是不够的，有可能导致系统无法运行。常见的问题是报Tomcat内存溢出错误，Out of Memory(系统内存不足)的异常，从而导致客户端显示500错误，一般调整Tomcat的使用内存即可解决此问题。 Windows环境下修改“%TOMCAT_HOME%\bin\catalina.bat”文件，在文件开头增加如下设置：set JAVA_OPTS=-Xms256m -Xmx512m Linux环境下修改“%TOMCAT_HOME%\bin\catalina.sh”文件，在文件开头增加如下设置：JAVA_OPTS=’-Xms256m -Xmx512m’ 其中，-Xms设置初始化内存大小，-Xmx设置可以使用的最大内存。2、错误提示：java.lang.OutOfMemoryError: PermGen space原因：PermGen space的全称是Permanent Generation space,是指内存的永久保存区域，这块内存主要是被JVM存放Class和Meta信息的,Class在被Loader时就会被放到PermGen space中，它和存放类实例(Instance)的Heap区域不同,GC(Garbage Collection)不会在主程序运行期对PermGen space进行清理，所以如果你的应用中有很CLASS的话,就很可能出现PermGen space错误，这种错误常见在web服务器对JSP进行pre compile的时候。如果你的WEB APP下都用了大量的第三方jar, 其大小超过了jvm默认的大小(4M)那么就会产生此错误信息了。解决方法：在catalina.bat的第一行增加：set JAVA_OPTS=-Xms64m -Xmx256m -XX:PermSize=128M -XX:MaxNewSize=256m -XX:MaxPermSize=256m在catalina.sh的第一行增加：JAVA_OPTS=-Xms64m -Xmx256m -XX:PermSize=128M -XX:MaxNewSize=256m -XX:MaxPermSize=256m三、JVM设置堆的尺寸 -Xmssize in bytes设定Java堆的初始尺寸，缺省尺寸是2097152 (2MB)。这个值必须是1024个字节（1KB）的倍数，且比它大。（-server选项把缺省尺寸增加到32M。） -Xmnsize in bytes为Eden对象设定初始Java堆的大小，缺省值为640K。（-server选项把缺省尺寸增加到2M。) -Xmxsize in bytes设定Java堆的最大尺寸，缺省值为64M，（-server选项把缺省尺寸增加到128M。） 最大的堆尺寸达到将近2GB（2048MB）。 请注意：很多垃圾收集器的选项依赖于堆大小的设定。请在微调垃圾收集器使用内存空间的方式之前，确认是否已经正确设定了堆的尺寸。 垃圾收集:内存的使用 -XX:MinHeapFreeRatio=percentage as a whole number修改垃圾回收之后堆中可用内存的最小百分比，缺省值是40。如果垃圾回收后至少还有40%的堆内存没有被释放，则系统将增加堆的尺寸。 -XX:MaxHeapFreeRatio=percentage as a whole number改变垃圾回收之后和堆内存缩小之前可用堆内存的最大百分比，缺省值为70。这意味着如果在垃圾回收之后还有大于70%的堆内存，则系统就会减少堆的尺寸。 -XX:NewSize=size in bytes为已分配内存的对象中的Eden代设置缺省的内存尺寸。它的缺省值是640K。（-server选项把缺省尺寸增加到2M。） -XX:MaxNewSize=size in bytes允许您改变初期对象空间的上限，新建对象所需的内存就是从这个空间中分配来的，这个选项的缺省值是640K。（-server选项把缺省尺寸增加到2M。） -XX:NewRatio=value改变新旧空间的尺寸比例，这个比例的缺省值是8，意思是新空间的尺寸是旧空间的1/8。 -XX:SurvivorRatio=number改变Eden对象空间和残存空间的尺寸比例，这个比例的缺省值是10，意思是Eden对象空间的尺寸比残存空间大survivorRatio+2倍。 -XX:TargetSurvivorRatio=percentage设定您所期望的空间提取后被使用的残存空间的百分比，缺省值是50。 -XX:MaxPermSize=size in MB长久代（permanent generation）的尺寸，缺省值为32（32MB）。 三、查看Tomcat的JVM内存Tomcat6中没有设置任何默认用户，因而需要手动往Tomcat6的conf文件夹下的tomcat-users.xml文件中添加用户。如： <user username="tomcat" password="tomcat" roles="manager"/> 注：添加完需要重启Tomcat6。访问http://localhost:8080/manager/status，输入上面添加的用户名和密码。然后在如下面的JVM下可以看到内存的使用情况。JVMFree memory: 2.50 MB Total memory: 15.53 MB Max memory: 63.56 MB 四、Tomcat连接数设置在tomcat配置文件server.xml中的配置中，和连接数相关的参数有：minProcessors：最小空闲连接线程数，用于提高系统处理性能，默认值为10maxProcessors：最大连接线程数，即：并发处理的最大请求数，默认值为75acceptCount：允许的最大连接数，应大于等于maxProcessors，默认值为100enableLookups：是否反查域名，取值为：true或false。为了提高处理能力，应设置为falseconnectionTimeout：网络连接超时，单位：毫秒。设置为0表示永不超时，这样设置有隐患的。通常可设置为30000毫秒。其中和最大连接数相关的参数为maxProcessors和acceptCount。如果要加大并发连接数，应同时加大这两个参数。web server允许的最大连接数还受制于操作系统的内核参数设置，通常Windows是2000个左右，Linux是1000个左右。Unix中如何设置这些参数，请参阅Unix常用监控和管理命令
Tomcat默认可以使用的内存为128MB，在较大型的应用项目中，这点内存是不够的，需要调大,并且Tomcat本身不能直接在计算机上运行，需要依赖于硬件基础之上的操作系统和一个java虚拟机。这里向大家描述一下如何使用Tomcat配置JVM参数，Tomcat本身不能直接在计算机上运行，需要依赖于硬件基础之上的操作系统和一个java虚拟机。您可以选择自己的需要选择不同的操作系统和对应的JDK的版本，但还是推荐您使用Sun公司发布的JDK。Tomcat配置JVM参数Tomcat本身不能直接在计算机上运行，需要依赖于硬件基础之上的操作系统和一个java虚拟机。您可以选择自己的需要选择不同的操作系统和对应的JDK的版本（只要是符合Sun发布的Java规范的），但我们推荐您使用Sun公司发布的JDK。确保您所使用的版本是最新的，因为Sun公司和其它一些公司一直在为提高性能而对java虚拟机做一些升级改进。一些报告显示JDK1.4在性能上比JDK1.3提高了将近10%到20%。可以给Java虚拟机设置使用的内存，但是如果你的选择不对的话，虚拟机不会补偿。可通过命令行的方式改变虚拟机使用内存的大小。如下表所示有两个参数用来设置虚拟机使用内存的大小。参数 描述-Xms JVM初始化堆的大小-Xmx JVM堆的最大值这两个值的大小一般根据需要进行设置。初始化堆的大小执行了虚拟机在启动时向系统申请的内存的大小。一般而言，这个参数不重要。但是有的应用程序在大负载的情况下会急剧地占用更多的内存，此时这个参数就是显得非常重要，如果虚拟机启动时设置使用的内存比较小而在这种情况下有许多对象进行初始化，虚拟机就必须重复地增加内存来满足使用。由于这种原因，我们一般把-Xms和-Xmx设为一样大，而堆的最大值受限于系统使用的物理内存。一般使用数据量较大的应用程序会使用持久对象，内存使用有可能迅速地增长。当应用程序需要的内存超出堆的最大值时虚拟机就会提示内存溢出，并且导致应用服务崩溃。因此一般建议堆的最大值设置为可用内存的最大值的80%。Tomcat默认可以使用的内存为128MB，在较大型的应用项目中，这点内存是不够的，需要调大。Windows下，在文件/bin/catalina.bat，Unix下，在文件/bin/catalina.sh的前面，增加如下设置：JAVA_OPTS='-Xms【初始化内存大小】 -Xmx【可以使用的最大内存】'需要把这个两个参数值调大。例如：JAVA_OPTS='-Xms256m -Xmx512m'，表示初始化内存为256MB，可以使用的最大内存为512MB。另外需要考虑的是Java提供的垃圾回收机制。虚拟机的堆大小决定了虚拟机花费在收集垃圾上的时间和频度。收集垃圾可以接受的速度与应用有关，应该通过分析实际的垃圾收集的时间和频率来调整。如果堆的大小很大，那么完全垃圾收集就会很慢，但是频度会降低。如果你把堆的大小和内存的需要一致，完全收集就很快，但是会更加频繁。调整堆大小的的目的是最小化垃圾收集的时间，以在特定的时间内最大化处理客户的请求。在基准测试的时候，为保证最好的性能，要把堆的大小设大，保证垃圾收集不在整个基准测试的过程中出现。如果系统花费很多的时间收集垃圾，请减小堆大小。一次完全的垃圾收集应该不超过3-5秒。如果垃圾收集成为瓶颈，那么需要指定代的大小，检查垃圾收集的详细输出，研究垃圾收集参数对性能的影响。一般说来，你应该使用物理内存的80%作为堆大小。当增加处理器时，记得增加内存，因为分配可以并行进行，而垃圾收集不是并行的。