将 URI 查询的字符串解析为键值对集合

回答 27 浏览 42.5万 2012-11-27

我把URI弄成这样了。

https://google.com.ua/oauth/authorize?client_id=SS&response_type=code&scope=N_FULL&access_type=offline&redirect_uri=http://localhost/Callback

我需要一个带有解析过的元素的集合。

NAME               VALUE
------------------------
client_id          SS
response_type      code
scope              N_FULL
access_type        offline
redirect_uri       http://localhost/Callback

确切地说,我需要一个与C#/.NET HttpUtility.ParseQueryString方法等效的Java方法。

Sergey Shafiev 提问于2012-11-27
请查看这个解决方案--坚实的库和工作实例,用于解析和格式化操作。stackoverflow.com/a/37744000/1882064arcseldon 2016-06-10
27 个回答
#1楼 已采纳
得票数 410

如果你正在寻找一种不使用外部库就能实现的方法,下面的代码会对你有所帮助。

public static Map<String, String> splitQuery(URL url) throws UnsupportedEncodingException {
    Map<String, String> query_pairs = new LinkedHashMap<String, String>();
    String query = url.getQuery();
    String[] pairs = query.split("&");
    for (String pair : pairs) {
        int idx = pair.indexOf("=");
        query_pairs.put(URLDecoder.decode(pair.substring(0, idx), "UTF-8"), URLDecoder.decode(pair.substring(idx + 1), "UTF-8"));
    }
    return query_pairs;
}

你可以使用<map>.get("client_id")来获取,在你的问题中给出的URL,这将返回"SS"。

UPDATE URL-Decoding的添加

更新由于这个答案仍然很受欢迎,我对上面的方法做了一个改进版本,它可以处理具有相同键的多个参数,也可以处理没有值的参数。

public static Map<String, List<String>> splitQuery(URL url) throws UnsupportedEncodingException {
  final Map<String, List<String>> query_pairs = new LinkedHashMap<String, List<String>>();
  final String[] pairs = url.getQuery().split("&");
  for (String pair : pairs) {
    final int idx = pair.indexOf("=");
    final String key = idx > 0 ? URLDecoder.decode(pair.substring(0, idx), "UTF-8") : pair;
    if (!query_pairs.containsKey(key)) {
      query_pairs.put(key, new LinkedList<String>());
    }
    final String value = idx > 0 && pair.length() > idx + 1 ? URLDecoder.decode(pair.substring(idx + 1), "UTF-8") : null;
    query_pairs.get(key).add(value);
  }
  return query_pairs;
}

更新Java8版本

public Map<String, List<String>> splitQuery(URL url) {
    if (Strings.isNullOrEmpty(url.getQuery())) {
        return Collections.emptyMap();
    }
    return Arrays.stream(url.getQuery().split("&"))
            .map(this::splitQueryParameter)
            .collect(Collectors.groupingBy(SimpleImmutableEntry::getKey, LinkedHashMap::new, mapping(Map.Entry::getValue, toList())));
}

public SimpleImmutableEntry<String, String> splitQueryParameter(String it) {
    final int idx = it.indexOf("=");
    final String key = idx > 0 ? it.substring(0, idx) : it;
    final String value = idx > 0 && it.length() > idx + 1 ? it.substring(idx + 1) : null;
    return new SimpleImmutableEntry<>(
        URLDecoder.decode(key, StandardCharsets.UTF_8),
        URLDecoder.decode(value, StandardCharsets.UTF_8)
    );
}

运行上面的方法,其网址是

https://stackoverflow.com?param1=value1&param2=&param3=value3&param3

返回该地图。

{param1=["value1"], param2=[null], param3=["value3", null]}
Pr0gr4mm3r 提问于2012-11-27
Dariusz 修改于2022-04-26
你忘了对名称和参数进行解码,这也是为什么通常让库来完成普通任务更好的原因之一。Ruan Mendes 2012-11-27
我的答案指出了一个常用的库(apache),如果你还没有使用它,可能就不值得了。现在的答案看起来不错,提供了OP想要的地图Ruan Mendes 2012-11-27
如果你有多个具有相同名称/键的参数,使用这个函数将覆盖具有类似键的值。fatarms 2013-07-08
@Chris 你混淆了xml/html转义和URL编码。你的示例URL应该是:a.com/q?1=a%26b&2=b%26csceaj 2015-05-19
如果能指出使用了哪些函数就更好了。Collectors.mapping(...) 和 Collectors.toList(...)Thomas Rebele 2018-04-08
#2楼
得票数 375

org.apache.http.client.utils.URLEncodedUtils

是一个众所周知的库,可以为你做这件事。

import org.apache.hc.client5.http.utils.URLEncodedUtils

String url = "http://www.example.com/something.html?one=1&two=2&three=3&three=3a";

List<NameValuePair> params = URLEncodedUtils.parse(new URI(url), Charset.forName("UTF-8"));

for (NameValuePair param : params) {
  System.out.println(param.getName() + " : " + param.getValue());
}

输出

one : 1
two : 2
three : 3
three : 3a
Ruan Mendes 提问于2012-11-27
Dariusz 修改于2022-04-26
我可以通过它的名字来接收值而不传递所有的元素吗?我的意思是像这样。System.out.print(params["one"])。Sergey Shafiev 2012-11-27
@SergeyShafiev 将List<NameValuePair>转换为Map<String,String>是很简单的,Java对哈希图没有括号访问,它看起来像map.get("one") 如果你不知道怎么做,应该是另一个问题(但先自己试试)。我们更喜欢保持问题的简洁性。Ruan Mendes 2012-11-27
请注意,如果你的URL中有两次相同的参数(即?a=1&a=2),URLEncodedUtils将抛出一个IllegalArgumentException。Crystark 2014-04-28
@Crystark 从httpclient 4.3.3开始,带有重复名称的查询字符串不会产生任何异常。它像预期的那样工作。System.out.println(URLEncodedUtils.parse(new URI("http://example.com/?foo=bar&foo=baz"), "UTF-8"));会打印[foo=bar, foo=baz]Akihiro HARAI 2014-06-02
从Android 6开始,Apache HTTP客户端库已被删除。这意味着URLEncodedUtils and NameValuePair不再可用(除非你按照这里的描述向传统的Apache库添加一个依赖关系)。Ted Hopp 2015-12-10
#3楼
得票数 150

如果你使用的是Spring框架,那么你就可以使用。

public static void main(String[] args) {
    String uri = "http://my.test.com/test?param1=ab&param2=cd&param2=ef";
    MultiValueMap<String, String> parameters =
            UriComponentsBuilder.fromUriString(uri).build().getQueryParams();
    List<String> param1 = parameters.get("param1");
    List<String> param2 = parameters.get("param2");
    System.out.println("param1: " + param1.get(0));
    System.out.println("param2: " + param2.get(0) + "," + param2.get(1));
}

你将会得到。

param1: ab
param2: cd,ef
xxg 提问于2016-09-06
xxg 修改于2019-03-05
对于URLs,使用UriComponentsBuilder.fromHttpUrl(url)Ilya Serbis 2020-02-11
请注意,getQueryParams()不会解码查询参数。所以对于一个http://foobar/path?param1=a%3Db的URL,你会得到param1: a%3Db而不是param1: a=b。你需要自己使用URLDecoder.decode()...。- getQueryParams()BROKENPeter V. Mørch 2021-01-11
#4楼
得票数 58

使用google Guava,用2行就可以完成。

import java.util.Map;
import com.google.common.base.Splitter;

public class Parser {
    public static void main(String... args) {
        String uri = "https://google.com.ua/oauth/authorize?client_id=SS&response_type=code&scope=N_FULL&access_type=offline&redirect_uri=http://localhost/Callback";
        String query = uri.split("\\?")[1];
        final Map<String, String> map = Splitter.on('&').trimResults().withKeyValueSeparator('=').split(query);
        System.out.println(map);
    }
}

这给你带来了

{client_id=SS, response_type=code, scope=N_FULL, access_type=offline, redirect_uri=http://localhost/Callback}
Amin Abbaspour 提问于2014-05-20
Vadzim 修改于2019-06-14
所选答案中描述的URL解码又是怎么回事?Clint Eastwood 2014-06-05
这也是对多个同名键的怀疑。根据javadocs,这将抛出一个IllegalArgumentException。jontro 2014-12-30
你应该使用new java.net.URL(uri).getQuery(),而不是手动分割uri,因为这为你赢得了对URL的自由输入验证权。avgvstvs 2015-03-06
用于解码: final Map<String, String> queryVars = Maps.transformValues(map, new Function<String,String>() { @Override public String apply(String value) { try { return URLDecoder.decode(value, "UTF-8"); } catch (UnsupportedEncodingException e) { // TODO 自动生成的 catch block e.printStackTrace(); }return value; }});phreakhead 2015-07-22
警告!!这样做并不安全,因为如果查询字符串中有重复的键,splitter.split()将抛出IllegalArgumentException。参见stackoverflow.com/questions/1746507/…Anderson 2015-11-27
#5楼
得票数 45

我发现的最短的方法是这个方法。

MultiValueMap<String, String> queryParams =
            UriComponentsBuilder.fromUriString(url).build().getQueryParams();

更新: UriComponentsBuilder来自Spring。这里链接

Dmitry Senkovich 提问于2017-10-26
Dmitry Senkovich 修改于2017-11-06
如果不知道这个UriComponentsBuilder类是从哪里来的,它就没有什么用处。Thomas Mortagne 2017-11-06
N.B.这需要URI。Java版本的URI不是URL的超集(这就是为什么toURI会引发异常)。Adam Gent 2019-06-28
请注意,getQueryParams()对查询参数进行而不是解码。所以对于一个http://foobar/path?param1=a%3Db的URL,你会得到param1: a%3Db而不是param1: a=b。你需要自己使用URLDecoder.decode()...。- getQueryParams()BROKENPeter V. Mørch 2021-01-11
此外,如果URL来自HTML文档,你必须先取消它的语法。org.springframework.web.util.HtmlUtils.htmlUnescape(myUrl)Michael Böckling 2022-09-14
#6楼
得票数 30

对于Android,如果你在你的项目中使用OkHttp。你可以看一下这个。它简单而有帮助。

final HttpUrl url = HttpUrl.parse(query);
if (url != null) {
    final String target = url.queryParameter("target");
    final String id = url.queryParameter("id");
}
THANN Phearum 提问于2018-02-03
HttpUrl是一个有点奇怪的名字,但这正是我需要的。谢谢。GuiSim 2018-06-21
更新。从OkHttp 4开始,HttpUrl.parse()已经被废弃了,但是用这里描述的新的OkHttp扩展函数仍然可以做到这一点。stackoverflow.com/a/63118203/2888763Trevor Halvorson 2021-10-03
#7楼
得票数 22

Java 11

给出了要分析的URL。

URL url = new URL("https://google.com.ua/oauth/authorize?client_id=SS&response_type=code&scope=N_FULL&access_type=offline&redirect_uri=http://localhost/Callback");

这个解决方案收集了一个成对的列表。

List<Map.Entry<String, String>> list = Pattern.compile("&")
   .splitAsStream(url.getQuery())
   .map(s -> Arrays.copyOf(s.split("=", 2), 2))
   .map(o -> Map.entry(decode(o[0]), decode(o[1])))
   .collect(Collectors.toList());

另一方面,这个解决方案收集了一个Map(考虑到在一个URL中可能有更多的同名但不同值的参数)。

Map<String, List<String>> list = Pattern.compile("&")
   .splitAsStream(url.getQuery())
   .map(s -> Arrays.copyOf(s.split("=", 2), 2))
   .collect(groupingBy(s -> decode(s[0]), mapping(s -> decode(s[1]), toList())));

两种解决方案都必须使用一个实用的函数来正确解码参数。

private static String decode(final String encoded) {
    return Optional.ofNullable(encoded)
                   .map(e -> URLDecoder.decode(e, StandardCharsets.UTF_8))
                   .orElse(null);
}
freedev 提问于2016-05-21
freedev 修改于2021-01-03
这更像是一个Java 8的方法,而不是一个Java 8的oneliner。Stephan 2017-06-16
在我看来,单行本应该很短,不应该跨越多行。Stephan 2017-06-16
这里涉及到多种说法。Stephan 2017-06-16
我想你可以用一行字写完一整堂课,但这不是通常所说的"单行字"的意思。Abhijit Sarkar 2018-08-09
如果你碰巧有Java 10或更高版本,有一个小小的改进--URLDecoder#decode(finally)有一个重载,它接受一个Charset(例如StandardCharsets.UTF_8),而不是一个字符串作为编码,这意味着你不需要捕捉UnsupportedEncodingException。chut 2020-08-25
#8楼
得票数 13

如果你使用的是servlet doGet,请试着这样做

request.getParameterMap()

Returns a java.util.Map of the parameters of this request.

Returns: an immutable java.util.Map containing parameter names as keys and parameter values as map values. The keys in the parameter map are of type String. The values in the parameter map are of type String array.

(Java doc)

Ansar Ozden 提问于2018-01-25
Roshana Pitigala 修改于2018-05-31
这也适用于Spring Web,在你的控制器中,你可以有一个HttpServletRequest类型的参数,在Mock MVC单元测试中,它也适用于MockHttpServletRequest类型。GameSalutes 2019-05-23
#9楼
得票数 11

在Android上,在包android.net中有一个Uri类。请注意,Uriandroid.net的一部分,而URIjava.net的一部分。

Uri类有许多函数可以从查询中提取键值对。 在此输入图片描述

下面的函数以HashMap的形式返回键值对。

在Java中。

Map<String, String> getQueryKeyValueMap(Uri uri){
    HashMap<String, String> keyValueMap = new HashMap();
    String key;
    String value;

    Set<String> keyNamesList = uri.getQueryParameterNames();
    Iterator iterator = keyNamesList.iterator();

    while (iterator.hasNext()){
        key = (String) iterator.next();
        value = uri.getQueryParameter(key);
        keyValueMap.put(key, value);
    }
    return keyValueMap;
}

在Kotlin中。

fun getQueryKeyValueMap(uri: Uri): HashMap<String, String> {
        val keyValueMap = HashMap<String, String>()
        var key: String
        var value: String

        val keyNamesList = uri.queryParameterNames
        val iterator = keyNamesList.iterator()

        while (iterator.hasNext()) {
            key = iterator.next() as String
            value = uri.getQueryParameter(key) as String
            keyValueMap.put(key, value)
        }
        return keyValueMap
    }
Ramakrishna Joshi 提问于2019-05-02
Ramakrishna Joshi 修改于2021-04-14
#10楼
得票数 9

Netty也提供了一个很好的查询字符串解析器,称为QueryStringDecoder。在一行代码中,它可以解析问题中的URL。 我喜欢,因为它不需要捕捉或抛出java.net.MalformedURLException

用一句话来说就是。

Map<String, List<String>> parameters = new QueryStringDecoder(url).parameters();

请看这里的javadocs。https://netty.io/4.1/api/io/netty/handler/codec/http/QueryStringDecoder.html

下面是一个简短的、自成一体的、正确的例子。

import io.netty.handler.codec.http.QueryStringDecoder;
import org.apache.commons.lang3.StringUtils;

import java.util.List;
import java.util.Map;

public class UrlParse {

  public static void main(String... args) {
    String url = "https://google.com.ua/oauth/authorize?client_id=SS&response_type=code&scope=N_FULL&access_type=offline&redirect_uri=http://localhost/Callback";
    QueryStringDecoder decoder = new QueryStringDecoder(url);
    Map<String, List<String>> parameters = decoder.parameters();
    print(parameters);
  }

  private static void print(final Map<String, List<String>> parameters) {
    System.out.println("NAME               VALUE");
    System.out.println("------------------------");
    parameters.forEach((key, values) ->
        values.forEach(val ->
            System.out.println(StringUtils.rightPad(key, 19) + val)));
  }
}

这就产生了

NAME               VALUE
------------------------
client_id          SS
response_type      code
scope              N_FULL
access_type        offline
redirect_uri       http://localhost/Callback
Kirby 提问于2019-05-15
Kirby 修改于2019-05-15
#11楼
得票数 8

如果你使用的是Java 8,并且你愿意写一些可重用的方法,那么你可以用一行字来完成它。

private Map<String, List<String>> parse(final String query) {
    return Arrays.asList(query.split("&")).stream().map(p -> p.split("=")).collect(Collectors.toMap(s -> decode(index(s, 0)), s -> Arrays.asList(decode(index(s, 1))), this::mergeLists));
}

private <T> List<T> mergeLists(final List<T> l1, final List<T> l2) {
    List<T> list = new ArrayList<>();
    list.addAll(l1);
    list.addAll(l2);
    return list;
}

private static <T> T index(final T[] array, final int index) {
    return index >= array.length ? null : array[index];
}

private static String decode(final String encoded) {
    try {
        return encoded == null ? null : URLDecoder.decode(encoded, "UTF-8");
    } catch(final UnsupportedEncodingException e) {
        throw new RuntimeException("Impossible: UTF-8 is a required encoding", e);
    }
}

但这是一条相当残酷的线。

Fuwjax 提问于2015-05-15
#12楼
得票数 4

有一个新版本的Apache HTTP客户端--org.apache.httpcomponents.client5--其中URLEncodedUtils现在已经被废弃。应该使用URIBuilder来代替。

import org.apache.hc.core5.http.NameValuePair;
import org.apache.hc.core5.net.URIBuilder;

private static Map<String, String> getQueryParameters(final String url) throws URISyntaxException {
    return new URIBuilder(new URI(url), StandardCharsets.UTF_8).getQueryParams()
                                                               .stream()
                                                               .collect(Collectors.toMap(NameValuePair::getName,
                                                                                         nameValuePair -> URLDecoder.decode(nameValuePair.getValue(), StandardCharsets.UTF_8)));
}
Enigo 提问于2022-03-15
#13楼
得票数 3

一个现成的URI查询部分解码的解决方案(包括解码和多参数值)。

评论内容

我对@Pr0gr4mm3r在https://stackoverflow.com/a/13592567/1211082中提供的代码并不满意。基于流的解决方案不做URLDecoding,可变的版本很笨重。

因此,我阐述了一个解决方案,即

  • 可以将URI的查询部分分解为Map<String, List<Optional<String>>>
  • 可以为同一参数名处理多个值
  • 可以正确地表示没有数值的参数Optional.empty()而不是null)。
  • 通过URLdecode对参数名称值进行正确的解码
  • 是基于Java 8 Streams的。
  • 是可以直接使用的(见下面的代码,包括进口)。
  • 允许适当的错误处理(这里通过将一个检查过的异常UnsupportedEncodingException变成一个运行时异常RuntimeUnsupportedEncodingException,允许与流相互作用。(将普通函数包装成抛出检查过的异常的函数是很麻烦的。而且Scala Try在Java语言默认情况下是不可用的)。

Java代码

import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.*;
import static java.util.stream.Collectors.*;

public class URIParameterDecode {
    /**
     * Decode parameters in query part of a URI into a map from parameter name to its parameter values.
     * For parameters that occur multiple times each value is collected.
     * Proper decoding of the parameters is performed.
     * 
     * Example
     *   <pre>a=1&b=2&c=&a=4</pre>
     * is converted into
     *   <pre>{a=[Optional[1], Optional[4]], b=[Optional[2]], c=[Optional.empty]}</pre>
     * @param query the query part of an URI 
     * @return map of parameters names into a list of their values.
     *         
     */
    public static Map<String, List<Optional<String>>> splitQuery(String query) {
        if (query == null || query.isEmpty()) {
            return Collections.emptyMap();
        }

        return Arrays.stream(query.split("&"))
                    .map(p -> splitQueryParameter(p))
                    .collect(groupingBy(e -> e.get0(), // group by parameter name
                            mapping(e -> e.get1(), toList())));// keep parameter values and assemble into list
    }

    public static Pair<String, Optional<String>> splitQueryParameter(String parameter) {
        final String enc = "UTF-8";
        List<String> keyValue = Arrays.stream(parameter.split("="))
                .map(e -> {
                    try {
                        return URLDecoder.decode(e, enc);
                    } catch (UnsupportedEncodingException ex) {
                        throw new RuntimeUnsupportedEncodingException(ex);
                    }
                }).collect(toList());

        if (keyValue.size() == 2) {
            return new Pair(keyValue.get(0), Optional.of(keyValue.get(1)));
        } else {
            return new Pair(keyValue.get(0), Optional.empty());
        }
    }

    /** Runtime exception (instead of checked exception) to denote unsupported enconding */
    public static class RuntimeUnsupportedEncodingException extends RuntimeException {
        public RuntimeUnsupportedEncodingException(Throwable cause) {
            super(cause);
        }
    }

    /**
     * A simple pair of two elements
     * @param <U> first element
     * @param <V> second element
     */
    public static class Pair<U, V> {
        U a;
        V b;

        public Pair(U u, V v) {
            this.a = u;
            this.b = v;
        }

        public U get0() {
            return a;
        }

        public V get1() {
            return b;
        }
    }
}

规模化代码

......为了完整起见,我忍不住要提供Scala中的解决方案,它以简洁和美观而著称

import java.net.URLDecoder

object Decode {
  def main(args: Array[String]): Unit = {
    val input = "a=1&b=2&c=&a=4";
    println(separate(input))
  }

  def separate(input: String) : Map[String, List[Option[String]]] = {
    case class Parameter(key: String, value: Option[String])

    def separateParameter(parameter: String) : Parameter =
      parameter.split("=")
               .map(e => URLDecoder.decode(e, "UTF-8")) match {
      case Array(key, value) =>  Parameter(key, Some(value))
      case Array(key) => Parameter(key, None)
    }

    input.split("&").toList
      .map(p => separateParameter(p))
      .groupBy(p => p.key)
      .mapValues(vs => vs.map(p => p.value))
  }
}
Martin Senne 提问于2019-09-19
#14楼
得票数 2

使用上面提到的评论和解决方案,我正在使用Map<String, Object>存储所有的查询参数,其中Objects可以是字符串或Set<String>。下面给出了解决方案。建议先使用某种url验证器来验证url,然后再调用convertQueryStringToMap方法。

private static final String DEFAULT_ENCODING_SCHEME = "UTF-8";

public static Map<String, Object> convertQueryStringToMap(String url) throws UnsupportedEncodingException, URISyntaxException {
    List<NameValuePair> params = URLEncodedUtils.parse(new URI(url), DEFAULT_ENCODING_SCHEME);
    Map<String, Object> queryStringMap = new HashMap<>();
    for(NameValuePair param : params){
        queryStringMap.put(param.getName(), handleMultiValuedQueryParam(queryStringMap, param.getName(), param.getValue()));
    }
    return queryStringMap;
}

private static Object handleMultiValuedQueryParam(Map responseMap, String key, String value) {
    if (!responseMap.containsKey(key)) {
        return value.contains(",") ? new HashSet<String>(Arrays.asList(value.split(","))) : value;
    } else {
        Set<String> queryValueSet = responseMap.get(key) instanceof Set ? (Set<String>) responseMap.get(key) : new HashSet<String>();
        if (value.contains(",")) {
            queryValueSet.addAll(Arrays.asList(value.split(",")));
        } else {
            queryValueSet.add(value);
        }
        return queryValueSet;
    }
}
SUMIT 提问于2016-06-02
SUMIT 修改于2016-06-02
对于参数顺序很重要的用例来说,Set是错误的数据类型。Peter V. Mørch 2021-01-11
#15楼
得票数 2

我试着做了一个Kotlin版本,看到这个版本在Google上是排名第一的结果。

@Throws(UnsupportedEncodingException::class)
fun splitQuery(url: URL): Map<String, List<String>> {

    val queryPairs = LinkedHashMap<String, ArrayList<String>>()

    url.query.split("&".toRegex())
            .dropLastWhile { it.isEmpty() }
            .map { it.split('=') }
            .map { it.getOrEmpty(0).decodeToUTF8() to it.getOrEmpty(1).decodeToUTF8() }
            .forEach { (key, value) ->

                if (!queryPairs.containsKey(key)) {
                    queryPairs[key] = arrayListOf(value)
                } else {

                    if(!queryPairs[key]!!.contains(value)) {
                        queryPairs[key]!!.add(value)
                    }
                }
            }

    return queryPairs
}

而扩展方法则是

fun List<String>.getOrEmpty(index: Int) : String {
    return getOrElse(index) {""}
}

fun String.decodeToUTF8(): String { 
    URLDecoder.decode(this, "UTF-8")
}
Graham Smith 提问于2018-06-25
平等地归功于stackoverflow.com/users/1203812/matthew-herod 50/50的努力,但不能成为共同作者。Graham Smith 2018-06-25
#16楼
得票数 2

另外,我推荐基于regex的URLParser的实现方式。

import java.util.regex.Matcher;
import java.util.regex.Pattern;

class URLParser {
    private final String query;
    
    public URLParser(String query) {
        this.query = query;
    }
    
    public String get(String name) {
        String regex = "(?:^|\\?|&)" + name + "=(.*?)(?:&|$)";
        Pattern pattern = Pattern.compile(regex);
        Matcher matcher = pattern.matcher(this.query);

        if (matcher.find()) {
            return matcher.group(1);
        }
        
        return "";
    }
}

这个类很容易使用。它在初始化时只需要URL或查询字符串,并通过给定的键解析值。

class Main {
    public static void main(String[] args) {
        URLParser parser = new URLParser("https://www.google.com/search?q=java+parse+url+params&oq=java+parse+url+params&aqs=chrome..69i57j0i10.18908j0j7&sourceid=chrome&ie=UTF-8");
        System.out.println(parser.get("q"));  // java+parse+url+params
        System.out.println(parser.get("sourceid"));  // chrome
        System.out.println(parser.get("ie"));  // UTF-8
    }
}
Lucy Goodman 提问于2022-08-07
#17楼
得票数 1

Kotlin的回答,最初参考了https://stackoverflow.com/a/51024552/3286489,但通过整理代码进行了改进,并提供了2个版本,并使用不可变的集合操作。

使用java.net.URI来提取查询。然后使用下面提供的扩展函数

  1. 假设你只想得到查询的最后一个值,即page2&page3会得到{page=3},请使用下面的扩展函数
    fun URI.getQueryMap(): Map<String, String> {
        if (query == null) return emptyMap()

        return query.split("&")
                .mapNotNull { element -> element.split("=")
                        .takeIf { it.size == 2 && it.none { it.isBlank() } } }
                .associateBy({ it[0].decodeUTF8() }, { it[1].decodeUTF8() })
    }

    private fun String.decodeUTF8() = URLDecoder.decode(this, "UTF-8") // decode page=%22ABC%22 to page="ABC"
  1. 假设你想得到一个查询的所有值的列表,即page2&page3会得到{page=[2, 3]}
    fun URI.getQueryMapList(): Map<String, List<String>> {
        if (query == null) return emptyMap()

        return query.split("&")
                .distinct()
                .mapNotNull { element -> element.split("=")
                        .takeIf { it.size == 2 && it.none { it.isBlank() } } }
                .groupBy({ it[0].decodeUTF8() }, { it[1].decodeUTF8() })
    }

    private fun String.decodeUTF8() = URLDecoder.decode(this, "UTF-8") // decode page=%22ABC%22 to page="ABC"

其使用方法如下

    val uri = URI("schema://host/path/?page=&page=2&page=2&page=3")
    println(uri.getQueryMapList()) // Result is {page=[2, 3]}
    println(uri.getQueryMap()) // Result is {page=3}
Elye 提问于2019-05-02
Elye 修改于2019-05-03
#18楼
得票数 1

当你的查询有单一的参数定义时,有很多答案可以适用于你的查询,正如你所指出的。在某些应用中,处理一些额外的查询参数的边缘情况可能是有用的,例如。

  • 参数值的列表,如param1&param1=value&param1=意味着param1被设置为List.of("", "value", "")
  • 无效的排列组合,如querypath?&=&&=noparamname&
  • 在地图中使用空字符串而不是空值 a=意味着"a"是List.of(""),以匹配网络服务程序的处理。

这使用了一个带有过滤器和groupingBy的Stream来收集到Map<String, List<String>>

public static Map<String, List<String>> getParameterValues(URL url) {
    return Arrays.stream(url.getQuery().split("&"))
            .map(s -> s.split("="))
            // filter out empty parameter names (as in Tomcat) "?&=&&=value&":
            .filter(arr -> arr.length > 0 && arr[0].length() > 0)
            .collect(Collectors.groupingBy(arr -> URLDecoder.decode(arr[0], StandardCharsets.UTF_8),
                     // drop this line for not-name definition order Map:
                     LinkedHashMap::new, 
                     Collectors.mapping(arr -> arr.length < 2 ? "" : URLDecoder.decode(arr[1], StandardCharsets.UTF_8), Collectors.toList())));
}
DuncG 提问于2021-09-19
#19楼
得票数 0

如果你使用的是Spring,在你的控制器方法中添加一个RequestParam Map<String,String>类型的参数,Spring就会为你构建地图。

mancini0 提问于2018-01-19
#20楼
得票数 0

仅仅是对Java 8版本的更新

public Map<String, List<String>> splitQuery(URL url) {
    if (Strings.isNullOrEmpty(url.getQuery())) {
        return Collections.emptyMap();
    }
    return Arrays.stream(url.getQuery().split("&"))
            .map(this::splitQueryParameter)
            .collect(Collectors.groupingBy(SimpleImmutableEntry::getKey, LinkedHashMap::new, **Collectors**.mapping(Map.Entry::getValue, **Collectors**.toList())));
}

mapping和toList()方法必须与Collectors一起使用,这一点在顶部的答案中没有提到。否则会在IDE中出现编译错误

Aarish Ramesh 提问于2018-06-07
看起来你也需要分享你的splitQueryParameters()方法?还有,**Collectors**是怎么回事?Kirby 2019-05-15
#21楼
得票数 0

在这里回答,因为这是个热门话题。这是在Kotlin中的一个干净的解决方案,使用了推荐的UrlQuerySanitizerapi。见官方文档。我已经添加了一个字符串生成器来连接和显示参数。

    var myURL: String? = null

    if (intent.hasExtra("my_value")) {
        myURL = intent.extras.getString("my_value")
    } else {
        myURL = intent.dataString
    }

    val sanitizer = UrlQuerySanitizer(myURL)
    // We don't want to manually define every expected query *key*, so we set this to true
    sanitizer.allowUnregisteredParamaters = true
    val parameterNamesToValues: List<UrlQuerySanitizer.ParameterValuePair> = sanitizer.parameterList
    val parameterIterator: Iterator<UrlQuerySanitizer.ParameterValuePair> = parameterNamesToValues.iterator()

    // Helper simply so we can display all values on screen
    val stringBuilder = StringBuilder()

    while (parameterIterator.hasNext()) {
        val parameterValuePair: UrlQuerySanitizer.ParameterValuePair = parameterIterator.next()
        val parameterName: String = parameterValuePair.mParameter
        val parameterValue: String = parameterValuePair.mValue

        // Append string to display all key value pairs
        stringBuilder.append("Key: $parameterName\nValue: $parameterValue\n\n")
    }

    // Set a textView's text to display the string
    val paramListString = stringBuilder.toString()
    val textView: TextView = findViewById(R.id.activity_title) as TextView
    textView.text = "Paramlist is \n\n$paramListString"

    // to check if the url has specific keys
    if (sanitizer.hasParameter("type")) {
        val type = sanitizer.getValue("type")
        println("sanitizer has type param $type")
    }
jungledev 提问于2018-11-16
#22楼
得票数 0

下面是我的解决方案,其中有reduceOptional

private Optional<SimpleImmutableEntry<String, String>> splitKeyValue(String text) {
    String[] v = text.split("=");
    if (v.length == 1 || v.length == 2) {
        String key = URLDecoder.decode(v[0], StandardCharsets.UTF_8);
        String value = v.length == 2 ? URLDecoder.decode(v[1], StandardCharsets.UTF_8) : null;
        return Optional.of(new SimpleImmutableEntry<String, String>(key, value));
    } else
        return Optional.empty();
}

private HashMap<String, String> parseQuery(URI uri) {
    HashMap<String, String> params = Arrays.stream(uri.getQuery()
            .split("&"))
            .map(this::splitKeyValue)
            .filter(Optional::isPresent)
            .map(Optional::get)
            .reduce(
                // initial value
                new HashMap<String, String>(), 
                // accumulator
                (map, kv) -> {
                     map.put(kv.getKey(), kv.getValue()); 
                     return map;
                }, 
                // combiner
                (a, b) -> {
                     a.putAll(b); 
                     return a;
                });
    return params;
}
  • 我忽略了重复的参数(我采取的是最后一个)。
  • 我使用Optional<SimpleImmutableEntry<String, String>> 忽略了后来的垃圾。
  • 缩减从一个空的地图开始,然后在每个SimpleImmutableEntry上填充它

如果你问,reduce在最后一个参数中需要这个奇怪的组合器,它只用于并行流中。它的目标是合并两个中间结果(这里是HashMap)。

Mike Dudley 提问于2019-08-12
Mike Dudley 修改于2019-08-12
#23楼
得票数 0

如果你碰巧在classpath上有cxf-core,并且你知道你没有重复的查询参数,你可能想使用UrlUtils.parseQueryString

dschulten 提问于2020-12-06
#24楼
得票数 0

Eclipse Jersey REST框架通过UriComponent支持这一点。例子。

import org.glassfish.jersey.uri.UriComponent;

String uri = "https://google.com.ua/oauth/authorize?client_id=SS&response_type=code&scope=N_FULL&access_type=offline&redirect_uri=http://localhost/Callback";
MultivaluedMap<String, String> params = UriComponent.decodeQuery(URI.create(uri), true);
for (String key : params.keySet()) {
  System.out.println(key + ": " + params.getFirst(key));
}
Jasper de Vries 提问于2021-06-21
#25楼
得票数 0

如果只是想让URL后面的参数来自一个字符串。那么下面的代码将工作。我只是假设简单的Url。我的意思是没有硬性的检查和解码。就像在我的一个测试案例中,我得到了Url,我知道我只需要参数的值。这个URL很简单。不需要编码解码。

String location = "https://google.com.ua/oauth/authorize?client_id=SS&response_type=code&scope=N_FULL&access_type=offline&redirect_uri=http://localhost/Callback";
String location1 = "https://stackoverflow.com?param1=value1&param2=value2&param3=value3";
String location2 = "https://stackoverflow.com?param1=value1&param2=&param3=value3&param3";
    
    Map<String, String> paramsMap = Stream.of(location)
        .filter(l -> l.indexOf("?") != -1)
        .map(l -> l.substring(l.indexOf("?") + 1, l.length()))
        .flatMap(q -> Pattern.compile("&").splitAsStream(q))
        .map(s -> s.split("="))
        .filter(a -> a.length == 2)
        .collect(Collectors.toMap(
            a -> a[0], 
            a -> a[1],
            (existing, replacement) -> existing + ", " + replacement,
            LinkedHashMap::new
        ));
    
    System.out.println(paramsMap);

谢谢你

Basit 提问于2021-09-08
#26楼
得票数 0

对我来说,这似乎是整洁的最佳方式。

static Map<String, String> decomposeQueryString(String query, Charset charset) {
    return Arrays.stream(query.split("&"))
        .map(pair -> pair.split("=", 2))
        .collect(Collectors.toMap(
            pair -> URLDecoder.decode(pair[0], charset),
            pair -> pair.length > 1 ? URLDecoder.decode(pair[1], charset) : null)
        );
}

前提是你的查询语法不允许有重复的参数。

Matthias Ronge 提问于2022-04-13
Matthias Ronge 修改于2022-04-20
#27楼
得票数 -1

org.keycloak.common.util.UriUtils

我不得不在一个Keycloak扩展中解析URI和查询参数,并发现这个实用类非常有用。

org.keycloak.common.util.UriUtils:
static MultivaluedHashMap<String,String> decodeQueryString(String queryString) 

还有一个有用的方法,可以删除一个查询参数。

static String   stripQueryParam(String url, String name)

为了解析URL,有一个org.keycloak.common.util.KeycloakUriBuilder。

KeycloakUriBuilder  uri(String uriTemplate)
String  getQuery()

和许多其他的好东西。

Frerk 提问于2022-06-01