java中都有哪些数据结构

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了java中都有哪些数据结构相关的知识,希望对你有一定的参考价值。

数据结构:
①数组 (Array)
在程序设计中,为了处理方便, 把具有相同类型的若干变量按有序的形式组织起来。这些按序排列的同类数
据元素的集合称为数组。在C语言中, 数组属于构造数据类型。一个数组可以分解为多个数组元素,这些数组
元素可以是基本数据类型或是构造类型。因此按数组元素的类型不同,数组又可分为数值数组、字符数组、指
针数组、结构数组等各种类别。
②栈 (Stack)
栈是只能在某一端插入和删除的特殊线性表。它按照先进后出的原则存储数据,先进入的数据被压入栈底,最后
的数据在栈顶,需要读数据的时候从栈顶开始弹出数据(最后一个数据被第一个读出来)。
③队列 (Queue)
一种特殊的线性表,它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作。进行
插入操作的端称为队尾,进行删除操作的端称为队头。队列中没有元素时,称为空队列。
④链表 (Linked List)
一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。
链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。每个结点包括两个部分:
一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。
⑤树 (Tree)
树是包含n(n>0)个结点的有穷集合K,且在K中定义了一个关系N,N满足 以下条件:
(1)有且仅有一个结点 k0,他对于关系N来说没有前驱,称K0为树的根结点。简称为根(root)
(2)除K0外,k中的每个结点,对于关系N来说有且仅有一个前驱。
(3)K中各结点,对关系N来说可以有m个后继(m>=0)。
⑥堆 (Heap)
在计算机科学中,堆是一种特殊的树形数据结构,每个结点都有一个值。通常我们所说的堆的数据结构,是指
二叉堆。堆的特点是根结点的值最小(或最大),且根结点的两个子树也是一个堆。
⑦图 (Graph)
图是由结点的有穷集合V和边的集合E组成。其中,为了与树形结构加以区别,在图结构中常常将结点称为顶点,
边是顶点的有序偶对,若两个顶点之间存在一条边,就表示这两个顶点具有相邻关系。
⑧散列表 (Hash)
若结构中存在关键字和K相等的记录,则必定在f(K)的存储位置上。由此,不需比较便可直接取得所查记录。称
这个对应关系f为散列函数(Hash function),按这个思想建立的表为散列表。
差不多我就知道这些了~
参考技术A 你好

标准 Kotlin 库中都有哪些 Java 8 Stream.collect 等效项?

【中文标题】标准 Kotlin 库中都有哪些 Java 8 Stream.collect 等效项?【英文标题】:What Java 8 Stream.collect equivalents are available in the standard Kotlin library?标准 Kotlin 库中有哪些 Java 8 Stream.collect 等效项? 【发布时间】:2016-04-11 01:58:23 【问题描述】:

在 Java 8 中,Stream.collect 允许对集合进行聚合。在 Kotlin 中,这并不以相同的方式存在,除了可能作为 stdlib 中的扩展函数集合之外。但目前尚不清楚不同用例的等价物是什么。

例如,top of the JavaDoc for Collectors 是为 Java 8 编写的示例,当将它们移植到 Kolin 时,您不能在不同的 JDK 版本上使用 Java 8 类,因此它们可能应该以不同的方式编写。

就显示 Kotlin 集合示例的在线资源而言,它们通常是微不足道的,并不能真正与相同的用例进行比较。什么是真正符合 Java 8 Stream.collect 记录的案例的好例子?列表如下:

将名称累积到列表中 将名称累积到 TreeSet 中 将元素转换为字符串并将它们连接起来,用逗号分隔 计算员工工资总和 按部门分组员工 按部门计算工资总和 将学生分为合格和不合格

上面链接的 JavaDoc 中有详细信息。

注意: 此问题由作者 (Self-Answered Questions) 特意编写和回答,以便提供对 Kotlin 常见问题的惯用回答在所以。还要澄清一些为 Kotlin alpha 编写的非常古老的答案,这些答案对于当今的 Kotlin 并不准确。

【问题讨论】:

如果您别无选择,只能使用 collect(Collectors.toList()) 或类似的,您可能会遇到此问题:***.com/a/35722167/3679676(该问题,有解决方法) 【参考方案1】:

在 Kotlin 标准库中有用于平均、计数、不同、过滤、查找、分组、连接、映射、最小值、最大值、分区、切片、排序、求和、到/从数组、到/从列表、到/from maps、union、co-iteration、所有功能范式等等。因此,您可以使用它们来创建小的 1-liner,而无需使用 Java 8 更复杂的语法。

我认为内置 Java 8 Collectors 类中唯一缺少的就是摘要(但在 another answer to this question 中是一个简单的解决方案)

两者都缺少按计数进行批处理,这在 another Stack Overflow answer 中可以看到,并且也有一个简单的答案。另一个有趣的案例也是来自 Stack Overflow:Idiomatic way to spilt sequence into three lists using Kotlin。如果您想为其他目的创建类似Stream.collect 的内容,请参阅Custom Stream.collect in Kotlin

EDIT 11.08.2017: 在 kotlin 1.2 M2 中添加了分块/窗口收集操作,请参阅 https://blog.jetbrains.com/kotlin/2017/08/kotlin-1-2-m2-is-out/


在创建可能已经存在的新功能之前,将API Reference for kotlin.collections 作为一个整体进行探索总是好的。

以下是一些从 Java 8 Stream.collect 示例到 Kotlin 中等效的转换:

将名称累积到列表中

// Java:  
List<String> list = people.stream().map(Person::getName).collect(Collectors.toList());
// Kotlin:
val list = people.map  it.name   // toList() not needed

将元素转换为字符串并将它们连接起来,用逗号分隔

// Java:
String joined = things.stream()
                       .map(Object::toString)
                       .collect(Collectors.joining(", "));
// Kotlin:
val joined = things.joinToString(", ")

计算员工工资总和

// Java:
int total = employees.stream()
                      .collect(Collectors.summingInt(Employee::getSalary)));
// Kotlin:
val total = employees.sumBy  it.salary 

按部门分组员工

// Java:
Map<Department, List<Employee>> byDept
     = employees.stream()
                .collect(Collectors.groupingBy(Employee::getDepartment));
// Kotlin:
val byDept = employees.groupBy  it.department 

按部门计算工资总和

// Java:
Map<Department, Integer> totalByDept
     = employees.stream()
                .collect(Collectors.groupingBy(Employee::getDepartment,
                     Collectors.summingInt(Employee::getSalary)));
// Kotlin:
val totalByDept = employees.groupBy  it.dept .mapValues  it.value.sumBy  it.salary 

将学生分为合格和不合格

// Java:
Map<Boolean, List<Student>> passingFailing =
     students.stream()
             .collect(Collectors.partitioningBy(s -> s.getGrade() >= PASS_THRESHOLD));
// Kotlin:
val passingFailing = students.partition  it.grade >= PASS_THRESHOLD 

男性成员姓名

// Java:
List<String> namesOfMaleMembers = roster
    .stream()
    .filter(p -> p.getGender() == Person.Sex.MALE)
    .map(p -> p.getName())
    .collect(Collectors.toList());
// Kotlin:
val namesOfMaleMembers = roster.filter  it.gender == Person.Sex.MALE .map  it.name 

按性别划分的名册中成员的组名

// Java:
Map<Person.Sex, List<String>> namesByGender =
      roster.stream().collect(
        Collectors.groupingBy(
            Person::getGender,                      
            Collectors.mapping(
                Person::getName,
                Collectors.toList())));
// Kotlin:
val namesByGender = roster.groupBy  it.gender .mapValues  it.value.map  it.name     

将一个列表过滤到另一个列表

// Java:
List<String> filtered = items.stream()
    .filter( item -> item.startsWith("o") )
    .collect(Collectors.toList());
// Kotlin:
val filtered = items.filter  it.startsWith('o')  

寻找最短的字符串列表

// Java:
String shortest = items.stream()
    .min(Comparator.comparing(item -> item.length()))
    .get();
// Kotlin:
val shortest = items.minBy  it.length 

应用过滤器后计算列表中的项目

// Java:
long count = items.stream().filter( item -> item.startsWith("t")).count();
// Kotlin:
val count = items.filter  it.startsWith('t') .size
// but better to not filter, but count with a predicate
val count = items.count  it.startsWith('t') 

然后继续……在所有情况下,都不需要特殊的折叠、减少或其他功能来模仿Stream.collect。如果你还有更多用例,添加到 cmets 中,我们可以看到!

关于懒惰

如果你想延迟处理一个链,你可以在链之前使用asSequence()转换成Sequence。在函数链的末尾,您通常也会以 Sequence 结束。然后你可以使用toList()toSet()toMap()或其他一些函数来实现最后的Sequence

// switch to and from lazy
val someList = items.asSequence().filter  ... .take(10).map  ... .toList()

// switch to lazy, but sorted() brings us out again at the end
val someList = items.asSequence().filter  ... .take(10).map  ... .sorted()

为什么没有类型?!?

您会注意到 Kotlin 示例没有指定类型。这是因为 Kotlin 具有完整的类型推断,并且在编译时是完全类型安全的。比 Java 更重要,因为它还具有可为空的类型,并且可以帮助防止可怕的 NPE。所以这在 Kotlin 中:

val someList = people.filter  it.age <= 30 .map  it.name 

等同于:

val someList: List<String> = people.filter  it.age <= 30 .map  it.name 

因为 Kotlin 知道 people 是什么,并且 people.ageInt,因此过滤器表达式只允许与 Int 进行比较,而 people.nameString,因此 map 步骤产生一个List&lt;String&gt;(只读List of String)。

现在,如果 people 可能是 null,那么就像 List&lt;People&gt;? 一样:

val someList = people?.filter  it.age <= 30 ?.map  it.name 

返回需要进行空值检查的List&lt;String&gt;?或使用其他 Kotlin 运算符之一作为可空值,请参阅此 Kotlin idiomatic way to deal with nullable values 和 Idiomatic way of handling nullable or empty list in Kotlin

另见:

extension functions for Iterable 的 API 参考 extension functions for Array 的 API 参考 extension functions for List 的 API 参考 extension functions to Map 的 API 参考

【讨论】:

Kotlin 中是否有与 Java8 的 parallelStream() 等价的功能? 关于不可变集合和 Kotlin 的答案与 @arnab 在这里并行的答案相同,其他库存在,使用它们:***.com/a/34476880/3679676 @arnab 您可能想查看今年早些时候提供的 Kotlin 对 Java 7/8 功能(特别是 kotlinx-support-jdk8)的支持:discuss.kotlinlang.org/t/jdk7-8-features-in-kotlin-1-0/1625 在一个语句中使用 3 个不同的“it”引用真的很习惯吗? 这是一个偏好,在上面的示例中,我将它们保持简短,并且仅在必要时为参数提供本地名称。【参考方案2】:

对于其他示例,这里是从 Java 8 Stream Tutorial 转换为 Kotlin 的所有示例。每个示例的标题,均来自源文章:

流的工作原理

// Java:
List<String> myList = Arrays.asList("a1", "a2", "b1", "c2", "c1");

myList.stream()
      .filter(s -> s.startsWith("c"))
      .map(String::toUpperCase)
     .sorted()
     .forEach(System.out::println);

// C1
// C2
// Kotlin:
val list = listOf("a1", "a2", "b1", "c2", "c1")
list.filter  it.startsWith('c') .map (String::toUpperCase).sorted()
        .forEach (::println)

不同种类的流#1

// Java:
Arrays.asList("a1", "a2", "a3")
    .stream()
    .findFirst()
    .ifPresent(System.out::println);    
// Kotlin:
listOf("a1", "a2", "a3").firstOrNull()?.apply(::println)

或者,在 String 上创建一个名为 ifPresent 的扩展函数:

// Kotlin:
inline fun String?.ifPresent(thenDo: (String)->Unit) = this?.apply  thenDo(this) 

// now use the new extension function:
listOf("a1", "a2", "a3").firstOrNull().ifPresent(::println)

另见:apply() function

另见:Extension Functions

另请参阅:?. Safe Call operator,以及一般可空性:In Kotlin, what is the idiomatic way to deal with nullable values, referencing or converting them

不同种类的流#2

// Java:
Stream.of("a1", "a2", "a3")
    .findFirst()
    .ifPresent(System.out::println);    
// Kotlin:
sequenceOf("a1", "a2", "a3").firstOrNull()?.apply(::println)

不同种类的流#3

// Java:
IntStream.range(1, 4).forEach(System.out::println);
// Kotlin:  (inclusive range)
(1..3).forEach(::println)

不同种类的流#4

// Java:
Arrays.stream(new int[] 1, 2, 3)
    .map(n -> 2 * n + 1)
    .average()
    .ifPresent(System.out::println); // 5.0    
// Kotlin:
arrayOf(1,2,3).map  2 * it + 1.average().apply(::println)

不同种类的流#5

// Java:
Stream.of("a1", "a2", "a3")
    .map(s -> s.substring(1))
    .mapToInt(Integer::parseInt)
    .max()
    .ifPresent(System.out::println);  // 3
// Kotlin:
sequenceOf("a1", "a2", "a3")
    .map  it.substring(1) 
    .map(String::toInt)
    .max().apply(::println)

不同种类的流#6

// Java:
IntStream.range(1, 4)
    .mapToObj(i -> "a" + i)
    .forEach(System.out::println);

// a1
// a2
// a3    
// Kotlin:  (inclusive range)
(1..3).map  "a$it" .forEach(::println)

不同种类的流#7

// Java:
Stream.of(1.0, 2.0, 3.0)
    .mapToInt(Double::intValue)
    .mapToObj(i -> "a" + i)
    .forEach(System.out::println);

// a1
// a2
// a3
// Kotlin:
sequenceOf(1.0, 2.0, 3.0).map(Double::toInt).map  "a$it" .forEach(::println)

为什么顺序很重要

Java 8 Stream 教程的这一部分与 Kotlin 和 Java 相同。

重用流

在 Kotlin 中,是否可以多次消费取决于集合的类型。 Sequence 每次都会生成一个新的迭代器,除非它断言“仅使用一次”,否则它可以在每次对其执行操作时重置为开始。因此,虽然以下在 Java 8 流中失败,但在 Kotlin 中有效:

// Java:
Stream<String> stream =
Stream.of("d2", "a2", "b1", "b3", "c").filter(s -> s.startsWith("b"));

stream.anyMatch(s -> true);    // ok
stream.noneMatch(s -> true);   // exception
// Kotlin:  
val stream = listOf("d2", "a2", "b1", "b3", "c").asSequence().filter  it.startsWith('b' ) 

stream.forEach(::println) // b1, b2

println("Any B $stream.any  it.startsWith('b') ") // Any B true
println("Any C $stream.any  it.startsWith('c') ") // Any C false

stream.forEach(::println) // b1, b2

在 Java 中获得相同的行为:

// Java:
Supplier<Stream<String>> streamSupplier =
    () -> Stream.of("d2", "a2", "b1", "b3", "c")
          .filter(s -> s.startsWith("a"));

streamSupplier.get().anyMatch(s -> true);   // ok
streamSupplier.get().noneMatch(s -> true);  // ok

因此,在 Kotlin 中,数据的提供者决定它是否可以重置并提供新的迭代器。但是,如果您想有意将Sequence 限制为一次迭代,您可以使用constrainOnce() 函数Sequence,如下所示:

val stream = listOf("d2", "a2", "b1", "b3", "c").asSequence().filter  it.startsWith('b' ) 
        .constrainOnce()

stream.forEach(::println) // b1, b2
stream.forEach(::println) // Error:java.lang.IllegalStateException: This sequence can be consumed only once. 

高级操作

收集示例 #5(是的,我跳过了其他答案中的示例)

// Java:
String phrase = persons
        .stream()
        .filter(p -> p.age >= 18)
        .map(p -> p.name)
        .collect(Collectors.joining(" and ", "In Germany ", " are of legal age."));

    System.out.println(phrase);
    // In Germany Max and Peter and Pamela are of legal age.    
// Kotlin:
val phrase = persons.filter  it.age >= 18 .map  it.name 
        .joinToString(" and ", "In Germany ", " are of legal age.")

println(phrase)
// In Germany Max and Peter and Pamela are of legal age.

附带说明一下,在 Kotlin 中,我们可以创建简单的 data classes 并实例化测试数据,如下所示:

// Kotlin:
// data class has equals, hashcode, toString, and copy methods automagically
data class Person(val name: String, val age: Int) 

val persons = listOf(Person("Tod", 5), Person("Max", 33), 
                     Person("Frank", 13), Person("Peter", 80),
                     Person("Pamela", 18))

收集示例#6

// Java:
Map<Integer, String> map = persons
        .stream()
        .collect(Collectors.toMap(
                p -> p.age,
                p -> p.name,
                (name1, name2) -> name1 + ";" + name2));

System.out.println(map);
// 18=Max, 23=Peter;Pamela, 12=David    

好的,这里有一个更有趣的 Kotlin 案例。首先是探索从集合/序列创建Map 的变体的错误答案:

// Kotlin:
val map1 = persons.map  it.age to it.name .toMap()
println(map1)
// output: 18=Max, 23=Pamela, 12=David 
// Result: duplicates overridden, no exception similar to Java 8

val map2 = persons.toMap( it.age ,  it.name )
println(map2)
// output: 18=Max, 23=Pamela, 12=David 
// Result: same as above, more verbose, duplicates overridden

val map3 = persons.toMapBy  it.age 
println(map3)
// output: 18=Person(name=Max, age=18), 23=Person(name=Pamela, age=23), 12=Person(name=David, age=12)
// Result: duplicates overridden again

val map4 = persons.groupBy  it.age 
println(map4)
// output: 18=[Person(name=Max, age=18)], 23=[Person(name=Peter, age=23), Person(name=Pamela, age=23)], 12=[Person(name=David, age=12)]
// Result: closer, but now have a Map<Int, List<Person>> instead of Map<Int, String>

val map5 = persons.groupBy  it.age .mapValues  it.value.map  it.name  
println(map5)
// output: 18=[Max], 23=[Peter, Pamela], 12=[David]
// Result: closer, but now have a Map<Int, List<String>> instead of Map<Int, String>

现在是正确答案:

// Kotlin:
val map6 = persons.groupBy  it.age .mapValues  it.value.joinToString(";")  it.name  

println(map6)
// output: 18=Max, 23=Peter;Pamela, 12=David
// Result: YAY!!

我们只需要加入匹配的值来折叠列表并为jointToString 提供一个转换器以从Person 实例移动到Person.name

收集示例#7

好的,这可以很容易地在没有自定义 Collector 的情况下完成,所以让我们用 Kotlin 的方式来解决它,然后设计一个新的例子来展示如何为 Collector.summarizingInt 做一个类似的过程,这在 Kotlin 中并不存在.

// Java:
Collector<Person, StringJoiner, String> personNameCollector =
Collector.of(
        () -> new StringJoiner(" | "),          // supplier
        (j, p) -> j.add(p.name.toUpperCase()),  // accumulator
        (j1, j2) -> j1.merge(j2),               // combiner
        StringJoiner::toString);                // finisher

String names = persons
        .stream()
        .collect(personNameCollector);

System.out.println(names);  // MAX | PETER | PAMELA | DAVID    
// Kotlin:
val names = persons.map  it.name.toUpperCase() .joinToString(" | ")

他们选择了一个微不足道的例子不是我的错!!!好的,这是一个用于 Kotlin 的新 summarizingInt 方法和一个匹配的示例:

SummarizingInt 示例

// Java:
IntSummaryStatistics ageSummary =
    persons.stream()
           .collect(Collectors.summarizingInt(p -> p.age));

System.out.println(ageSummary);
// IntSummaryStatisticscount=4, sum=76, min=12, average=19.000000, max=23    
// Kotlin:

// something to hold the stats...
data class SummaryStatisticsInt(var count: Int = 0,  
                                var sum: Int = 0, 
                                var min: Int = Int.MAX_VALUE, 
                                var max: Int = Int.MIN_VALUE, 
                                var avg: Double = 0.0) 
    fun accumulate(newInt: Int): SummaryStatisticsInt 
        count++
        sum += newInt
        min = min.coerceAtMost(newInt)
        max = max.coerceAtLeast(newInt)
        avg = sum.toDouble() / count
        return this
    


// Now manually doing a fold, since Stream.collect is really just a fold
val stats = persons.fold(SummaryStatisticsInt())  stats, person -> stats.accumulate(person.age) 

println(stats)
// output: SummaryStatisticsInt(count=4, sum=76, min=12, max=23, avg=19.0)

但是最好创建一个扩展函数,2其实是为了匹配Kotlin stdlib中的样式:

// Kotlin:
inline fun Collection<Int>.summarizingInt(): SummaryStatisticsInt
        = this.fold(SummaryStatisticsInt())  stats, num -> stats.accumulate(num) 

inline fun <T: Any> Collection<T>.summarizingInt(transform: (T)->Int): SummaryStatisticsInt =
        this.fold(SummaryStatisticsInt())  stats, item -> stats.accumulate(transform(item)) 

现在您有两种方法可以使用新的summarizingInt 函数:

val stats2 = persons.map  it.age .summarizingInt()

// or

val stats3 = persons.summarizingInt  it.age 

所有这些都会产生相同的结果。我们还可以创建这个扩展来处理Sequence 和适当的原始类型。

为了好玩,实现此摘要需要compare the Java JDK code vs. Kotlin custom code。

【讨论】:

在流 5 中,使用两个映射而不是一个 .map it.substring(1).toInt() 没有任何好处:正如您所知,推断类型是 kotlin 的强大功能之一。 是的,但也没有缺点(为了可比性,我将它们分开) 但是 Java 代码可以很容易地并行化,所以在很多情况下你最好从 Kotlin 调用 Java 流代码。 @HowardLovatt 在很多情况下并行不是可行的方法,尤其是在您已经在线程池中的大量并发环境中。我敢打赌,平均用例不是并行的,而且这种情况很少见。但是,当然,您始终可以选择使用您认为合适的 Java 类,而这些都不是本问答的真正目的。【参考方案3】:

在某些情况下,很难避免拨打collect(Collectors.toList()) 或类似电话。在这些情况下,您可以使用以下扩展函数更快地更改为 Kotlin 等效项:

fun <T: Any> Stream<T>.toList(): List<T> = this.collect(Collectors.toList<T>())
fun <T: Any> Stream<T>.asSequence(): Sequence<T> = this.iterator().asSequence()

然后您可以简单地 stream.toList()stream.asSequence() 移回 Kotlin API。 Files.list(path) 这样的案例会在您可能不想要时强制您使用 Stream,这些扩展可以帮助您转回标准集合和 Kotlin API。

【讨论】:

【参考方案4】:

更多关于懒惰

我们以Jayson给出的“按部门计算工资总和”的示例解决方案:

val totalByDept = employees.groupBy  it.dept .mapValues  it.value.sumBy  it.salary 

为了使这个变得懒惰(即避免在groupBy 步骤中创建中间映射),不能使用asSequence()。相反,我们必须使用groupingByfold 操作:

val totalByDept = employees.groupingBy  it.dept .fold(0)  acc, e -> acc + e.salary 

对于某些人来说,这可能更具可读性,因为您不处理地图条目:解决方案中的 it.value 部分起初也让我感到困惑。

由于这是一种常见情况,我们不想每次都写出fold,最好只在Grouping上提供一个通用的sumBy函数:

public inline fun <T, K> Grouping<T, K>.sumBy(
        selector: (T) -> Int
): Map<K, Int> = 
        fold(0)  acc, element -> acc + selector(element) 

这样我们就可以简单地写:

val totalByDept = employees.groupingBy  it.dept .sumBy  it.salary 

【讨论】:

以上是关于java中都有哪些数据结构的主要内容,如果未能解决你的问题,请参考以下文章

java.lang中都有哪些类,怎么查看?

ORACLE中都有哪些数据类型

java的API中都有哪些常用的包?

Oracle里怎么查某个实例中都有哪些用户?

java项目中都有哪些层?我知道有dao、service、util,还有啥?作用都是啥?

聚类问题中都有哪些常用的测试数据集