专栏首页WebJ2EEWEB:字符集、编码、乱码 —— 看这篇就够了

WEB:字符集、编码、乱码 —— 看这篇就够了

1. “联通”怪事

新建一个名为 mytxt.txt 的记事本文件,输入“联通”后,保存并关闭。

“联通”乱码了。

2. 字符如何存储与显示?

字符是如何存储的?

存储的是表示字符的“内码”(二进制)。

字符是如何在屏幕上展示的?

  1. 字符的展现离不开字形库(字体)。
  2. 字形库存放的是字符字形以及内码与字形的映射表。

3. 字符集与字符编码

  • 字符集(Charset)是一个系统支持的所有抽象字符的集合。字符是各种文字和符号的总称,包括各国家文字、标点符号、图形符号、数字等。常见字符集有:ASCII字符集、GB2312字符集、BIG5字符集、GB18030字符集、Unicode字符集等。
  • 字符编码(Character Encoding)是一套规则。字符编码就是将字符转换成计算机能识别的二进制串的法则。例如:GBK 字符集可通过查表来完成字符到二进制串的转换。

例如:“联” 的 GBK 编码的二进制表示为 0xC1AA。

3.1. ASCII

ASCII(美国信息交换标准代码)是基于拉丁字母的一套电脑编码系统。它主要用于显示现代英语,是现今最通用的单字节编码系统

  • ASCII字符集:主要包括控制字符(回车键、退格、换行键等);可显示字符(英文大小写字符、阿拉伯数字和西文符号)。
  • ASCII编码:将ASCII字符集转换为计算机可以接受的数字系统的数的规则。使用7位(bits)表示一个字符,共128字符;

3.2. ISO-8859系列

ASCII 是单字节编码系统,但它只用了 7 位,即只能表示 128 个字符。为了表示更多的欧洲常用字符,ISO 组织对 ASCII 进行了扩展,制定了一系列标准扩展 ASCII 码,他们是ISO-8859-1 ~ ISO-8859-16,其中 ISO-8859-1 涵盖了大多数西欧语言字符,应用得最广泛。

  • 它们的全都是单字节编码、且都与 ASCII 编码相兼容;
  • 它们都采用扩充 ASCII 码的形式(即利用 ASCII 没使用的那 128 个字符空间),制定了适用于不同国家和地区的字符集标准。

3.3. BIG5/GB2312/GBK/GB18030

BIG5

  • 通行于台湾、香港地区的一个繁体字编码方案。
  • 双字节编码,共收录13053个汉字。

世界上 ,沒有一拳解決不了的事,如果有,那就兩拳。 琦玉

GB2312

  • 一个简体中文字符集的中国国家标准。
  • 双字节编码,共收录6763个汉字和682个非汉字图形。
  • 人名、古汉语等方面的罕用字,GB2312不能处理,这导致了后来GBK及GB18030汉字字符集的出现。(例如:GB2312不能表示“喆”、“镕”字)

GBK

  • GB2312的扩充,完全兼容GB2312,并加入对繁体中文、日语、韩语等支持。
  • 双字节编码,共收录了21003个汉字。

GB18030

  • GBK编码的扩充,兼容GBK和GB2312字符集,覆盖中文、日文、朝鲜语和中国少数民族文字。
  • 采用单字节、双字节和四字节三种编码方式,共收录27484个汉字。

3.4. UNICODE与UTF-8

Unicode 是国际组织制定的可以容纳世界上所有文字和符号的字符编码方案。它使用4字节的数字来表达每个字母、符号,或者表意文字(ideograph)。每个数字代表唯一的至少在某种语言中使用的符号。

注:Unicode 只是一个符号集,它只规定了符号的二进制代码,却没有规定这个二进制代码应该如何存储。UTF-8 则是目前使用最广的一种 Unicode 编码方式。

UTF-8(8-bit Unicode Transformation Format)是一种针对Unicode的可变长度字符编码。UTF-8 用 1 到 4 个字节编码 Unicode 字符。UTF-8的编码规则很简单,只有二条:

  • 对于单字节的符号,字节的第一位设为0,后面 7 位为这个符号的 Unicode 码。因此对于英语字母,UTF-8 编码和 ASCII 码是相同的。
  • 对于 n 字节的符号(n>1),第一个字节的前n位都设为1,第n+1位设为0,后面字节的前两位一律设为10。剩下的没有提及的二进制位,全部为这个符号的 Unicode码。

例:“联” 的 Unicode 编码是 0x8054。由于 0x8054 位于 0x0800-0xFFFF 之间,所以使用 3 字节 UTF-8 转换模板:1110xxxx 10xxxxxx 10xxxxxx。0x8054 的二进制表示是:1000 0000 0101 0100,用这个比特流依次代替模板中的x,得到: 11101000 10000001 10010100,即E8 81 94。

4. “联通”怪事揭秘

“记事本”默认用 GBK 编码保存数据,“联通”两字的GBK编码如下:

巧合的是,“联”的两个字节、“通”的两个字节的起始部分的都是"110"和"10",正好符合 2 字节 UTF8 编码的规律,于是再次打开记事本时,记事本就误认为这是一个用 UTF-8 编码的文件,用 UTF-8 去解析 GBK 的数据,造成了乱码。

5. 编码衍生问题汇总

5.1. BOM 与 UTF-8

1. 什么是 BOM 头?

BOM 头是放在 UTF-8 编码格式文件的头部,占三个字节(0xEF 0xBB 0xBF),用来标识该文件属于UTF-8编码。

注:window记事本在用UTF-8格式保存文件时,会自动加上BOM头。

2. 有什么问题?

有些软件不能正确识别BOM头

比如,Linux/Unix 环境下,不能编译 UTF-8-BOM 格式 Java 文件。

3. 如何去掉 BOM 头?

用软件咯,以Notepad++为例,依次选择【编码】->【转为UTF-8】,保存即可。

5.2. ZipOutputStream 中的编码问题

  • ZipOutputStream 是 Java 生成压缩包最常用的方式。
  • 压缩包中的“路径名”和“文件名”涉及编码问题(即:Entry)。
  • ZipOutputStream 有两大类实现:java.util.zip.ZipOutputStream 和 org.apache.tools.zip.ZipOutputStream。

下面分类说明

1. JDK6 的 java.util.zip.ZipOutputStream

  • JDK6 的 ZipOutputStream,Entry 采用 UTF-8 编码,而且不支持自定义;
  • 一些压缩软件不能正常识别,需要手动调整压缩软件的解析字符集为UTF-8才行;
package encoding;

import java.io.FileOutputStream;
import java.io.IOException;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

public class ZipDemo {
  public static void main(String[] args) throws IOException {
    FileOutputStream fos = new FileOutputStream("d:/webj2ee.zip");
    ZipOutputStream zos = new ZipOutputStream(fos);
    zos.putNextEntry(new ZipEntry("中文路径/中文名称.txt"));
    zos.write("没有什么是一拳解决不了的,如果有,那就两拳。——琦玉".getBytes("UTF-8"));
    zos.closeEntry();
    zos.close();
    fos.close();
  }
}

2. Apache Ant 的 org.apache.tools.zip.ZipOutputStream

  • 在 JDK6 环境中,常用作 java.util.zip.ZipOutputStream 的替代方案。
  • org.apache.tools.zip.ZipOutputStream 支持自定义 Entry 的编码。
package encoding;

import java.io.FileOutputStream;
import java.io.IOException;

import org.apache.tools.zip.ZipEntry;
import org.apache.tools.zip.ZipOutputStream;


public class ZipDemo {
  public static void main(String[] args) throws IOException {
    FileOutputStream fos = new FileOutputStream("d:/webj2ee.zip");
    ZipOutputStream zos = new ZipOutputStream(fos);
    zos.setEncoding("GBK");
    zos.putNextEntry(new ZipEntry("中文路径/中文名称.txt"));
    zos.write("没有什么是一拳解决不了的,如果有,那就两拳。——琦玉".getBytes("UTF-8"));
    zos.closeEntry();
    zos.close();
    fos.close();
  }
}

3. ≥JDK7 的 java.util.zip.ZipOutputStream

  • JDK7 的 java.util.zip.ZipOutputStream 支持自定义 Entry 的编码。
  • 不需要使用 org.apache.tools.zip.ZipOutputStream 了。
package encoding;

import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

public class ZipDemo {
  public static void main(String[] args) throws IOException {
    FileOutputStream fos = new FileOutputStream("d:/webj2ee.zip");
    ZipOutputStream zos = new ZipOutputStream(fos, Charset.forName("UTF-8"));
    zos.putNextEntry(new ZipEntry("中文路径/中文名称.txt"));
    zos.write("没有什么是一拳解决不了的,如果有,那就两拳。——琦玉".getBytes("UTF-8"));
    zos.closeEntry();
    zos.close();
    fos.close();
  }
}

5.3. String.getBytes(charset) 与 new String(bytes, charset)

  • String.getBytes(String charset) 返回字符串在指定 charset 编码下的 byte 数组表示;
  • new String(byte[], charset) 是按照 charset 指定的编码来解析 byte[] 为字符串。

1. 不要想当然认为它们可逆:

public static void main(String[] args) throws UnsupportedEncodingException {
  String r1 = new String("联通".getBytes("ISO8859-1"), "ISO8859-1");
  System.err.println(r1);
}

注:ISO8859-1根本无法表示中文,当然也就无法得到“联通”两字在ISO8859-1中的编码值了,所以再通过new String()还原就更无从谈起了。

2. 下面这种形式常用于网络数据传输:

public static void main(String[] args) throws UnsupportedEncodingException {
  String sent = new String("联通".getBytes("UTF-8"), "ISO8859-1");
  System.out.println(sent);
  String recv = new String(sent.getBytes("ISO8859-1"), "UTF-8");
  System.out.println(recv);
}

5.4. I/O 流与字符集

  • Java 的 I/O 流,分“字节流”与“字符流”;
  • “字符流”与“字节流”转换时,存在编码问题,需要注意;

示例:

package encoding;

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;

public class IOs {
  public static void main(String[] args) throws IOException {

    // 1. “字符流” -> “字节流”
    try (FileOutputStream fos = new FileOutputStream("d:/one-punch-man.txt");
        OutputStreamWriter osw = new OutputStreamWriter(fos, "UTF-8");) {
      osw.write("没有什么是一拳解决不了的,如果有,那就两拳。——琦玉");
    } catch (IOException e) {
      throw new RuntimeException(e.getMessage(), e);
    }

    // 2. “字节流” -> “字符流”
    try (FileInputStream fis = new FileInputStream("d:/one-punch-man.txt");
        InputStreamReader isr = new InputStreamReader(fis, "UTF-8");
        BufferedReader br = new BufferedReader(isr);) {
      String l = br.readLine();
      System.out.println(l);
    } catch (IOException e) {
      throw new RuntimeException(e.getMessage(), e);
    }
  }
}

5.5. JVM 默认字符集

1. 有一些接口与 JVM 默认字符集有关:

  • String.getBytes()、new String(byte[]);
  • new OutputStreamWriter(OutputStream out);
  • new InputStreamReader(InputStream in)
  • new FileWriter(...)
  • URLEncoder.encode(String)

2. JVM 默认字符集如何确定?

先上一段JDK源码

  • 不主动配置 -Dfile.encoding 的情况下,默认是操作系统的编码;
  • 配置 JVM 启动参数 -Dfile.encoding,可更改 JVM 默认字符集编码:
  • JVM 启动后, 修改 file.encoding 只会修改配置项值,不会改变 JVM 默认字符集编码;
java -Dfile.encoding=utf-8 ...

5.6. Eclipse - Console 中的编码


不慌,

轮到“前端”的编码问题了

5.7. 浏览器如何解析 HTML 文件?

解析策略:

万能用例代码:

protected void doGet(HttpServletRequest request, HttpServletResponse response)
      throws ServletException, IOException {
    StringBuffer sb = new StringBuffer();
    sb.append("\r\n");
    sb.append("<!DOCTYPE html><html><head>\r\n");
    sb.append("  <meta charset=\"gbk\">\r\n");
    sb.append("  <title></title>\r\n");
    sb.append("</head><body>\r\n");
    sb.append("\t没有什么是一拳解决不了的,如果有,那就两拳。——琦玉\r\n");
    sb.append("</body></html>");
    String html = sb.toString();

    // HTML类型
    response.setContentType("text/html");

    // BOM头
    response.getOutputStream().write(0xEF);
    response.getOutputStream().write(0xBB);
    response.getOutputStream().write(0xBF);
    response.getOutputStream().write(html.getBytes("UTF-8"));
  }

例1: 没有BOM头、没有content-type,按<meta chaset>解析

例2: 有BOM头,按UTF-8解析

例3: 没BOM头,有content-type,按content-type解析

5.8. 浏览器如何解析外部 JS 文件?

通过<script>标记引入外部 JS:

<script charset="UTF-8" src="webj2ee.js">

解析逻辑如下:

例1:没有BOM头、没有content-type,有charset声明;

例2:有 BOM 头;

例3:没有BOM头、有content-type;

5.9. URL编码、解码函数?

5.9.1. escape()、unescape()

escape() 是将字符转换为Unicode编码值。解码是通过unescape()函数。

  1. ISO 8859-1字符集内的字母(A-Z、a-z)、数字(0-9)、标点符号(* @ - _ + . /)不会被转换;
  2. ISO 8859-1字符集内其它字符,都会以%xy格式表示(xy为字符的16进制表示,因为ISO 8859-1是单字节编码,所以2位就够了);
  3. 其它字符(比如中文),转换成%uxxxx的格式(xxxx为字符的16进制 Unicode表示)。

注意:ECMAScript v3 标准不建议使用escape()处理URL编码。应该使用encodeURI和encodeURIComponent()来代替

5.9.2. encodeURI()

encodeURI()是将字符串进行UTF-8编码。解码通过decodeURI()。

  1. ISO8859-1字符集内的字母(A-Z、a-z)、数字(0-9)、标点符号( - _ . ! ~ * ' ( ) )不会被转换。而且该方法的目的是对 URI 进行完整的编码,因此对以下在 URI 中具有特殊含义的 ASCII 标点符号( ; / ? : @ & = + $ , # )也不会被转换。
  2. ISO 8859-1字符集内其它字符,都会以%xy格式表示(xy为字节的16进制表示);
  3. 其它字符首先会按照UTF-8规则转换为字节串,每个字节再以%xy的形式表示。其中xy就是字节的16进制表示形式。

5.9.3. encodeURIComponent()

encodeURIComponent()也是将字符串进行UTF-8编码,它比encodeURI的编码还要彻底,在encodeURI的基础上,将那些在URI中有特殊含义的标点符号也一起编码了。

  1. ISO8859-1字符集内的字母(A-Z、a-z)、数字(0-9)、标点符号( - _ . ! ~ * ' ( ) )不会被转换。
  2. ISO 8859-1字符集内其它字符,都会以%xy格式表示(xy为字符的16进制表示);
  3. 其它字符首先会按照UTF-8规则转换为字节串,每个字节再以%xy的形式表示。其中xy就是字节的16进制表示形式。

注意:这个函数通常用于将一个URL当做一个参数放在另一个URL中。

5.9.4. URLEncoder.encode(charset)

Java 端的URL编码类,解码类为 java.net.URLDecoder。

  1. ISO8859-1字符集内的字母(A-Z、a-z)、数字(0-9)、标点符号( - _ . *)不会被转换。
  2. 空格会被转换为“+”。
  3. 其它字符首先会按照某种编码规则转换为1个或多个字节,每个字节再以%xy的形式表示。其中xy就是字节的16进制表示形式。

注意:建议使用 UTF-8 字符集进行编码。

注意:基本等同于 JS 的 encodeURIComponent 函数(主要是标点、空格不同)。

5.10. Http Header 中的编码

Http 的 Header 中传递的内容(比如:Cookie),编解码统一用的是ISO8859-1字符集,而且不能更改,所以在Header中不能使用非ASCII字符。

示例:在Cookie中传输中文会报错

<%@ page language="java" pageEncoding="UTF-8"%>
<% 
  Cookie c = new Cookie("name", "联通");
  response.addCookie(c);
%>
<!DOCTYPE html>
<html><head>
  <meta charset="utf-8">
  <title></title>
</head><body></body></html>

注:如果要在Cookie中存储中文信息,可以用base64等技术编码一下。

5.11. GET 请求中的编码、解码

5.11.1. URL结构

以Tomcat作为Servlet Engine 为例,它们分别对应到下面这些配置文件中:

  • Port :对应Tomcat的<Connector port=”8080″/> 配置。
<Connector connectionTimeout="20000" port="8080" protocol="HTTP/1.1" redirectPort="8443"/>
  • ContextPath:对应<Context path=”/code”/> 配置。
<Context docBase="code" path="/code" reloadable="true" source="org.eclipse.jst.jee.server:code"/>
  • ServletPath:对应web.xml中Servlet的<url-pattern>配置
<servlet>
     <servlet-name>Code</servlet-name>
     <servlet-class>com.Code</servlet-class>
</servlet>
<servlet-mapping>
     <servlet-name>Code</servlet-name>
     <url-pattern>/servlet/servlet/*</url-pattern>
</servlet-mapping>
  • PathInfo: 对应我们请求的具体的 Servlet。
  • QueryString 是要传递的参数。

5.11.2. 浏览器如何编码 PathInfo 和 QueryString

备注:

  1. 分析浏览器编码,使用的是Fiddler抓包工具;
  2. “联通”的UTF-8编码为:0xE88194 0xE9809A
  3. “联通”的GBK编码为:0xC1AA 0xCDA8
  • CHROME:对PathInfo和QueryString 都采用 UTF-8 编码
  • Firefox:对 PathInfo 和 QueryString 都采用 UTF-8 编码
  • Safari:对 PathInfo 和 QueryString 都采用UTF-8编码
  • IE:对 PathInfo 采用 UTF-8 编码;对 QueryString 采用 GBK 编码;
  • IE:如果在【选项 -> 高级 -> 国际】里面,取消勾选【以UTF-8形式发送URL路径】选项,则IE对PathInfo和QueryString都将采用GBK编码;

5.11.3. 服务器如何解码 PathInfo 和 QueryString

5.11.3.1. PathInfo 解码(实际是 URI,包含PathInfo):

Tomcat 对 URI 解码的字符集由 Connector 中的 URIEncoding 属性指定,默认 ISO-8859-1。

<Connector connectionTimeout="20000" port="8080" protocol="HTTP/1.1" redirectPort="8443" URIEncoding="UTF-8"/>

5.11.3.2. 对QueryString:

  • 通过 request.getParameter 可获取 QueryString 中的参数值。
  • QueryString 是通过HTTP的Header传到服务端的,并且也在URL中。
  • QueryString 的解码是在第一次调用request.getParameter发生的。
  • QueryString 的解码字符集要么是Header中ContentType中定义的Charset要么就是默认的 ISO-8859-1,要使用ContentType中定义的编码就要设置useBodyEncodingForURI为true。

总结:不要在GET请求中使用中文字符。如果必须要传输中文字符,那可以先用encodeURIComponent()方法对中文字符编码,再发送GET请求。

5.12. Request 中的编码问题(POST请求)

  • 通过request.getParameter可获取POST请求中的参数值。
  • POST请求中的参数是通过HTTP的BODY传递到服务端的。
  • POST请求中的参数解码是在第一次调用request.getParameter发生的。
  • POST请求中的参数的解码字符集由request.getCharacterEncoding的值确定。

5.12.1. 通过 Form 发的 POST 请求:

  • 当点击 submit 按钮时,浏览器会根据网页的charset对表单填的参数进行编码,然后提交到服务器端。
  • 在默认情况下浏览器在提交Form表单时,提交的content-type中不会含有charset信息的(即request.getCharacterEncoding()会得到null值),所以如果没有设置requeset.setCharacterEncoding,那么表单提交的数据将会被按照系统的默认编码方式解析。

5.12.2. 通过 $.ajax 发的 POST 请求:

  • $.ajax 始终使用 UTF-8 对 POST 参数进行编码,然后提交到服务器;
  • $.ajax 经过配置,可以在 POST 请求 Header 中的 conten-type 字段携带 charset 信息;

最佳实践:下面的4部分字符集要统一、建议使用UTF-8

  1. HTML、JSP文件自身字符集要是UTF-8;
  2. HTML中 <meta charset="UTF-8"> 要声明为UTF-8;
  3. Ajax 请求中 contentType中的charset也要声明为UTF-8;
  4. 增加一个EncodingFilter,配置request.setCharacterEncoding为UTF-8;(可以考虑使用Spring的编码过滤器)

5.13. Response 中的编码问题

response.setCharacterEncoding("UTF-8");
response.setContentType("text/html;charset=UTF-8");
response.getWriter().write("没有什么是一拳解决不了的,如果有,那就两拳。——琦玉");
  1. content-type 用于指示浏览器采用何种编码解析数据流;
  2. 当使用 writer 回写数据时,response.setCharacterEncoding 用于指示服务器采用何种编码将字符流转换为字节流,写会浏览器。

5.14. 认识 Spring 的编码过滤器

<filter>  
    <filter-name>characterEncodingFilter</filter-name>  
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>  
    <init-param>  
        <param-name>encoding</param-name>  
        <param-value>UTF-8</param-value>  
    </init-param>  
    <init-param>  
        <param-name>forceEncoding</param-name>  
        <param-value>true</param-value>  
    </init-param>  
</filter>  
<filter-mapping>  
    <filter-name>characterEncodingFilter</filter-name>  
    <url-pattern>/*</url-pattern>  
</filter-mapping> 

Spring 源码:

@Override  
protected void doFilterInternal(HttpServletRequest request, 
       HttpServletResponse response, FilterChain filterChain)  
   throws ServletException, IOException {  
   if (this.encoding != null && (this.forceEncoding || request.getCharacterEncoding() == null)) {  
        request.setCharacterEncoding(this.encoding);  
        if (this.forceEncoding) {  
            response.setCharacterEncoding(this.encoding);  
        }  
    }  
    filterChain.doFilter(request, response);  
 }

所以encoding=true、forceEncoding=true合起来,意味着:

request.setCharacterEncoding("UTF-8");  
response.setCharacterEncoding("UTF-8");

5.15. JSP 文件头中的编码问题

<%@ page language="java" pageEncoding="UTF-8" 
  contentType="text/html; charset=UTF-8"%>
  • pageEncoding:一方面,编译JSP时,指示采用何种编码解析当前JSP文件。
  • contentType:就是 response.setContentType。

注:如果不写contentType,只有 pageEncoding,则自动生成与 pageEncoding 一样编码的 contentType。

5.16. 䶮——“飞龙在天”

字音:yǎn; 起源:五代时南汉刘岩为自己名字造的字; 含义:“飞龙在天”的意思;

5.16.1. “䶮”的特殊性

GBK编码字符“䶮”,在Unicode中存在两种表示:PUA区的0xE863、非PUA区的0x4DAE。

注:PUA, Private Use Area

5.16.2. “䶮”为什么特殊?

GBK字符集中有80个增补字符最初并未在Unicode中定义,于是使用了Unicode的PUA区域的代码点表示。后来Unicode使用非PUA区域代码点正式定义了这80个字符。这样就出现有80个汉字在Unicode定义的代码点区域中有两种不同的表示方法。

GBK字符集80个增补字符:

5.16.3. “䶮”——可能会遇到什么问题?

例如:

Oracle使用ZHS16GBK字符集存储字符“䶮”,但AIX系统从数据库中读出后,展示为问号 (?)。

问题原因:

该问题是由Oracle ZHS16GBK字符集和IBM® GBK 转换器之间GBK未定义的代码范围Unicode映射的不兼容性导致的。

测试代码:

public class Code {
  public static void main(String[] args) {
    System.err.println(System.getProperty("file.encoding"));
    printBytes(""); // GBK
  }

  private static void printBytes(String chars) {
    byte[] bytes = chars.getBytes();
    StringBuffer sb = new StringBuffer();
    for(int i=0; i<bytes.length; i++){
      int mask = 0x80;
      do{
        if((mask & bytes[i]) != 0){
          sb.append("1");
        }else{
          sb.append("0");
        }
      }while((mask >>= 1) !=0);
      sb.append(" ");
    }
    System.err.println(sb.toString());
  }
}

AIX上的运行结果:

REDHAT上的运行结果:

5.17. 文件下载中的编码问题

...直接看代码吧...

  protected void doGet(HttpServletRequest request, HttpServletResponse response)
      throws ServletException, IOException {
    String fileContent = "没有什么是一拳解决不了的,如果有,那就两拳。——琦玉";
    String fileName = "琦玉名言.txt";

    String encodedFileName = null;
    if (request.getHeader("User-Agent").toLowerCase().indexOf("firefox") > -1) {
      encodedFileName = new String(fileName.getBytes("UTF-8"), "ISO8859-1");
    } else {
      encodedFileName = URLEncoder.encode(fileName, "UTF-8");
    }

    response.setContentType("application/octet-stream");
    response.setHeader("Content-Disposition", "attachment;filename=" + encodedFileName);
    response.getOutputStream().write(fileContent.getBytes("UTF-8"));
  }

本文分享自微信公众号 - WebJ2EE(WebJ2EE)

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-07-07

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • python apscheduler interval/cron触发器详解 常用

    interval 触发器 参数 说明 weeks (int) 间隔几周 days (int) 间隔几天 hours (int) 间隔几小时 min...

    用户5760343
  • python apscheduler crontrigger触发器

    year,month,day,week,day_of_week,hour,minute,second,start_date,end_date,timezone,...

    用户5760343
  • windows7配置Nginx+php+mysql的详细教程

    这篇文章主要介绍了windows7配置Nginx+php+mysql的详细教程 的相关资料,需要的朋友可以参考下

    习惯说一说
  • 7、web爬虫讲解2—urllib库爬虫—状态吗—异常处理—浏览器伪装技术、设置用户代理

    如果爬虫没有异常处理,那么爬行中一旦出现错误,程序将崩溃停止工作,有异常处理即使出现错误也能继续执行下去

    天降攻城狮
  • ajax post请求之后 实现页面跳转和带参数跳转问题

    url, {method:"regist",userName:$nameEle.val(),email:$emailEle.val(),password:$...

    wust小吴
  • RESTful API设计--指南

    作为软件开发人员,我们大多数人在日常生活中使用或构建 REST api。API 是系统之间的默认通信方式。亚马逊是如何有效地使用 api 进行通信的最佳例子。

    软测小生
  • 8、web爬虫讲解2—urllib库爬虫—ip代理—用户代理和ip代理结合应用

    ProxyHandler()格式化IP,第一个参数,请求目标可能是http或者https,对应设置

    天降攻城狮
  • 服务端 I/O 性能大比拼:Node、PHP、Java、Go哪家强?

    理解应用程序的输入/输出(I/O)模型,意味着其在计划处理负载与残酷的实际使用场景之间的差异。若应用程序比较小,也没有服务于很高的负载,也许它影响甚微。但随着应...

    Java技术栈
  • IIS服务器下做301永久重定向设置方法[可以传参][图文]

    以前也没怎么关注301重定向,第一因为没有网站要重定向,第二对于不带www的域名我都是用的转发到带www的域名。不过一场风波之后,很多服务商已经不提供转发服务了...

    习惯说一说
  • bootstrap nav 导航菜单

    nav nav-tabs <p>标签式的导航菜单</p> <ul class="nav nav-tabs"> <li class="active"><a ...

    用户5760343

扫码关注云+社区

领取腾讯云代金券