URLEncoder не может перевести символ пробела

Я жду

System.out.println(java.net.URLEncoder.encode("Hello World", "UTF-8"));

для вывода:

Hello%20World

(20 - это шестнадцатеричный код ASCII для пробела)

Однако я получаю следующее:

Hello+World

Я использую неправильный метод? Какой правильный метод мне следует использовать?


person Cheok Yan Cheng    schedule 19.01.2011    source источник
comment
имя класса действительно сбивает с толку, и многие люди использовали его неправильно. однако они этого не замечают, потому что при применении URLDecoder исходное значение восстанавливается, поэтому + или% 20 для них не имеют значения.   -  person irreputable    schedule 20.01.2011


Ответы (17)


Это ведет себя так, как ожидалось. URLEncoder реализует спецификации HTML для кодирования URL-адресов в формах HTML.

Из javadocs :

Этот класс содержит статические методы для преобразования String в формат MIME application / x-www-form-urlencoded.

и из Спецификации HTML < / а>:

application / x-www-form-urlencoded

Формы, отправленные с этим типом контента, должны быть закодированы следующим образом:

  1. Имена и значения элементов управления экранированы. Пробелы заменяются на `+ '

Вам нужно будет заменить его, например:

System.out.println(java.net.URLEncoder.encode("Hello World", "UTF-8").replace("+", "%20"));
person dogbane    schedule 19.01.2011
comment
ну Это действительно ответ, а не замена, разве нет библиотеки java или функции для выполнения задачи /? - person co2f2e; 22.04.2013
comment
Знак плюс нужно экранировать t.println(java.net.URLEncoder.encode("Hello World", "UTF-8").replace("\\+", "%20")); - person George; 16.08.2013
comment
@congliu это неверно - вы, вероятно, думаете о replaceAll (), который работает с регулярным выражением - replace () - это простая замена последовательности символов. - person CupawnTae; 25.09.2013
comment
Да @congliu, хороший способ: URLEncoder.encode (Myurl, utf-8) .replaceAll (\\ +,% 20); - person eento; 09.10.2013
comment
@eento Почему решение, предложенное congliu, неверно? Он почти такой же, как у вас. - person Alston; 03.10.2014
comment
@pyb Хотел бы я проголосовать против вашего комментария. Вопрос был именно о космическом характере ... зачем все обобщать? Точно так же, если пользователи хотят заменить все знаки плюса на %20, этот ответ не будет на 100% точным, так как им нужно будет использовать String#replaceAll(regex, replacement), в этом случае "\\+" будет обязательным, но опять же, этот ответ эффективно отвечает на точный вопрос, заданный @dogbane. - person Clint Eastwood; 17.05.2017
comment
@ClintEastwood Этот ответ поощряет использование java.net.URLEncoder, который не выполняет то, о чем изначально просили. Итак, этот ответ предлагает патч с использованием replace () поверх него. Почему нет? Поскольку это решение подвержено ошибкам и может привести к 20 другим аналогичным вопросам, но с другим характером. Вот почему я сказал, что это было недальновидно. - person pyb; 17.05.2017
comment
Он реализует работу по кодированию имен и значений параметров формы. Не URL-адреса. - person user207421; 15.09.2017
comment
@pyb Я думаю, что любой ответ может к чему-то привести, поскольку любой код может иметь побочные эффекты, которых изначально не ожидалось. Можете ли вы указать на соответствующую проблему с кодом, опубликованным в этом ответе? - person eis; 31.05.2018
comment
@eis Как заметил спрашивающий, URLEncoder.encode не делает то, что спрашивает. Код, опубликованный в этом ответе, исправляет его, вызывая String.replace. Это слабо (работает только для символа пробела) и слишком сложно: просто используйте правильную кодировку. См., Например, stackoverflow.com/a/31595036/2223027 Чтобы использовать аналогию, если ответ был "Почему не 2+2 дает 5 ?, это все равно что предлагать просто сделать 2+2+1, и вы получите 5. - person pyb; 31.05.2018
comment
@pyb мне грустно видеть, что выполнение одного вызова replace () считается более сложным, чем добавление полноценной библиотеки, такой как guava, что действительно значительно усложняет программное обеспечение. Я хотел сказать, что существует ли проблема с простой заменой символа пробела? Я еще не видел реальных примеров, где разница между двумя кодировками вызвала бы проблему. Насколько мне известно, другими отличиями являются символы, перечисленные здесь, которые при необходимости добавить в список замены тривиально. - person eis; 01.06.2018
comment
когда я делаю это, я получаю Hello%2520World - person user1870400; 13.07.2018

Пробел кодируется как %20 в URL-адресах и как + в представленных данных форм (тип содержимого application / x-www-form-urlencoded). Вам нужно первое.

Используя Guava:

dependencies {
     compile 'com.google.guava:guava:23.0'
     // or, for Android:
     compile 'com.google.guava:guava:23.0-android'
}

Вы можете использовать UrlEscapers:

String encodedString = UrlEscapers.urlFragmentEscaper().escape(inputString);

Не используйте String.replace, это только закодирует пробел. Вместо этого используйте библиотеку.

person pyb    schedule 23.07.2015
comment
Он также работает для Android, com.google.guava: guava: 22.0-rc1-android. - person Bevor; 12.05.2017
comment
@Bevor rc1 означает 1st Release Candidate, то есть версию, еще не утвержденную для общего выпуска. Если можете, выберите версию без снапшота, альфа, бета, rc, поскольку они, как известно, содержат ошибки. - person pyb; 17.05.2017
comment
@pyb Спасибо, но я все равно обновлю библиотеки, когда мой проект будет завершен. Значит, без финальных версий на прод не пойду. И это все еще занимает много недель, так что я думаю, что тогда будет финальная версия. - person Bevor; 17.05.2017
comment
К сожалению, Guava не предоставляет декодер, в отличие от Apache URLCodec. - person Benny Bottema; 09.03.2018

Этот класс выполняет кодирование типа application/x-www-form-urlencoded, а не процентное кодирование, поэтому замена with + является правильным поведением.

Из javadoc:

При кодировании String применяются следующие правила:

  • Буквенно-цифровые символы от «a» до «z», от «A» до «Z» и от «0» до «9» остаются прежними.
  • Специальные символы «.», «-», «*» и «_» остаются прежними.
  • Пробел "" преобразуется в знак плюса "+".
  • Все остальные символы небезопасны и сначала преобразуются в один или несколько байтов с использованием некоторой схемы кодирования. Затем каждый байт представлен трехзначной строкой «% xy», где xy - двузначное шестнадцатеричное представление байта. Рекомендуемая схема кодирования - UTF-8. Однако из соображений совместимости, если кодировка не указана, используется кодировка платформы по умолчанию.
person axtavt    schedule 19.01.2011
comment
@axtavt Хорошее объяснение. Но у меня остались вопросы. В url пробел следует интерпретировать как %20. Значит, нам нужно сделать url.replaceAll("\\+", "%20")? А если это javascript, нам не следует использовать функцию escape. Вместо этого используйте encodeURI или encodeURIComponent. Это то, о чем я думал. - person Alston; 03.10.2014
comment
@Stallman, это Java, а не JavaScript. Совершенно разные языки. - person Charles Wood; 25.11.2014

Кодировать параметры запроса

org.apache.commons.httpclient.util.URIUtil
    URIUtil.encodeQuery(input);

ИЛИ, если вы хотите избежать символов в URI

public static String escapeURIPathParam(String input) {
  StringBuilder resultStr = new StringBuilder();
  for (char ch : input.toCharArray()) {
   if (isUnsafe(ch)) {
    resultStr.append('%');
    resultStr.append(toHex(ch / 16));
    resultStr.append(toHex(ch % 16));
   } else{
    resultStr.append(ch);
   }
  }
  return resultStr.toString();
 }

 private static char toHex(int ch) {
  return (char) (ch < 10 ? '0' + ch : 'A' + ch - 10);
 }

 private static boolean isUnsafe(char ch) {
  if (ch > 128 || ch < 0)
   return true;
  return " %$&+,/:;=?@<>#%".indexOf(ch) >= 0;
 }
person fmucar    schedule 19.01.2011
comment
Использование org.apache.commons.httpclient.util.URIUtil кажется наиболее эффективным способом решения проблемы! - person Stéphane Ammar; 29.01.2018
comment
URIUtil, похоже, исчез в текущих версиях, есть ли альтернативы? - person wutzebaer; 15.09.2020

Hello+World - это то, как браузер будет кодировать данные формы (application/x-www-form-urlencoded) для GET запроса, и это общепринятая форма для части запроса URI.

http://host/path/?message=Hello+World

Если вы отправили этот запрос сервлету Java, сервлет правильно декодировал бы значение параметра. Обычно проблемы возникают только в том случае, если кодировка не совпадает.

Строго говоря, спецификации HTTP или URI не требуют кодирования части запроса с использованием пар "ключ-значение" application/x-www-form-urlencoded; часть запроса просто должна быть в форме, которую принимает веб-сервер. На практике это вряд ли станет проблемой.

Как правило, было бы неправильно использовать эту кодировку для других частей URI (например, пути). В этом случае следует использовать схему кодирования, описанную в RFC 3986.

http://host/Hello%20World

Подробнее здесь.

person McDowell    schedule 19.01.2011

Просто боролся с этим и на Android, сумел наткнуться на Uri.encode (String, String), в то время как специфический для android (android.net.Uri) может быть полезен для некоторых.

статический строковый код (String s, String allow)

https://developer.android.com/reference/android/net/Uri.html#encode(java.lang.String,%20java.lang.String)

person Chrispix    schedule 11.09.2017

В других ответах либо представлена ​​замена строки вручную, URLEncoder, который фактически кодирует формат HTML, Apache заброшен URIUtil или с помощью UrlEscapers < / а>. Последний вариант хорош, но в нем нет декодера.

Apache Commons Lang предоставляет URLCodec, который кодирует и декодирует в соответствии с форматом URL rfc3986.

String encoded = new URLCodec().encode(str);
String decoded = new URLCodec().decode(str);

Если вы уже используете Spring, вы также можете выбрать использование его UriUtils.

person Benny Bottema    schedule 09.03.2018
comment
URLCodec здесь не лучшее решение, потому что он кодирует пробелы как плюсы, но вопрос заключается в том, чтобы пробелы были закодированы как% 20. - person davidwebster48; 03.12.2018

Если вы используете причал, org.eclipse.jetty.util.URIUtil решит проблему.

String encoded_string = URIUtil.encodePath(not_encoded_string).toString();
person gourab ghosh    schedule 30.09.2020

Это сработало для меня

org.apache.catalina.util.URLEncoder ul = new org.apache.catalina.util.URLEncoder().encode("MY URL");
person Hitesh Kumar    schedule 07.08.2015

Хотя довольно старый, но быстрый ответ:

Spring предоставляет UriUtils - с его помощью вы можете указать, как кодировать и какая часть связана с URI, например

encodePathSegment
encodePort
encodeFragment
encodeUriVariables
....

Я использую их, потому что мы уже используем Spring, т.е. дополнительных библиотек не требуется!

person LeO    schedule 03.08.2018
comment
Есть ли что-нибудь еще в Spring, которое выполняет кодирование URL-адресов? Я спрашиваю, потому что, когда я делаю тестовый запрос с использованием getForObject (часть RestTemplate), записываемый URL оставляет незашифрованные запятые, но UriUtils.encode(...) кодирует запятые, что означает, что мой MockRestServiceServer не соответствует пути, если я использую вывод из UriUtils.encode. - person IpsRich; 16.10.2020
comment
Думаю, это ответ на мой вопрос: stackoverflow.com/a/20885702/1999993 - person IpsRich; 16.10.2020

+ правильно. Если вам действительно нужно% 20, то замените Plusses самостоятельно.

Предупреждение. Этот ответ сильно оспаривается (+8 против -6), так что относитесь к нему с недоверием.

person Daniel    schedule 19.01.2011
comment
Если исходная строка действительно содержала символ +, может возникнуть проблема. - person Alexis Dufrenoy; 11.06.2013
comment
@Traroth - Не совсем. Символ + в исходном тексте должен быть закодирован как %2B. - person Ted Hopp; 20.08.2013
comment
говорить, что + правильно, не зная контекста, по крайней мере, педантично. Проголосовали против. Прочтите другие ответы, чтобы узнать, когда следует использовать + или% 20. - person Clint Eastwood; 17.05.2017
comment
@ClintEastwood: Можете ли вы рассказать мне о каком-либо варианте использования, в котором символ + для пробелов в URL-адресах неверен? За исключением случаев, когда на другой стороне находится несоответствующий парсер URL? - person Daniel; 18.05.2017
comment
@ Дэниел, конечно, не говорю, что неправильно, но не подходит? да. Инструменты аналитики часто используют параметры запроса со значениями, разделенными определенным символом, например +. В этом случае использование + вместо% 20 будет неправильным. + используется для экранирования пробелов в форме, тогда как процентное кодирование (также известное как кодирование URL) больше ориентировано на URL-адреса. - person Clint Eastwood; 19.05.2017

Если вы хотите кодировать компоненты пути URI, вы также можете использовать стандартные функции JDK, например

public static String encodeURLPathComponent(String path) {
    try {
        return new URI(null, null, path, null).toASCIIString();
    } catch (URISyntaxException e) {
        // do some error handling
    }
    return "";
}

Класс URI также можно использовать для кодирования различных частей или целых URI.

person MrTux    schedule 20.01.2021

Это не однострочник, но вы можете использовать:

URL url = new URL("https://some-host.net/dav/files/selling_Rosetta Stone Case Study.png.aes");
URI uri = new URI(url.getProtocol(), url.getUserInfo(), url.getHost(), url.getPort(), url.getPath(), url.getQuery(), url.getRef());
System.out.println(uri.toString());

Это даст вам результат:

https://some-host.net/dav/files/selling_Rosetta%20Stone%20Case%20Study.png.aes
person tchudyk    schedule 30.04.2021

Ознакомьтесь с классом java.net.URI.

person Fredrik Widerberg    schedule 19.01.2011

Я использую неправильный метод? Какой правильный метод мне следует использовать?

Да, этот метод java.net.URLEncoder.encode не предназначен для преобразования "" в "20%" согласно спецификации (источник).

Пробел "" преобразуется в знак плюса "+".

Даже если это неправильный метод, вы можете изменить его на: System.out.println(java.net.URLEncoder.encode("Hello World", "UTF-8").replaceAll("\\+", "%20"));have a nice day =).

person Pregunton    schedule 08.06.2017
comment
Вы предлагаете использовать метод, который не подходит (URLEncoder.encode), и исправить его, используя replaceAll, который будет работать только в этом конкретном случае. Вместо этого используйте правильный класс и метод, см. Другие ответы. - person pyb; 03.08.2017
comment
@pyb похоже, вы не понимаете, что я написал. Я никогда не говорил, что предлагаю его использовать, я говорил, что можно. Пожалуйста, прочтите и поймите, прежде чем писать. - person Pregunton; 21.08.2017
comment
Это сайт вопросов и ответов, а не обычная доска объявлений, на которой люди болтают. Если у вас есть боковые комментарии, используйте комментарии. Подольше поговорить? Воспользуйтесь чатом. Не размещайте код, с которым вы не согласны, в качестве ответа. Пожалуйста, прочтите и поймите правила этого сайта, прежде чем писать лекции другим. - person pyb; 22.08.2017
comment
Я поддерживаю его, потому что большинство других решений предоставляют тот же совет. Никаких конкретных случаев, доказывающих неправильность этого метода, представлено не было. Использование apache commons с блоками try-catch или зависимостями - слишком большая проблема для метода, который можно эффективно исправить с помощью replaceAll. - person Eugene Kartoyev; 16.07.2018

ИСПОЛЬЗУЙТЕ MyUrlEncode.URLencoding (String url, String enc) для решения проблемы.

    public class MyUrlEncode {
    static BitSet dontNeedEncoding = null;
    static final int caseDiff = ('a' - 'A');
    static {
        dontNeedEncoding = new BitSet(256);
        int i;
        for (i = 'a'; i <= 'z'; i++) {
            dontNeedEncoding.set(i);
        }
        for (i = 'A'; i <= 'Z'; i++) {
            dontNeedEncoding.set(i);
        }
        for (i = '0'; i <= '9'; i++) {
            dontNeedEncoding.set(i);
        }
        dontNeedEncoding.set('-');
        dontNeedEncoding.set('_');
        dontNeedEncoding.set('.');
        dontNeedEncoding.set('*');
        dontNeedEncoding.set('&');
        dontNeedEncoding.set('=');
    }
    public static String char2Unicode(char c) {
        if(dontNeedEncoding.get(c)) {
            return String.valueOf(c);
        }
        StringBuffer resultBuffer = new StringBuffer();
        resultBuffer.append("%");
        char ch = Character.forDigit((c >> 4) & 0xF, 16);
            if (Character.isLetter(ch)) {
            ch -= caseDiff;
        }
        resultBuffer.append(ch);
            ch = Character.forDigit(c & 0xF, 16);
            if (Character.isLetter(ch)) {
            ch -= caseDiff;
        }
         resultBuffer.append(ch);
        return resultBuffer.toString();
    }
    private static String URLEncoding(String url,String enc) throws UnsupportedEncodingException {
        StringBuffer stringBuffer = new StringBuffer();
        if(!dontNeedEncoding.get('/')) {
            dontNeedEncoding.set('/');
        }
        if(!dontNeedEncoding.get(':')) {
            dontNeedEncoding.set(':');
        }
        byte [] buff = url.getBytes(enc);
        for (int i = 0; i < buff.length; i++) {
            stringBuffer.append(char2Unicode((char)buff[i]));
        }
        return stringBuffer.toString();
    }
    private static String URIEncoding(String uri , String enc) throws UnsupportedEncodingException { //对请求参数进行编码
        StringBuffer stringBuffer = new StringBuffer();
        if(dontNeedEncoding.get('/')) {
            dontNeedEncoding.clear('/');
        }
        if(dontNeedEncoding.get(':')) {
            dontNeedEncoding.clear(':');
        }
        byte [] buff = uri.getBytes(enc);
        for (int i = 0; i < buff.length; i++) {
            stringBuffer.append(char2Unicode((char)buff[i]));
        }
        return stringBuffer.toString();
    }

    public static String URLencoding(String url , String enc) throws UnsupportedEncodingException {
        int index = url.indexOf('?');
        StringBuffer result = new StringBuffer();
        if(index == -1) {
            result.append(URLEncoding(url, enc));
        }else {
            result.append(URLEncoding(url.substring(0 , index),enc));
            result.append("?");
            result.append(URIEncoding(url.substring(index+1),enc));
        }
        return result.toString();
    }

}
person IloveIniesta    schedule 21.11.2012
comment
изобретать колесо заново, добавлять в кодовую базу очень подверженный ошибкам код - почти всегда плохое решение. - person Clint Eastwood; 17.05.2017

используйте набор символов "ISO-8859-1" для URLEncoder

person Akhil Sikri    schedule 09.08.2012