Odnoklassniki在Joker 2019上的解析



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 

另外, minmax返回Optional ,对其进行get()操作将为空流抛出NoSuchElementException 。 因此,在调用get()之前使用isPresent get()或使用其他Optional方法( 例如orElseorElseThrowisPresent()更为正确。

最后,两个int之间的差异不再适合int的事实将使逃避谨慎的开发人员,这值得将返回值的类型更改为long

3.安全缓冲区


ByteBuffer Java同步原语可以使通用的ByteBuffer上的putget操作线程安全?

 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) { // Implement me } 

解决方案
TreeSetPriorityQueue自然适合作为数据结构,以便有效地添加礼物并删除最旧的内容而不比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); 

解决方案
awaitTermination文档建议关闭请求应在执行之前。 很简单:Julia忘记了调用executor.shutdown()

Source: https://habr.com/ru/post/zh-CN474502/


All Articles