如何使用ResourceBundle在资源属性中使用UTF-8


259

我需要使用Java的资源属性中使用UTF-8 ResourceBundle。当我直接在属性文件中输入文本时,它显示为mojibake。

我的应用程序在Google App Engine上运行。

谁能给我一个例子吗?我无法完成这项工作。


1
Java 1.6解决了此问题,因为您可以传入Reader。请参阅下面的@Chinaxing答案一路下滑
威尔

1
@Will:问题主要是关于通过java.util.ResourceBundle而不是通过阅读java.util.Properties
BalusC 2014年

1

6
JDK9应该原生支持UTF-8,请参阅JEP 226
Paolo Fulgoni,2015年

Answers:


374

ResourceBundle#getBundle()封面下使用PropertyResourceBundle时,.properties被指定的文件。默认情况下Properties#load(InputStream),这反过来使用它来加载那些属性文件。根据javadoc,默认情况下将其读取为ISO-8859-1。

public void load(InputStream inStream) throws IOException

从输入字节流中读取属性列表(键和元素对)。输入流采用load(Reader)中指定的面向行的简单格式,并假定使用ISO 8859-1字符编码;即每个字节都是一个Latin1字符。根据Java™语言规范第3.3节的定义,使用Unicode转义在键和元素中表示不在Latin1中的字符以及某些特殊字符。

因此,您需要将它们另存为ISO-8859-1。如果您有任何超出ISO-8859-1范围的字符,并且不能使用\uXXXX头顶字符,因此被迫将文件另存为UTF-8,则需要使用native2ascii工具来转换UTF-8保存的属性文件到ISO-8859-1保存的属性文件,其中所有未发现的字符都转换为\uXXXX格式。下面的示例将UTF-8编码的属性文件text_utf8.properties转换为有效的ISO-8859-1编码的属性文件text.properties

native2ascii-编码UTF-8 text_utf8.properties text.properties

当使用健全的IDE(例如Eclipse)时,.properties在基于Java的项目中创建文件并使用Eclipse自己的编辑器时,这已经自动完成。Eclipse将透明地将超出ISO-8859-1范围的字符转换为\uXXXX格式。另请参见下面的屏幕截图(请注意底部的“属性”和“源”选项卡,单击以放大):

“属性”标签 “来源”标签

另外,您还可以创建一个自定义ResourceBundle.Control实现,其中您可以使用来将属性文件显式读取为UTF-8 InputStreamReader,这样您就可以将它们另存为UTF-8,而无需麻烦native2ascii。这是一个启动示例:

public class UTF8Control extends Control {
    public ResourceBundle newBundle
        (String baseName, Locale locale, String format, ClassLoader loader, boolean reload)
            throws IllegalAccessException, InstantiationException, IOException
    {
        // The below is a copy of the default implementation.
        String bundleName = toBundleName(baseName, locale);
        String resourceName = toResourceName(bundleName, "properties");
        ResourceBundle bundle = null;
        InputStream stream = null;
        if (reload) {
            URL url = loader.getResource(resourceName);
            if (url != null) {
                URLConnection connection = url.openConnection();
                if (connection != null) {
                    connection.setUseCaches(false);
                    stream = connection.getInputStream();
                }
            }
        } else {
            stream = loader.getResourceAsStream(resourceName);
        }
        if (stream != null) {
            try {
                // Only this line is changed to make it to read properties files as UTF-8.
                bundle = new PropertyResourceBundle(new InputStreamReader(stream, "UTF-8"));
            } finally {
                stream.close();
            }
        }
        return bundle;
    }
}

可以如下使用:

ResourceBundle bundle = ResourceBundle.getBundle("com.example.i18n.text", new UTF8Control());

也可以看看:


谢谢。顺便说一句,重写getFormats返回FORMAT_PROPERTIES似乎是一个好主意。
弗拉维奥伊特鲁里亚

您能否详细说明覆盖getFormats()的建议?
Mark Roper 2014年

1
@ imgx64:谢谢您的通知。答案已固定。
BalusC 2015年

10
StandardCharsets.UTF_8如果您使用的是Java 7+,请随时使用
Niks

1
@Nyerguds:如果您发现有理由以编程方式更改它(尽管我无法终生想象),请随时进行更改。我发布的所有代码片段毕竟只是开端示例。
BalusC

131

假设您有一个ResourceBundle实例,则可以通过以下方式获取String:

String val = bundle.getString(key); 

我通过以下方法解决了日语显示问题:

return new String(val.getBytes("ISO-8859-1"), "UTF-8");

36
对于所有天真的支持者/评论者:这不是解决方案,而是解决方法。真正的根本问题仍然存在,需要解决。
BalusC 2014年

2
这解决了我的问题。解决方案是让Java从本地开始在资源包和属性文件中处理UTF-8。在此之前,我将使用一种解决方法。
JohnRDOrazio

@BalusC; 这种方法的缺点是什么?(除了创建额外的String之外?)
Paaske,2015年

8
@Paaske:这是一种解决方法,而不是解决方案。您需要在整个代码库的所有字符串变量的所有位置上重新应用变通办法。这纯粹是胡说八道。只需将其固定在正确的位置,以便字符串变量立即包含正确的值。绝对没有必要修改客户端。
BalusC 2015年

3
是的,如果您必须修改整个应用程序,那当然是不好的。但是,如果您已经将ResourceBundle作为单例使用,则只需修复一次即可。我的印象是单例方法是使用ResourceBundle的最常用方法。
Paaske 2015年

50

看看这个:http : //docs.oracle.com/javase/6/docs/api/java/util/Properties.html#load(java.io.Reader)

属性接受 Reader对象作为参数,您可以从InputStream创建该对象。

在创建时,您可以指定阅读器的编码:

InputStreamReader isr = new InputStreamReader(stream, "UTF-8");

然后将此Reader应用于load方法:

prop.load(isr);

顺便说一句:从.properties获取流文件:

 InputStream stream = this.class.getClassLoader().getResourceAsStream("a.properties");

顺便说一句:从获取资源包InputStreamReader

ResourceBundle rb = new PropertyResourceBundle(isr);

希望这可以帮到你 !


3
ResourceBundle不过,这里的实际问题是关于。
Nyerguds '16

1
没错,如果您正在使用Properties并且想要检索UTF-8String,则应该接受此答案,这就像一个符咒。然而,对于ResourceBundle诸如语言资源这样的语言,那么可接受的答案是优雅的。尽管如此,还是投了赞成票。
伊尔吉特·耶尔德勒姆

ResourceBundle rb = new PropertyResourceBundle(new InputStreamReader(stream, "UTF-8"))
dedek

22

ResourceBundle.Control 例如,如果属性文件使用cp1251字符集,则不能使用带有UTF-8和新String方法的方法。

因此,我推荐使用一种通用方法:用unicode编写符号。为了这:

IDEA-具有特殊的透明的本机到ASCII转换选项(“设置”>“文件编码”)。

Eclipse-有一个插件Properties Editor。它可以作为单独的应用程序工作。


3
在IntelliJ IDEA 14中,它位于设置->编辑器->文件编码中。我还必须删除任何现有的属性文件,然后重新创建它们,此选项才能生效。
密码

IDE并不是与答案特别相关,而只是那些工具真正不能解决不以UTF-8字符集存储内容的根本问题....可以立即解决问题,而无需进行转换或编写属性之类的黑客工具在用不同字符集定义的文件内的Unicode符号中。
达雷尔·蒂格

21

这个问题终于在Java 9中得到了解决:https : //docs.oracle.com/javase/9​​/intl/internationalization-enhancements-jdk-9

现在,属性文件的默认编码为UTF-8。

大多数现有的属性文件都不会受到影响:UTF-8和ISO-8859-1的ASCII字符编码相同,并且人类可读的非ASCII ISO-8859-1编码无效的UTF-8。如果检测到无效的UTF-8字节序列,则Java运行时将自动重新读取ISO-8859-1中的文件。


19

我们创建一个resources.utf8文件,其中包含UTF-8中的资源,并具有运行以下规则:

native2ascii -encoding utf8 resources.utf8 resources.properties

我们native2ascii从哪里得到?我只是做find / -name native2ascii*了而没有任何结果,所以我认为它不仅仅是JDK的一部分...
ArtOfWarfare 2015年

嗯 它不是IBM JDK的一部分,但似乎包含在Oracle JDK中jdk1.*.0_*/bin
ArtOfWarfare 2015年

它似乎是IBM JDK的一部分,至少在JDK 6
埃里克芬兰

19
package com.varaneckas.utils;  

import java.io.UnsupportedEncodingException;  
import java.util.Enumeration;  
import java.util.PropertyResourceBundle;  
import java.util.ResourceBundle;  

/** 
 * UTF-8 friendly ResourceBundle support 
 *  
 * Utility that allows having multi-byte characters inside java .property files. 
 * It removes the need for Sun's native2ascii application, you can simply have 
 * UTF-8 encoded editable .property files. 
 *  
 * Use:  
 * ResourceBundle bundle = Utf8ResourceBundle.getBundle("bundle_name"); 
 *  
 * @author Tomas Varaneckas <tomas.varaneckas@gmail.com> 
 */  
public abstract class Utf8ResourceBundle {  

    /** 
     * Gets the unicode friendly resource bundle 
     *  
     * @param baseName 
     * @see ResourceBundle#getBundle(String) 
     * @return Unicode friendly resource bundle 
     */  
    public static final ResourceBundle getBundle(final String baseName) {  
        return createUtf8PropertyResourceBundle(  
                ResourceBundle.getBundle(baseName));  
    }  

    /** 
     * Creates unicode friendly {@link PropertyResourceBundle} if possible. 
     *  
     * @param bundle  
     * @return Unicode friendly property resource bundle 
     */  
    private static ResourceBundle createUtf8PropertyResourceBundle(  
            final ResourceBundle bundle) {  
        if (!(bundle instanceof PropertyResourceBundle)) {  
            return bundle;  
        }  
        return new Utf8PropertyResourceBundle((PropertyResourceBundle) bundle);  
    }  

    /** 
     * Resource Bundle that does the hard work 
     */  
    private static class Utf8PropertyResourceBundle extends ResourceBundle {  

        /** 
         * Bundle with unicode data 
         */  
        private final PropertyResourceBundle bundle;  

        /** 
         * Initializing constructor 
         *  
         * @param bundle 
         */  
        private Utf8PropertyResourceBundle(final PropertyResourceBundle bundle) {  
            this.bundle = bundle;  
        }  

        @Override  
        @SuppressWarnings("unchecked")  
        public Enumeration getKeys() {  
            return bundle.getKeys();  
        }  

        @Override  
        protected Object handleGetObject(final String key) {  
            final String value = bundle.getString(key);  
            if (value == null)  
                return null;  
            try {  
                return new String(value.getBytes("ISO-8859-1"), "UTF-8");  
            } catch (final UnsupportedEncodingException e) {  
                throw new RuntimeException("Encoding not supported", e);  
            }  
        }  
    }  
}  

1
我喜欢这种解决方案,并且喜欢Gist gist.github.com/enginer/3168dd4a374994718f0e
Sllouyssgort 2014年

这很好。刚刚在UTF8中添加了中文翻译属性文件,该文件加载时没有任何问题。
tresf

9

注意:java属性文件应使用ISO 8859-1编码!

ISO 8859-1字符编码。不能使用此编码直接表示的字符可以使用Unicode转义符编写;转义序列中仅允许使用一个'u'字符。

@see属性Java文档

如果您仍然真的想要这样做:看一下: Eclipse中的Java属性UTF-8编码 -有一些代码示例


1
Java!= Eclipse ...后者是IDE。其他数据!= Java。Java支持使用大量字符集进行流处理,这对于国际化来说(毕竟是关于ResourceBundles的问题)...决定使用UTF-8作为最直接的答案。用目标语言不支持的字符集编写属性文件会使问题变得不必要。
达雷尔·蒂格

@Darell Teague:为ResouceBundle加载的属性文件必须是ISO 8859-1的“提示”是一个Java语句:docs.oracle.com/javase/8/docs/api/java/util / ... ..我的答案的第二部分仅仅是“提示”如何处理帽子问题。
拉尔夫


3

这是一个Java 7解决方案,它使用了Guava出色的支持库和try-with-resources构造。它使用UTF-8读写属性文件,以获得最简单的整体体验。

要将属性文件读取为UTF-8:

File file =  new File("/path/to/example.properties");

// Create an empty set of properties
Properties properties = new Properties();

if (file.exists()) {

  // Use a UTF-8 reader from Guava
  try (Reader reader = Files.newReader(file, Charsets.UTF_8)) {
    properties.load(reader);
  } catch (IOException e) {
    // Do something
  }
}

要将属性文件编写为UTF-8:

File file =  new File("/path/to/example.properties");

// Use a UTF-8 writer from Guava
try (Writer writer = Files.newWriter(file, Charsets.UTF_8)) {
  properties.store(writer, "Your title here");
  writer.flush();
} catch (IOException e) {
  // Do something
}

这个答案很有用。这里有各种答案的核心问题似乎是对数据和字符集的误解。Java可以通过简单地指定存储字符集的方式(正确)读取任何数据,如上所示。UTF-8通常用于支持地球上大多数(如果不是每种语言)的语言,因此非常适用于基于ResourceBundle的属性。
达雷尔·蒂格

@DarrellTeague:好吧,“ UTF-8通常用于支持...”-应该宁可使用“ Unicode通常用于支持...” :),因为UTF-8只是Unicode的字符编码(en .wikipedia.org / wiki / UTF-8)。
Honza Zidek '17

实际上,UTF-8被专门称为“字符集”(相对于仅引用“任何UniCode字符集”),因为在这种情况下(数据)UTF-8在Internet上的使用程度高达67%。参考文献:stackoverflow.com/questions/8509339/...
达雷尔-蒂格

3

正如一个建议一样,我经历了资源包的实现..但这没有帮助..因为总是在en_US语言环境下调用资源包...我试图将默认语言环境设置为另一种语言,但仍然是我对资源包的实现使用en_US调用控件...我试图放置日志消息并进行调试,以查看在运行时通过xhtml和JSF调用更改语言环境后是否正在进行其他本地调用...这没有发生...然后我尝试将系统默认设置为utf8,以便通过我的服务器(tomcat服务器)读取文件。但这会导致问题,因为我的所有类库都没有在utf8下编译,并且tomcat开始以utf8格式读取并且服务器运行不正常...然后,我最终在java控制器中实现了从xhtml文件调用的方法。在这种方法中,我执行了以下操作:

        public String message(String key, boolean toUTF8) throws Throwable{
            String result = "";
            try{
                FacesContext context = FacesContext.getCurrentInstance();
                String message = context.getApplication().getResourceBundle(context, "messages").getString(key);

                result = message==null ? "" : toUTF8 ? new String(message.getBytes("iso8859-1"), "utf-8") : message;
            }catch(Throwable t){}
            return result;
        }

我特别紧张,因为这可能会降低我的应用程序的性能……但是,实现此功能之后,我的应用程序看起来好像现在更快了。我认为这是因为,我现在直接访问属性而不是让它JSF解析其访问属性的方式...我在此调用中专门传递了布尔参数,因为我知道某些属性将不会被翻译,并且不需要采用utf8格式...

现在,我已将属性文件保存为UTF8格式,并且可以正常工作,因为应用程序中的每个用户都具有引用的语言环境首选项。


2
Properties prop = new Properties();
String fileName = "./src/test/resources/predefined.properties";
FileInputStream inputStream = new FileInputStream(fileName);
InputStreamReader reader = new InputStreamReader(inputStream,"UTF-8");

1

对于我的问题,值得考虑的是文件本身的编码错误。使用iconv对我有用

iconv -f ISO-8859-15 -t UTF-8  messages_nl.properties > messages_nl.properties.new

+1提及iconv。我以前从未听说过它,但是我把它输入到控制台中,瞧,这确实存在(无论如何,在CentOS 6中。)
ArtOfWarfare 2015年

现在,尽管我实际上已经尝试过使用它,但是它不起作用:它出现在无法转换为ISO-8559-1的第一个字符上。
ArtOfWarfare 2015年

1

我尝试使用Rod提供的方法,但考虑到BalusC担心在所有应用程序中不重复相同的解决方法,因此附带了此类:

import java.io.UnsupportedEncodingException;
import java.util.Locale;
import java.util.ResourceBundle;

public class MyResourceBundle {

    // feature variables
    private ResourceBundle bundle;
    private String fileEncoding;

    public MyResourceBundle(Locale locale, String fileEncoding){
        this.bundle = ResourceBundle.getBundle("com.app.Bundle", locale);
        this.fileEncoding = fileEncoding;
    }

    public MyResourceBundle(Locale locale){
        this(locale, "UTF-8");
    }

    public String getString(String key){
        String value = bundle.getString(key); 
        try {
            return new String(value.getBytes("ISO-8859-1"), fileEncoding);
        } catch (UnsupportedEncodingException e) {
            return value;
        }
    }
}

使用此方法的方式将与常规ResourceBundle的用法非常相似:

private MyResourceBundle labels = new MyResourceBundle("es", "UTF-8");
String label = labels.getString(key)

或者,您可以使用默认情况下使用UTF-8的备用构造函数:

private MyResourceBundle labels = new MyResourceBundle("es");

0

打开“设置/首选项”对话框(Ctrl+ Alt+ S),然后单击“编辑器”和“文件编码”。

显示的屏幕截图

然后,在底部,您将为属性文件指定默认编码。选择您的编码类型。

或者,您可以使用unicode符号代替资源包中的文本(例如"ів"equals \u0456\u0432


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.