Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >Java示例演示Functor 和monad

Java示例演示Functor 和monad

作者头像
Dylan Liu
发布于 2019-07-01 05:14:32
发布于 2019-07-01 05:14:32
6340
举报
文章被收录于专栏:dylanliudylanliu

This article was initially an appendix in our Reactive Programming with RxJavabook. However introduction to monads, albeit very much related to reactive programming, didn't suit very well. So I decided to take it out and publish separately as a blog post. I am aware that "my very own, half correct and half complete explanation of monads" is the new "Hello, world" on programming blogs. Yet the article looks at functors and monads from a specific angle of Java data structures and libraries. Thus I thought it's worthwhile sharing. RxJava was designed and built on top of very fundamental concepts like functors , monoids and monads . Even though Rx was modeled initially for imperative C# language and we are learning about RxJava, working on top of similarly imperative language, the library has its roots in functional programming. You should not be surprised after you realize how compact RxJava API is. There are pretty much just a handful of core classes, typically immutable, and everything is composed using mostly pure functions. With a recent rise of functional programming (or functional style), most commonly expressed in modern languages like Scala or Clojure, monads became a widely discussed topic. There is a lot of folklore around them: A monad is a monoid in the category of endofunctors, what's the problem? James Iry The curse of the monad is that once you get the epiphany, once you understand - "oh that's what it is" - you lose the ability to explain it to anybody. Douglas Crockford The vast majority of programmers, especially those without functional programming background, tend to believe monads are some arcane computer science concept, so theoretical that it can not possibly help in their programming career. This negative perspective can be attributed to dozens of articles and blog posts being either too abstract or too narrow. But it turns out that monads are all around us, even is standard Java library, especially since Java Development Kit (JDK) 8 (more on that later). What is absolutely brilliant is that once you understand monads for the first time, suddenly several unrelated classes and abstractions, serving entirely different purposes, become familiar. Monads generalize various seemingly independent concepts so that learning yet another incarnation of monad takes very little time. For example you do not have to learn how CompletableFuture works in Java 8, once you realize it is a monad, you know precisely how it works and what can you expect from its semantics. And then you hear about RxJava which sounds so much different but because Observable is a monad, there is not much to add. There are numerous other examples of monads you already came across without knowing that. Therefore this section will be a useful refresher even if you fail to actually use RxJava.

Functors

Before we explain what a monad is, let's explore simpler construct called a functor . A functor is a typed data structure that encapsulates some value(s). From syntactic perspective a functor is a container with the following API:

1 2 3 4 5 6 7

import java.util.function.Function; interface Functor<T> { <R> Functor<R> map(Function<T, R> f); }

But mere syntax is not enough to understand what functor is. The only operation that functor provides is map() that takes a function f. This function receives whatever is inside a box, transforms it and wraps the result as-is into a second functor. Please read that carefully. Functor<T> is always an immutable container, thus map never mutates original object it was executed on. Instead it returns the result (or results - be patient) wrapped in a brand new functor, possibly of different type R. Additionally functors should not perform any actions when identity function is applied, that is map(x -> x). Such a pattern should always return either the same functor or an equal instance. Often Functor<T> is compared to a box holding instance of T where the only way of interacting with this value is by transforming it. However there is no idiomatic way of unwrapping or escaping from the functor. The value(s) always stay within the context of functor. Why are functors useful? They generalize multiple common idioms like collections, promises, optionals, etc. with a single, uniform API that works across all of them. Let me introduce a couple of functors to make you more fluent with this API:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16

interface Functor<T,F extends Functor<?,?>> { <R> F map(Function<T,R> f); } class Identity<T> implements Functor<T,Identity<?>> { private final T value; Identity(T value) { this.value = value; } public <R> Identity<R> map(Function<T,R> f) { final R result = f.apply(value); return new Identity<>(result); } }

An extra F type parameter was required to make Identity compile. What you saw in the preceding example was the simplest functor just holding a value. All you can do with that value is transforming it inside map method, but there is no way to extract it. This is considered beyond the scope of pure functor. The only way to interact with functor is by applying sequences of type-safe transformations:

1 2

Identity<String> idString = new Identity<>("abc"); Identity<Integer> idInt = idString.map(String::length);

Or fluently, just like you compose functions:

1 2 3 4 5 6

Identity<byte[]> idBytes = new Identity<>(customer) .map(Customer::getAddress) .map(Address::street) .map((String s) -> s.substring(0, 3)) .map(String::toLowerCase) .map(String::getBytes);

From this perspective mapping over a functor is not much different than just invoking chained functions:

1 2 3 4 5 6

byte[] bytes = customer .getAddress() .street() .substring(0, 3) .toLowerCase() .getBytes();

Why would you even bother with such verbose wrapping that not only does not provide any added value, but also is not capable of extracting the contents back? Well, it turns out you can model several other concepts using this raw functor abstraction. For example java.util.Optional<T> starting from Java 8 is a functor with map() method. Let us implement it from scratch:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24

class FOptional<T> implements Functor<T,FOptional<?>> { private final T valueOrNull; private FOptional(T valueOrNull) { this.valueOrNull = valueOrNull; } public <R> FOptional<R> map(Function<T,R> f) { if (valueOrNull == null) return empty(); else return of(f.apply(valueOrNull)); } public static <T> FOptional<T> of(T a) { return new FOptional<T>(a); } public static <T> FOptional<T> empty() { return new FOptional<T>(null); } }

Now it becomes interesting. An FOptional<T> functor may hold a value, but just as well it might be empty. It's a type-safe way of encoding null. There are two ways of constructing FOptional - by supplying a value or creating empty() instance. In both cases, just like with Identity, FOptional is immutable and we can only interact with the value from inside. What differs FOptional is that the transformation function f may not be applied to any value if it is empty. This means functor may not necessarily encapsulate exactly one value of type T. It can just as well wrap arbitrary number of values, just like List... functor:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19

import com.google.common.collect.ImmutableList; class FList<T> implements Functor<T, FList<?>> { private final ImmutableList<T> list; FList(Iterable<T> value) { this.list = ImmutableList.copyOf(value); } @Override public <R> FList<?> map(Function<T, R> f) { ArrayList<R> result = new ArrayList<R>(list.size()); for (T t : list) { result.add(f.apply(t)); } return new FList<>(result); } }

The API remains the same: you take a functor in a transformation T -> R - but the behavior is much different. Now we apply a transformation on each and every item in the FList, declaratively transforming whole list. So if you have a list of customers and you want a list of their streets, it's as simple as:

1 2 3 4 5 6 7

import static java.util.Arrays.asList; FList<Customer> customers = new FList<>(asList(cust1, cust2)); FList<String> streets = customers .map(Customer::getAddress) .map(Address::street);

It's no longer as simple as saying customers.getAddress().street(), you can't invoke getAddress() on a collection of customers, you must invoke getAddress() on each individual customer and then place it back in a collection. By the way Groovy found this pattern so common that it actually has a syntax sugar for that: customer*.getAddress()*.street(). This operator, known as spread-dot, is actually a mapin disguise. Maybe you are wondering why I iterate over list manually inside map rather than using Streams from Java 8: list.stream().map(f).collect(toList())? Does this ring a bell? What if I told you java.util.stream.Stream<T> in Java is a functor as well? And by the way, also a monad? Now you should see the first benefits of functors - they abstract away the internal representation and provide consistent, easy to use API over various data structures. As the last example let me introduce promise functor, similar to Future. Promise "promises" that a value will become available one day. It is not yet there, maybe because some background computation was spawned or we are waiting for external event. But it will appear some time in the future. The mechanics of completing a Promise<T> are not interesting, but the functor nature is:

1 2 3 4 5 6 7

Promise<Customer> customer = //... Promise<byte[]> bytes = customer .map(Customer::getAddress) .map(Address::street) .map((String s) -> s.substring(0, 3)) .map(String::toLowerCase) .map(String::getBytes);

Looks familiar? That is the point! The implementation of Promise functor is beyond the scope of this article and not even important. Enough to say that we are very close to implementing CompletableFuture from Java 8 and we almost discovered Observable from RxJava. But back to functors. Promise<Customer> does not hold a value of Customer just yet. It promises to have such value in the future. But we can still map over such functor, just like we did with FOptional and FList - the syntax and semantics are exactly the same. The behavior follows what the functor represents. Invoking customer.map(Customer::getAddress) yields Promise<Address> which means map is non-blocking. customer.map() will not wait for the underlying customer promise to complete. Instead it returns another promise, of different type. When upstream promise completes, downstream promise applies a function passed to map() and passes the result downstream. Suddenly our functor allows us to pipeline asynchronous computations in a non-blocking manner. But you do not have to understand or learn that - because Promise is a functor, it must follow syntax and laws. There are many other great examples of functors, for example representing value or error in a compositional manner. But it is high time to look at monads.

From functors to monads

I assume you understand how functors work and why are they a useful abstraction. But functors are not that universal as one might expect. What happens if your transformation function (the one passed as an argument to map()) returns functor instance rather than simple value? Well, functor is just a value as well, so nothing bad happens. Whatever was returned is placed back in a functor so all behaves consistently. However imagine you have this handy method for parsing Strings:

1 2 3 4 5 6 7 8

FOptional<Integer> tryParse(String s) { try { final int i = Integer.parseInt(s); return FOptional.of(i); } catch (NumberFormatException e) { return FOptional.empty(); } }

Exceptions are side-effects that undermine type system and functional purity. In pure functional languages there is no place for exceptions, after all we never heard about throwing exceptions during math classes, right? Errors and illegal conditions are represented explicitly using values and wrappers. For example tryParse() takes a Stringbut does not simply return an int or silently throw an exception at runtime. We explicitly tell, through the type system, that tryParse() can fail, there is nothing exceptional or erroneous in having a malformed string. This semi-failure is represented by optional result. Interestingly Java has checked exceptions, the ones that must be declared and handled, so in some sense Java is purer in that regard, it does not hide side-effects. But for better or worse checked exceptions are often discouraged in Java, so let's get back to tryParse(). It seems useful to compose tryParse with String already wrapped in FOptional:

1 2

FOptional<String> str = FOptional.of("42"); FOptional<FOptional<Integer>> num = str.map(this::tryParse);

That should not come as a surprise. If tryParse() would return an int you would get FOptional<Integer> num, but because map() function returns FOptional<Integer> itself, it gets wrapped twice into awkward FOptional<FOptional<Integer>>. Please look carefully at the types, you must understand why we got this double wrapper here. Apart from looking horrible, having a functor in functor ruins composition and fluent chaining:

1 2 3 4 5 6 7

FOptional<Integer> num1 = //... FOptional<FOptional<Integer>> num2 = //... FOptional<Date> date1 = num1.map(t -> new Date(t)); //doesn't compile! FOptional<Date> date2 = num2.map(t -> new Date(t));

Here we try to map over the contents of FOptional by turning int into +Date+. Having a function of int -> Date we can easily transform from Functor<Integer> to Functor<Date>, we know how it works. But in case of num2 situation becomes complicated. What num2.map() receives as input is no longer an int but an FOoption<Integer> and obviously java.util.Date does not have such a constructor. We broke our functor by double wrapping it. However having a function that returns a functor rather than simple value is so common (like tryParse()) that we can not simply ignore such requirement. One approach is to introduce a special parameterless join()method that "flattens" nested functors:

1

FOptional<Integer> num3 = num2.join()

It works but because this pattern is so common, special method named flatMap() was introduced. flatMap() is very similar to map but expects the function received as an argument to return a functor - or monad to be precise:

1 2 3

interface Monad<T,M extends Monad<?,?>> extends Functor<T,M> { M flatMap(Function<T,M> f); }

We simply concluded that flatMap is just a syntactic sugar to allow better composition. But flatMap method (often called bind or >>= from Haskell) makes all the difference since it allows complex transformations to be composed in a pure, functional style. If FOptional was an instance of monad, parsing suddenly works as expected:

1 2

FOptional<String> num = FOptional.of("42"); FOptional<Integer> answer = num.flatMap(this::tryParse);

Monads do not need to implement map, it can be implemented on top of flatMap() easily. As a matter of fact flatMap is the essential operator that enables a whole new universe of transformations. Obviously just like with functors, syntactic compliance is not enough to call some class a monad, the flatMap() operator has to follow monad laws, but they are fairly intuitive like associativity of flatMap() and identity. The latter requires that m(x).flatMap(f) is the same as f(x) for any monad holding a value x and any function f. We are not going to dive too deep into monad theory, instead let's focus on practical implications. Monads shine when their internal structure is not trivial, for example Promise monad that will hold a value in the future. Can you guess from the type system how Promise will behave in the following program? First all methods that can potentially take some time to complete return a Promise:

1 2 3 4 5 6 7 8 9 10 11 12 13 14

import java.time.DayOfWeek; Promise<Customer> loadCustomer(int id) { //... } Promise<Basket> readBasket(Customer customer) { //... } Promise<BigDecimal> calculateDiscount(Basket basket, DayOfWeek dow) { //... }

We can now compose these functions as if they were all blocking using monadic operators:

1 2 3 4

Promise<BigDecimal> discount = loadCustomer(42) .flatMap(this::readBasket) .flatMap(b -> calculateDiscount(b, DayOfWeek.FRIDAY));

This becomes interesting. flatMap() must preserve monadic type therefor all intermediate objects are Promises. It is not just about keeping the types in order - preceding program is suddenly fully asynchronous! loadCustomer() returns a Promise so it does not block. readBasket() takes whatever the Promise has (will have) and applies a function returning another Promise and so on and so forth. Basically we built an asynchronous pipeline of computation where the completion of one step in background automatically triggers next step.

Exploring flatMap()

It is very common to have two monads and combining the value they enclose together. However both functors and monads do not allow direct access to their internals, which would be impure. Instead we must carefully apply transformation without escaping the monad. Imagine you have two monads and you want to combine them:

1 2 3 4 5 6 7 8 9 10

import java.time.LocalDate; import java.time.Month; Monad<Month> month = //... Monad<Integer> dayOfMonth = //... Monad<LocalDate> date = month.flatMap((Month m) -> dayOfMonth .map((int d) -> LocalDate.of(2016, m, d)));

Please take your time to study the preceding pseudo-code. I don't use any real monad implementation like Promise or List to emphasize the core concept. We have two independent monads, one of type Month and the other of type Integer. In order to build LocalDate out of them we must build a nested transformation that has access to the internals of both monads. Work through the types, especially making sure you understand why we use flatMap in one place and map() in the other. Think how you would structure this code if you had a third Monad<Year> as well. This pattern of applying a function of two arguments (m and d in our case) is so common that in Haskell there is special helper function called liftM2 that does exactly this transformation, implemented on top of map and flatMap. In Java pseudo-syntax it would look somewhat like this:

1 2 3 4 5

Monad<R> liftM2(Monad<T1> t1, Monad<T2> t2, BiFunction<T1, T2, R> fun) { return t1.flatMap((T1 tv1) -> t2.map((T2 tv2) -> fun.apply(tv1, tv2)) ); }

You don't have to implement this method for every monad, flatMap() is enough, moreover it works consistently for all monads. liftM2 is extremely useful when you consider how it can be used with various monads. For example liftM2(list1, list2, function) will apply function on every possible pair of items from list1 and list2(Cartesian product). On the other hand for optionals it will apply a function only when both optionals are non-empty. Even better, for Promise monad a function will be executed asynchronously when both Promises are completed. This means we just invented a simple synchronization mechanism (join() in fork-join algorithms) of two asynchronous steps. Another useful operator that we can easily build on top of flatMap() is filter(Predicate<T>) which takes whatever is inside a monad and discards it entirely if it does not meet certain predicate. In a way it is similar to map but rather than 1-to-1 mapping we have 1-to-0-or-1. Again filter() has the same semantics for every monad but quite amazing functionality depending on which monad we actually use. Obviously it allows filtering out certain elements from a list:

1 2

FList<Customer> vips = customers.filter(c -> c.totalOrders > 1_000);

But it works just as well e.g. for optionals. In that case we can transform non-empty optional into an empty one if the contents of optional does not meet some criteria. Empty optionals are left intact.

From list of monads to monad of list

Another useful operator that originates from flatMap() is sequence(). You can easily guess what it does simply by looking at type signature:

1

Monad<Iterable<T>> sequence(Iterable<Monad<T>> monads)

Often we have a bunch of monads of the same type and we want to have a single monad of a list of that type. This might sounds abstract to you, but it is impressively useful. Imagine you wanted to load a few customers from the database concurrently by ID so you used loadCustomer(id) method several times for different IDs, each invocation returning Promise<Customer>. Now you have a list of Promises but what you really want is a list of customers, e.g. to be displayed in the web browser. sequence() (in RxJava sequence() is called concat() or merge(), depending on use-case) operator is built just for that:

1 2 3 4 5 6 7

FList<Promise<Customer>> custPromises = FList .of(1, 2, 3) .map(database::loadCustomer); Promise<FList<Customer>> customers = custPromises.sequence(); customers.map((FList<Customer> c) -> ...);

Having an FList<Integer> representing customer IDs we map over it (do you see how it helps that FList is a functor?) by calling database.loadCustomer(id) for each ID. This leads to rather inconvenient list of Promises. sequence() saves the day, but once again this is not just a syntactic sugar. Preceding code is fully non-blocking. For different kinds of monads sequence() still makes sense, but in a different computational context. For example it can change a FList<FOptional<T>> into FOptional<FList<T>>. And by the way, you can implement sequence() (just like map()) on top of flatMap(). This is just the tip of the iceberg when it comes to usefulness of flatMap() and monads in general. Despite coming from rather obscure category theory, monads proved to be extremely useful abstraction even in object-oriented programming languages such as Java. Being able to compose functions returning monads is so universally helpful that dozens of unrelated classes follow monadic behavior. Moreover once you encapsulate data inside monad, it is often hard to get it out explicitly. Such operation is not part of the monad behavior and often leads to non-idiomatic code. For example Promise.get() on Promise<T> can technically return T, but only by blocking, whereas all operators based on flatMap() are non-blocking. Another example is FOptional.get() that can fail because FOptional may be empty. Even FList.get(idx) that peeks particular element from a list sounds awkward because you can replace for loops with map() quite often. I hope you now understand why monads are so popular these days. Even in object-oriented(-ish) language like Java they are quite useful abstraction.

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
neo4j官方开发文档阅读记录
neo-4j由两部分组成:relationship,label和property,label或者relationship中包含property,label与label之间形成关系.
DuncanZhou
2018/09/04
1.7K0
Neo4j常用查询语句
Cypher使用match子句查询数据,是Cypher最基本的查询子句。在查询数据时,使用Match子句指定搜索的模式,这是从Neo4j数据库查询数据的最主要的方法。match子句之后通常会跟着where子句,向模式中添加过滤性的谓词,用于对数据进行过滤。在查询数据时,查询语句分为多个部分,with子句用于对上一个查询部分的结果进行处理,以输出到下一个查询部分。
水煮麥楽雞
2022/11/20
2.7K0
Neo4j使用Cypher查询图形数据
原文出处:http://www.yund.tech/zdetail.html?type=1&id=e5a7ca6d4e801e88790cc85b94e1f405 作者:jstarseven  Neo
大道七哥
2019/08/23
2.7K0
Neo4j使用Cypher查询图形数据
快速初步了解Neo4j与使用
Neo4j是一个高性能的,NOSQL图形数据库,它将结构化数据存储在网络上而不是表中。它是一个嵌入式的、基于磁盘的、具备完全的事务特性的Java持久化引擎,但是它将结构化数据存储在网络(从数学角度叫做图)上而不是表中。Neo4j也可以被看作是一个高性能的图引擎,该引擎具有成熟数据库的所有特性。程序员工作在一个面向对象的、灵活的网络结构下而不是严格、静态的表中——但是他们可以享受到具备完全的事务特性、企业级的数据库的所有好处。
Dream城堡
2018/09/10
1.8K0
快速初步了解Neo4j与使用
图数据库neo4j学习
在日常运维开发过程中,可能遇到需要存储或者展示依赖关系的情况,这种比较适合用图数据库去存储。
保持热爱奔赴山海
2025/03/26
1690
neo4j︱图数据库基本概念、操作罗列与整理(一)
版权声明:博主原创文章,微信公众号:素质云笔记,转载请注明来源“素质云博客”,谢谢合作!! https://blog.csdn.net/sinat_26917383/article/details/79883503
悟乙己
2019/05/26
2.7K0
neo4j︱neo4j批量导入neo4j-import (五)
版权声明:博主原创文章,微信公众号:素质云笔记,转载请注明来源“素质云博客”,谢谢合作!! https://blog.csdn.net/sinat_26917383/article/details/82424508
悟乙己
2019/05/26
3.8K0
图形数据库Neo4j基本了解
原文出处:http://www.yund.tech/zdetail.html?type=1&id=f519df57f29b22863d2a6a79326bd22b 作者:jstarseven  在深入
大道七哥
2019/08/23
3.1K0
图形数据库Neo4j基本了解
2018-11-19 如何将大规模数据导入Neo4j及导入具体步骤及Demo
博文原地址:https://my.oschina.net/zlb1992/blog/918243
Albert陈凯
2018/12/14
2.5K0
2018-11-19 如何将大规模数据导入Neo4j及导入具体步骤及Demo
Neo4j 之 Cypher 笔记
Cypher 是 Neo4j 提出的图查询语言,是一种声明式的图数据库查询语言,如同关系数据库中的 SQL,它拥有精简的语法和强大的表现力,能够精准且高效地对图数据进行查询和更新。
EmoryHuang
2022/10/31
1.4K0
NEO4J 图数据库哪里和哪里 从哪里开始
上期已经安装了图数据库,本期就该讨论到底这个图数据库里面的一些基本的概念和如何操作。最近听到一句话,年轻不年轻,不是看年龄,而是看你对新鲜事物的热情,即使你20岁,谈起新事物也是一脸的不屑,只能说明身体和灵魂分了家。闲话不谈 回归正题。
AustinDatabases
2020/05/09
3.2K0
关于neo4j图数据库笔记六-电影库和最短路径问题
知识图谱在工商企业、交往圈模型、系统架构、血缘关系、关联聚合场景、区域最短路径上都能发挥很大的作用,本笔记也只是简单的介绍了一下,介绍到此为止。
python与大数据分析
2022/03/11
8010
关于neo4j图数据库笔记六-电影库和最短路径问题
图数据库neo4j(二)python 连接neo4j
在完成安装之后,在python中调用py2neo即可,常用的有Graph,Node, Relationship。
学到老
2019/01/25
6.9K1
图数据库neo4j(二)python 连接neo4j
电影关系图谱
“电影关系图”实例将电影、电影导演、演员之间的复杂网状关系作为蓝本,使用Neo4j创建三者关系的图结构,虽然实例数据规模小但五脏俱全。
伊泽瑞尔
2022/06/01
1.5K0
电影关系图谱
一文速学-知识图谱从零开始构建实战:知识图谱搭建
“好事”文章推荐:BuildAdmin19:前端项目如何设计一个异步API请求模块
fanstuck
2024/11/26
6140
一文速学-知识图谱从零开始构建实战:知识图谱搭建
使用Neo4j和Java进行大数据分析 第1部分
几十年来,关系数据库一直主导着数据管理,但它们最近已经失去了NoSQL的替代品。虽然NoSQL数据存储不适合每个用例,但它们通常更适合大数据,这是处理大量数据的系统的简写。四种类型的数据存储用于大数据:
银河1号
2019/04/12
3.5K0
使用Neo4j和Java进行大数据分析 第1部分
解读Neo4j全新的Python驱动程序
尽管Neo4j社区目前已发布了Java、Python、JavaScript和.NET官方支持的驱动程序,但其发展并未停步。本周,Neo4j发布驱动程序py2neo 3.1版本,同时还为Python用户
CSDN技术头条
2018/02/12
1.6K0
解读Neo4j全新的Python驱动程序
Neo4j 系列(1) —— 初识 Neo4j
图数据库是基于图论实现的一种NoSQL数据库,其数据存储结构和数据查询方式都是以图论为基础的,图数据库主要用于存储更多的连接数据
求和小熊猫
2021/12/06
3K0
neo4j import tool
Use the import tool 这篇教程提供了使用import tool的详细案例 当使用csv文件载入数据库时,为了能够创建节点之间的关系,每一个节点必须有一个独一无二的标识,节点ID。 关系通过连接两个节点之间的ID被创建,在下面的例子中,节点标识符作为属性存储在节点上。 节点标识符稍后可能对其他系统的交叉引用,可追溯性等感兴趣,但它们不是强制性的。 如果您不希望标识符在完成导入后保留,则不要在:ID字段中指定属性名称。
学到老
2019/01/25
7780
使用Neo4j和Java进行大数据分析 第2部分
本文的第一部分介绍了Neo4j及其Cypher查询语言。如果您已经阅读了第1部分,那么您已经了解了为什么Neo4j和其他图形数据库特别受社交图形或网络中用户之间关系建模的影响。您还在开发环境中安装了Neo4j,并概述了使用此数据存储的基本概念 - 即节点和关系。
银河1号
2019/04/12
5K1
推荐阅读
相关推荐
neo4j官方开发文档阅读记录
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档