
10月28日至29日,
Joker 2019在圣彼得堡举行-这是俄罗斯广大致力于Java开发的规模最大,最核心的会议。 该活动已第七次举行,并且一如既往地打破了出席人数的记录,这次活动吸引了2000多名专家。
传统上,同学是Joker的活动伙伴。 今年,在我们的展位上,人们可以尝试应对OK.RU首席工程师带来的著名的“无法解决”的任务。 正确回答问题的与会人员将获得奖品。
公平地说,我必须说,在完成任务的1,000张传单中,只有不到100张退回了,最好的是解决方案,满分为5分,满分为4.5分。
我们发布任务及其解决方案,以便您可以测试自己的力量。
1.英雄列举
一个鲜为人知的游戏的源代码揭示了这样的代码。 什么是
Group.of
的错误实现,以及如何解决?
enum Group { Few(1, 4), Several(5, 9), Pack(10, 19), Lots(20, 49), Swarm(50, Integer.MAX_VALUE); Group(int min, int max) { ... } public static Group of(int count) { for (Group group : Group.values()) { if (count >= group.min && count <= group.max) { return group; } } throw new IllegalArgumentException(); } }
解决方案如果您不谈论编码样式,那么此片段有一个客观的缺点-潜在的性能问题。 尽管线性搜索通常会成为瓶颈,但在这种情况下并非如此,因为该枚举只有五个元素。 真正会对性能产生负面影响的是调用
Group.values()
时过多的内存分配。 问题是每次枚举的
values()
方法都会返回该数组的新副本,而HotSpot尚无法对其进行优化。 一个简单的解决方案是使自己的
values()
数组副本并对其进行迭代:
private static final Group[] ALL_GROUPS = Group.values(); public static Group of(int count) { for (Group group : ALL_GROUPS) { .... }
2.梦想
Java 13已经发布,并且Nikolai仍然只理解流。 指出计算最大和最小流元素之间的差异的方法中的错误。
int getDiameter(Stream<Integer> stream) { int min = stream.min(Integer::compare).get(); int max = stream.max(Integer::compare).get(); return max - min; }
解决方案Java中的流通常是一次性的:调用第二个终端操作(在这种情况下为
max
)将失败:
java.lang.IllegalStateException: stream has already been operated upon or closed
另外,
min
和
max
返回
Optional
,对其进行
get()
操作将为空流抛出
NoSuchElementException
。 因此,在调用
get()
之前使用
isPresent get()
或使用其他
Optional
方法(
例如orElse ,
orElseThrow等
isPresent()
更为正确。
最后,两个
int
之间的差异不再适合
int
的事实将使逃避谨慎的开发人员,这值得将返回值的类型更改为
long
。
3.安全缓冲区
ByteBuffer
Java同步原语可以使通用的
ByteBuffer
上的
put
和
get
操作线程安全?
final ByteBuffer buf = ByteBuffer.allocate(SIZE); int get(int offset) { return buf.get(offset); } void put(int offset, int value) { buf.putInt(offset, value); }
如果您知道有很多线程,并且得到的运行次数比投入的次数多,请选择最有效的选择。
- 已同步
- 再入锁
- 重入ReadWriteLock
- 印花锁
- 信号量
- 用Java读写int总是原子的
解决方案ReentrantReadWriteLock
恳求读者和作家的
ReentrantReadWriteLock
,通常这将是一个有效的解决方案。 但是请注意,在这种情况下,获取和放置操作非常简单-竞争性放置权会干扰获取的可能性很小,此外,放置操作不太可能出现放置条件。 因此,您可以应用
StampedLock提供的
乐观锁定机制。
StampedLock
将比
ReentrantReadWriteLock
更有效率,因为在乐观的快速路径成功的情况下,共享变量根本不会更新,而
ReentrantReadWriteLock
最多只能执行一个
CAS 。
4.礼物
Ilya正在社交网络上展示礼物。 帮助他为不超过N个最新礼物的结构编写
add
方法。 如果礼物已经存在或早于N的其余部分,则不应添加该礼物。
interface Present { long getId(); Date getCreated(); } void add(Present p) {
解决方案TreeSet
或
PriorityQueue
自然适合作为数据结构,以便有效地添加礼物并删除最旧的内容而不比O的糟糕(日志N)。 所有的技巧都只在比较器中:仅将礼物与
getCreated()
进行比较是不够的,因为创建日期不必是唯一的。 因此,您需要先通过
getCreated()
进行比较,然后通过
getId()
。 这样的比较器将确保元素的唯一性和按日期排序。
TreeSet<Present> tree = new TreeSet<>( Comparator.comparing(Present::getCreated) .thenComparing(Present::getId));
这仍然是一个小问题:添加礼物时,请确保大小不超过N,并在必要时删除集合中第一个(最早的)元素。
void add(Present p) { if (tree.add(p) && tree.size() > N) { tree.pollFirst(); } }
5.你不会等待
为什么Julia永远不会等待程序结束?
var executor = Executors.newFixedThreadPool(4); for (File f : File.listRoots()) { executor.submit(() -> f.delete()); } executor.awaitTermination(2, TimeUnit.HOURS);