在 Android 上解析查询字符串
Posted
技术标签:
【中文标题】在 Android 上解析查询字符串【英文标题】:Parsing query strings on Android 【发布时间】:2010-12-12 15:25:26 【问题描述】:Java EE 有 ServletRequest.getParameterValues()
。
在非 EE 平台上,URL.getQuery()
只返回一个字符串。
在 Java EE 上 not 时正确解析 URL 中的查询字符串的正常方法是什么?
尝试制作自己的解析器的答案很受欢迎。这是一个非常有趣且令人兴奋的微编码项目,但是我不能说这是个好主意。
下面的代码 sn-ps 通常有缺陷或损坏。打破它们对读者来说是一个有趣的练习。 以及攻击使用它们的网站的黑客。
解析查询字符串是一个定义明确的问题,但阅读规范并理解其中的细微差别并非易事。最好还是让一些平台库的程序员来替你做苦力,为你做修复!
【问题讨论】:
您能否发布一个示例 URL、您从getQuery()
获得的内容以及您想要作为输出获得的内容?
您想从 servlet 还是 JSP 页面执行此操作?在我回答之前我需要澄清一下。
我正在尝试在 android 上执行此操作,但所有平台上的所有答案都将是有用的答案,可能会提供指导(也给可能遇到此问题的其他人)所以不要退缩!
你还需要解析POST参数吗?
即使你在 J2EE 上(或者在 SE 上,像我一样通过 OSGi 添加了选定的 EE 包),这个问题也可能有意义。在我的例子中,查询字符串 / url 编码的 POST 正文由系统的一部分处理,该部分故意与 ServletRequest
之类的东西无关。
【参考方案1】:
对于 servlet 或 JSP 页面,您可以使用 request.getParameter("paramname") 获取查询字符串键/值对
String name = request.getParameter("name");
还有其他方法可以做到这一点,但我在我创建的所有 servlet 和 jsp 页面中都是这样做的。
【讨论】:
HttpServletRequest 是他没有的 J2EE 的一部分。同样使用 getParamter() 也不是真正的解析。 请花时间阅读我要求澄清他的问题的评论。这个答案是为了回应他对他所说的评论的回答,“我正在尝试在 Android 上执行此操作,但所有平台上的所有答案都是有用的答案,可能会提供指导(也适用于可能遇到此问题的其他人)问题)所以不要退缩!”我根据该评论回答了他的问题。如果您没有任何有用的东西要添加,请不要添加任何东西 不要太沮丧。 IMO,添加“这不能回答问题”很有用。 Android 与否无关紧要,问题是如何解析包含 URL 的字符串并从中获取 URL 参数。您在此处移植的是 Servlet API 的一部分,其中 Servlet 容器为您解析来自 HTTP 请求的传入参数。这无关紧要,因为问题是关于解析包含 URL 的字符串,而不是 HTTP 请求,而不是在 Servlet 容器内。【参考方案2】:您说“Java”但“不是 Java EE”。您的意思是您使用的是 JSP 和/或 servlet,但不是完整的 Java EE 堆栈?如果是这种情况,那么您应该仍然可以使用 request.getParameter()。
如果您的意思是您正在编写 Java,但您没有编写 JSP 或 servlet,或者您只是使用 Java 作为您的参考点,但您在其他一些没有内置参数解析的平台上。 ..哇,这听起来像是一个不太可能的问题,但如果是这样,原则是:
xparm=0
word=""
loop
get next char
if no char
exit loop
if char=='='
param_name[xparm]=word
word=""
else if char=='&'
param_value[xparm]=word
word=""
xparm=xparm+1
else if char=='%'
read next two chars
word=word+interpret the chars as hex digits to make a byte
else
word=word+char
(我可以编写 Java 代码,但这毫无意义,因为如果您有 Java 可用,则可以使用 request.getParameters。)
【讨论】:
在对十六进制数字进行 url 解码时注意字符编码。 它是 Android,因此是 Java,但不是 J2EE。 我忘了说:你还需要检查“+”,它应该被翻译成空格。查询字符串中的嵌入空格是非法的。【参考方案3】:解析查询字符串比看起来要复杂一些,具体取决于您想要的宽容程度。
首先,查询字符串是 ascii 字节。您一次读取一个字节并将它们转换为字符。如果字符是 ?或 & 然后它表示参数名称的开始。如果字符是 =,那么它表示参数值的开始。如果字符是 %,那么它表示编码字节的开始。这就是棘手的地方。
当您读取 % 字符时,您必须读取接下来的两个字节并将它们解释为十六进制数字。这意味着接下来的两个字节将是 0-9、a-f 或 A-F。将这两个十六进制数字粘合在一起以获得您的字节值。但请记住,字节不是字符。您必须知道使用什么编码来对字符进行编码。字符 é 在 UTF-8 中的编码方式与在 ISO-8859-1 中的不同。一般来说,不可能知道给定字符集使用了什么编码。我总是使用 UTF-8,因为我的网站配置为始终使用 UTF-8 提供所有服务,但实际上您无法确定。一些用户代理会告诉你请求中的字符编码;如果您有完整的 HTTP 请求,您可以尝试阅读。如果你只有一个单独的 url,祝你好运。
无论如何,假设您使用 UTF-8 或其他一些多字节字符编码,现在您已经解码了一个编码字节,您必须将其放在一边,直到您捕获下一个字节。您需要将所有编码字节放在一起,因为您无法一次正确地对一个字节进行 url 解码。把所有的字节放在一边,然后一次解码它们以重建你的角色。
另外,如果您想宽容并考虑破坏 url 的用户代理,它会变得更有趣。例如,一些网络邮件客户端对事物进行双重编码。或者将 ?&= 字符加倍(例如:http://yoursite.com/blah??p1==v1&&p2==v2
)。如果您想尝试优雅地处理此问题,则需要向解析器添加更多逻辑。
【讨论】:
这并没有解释如何解析或检索查询字符串参数值 对,但是有点麻烦。为此,我们已经有了 URLDecoder。 @ChadNC:第三句告诉你如何解析:一次读入一个字节并转换为字符。第四句警告您注意特殊字符。等等,也许你没有看答案? @BalusC:URLDecoder 可以工作,但如果您想更宽容地接受您接受的 URL,它会出现一些故障模式。 同意@Mr.ShinyAndNew 解析查询参数并不容易。我支持 FIQL,这真的很痛苦。例如:yoursite.com/blah??p1==v1&&p2==v2,p2==v3;p2==v4【参考方案4】:我认为 JRE 中没有。您可以在 Apache HttpClient 等其他包中找到类似的功能。如果你不使用任何其他包,你只需要编写你自己的。这并不难。这是我使用的,
public class QueryString
private Map<String, List<String>> parameters;
public QueryString(String qs)
parameters = new TreeMap<String, List<String>>();
// Parse query string
String pairs[] = qs.split("&");
for (String pair : pairs)
String name;
String value;
int pos = pair.indexOf('=');
// for "n=", the value is "", for "n", the value is null
if (pos == -1)
name = pair;
value = null;
else
try
name = URLDecoder.decode(pair.substring(0, pos), "UTF-8");
value = URLDecoder.decode(pair.substring(pos+1, pair.length()), "UTF-8");
catch (UnsupportedEncodingException e)
// Not really possible, throw unchecked
throw new IllegalStateException("No UTF-8");
List<String> list = parameters.get(name);
if (list == null)
list = new ArrayList<String>();
parameters.put(name, list);
list.add(value);
public String getParameter(String name)
List<String> values = parameters.get(name);
if (values == null)
return null;
if (values.size() == 0)
return "";
return values.get(0);
public String[] getParameterValues(String name)
List<String> values = parameters.get(name);
if (values == null)
return null;
return (String[])values.toArray(new String[values.size()]);
public Enumeration<String> getParameterNames()
return Collections.enumeration(parameters.keySet());
public Map<String, String[]> getParameterMap()
Map<String, String[]> map = new TreeMap<String, String[]>();
for (Map.Entry<String, List<String>> entry : parameters.entrySet())
List<String> list = entry.getValue();
String[] values;
if (list == null)
values = null;
else
values = (String[]) list.toArray(new String[list.size()]);
map.put(entry.getKey(), values);
return map;
【讨论】:
apache 类是怎么回事? 可以使用 parse() 方法:hc.apache.org/httpcomponents-client/httpclient/apidocs/org/… 请将 apache commons 链接放在它自己的答案中,以便我投票。【参考方案5】:在 Android 上,Apache 库提供查询解析器:
http://developer.android.com/reference/org/apache/http/client/utils/URLEncodedUtils.html 和 http://hc.apache.org/httpcomponents-client-ga/httpclient/apidocs/org/apache/http/client/utils/URLEncodedUtils.html
【讨论】:
这在 apache http 客户端库中可用,不仅在 Android 上。顺便说一句,到 apache 的链接已更改。最新的是:hc.apache.org/httpcomponents-client-ga/httpclient/apidocs/org/… 烦人的URLEncodedUtils.parse()
返回一个List
,然后您必须循环查找特定键的值。如果它返回一个 Map
就像在 BalusC 的答案中那样会更好。
@Hanno Fietz 你的意思是你信任这些替代品?我知道他们是越野车。我知道指出我看到的错误只会鼓励人们采用“修复”版本,而不是自己寻找我忽略的错误。
@Will - 好吧,我永远不会只是信任复制和粘贴我从任何网站获得的 sn-ps,而且没有人应该这样做。但在这里,这些 sn-ps 得到了很好的审查和评论,因此 are
实际上真的很有帮助。简单地看到一些关于代码可能有问题的建议已经对我自己思考有很大帮助。请注意,我并不是说“自己动手做会更好”,而是说在我自己的代码中为明智的决定提供好的材料真是太好了。
我想 parse 返回一个列表,以便它保持位置顺序并且更容易允许重复条目。【参考方案6】:
if (queryString != null)
final String[] arrParameters = queryString.split("&");
for (final String tempParameterString : arrParameters)
final String[] arrTempParameter = tempParameterString.split("=");
if (arrTempParameter.length >= 2)
final String parameterKey = arrTempParameter[0];
final String parameterValue = arrTempParameter[1];
//do something with the parameters
【讨论】:
【参考方案7】:在 Android 上,您可以使用 android.net.Uri 类的 Uri.parse 静态方法来完成繁重的工作。如果您正在使用 URI 和 Intents 做任何事情,那么无论如何您都会想要使用它。
【讨论】:
【参考方案8】:public static Map<String, List<String>> getUrlParameters(String url)
throws UnsupportedEncodingException
Map<String, List<String>> params = new HashMap<String, List<String>>();
String[] urlParts = url.split("\\?");
if (urlParts.length > 1)
String query = urlParts[1];
for (String param : query.split("&"))
String pair[] = param.split("=", 2);
String key = URLDecoder.decode(pair[0], "UTF-8");
String value = "";
if (pair.length > 1)
value = URLDecoder.decode(pair[1], "UTF-8");
List<String> values = params.get(key);
if (values == null)
values = new ArrayList<String>();
params.put(key, values);
values.add(value);
return params;
【讨论】:
JVM 注意:我已经使用 Java 集合在 Scala 中实现了一个等效的形式;这是 github 要点:gist.github.com/3504765 我建议将String pair[] = param.split("=");
更改为 String pair[] = param.split("=", 2);
以仅在第一次出现时拆分 key=value 对。我相信它允许在值中包含未编码的等号。
感谢@Dennie,添加了。【参考方案9】:
在安卓上:
import android.net.Uri;
[...]
Uri uri=Uri.parse(url_string);
uri.getQueryParameter("para1");
【讨论】:
注意这里使用的是 Uri 类而不是 URI 类(Uri 是 android.net 的一部分,而 URI 是 java.net 的一部分) 另请注意,在 Ice Cream Sandwich 之前,这无法将值中的 + 字符解析为空格字符。 @rpetrich 实际上,文档说该错误早于果冻豆,包括冰淇淋三明治。 ref【参考方案10】:public static Map <String, String> parseQueryString (final URL url)
throws UnsupportedEncodingException
final Map <String, String> qps = new TreeMap <String, String> ();
final StringTokenizer pairs = new StringTokenizer (url.getQuery (), "&");
while (pairs.hasMoreTokens ())
final String pair = pairs.nextToken ();
final StringTokenizer parts = new StringTokenizer (pair, "=");
final String name = URLDecoder.decode (parts.nextToken (), "ISO-8859-1");
final String value = URLDecoder.decode (parts.nextToken (), "ISO-8859-1");
qps.put (name, value);
return qps;
【讨论】:
【参考方案11】:如果您的类路径中有 jetty(服务器或客户端)库,您可以使用 jetty util 类(参见 javadoc),例如:
import org.eclipse.jetty.util.*;
URL url = new URL("www.example.com/index.php?foo=bar&bla=blub");
MultiMap<String> params = new MultiMap<String>();
UrlEncoded.decodeTo(url.getQuery(), params, "UTF-8");
assert params.getString("foo").equals("bar");
assert params.getString("bla").equals("blub");
【讨论】:
【参考方案12】:仅供参考,这是我最终得到的(基于 URLEncodedUtils,并返回一个地图)。
特点:
它接受url的查询字符串部分(你可以使用request.getQueryString()
)
一个空的查询字符串将产生一个空的Map
一个没有值的参数 (?test) 将被映射到一个空的List<String>
代码:
public static Map<String, List<String>> getParameterMapOfLists(String queryString)
Map<String, List<String>> mapOfLists = new HashMap<String, List<String>>();
if (queryString == null || queryString.length() == 0)
return mapOfLists;
List<NameValuePair> list = URLEncodedUtils.parse(URI.create("http://localhost/?" + queryString), "UTF-8");
for (NameValuePair pair : list)
List<String> values = mapOfLists.get(pair.getName());
if (values == null)
values = new ArrayList<String>();
mapOfLists.put(pair.getName(), values);
if (pair.getValue() != null)
values.add(pair.getValue());
return mapOfLists;
兼容性帮助器(值存储在字符串数组中,就像在ServletRequest.getParameterMap() 中一样):
public static Map<String, String[]> getParameterMap(String queryString)
Map<String, List<String>> mapOfLists = getParameterMapOfLists(queryString);
Map<String, String[]> mapOfArrays = new HashMap<String, String[]>();
for (String key : mapOfLists.keySet())
mapOfArrays.put(key, mapOfLists.get(key).toArray(new String[] ));
return mapOfArrays;
【讨论】:
【参考方案13】:Apache AXIS2 有一个独立的 QueryStringParser.java 实现。如果您不使用 Axis2,只需从这里下载源代码和测试用例 -
http://svn.apache.org/repos/asf/axis/axis2/java/core/trunk/modules/kernel/src/org/apache/axis2/transport/http/util/QueryStringParser.java
http://svn.apache.org/repos/asf/axis/axis2/java/core/trunk/modules/kernel/test/org/apache/axis2/transport/http/util/QueryStringParserTest.java
【讨论】:
【参考方案14】:这对我有用.. 我不知道为什么每个人都在地图之后,列表> 我只需要一个简单的名称值映射。
为了简单起见,我使用了 URI.getQuery(); 中的构建。
public static Map<String, String> getUrlParameters(URI uri)
throws UnsupportedEncodingException
Map<String, String> params = new HashMap<String, String>();
for (String param : uri.getQuery().split("&"))
String pair[] = param.split("=");
String key = URLDecoder.decode(pair[0], "UTF-8");
String value = "";
if (pair.length > 1)
value = URLDecoder.decode(pair[1], "UTF-8");
params.put(new String(key), new String(value));
return params;
【讨论】:
多选表单怎么样?在合法的查询字符串(和 POST 表单正文)中重复键是完全正常的。还有其他未涵盖的缺陷和极端情况;其中许多已在对其他方法的评论中提到。根据我在问题中的rant,我会避免指出它们,以免您修复它而不是使用质量库;)【参考方案15】:Guava 的 Multimap 更适合这种情况。这是一个简短的干净版本:
Multimap<String, String> getUrlParameters(String url)
try
Multimap<String, String> ret = ArrayListMultimap.create();
for (NameValuePair param : URLEncodedUtils.parse(new URI(url), "UTF-8"))
ret.put(param.getName(), param.getValue());
return ret;
catch (URISyntaxException e)
throw new RuntimeException(e);
【讨论】:
【参考方案16】:使用 Apache HttpComponents 并将其与一些集合代码连接起来以按值访问参数:http://www.joelgerard.com/2012/09/14/parsing-query-strings-in-java-and-accessing-values-by-key/
【讨论】:
【参考方案17】:在 Android 上,我尝试使用@diyism 答案,但遇到了@rpetrich 提出的空格字符问题,例如:
我填写了一个表格,其中 username = "us+us"
和 password = "pw pw"
导致 URL 字符串如下所示:
http://somewhere?username=us%2Bus&password=pw+pw
但是,@diyism 代码返回 "us+us"
和 "pw+pw"
,即它不检测空格字符。如果 URL 被 %20
重写,则空格字符被识别:
http://somewhere?username=us%2Bus&password=pw%20pw
这导致以下修复:
Uri uri = Uri.parse(url_string.replace("+", "%20"));
uri.getQueryParameter("para1");
【讨论】:
replace(" ", "%20")
这感觉不对。但是对我有用吗:D
正确的语法应该是 "some string".replaceAll("[+]", "%20");【参考方案18】:
使用番石榴:
Multimap<String,String> parseQueryString(String queryString, String encoding)
LinkedListMultimap<String, String> result = LinkedListMultimap.create();
for(String entry : Splitter.on("&").omitEmptyStrings().split(queryString))
String pair [] = entry.split("=", 2);
try
result.put(URLDecoder.decode(pair[0], encoding), pair.length == 2 ? URLDecoder.decode(pair[1], encoding) : null);
catch (UnsupportedEncodingException e)
throw new RuntimeException(e);
return result;
【讨论】:
【参考方案19】:如果您使用的是 Spring 3.1 或更高版本(哎呀,希望支持更进一步),您可以使用 UriComponents
和 UriComponentsBuilder
:
UriComponents components = UriComponentsBuilder.fromUri(uri).build();
List<String> myParam = components.getQueryParams().get("myParam");
components.getQueryParams()
返回一个MultiValueMap<String, String>
Here's some more documentation.
【讨论】:
这是我正在寻找的东西。我的问题是如何获得uri?我坚持维护代码,我无法改变太多,而且我们没有使用 HttpServlet。而只是使用注释和 Spring (@Get, @Produces(mediaType) 和 @Path("/dataAsJSON/datafield/datafield)) 只需要知道如何获取查询字符串,以便我可以解析它,如图所示这个例子。【参考方案20】:此方法获取 uri 并返回 par 名称和 par 值的映射
public static Map<String, String> getQueryMap(String uri)
String queryParms[] = uri.split("\\?");
Map<String, String> map = new HashMap<>();//
if (queryParms == null || queryParms.length == 0) return map;
String[] params = queryParms[1].split("&");
for (String param : params)
String name = param.split("=")[0];
String value = param.split("=")[1];
map.put(name, value);
return map;
【讨论】:
根据我上面的咆哮,这可以很容易地崩溃。不用费心修复,只需使用专业的实用程序库即可。【参考方案21】:自从 Android M 以来,事情变得更加复杂。 android.net.URI.getQueryParameter()
的答案有一个错误,它会破坏 JellyBean 之前的空格。
Apache URLEncodedUtils.parse()
工作,但是是 deprecated in L 和 removed in M。
所以现在最好的答案是UrlQuerySanitizer
。这从 API 级别 1 就已经存在并且仍然存在。它还让您思考如何处理特殊字符或重复值等棘手的问题。
最简单的代码是
UrlQuerySanitizer.ValueSanitizer sanitizer = UrlQuerySanitizer.getAllButNullLegal();
// remember to decide if you want the first or last parameter with the same name
// If you want the first call setPreferFirstRepeatedParameter(true);
sanitizer.parseUrl(url);
String value = sanitizer.getValue("paramName");
如果您对默认的解析行为感到满意,您可以这样做:
new UrlQuerySanitizer(url).getValue("paramName")
但您应该确保您了解默认的解析行为是什么,因为它可能不是您想要的。
【讨论】:
不工作。 sdk-23 中的UrlQuerySanitizer
只有一种方法sanitize()
这会将特殊字符和表情符号解码为_
。我不得不选择***.com/a/35638979/1155282
有这个的spring框架等效库吗?【参考方案22】:
我有办法做到这一点:
1):
public static String getQueryString(String url, String tag)
String[] params = url.split("&");
Map<String, String> map = new HashMap<String, String>();
for (String param : params)
String name = param.split("=")[0];
String value = param.split("=")[1];
map.put(name, value);
Set<String> keys = map.keySet();
for (String key : keys)
if(key.equals(tag))
return map.get(key);
System.out.println("Name=" + key);
System.out.println("Value=" + map.get(key));
return "";
2) 以及使用Uri 类的最简单方法:
public static String getQueryString(String url, String tag)
try
Uri uri=Uri.parse(url);
return uri.getQueryParameter(tag);
catch(Exception e)
Log.e(TAG,"getQueryString() " + e.getMessage());
return "";
这是如何使用两种方法之一的示例:
String url = "http://www.jorgesys.com/advertisements/publicidadmobile.htm?position=x46&site=reform&awidth=800&aheight=120";
String tagValue = getQueryString(url,"awidth");
tagValue 的值为800
【讨论】:
【参考方案23】:在 Android 上很简单,如下代码:
UrlQuerySanitizer sanitzer = new UrlQuerySanitizer(url);
String value = sanitzer.getValue("your_get_parameter");
此外,如果您不想注册每个预期的查询键使用:
sanitzer.setAllowUnregisteredParamaters(true)
调用前:
sanitzer.parseUrl(yourUrl)
【讨论】:
【参考方案24】:在这里回答是因为这是一个热门话题。这是 Kotlin 中使用推荐的 UrlQuerySanitizer
api 的干净解决方案。 See the official documentation。我添加了一个字符串生成器来连接和显示参数。
var myURL: String? = null
// if the url is sent from a different activity where you set it to a value
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")
【讨论】:
【参考方案25】:原回复here
在 Android 上,包 android.net 中有 Uri 类。请注意,Uri 是 android.net 的一部分,而 URI 是 java.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
【讨论】:
这应该是一个可接受的答案,因为它也显示表情符号。如果需要,这也可以与这个答案一起使用:org.apache.commons.text.StringEscapeUtils.escapeJava & org.apache.commons.text.StringEscapeUtils.unescapeJava以上是关于在 Android 上解析查询字符串的主要内容,如果未能解决你的问题,请参考以下文章