大家好, 这里是K字的研究.
这篇准备开一个新的标签:🏷 Java101
. 记录分享一些比较基础的代码,并对其进行解说. 准备尽量放Java标准库里的内容, 不会涉及第三方框架.
今天从字符串开始.
计字符串中的每种字符的个数
这种统计类的内容, 肯定是用hash比较好,弄个map,遍历一遍往里丢就行.
新手版
刚写Java的程序员,直接上手就写的话, 可能是这样的.
static Map<Character, Integer> countChar1(String s) {
Map<Character, Integer> map = new HashMap<>();
for (Character c : s.toCharArray()) {
Integer r = map.get(c);
if (r == null) r = 0;
map.put(c, ++r);
}
return map;
}
能用, 但是写了一阵子Java熟悉Map.getOrDefault接口的程序员,可能会写成这样.
Map.getOrDefault 版本
static Map<Character, Integer> countChar2(String s) {
Map<Character, Integer> map = new HashMap<>();
for (Character c : s.toCharArray()) {
int r = map.getOrDefault(c, 0);
map.put(c, ++r);
}
return map;
}
这个写法用了默认值接口,没有值时候就返回0.
但是,新派学了Java8 lambda语法的程序员,可能会这么写.
Map.compute版本
static Map<Character, Integer> countChar(String s) {
Map<Character, Integer> map = new HashMap<>();
for (Character c : s.toCharArray()) {
map.compute(c, (k, v) -> v == null ? 1 : ++v);
}
return map;
}
这个compute
函数的意思是, 根据c
去查找,然后把得到的结果v
和查找的k
一起送进一个BiFunction
计算,把结果再塞回map
.
Java8支持函数式编程, 并对函数做了个分类.
-
Function 有入参有返回的叫Function, 特点是有进有出 -
Consumer 有入参没返回的叫Consumer, 特点是只进不出 -
Supplier 没入参有返回的叫Supplier, 特点是只出不进 这个和 Callable
类似,只是那个是Java5引入的
还有一种不进不出的, 比较老了, 大家都熟悉: Runnable
这种涉及到参数的 根据入参数多少, 分为几个前缀:
-
Unary
`Unary. 这类表示1个 -
Bi
这个表示两个 这俩都是拉丁语来的前缀, 意思其实就是1,2
根据返回值类型, 又分为几类:
-
Predicate 这种表示,返回值是 boolean
-
Operator 这个表示入参出参是同一种类型
这些都是泛型的. 可以应用在Object
的所有子类上比如Integer
,Long
等都可以使用.
如果要对基础类型int
,long
进行操作的, Java提供了专门的,包含Int
,Long
等字的函数.
java.util.function
包下东西太多记不住没关系.这几个条件,排列组合一下,就可以覆盖大部分的场景了.比如说,
-
BiFunction 一看就知道, 有两个参数,有返回的函数. -
BinaryOperator 返回值和入参相同,且入参有2个的函数. -
IntToLongFunction 入参是 int
,返回是long
的函数. -
DoubleUnaryOperator 入参是 double
,返回还是double
的函数. …
基本都符合这个规律. 这就是这个Map.compute
入参BiFunction
的来历. 不过,其实还有别的写法可以写. 这不, 又来了一个学完lambda,还学了Stream
语法的程序员.
Stream版
s.chars().mapToObj(c -> (char) c)
.collect(Collectors.groupingBy(
Function.identity(),
Collectors.counting()))
这个版本就紧凑了些,而且有点难懂.
-
首先是 s.chars()
会产生一个IntStream
, -
然后是 mapToObj(c->(char)c)
, int强转char,然后通过mapToObj
发生装箱变成Character
. -
最后 collect
, 按照字符进行groupingBy
分组, 并计算出个数. -
Function.identity()
其实就是x->x
的快速写法, 属于UnaryOperator
了其实. -
Collectors.counting()
就是返回了一个计数CollectorImpl
实例.
这里面Collector
比较复杂, 回头专门写吧. 记得这几个常用的就够了.
小结
这道计算每种字符串数目, 用到的都是Java标准接口, 你会了吗? 我们开始下一题
计算字符串中的每种字符的个数(威力加强Unicode版)
(⊙o⊙)…, 不是下一题了吗? 怎么还是这个啊.
刚刚的版本,只在一般性的输入时候,是正确的.
-
一个字符可以用一个char来代表的,没问题. -
一个字符需要用两个char来代表,那就不好意思了.
什么时候一个字符需要用两个char
来代表? 那要从Unicode
早期说起.(有没有发现Uni
其实也是1的意思.)
早期的Unicode
制定者,较幼稚. 他们以为,英文,用7个8个bit
,就够表示了.我用16个bit
,最多能编码个字符.难道地球上的字符,6万多个还不够表示吗? 直到,他们遇到了中文,中文要是全放进去去, 16位版本的Unicode还真装不下. 这个残废版本的Unicode,被称为UTF16
. 后来他们学乖了, 改用32个bit来表示字符.新版本称为UTF32
.
很遗憾, Java诞生的时候还没有UTF32. 所以, Java实现的是UTF16版本. 然后, 这个就被兼容下来了.这里面的故事非常复杂, 展开1万字都写不完.所以我们就不提了,有兴趣的可以自己查.
我们现在用一个复杂的字符串s="🐶喜欢😍骨头🦴"
. 咱就用emoji吧, 比用别的复杂字符形象. 现在这个字符串, 按照我们的常识, 应该是7个字符.
String s = "🐶喜欢😍骨头🦴";
System.out.println(s.length());
System.out.println(countChar(s));
//10
//{欢=1, ?=1, 头=1, ?=1, 骨=1, 喜=1, ?=1, ?=2, ?=1}
尴尬, 好像不是.因为超出UTF16表示范围的字符, 会被拆成2个.
那怎么修改这个代码, 来让他可以把emoji识别成一个呢? 不用Character
,而是使用CodePoint
这个概念. 字符串的组成单位, 按照CodePoint
来考虑,就是对的了.一个emoji,也只是一个CodePoint
.
普通Java版本的统计
static Map<String, Integer> countChar4(String s) {
Map<String, Integer> map = new HashMap<>();
for (int i = 0; i < s.length(); i++) {
int codePoint = s.codePointAt(i);
//如果这个码点要用两个字节表示
if (Character.charCount(codePoint) == 2) {
i++;
}
//码点转成字符串
String c = String.valueOf(Character.toChars(codePoint));
map.compute(c, (k, v) -> v == null ? 1 : ++v);
}
return map;
}
Stream语法版本的统计
String s = "🐶喜欢😍骨头🦴";
//计算码点数量
System.out.println(s.codePoints().count());
System.out.println(s.codePoints()
// codePoint转字符串
.mapToObj(x -> String.valueOf(Character.toChars(x)))
.collect(Collectors.groupingBy(Function.identity(), Collectors.counting())));
//7
//{欢=1, 🐶=1, 头=1, 骨=1, 😍=1, 喜=1, 🦴=1}
小结
-
Java字符串使用UTF16编码时,当超过UTF16能表示的范围, 会变成两个char. -
String长度更符合直觉的统计方式是按照CodePoint统计. -
一个大号CodePoint转成一个字符的写法是String.valueOf(Character.toChars(x) -
判断一个CodePoint是否超出UTF16的方式是Character.charCount
后话
其实Unicode我也不太熟悉, 刚开始写Java基础这个方向, 速度和节奏把控也不太好. 今天本来准备写几道题的,结果写来写去, 就这么一个.
最后简单介绍一下UTF8, 他和上面的编码是一个体系的.不过那两个是定长编码,一个字符对应字节是固定的. UTF8是变长编码,前256个和ASCII一样,是一个自己,后面是2个, 最不常用的部分,一个字符要用4个字节来编码.
MySQL一开始实现UTF8时候,实现者不知道UTF8有可能会用到4个字节,实现出来的UTF8其实是错的.后来出了个补充版本叫UTF8mb4. 这个也就是现在常用的版本.
最近陷入瓶颈期, 不知道写什么研究什么比较好了. 风格和格式也还在调整, 比较混乱,请多多担待. 有什么问题都可以留言告诉我.
原文始发于微信公众号(K字的研究):如何正确统计字符串里每种字符的个数-你不一定知道的Java基础知识
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/24900.html