`

mongdb主键生成方式

 
阅读更多

目的:为了生成一个不重复的主键,主键生成通过客户端程序生成。

规则主要是根据机器的网络接口信息、线程信息、时间和随机数生成一个不会重复的主键。

详细类如下,改了下名字,根据自己理解加了些注释

 

 最后输出的结果是,前四位是时间标记,然后三位是机器标记,然后1位是进程标记,然后一位是classloader标记,最后三位是自增数字。

0 1 2 3 4 5 6 7 8 9 10 11
时间标志 机器标志 进程标志 类加载器标志 自增

 

 此对象取出后可以使用org.bson.types下的ObjectId对象,取出相应的时间等内容。

/**
 *      Copyright (C) 2008 10gen Inc.
 *
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *   limitations under the License.
 */



import java.net.*;
import java.nio.*;
import java.util.*;
import java.util.concurrent.atomic.*;
import java.util.logging.*;

/**
 * 全局对象唯一标示符
 * A globally unique identifier for objects.
 * <p>Consists of 12 bytes, divided as follows:
 * <blockquote><pre>
 * <table border="1">
 * <tr><td>0</td><td>1</td><td>2</td><td>3</td><td>4</td><td>5</td><td>6</td>
 *     <td>7</td><td>8</td><td>9</td><td>10</td><td>11</td></tr>
 * <tr><td colspan="4">time</td><td colspan="3">machine</td>
 *     <td colspan="2">pid</td><td colspan="3">inc</td></tr>
 * </table>
 * </pre></blockquote>
 *
 * @dochub CreateGuIds
 */
public class CreateGuId implements Comparable<CreateGuId> , java.io.Serializable {

    private static final long serialVersionUID = -4415279469780082174L;

    static final Logger LOGGER = Logger.getLogger( "com.easy.todo.util" );

    public static  void main(String args[]){
        System.out.println(CreateGuId.get());
    }

    /** 获取一个新的对象id
     * Gets a new object id.
     * @return the new id
     */
    public static CreateGuId get(){
        return new CreateGuId();
    }

    /** Checks if a string could be an <code>CreateGuId</code>.
     * @return whether the string could be an object id
     */
    public static boolean isValid( String s ){
        if ( s == null )
            return false;

        final int len = s.length();
        if ( len != 24 )
            return false;

        for ( int i=0; i<len; i++ ){
            char c = s.charAt( i );
            if ( c >= '0' && c <= '9' )
                continue;
            if ( c >= 'a' && c <= 'f' )
                continue;
            if ( c >= 'A' && c <= 'F' )
                continue;

            return false;
        }

        return true;
    }

    /** Turn an object into an <code>CreateGuId</code>, if possible.
     * Strings will be converted into <code>CreateGuId</code>s, if possible, and <code>CreateGuId</code>s will
     * be cast and returned.  Passing in <code>null</code> returns <code>null</code>.
     * @param o the object to convert
     * @return an <code>CreateGuId</code> if it can be massaged, null otherwise
     */
    public static CreateGuId massageToCreateGuId( Object o ){
        if ( o == null )
            return null;

        if ( o instanceof CreateGuId )
            return (CreateGuId)o;

        if ( o instanceof String ){
            String s = o.toString();
            if ( isValid( s ) )
                return new CreateGuId( s );
        }

        return null;
    }

    public CreateGuId( Date time ){
        this(time, _genmachine, _nextInc.getAndIncrement());//原子操作加1
    }

    public CreateGuId( Date time , int inc ){
        this( time , _genmachine , inc );
    }

    public CreateGuId( Date time , int machine , int inc ){
        _time = (int)(time.getTime() / 1000); //获取时间戳
        _machine = machine;
        _inc = inc;
        _new = false;
    }

    /** Creates a new instance from a string.
     * @param s the string to convert
     * @throws IllegalArgumentException if the string is not a valid id
     */
    public CreateGuId( String s ){
        this( s , false );
    }

    public CreateGuId( String s , boolean babble ){

        if ( ! isValid( s ) )
            throw new IllegalArgumentException( "invalid CreateGuId [" + s + "]" );

        if ( babble )
            s = babbleToMongod( s );

        byte b[] = new byte[12];
        for ( int i=0; i<b.length; i++ ){
            b[i] = (byte)Integer.parseInt( s.substring( i*2 , i*2 + 2) , 16 );
        }
        //将 byte 数组包装到缓冲区中,新的缓冲区将由给定的 byte 数组支持;也就是说,缓冲区修改将导致数组修改,反之亦然
        ByteBuffer bb = ByteBuffer.wrap( b );
        _time = bb.getInt();
        _machine = bb.getInt();
        _inc = bb.getInt();
        _new = false;
    }

    public CreateGuId( byte[] b ){
        if ( b.length != 12 )
            throw new IllegalArgumentException( "need 12 bytes" );
        ByteBuffer bb = ByteBuffer.wrap( b );
        _time = bb.getInt();
        _machine = bb.getInt();
        _inc = bb.getInt();
        _new = false;
    }

    /**
     * Creates an CreateGuId
     * @param time time in seconds
     * @param machine machine ID
     * @param inc incremental value
     */
    public CreateGuId( int time , int machine , int inc ){
        _time = time;
        _machine = machine;
        _inc = inc;
        _new = false;
    }

    /** Create a new object id.
     */
    public CreateGuId(){
        _time = (int) (System.currentTimeMillis() / 1000);
        _machine = _genmachine;
        _inc = _nextInc.getAndIncrement();
        _new = true;
    }

    public int hashCode(){
        int x = _time;
        x += ( _machine * 111 );
        x += ( _inc * 17 );
        return x;
    }

    public boolean equals( Object o ){

        if ( this == o )
            return true;

        CreateGuId other = massageToCreateGuId( o );
        if ( other == null )
            return false;

        return
            _time == other._time &&
            _machine == other._machine &&
            _inc == other._inc;
    }

    public String toStringBabble(){
        return babbleToMongod( toStringMongod() );
    }

    public String toStringMongod(){
        byte b[] = toByteArray();

        StringBuilder buf = new StringBuilder(24);

        for ( int i=0; i<b.length; i++ ){
            int x = b[i] & 0xFF;
            //以十六进制(基数 16)无符号整数形式返回一个整数参数的字符串表示形式
            String s = Integer.toHexString( x );
            if ( s.length() == 1 )
                buf.append( "0" );
            buf.append( s );
        }

        return buf.toString();
    }

    public byte[] toByteArray(){
        byte b[] = new byte[12];
        ByteBuffer bb = ByteBuffer.wrap( b );
        // by default BB is big endian like we need
        bb.putInt( _time );
        bb.putInt( _machine );
        bb.putInt( _inc );
        return b;
    }

    static String _pos( String s , int p ){
        return s.substring( p * 2 , ( p * 2 ) + 2 );
    }

    public static String babbleToMongod( String b ){
        if ( ! isValid( b ) )
            throw new IllegalArgumentException( "invalid object id: " + b );

        StringBuilder buf = new StringBuilder( 24 );
        for ( int i=7; i>=0; i-- )
            buf.append( _pos( b , i ) );
        for ( int i=11; i>=8; i-- )
            buf.append( _pos( b , i ) );

        return buf.toString();
    }

    public String toString(){
        return toStringMongod();
    }

    int _compareUnsigned( int i , int j ){
        long li = 0xFFFFFFFFL;
        li = i & li;
        long lj = 0xFFFFFFFFL;
        lj = j & lj;
        long diff = li - lj;
        if (diff < Integer.MIN_VALUE)
            return Integer.MIN_VALUE;
        if (diff > Integer.MAX_VALUE)
            return Integer.MAX_VALUE;
        return (int) diff;
    }

    public int compareTo( CreateGuId id ){
        if ( id == null )
            return -1;

        int x = _compareUnsigned( _time , id._time );
        if ( x != 0 )
            return x;

        x = _compareUnsigned( _machine , id._machine );
        if ( x != 0 )
            return x;

        return _compareUnsigned( _inc , id._inc );
    }

    public int getMachine(){
        return _machine;
    }

    /**
     * 获取id的时间,单位为毫秒 Gets the time of this ID, in milliseconds
     */
    public long getTime(){
        return _time * 1000L;
    }

    /**
     * 获取id的时间,单位为秒 Gets the time of this ID, in seconds
     */
    public int getTimeSecond(){
        return _time;
    }

    public int getInc(){
        return _inc;
    }

    public int _time(){
        return _time;
    }
    public int _machine(){
        return _machine;
    }
    public int _inc(){
        return _inc;
    }

    public boolean isNew(){
        return _new;
    }

    public void notNew(){
        _new = false;
    }

    /**
     * 获取生成的机器id,此id根据机器网络接口信息,进程和类加载器共同来生成
     * Gets the generated machine ID, identifying the machine / process / class loader
     */
    public static int getGenMachineId() {
        return _genmachine;
    }

    /**
     * 获取自增数字的值
     * Gets the current value of the auto increment
     */
    public static int getCurrentInc() {
        return _nextInc.get();
    }

    final int _time;
    final int _machine;
    final int _inc;

    boolean _new;

    public static int _flip( int x ){
        int z = 0;
        z |= ( ( x << 24 ) & 0xFF000000 );  //向左位移24位,然后与0xFF000000做按位与运算
        z |= ( ( x << 8 )  & 0x00FF0000 );
        z |= ( ( x >> 8 )  & 0x0000FF00 );
        z |= ( ( x >> 24 ) & 0x000000FF );
        return z;
    }

    private static AtomicInteger _nextInc = new AtomicInteger( (new java.util.Random()).nextInt() );

    private static final int _genmachine;
    static {

        try {
            // 根据机器的网络信息创建两个字节的机器标识
            // build a 2-byte machine piece based on NICs info
            int machinePiece;
            {
                try {
                    StringBuilder sb = new StringBuilder();
                    Enumeration<NetworkInterface> e = NetworkInterface.getNetworkInterfaces();
                    while ( e.hasMoreElements() ){
                        NetworkInterface ni = e.nextElement();
                        sb.append( ni.toString() );
                    }
                    //网络接口信息例如  name:utun0 (utun0)name:vnic1 (vnic1)name:vnic0 (vnic0)name:en1 (en1)name:lo0 (lo0) 总共有五个网卡接口
                    //int共32位,左移16位,右边补零,最高位为符号位,正负可能变化,java存负数使用的补码例。
                    // 位移后是负数的反向计算的时候是先减1再位移,再换算成十进制
                    // 例如如 782745866 <<16 = -1056309248
                    // 0010 1110 1010 0111 1100 0001 0000 1010<<16=1100 0001 0000 1010 0000 0000 0000 0000
                    //再反向转换成十进制为0011 1110 1111 0110 0000 0000 0000 0000
                    machinePiece = sb.toString().hashCode() << 16;
                } catch (Throwable e) {
                    // exception sometimes happens with IBM JVM, use random
                    LOGGER.log(Level.WARNING, e.getMessage(), e);
                    machinePiece = (new Random().nextInt()) << 16;
                }
                LOGGER.fine( "machine piece post: " + Integer.toHexString( machinePiece ) );
            }

            // 添加 2字节的进程标示,他由jvm和类加载器共同决定
            //由于静态变量由类加载器决定,所以可能引发冲突
            // add a 2 byte process piece. It must represent not only the JVM but the class loader.
            // Since static var belong to class loader there could be collisions otherwise
            final int processPiece;
            {
                int processId = new java.util.Random().nextInt();
                try {
                    //获取进程标识的hashcode,例如2096@Macintosh.local的hashcode
                    processId = java.lang.management.ManagementFactory.getRuntimeMXBean().getName().hashCode();
                }
                catch ( Throwable t ){
                }

                ClassLoader loader = CreateGuId.class.getClassLoader();
                //不论ClassLoader有没有复写hashCode方法,都调用原始的hashCode方法。防止不同的loader重写hashcode方法,造成不同的loader的hashcode相同
                int loaderId = loader != null ? System.identityHashCode(loader) : 0;

                StringBuilder sb = new StringBuilder();
                sb.append(Integer.toHexString(processId)); //显示一个byte型的单字节十六进制(两位十六进制表示)的编码
                sb.append(Integer.toHexString(loaderId));  //10进制10位改变为16进制等于八位
                //
                //572284967  &  65535  = 24615
                processPiece = sb.toString().hashCode() & 0xFFFF;   //16位16进制的数取hashcode,再与0xFFFF与操作
                LOGGER.fine( "process piece: " + Integer.toHexString( processPiece ) );
            }
            //  -1056284633 =   -1056309248 | 24615
            _genmachine = machinePiece | processPiece;
            LOGGER.fine( "machine : " + Integer.toHexString( _genmachine ) );
        }
        catch ( Exception e ){
            throw new RuntimeException( e );
        }

    }
}

 

 

 

 

分享到:
评论

相关推荐

    C#编程指南:MongoDB中的主键ObjectId生成方法与应用

    内容概要:本文详细介绍了如何在C#中生成MongoDB的主键ObjectId,包括ObjectId的结构组成、生成方法以及具体的C#实现方式。同时,文章还提供了MongoDB.Driver的安装方法,并展示了如何调用ObjectId.GenerateNewId()...

    MongoDB底层类生成器

    MongoDB底层类生成器是一个辅助工具,它根据主键名称自动生成DBHelper类,帮助开发者更便捷地操作数据库,尤其适合对MongoDB初学者。 在关系型数据库中,我们通常会使用ORM(对象关系映射)框架来简化数据操作,而...

    自动生成主键uuid.zip

    在实际应用中,根据业务需求,还可以考虑使用更复杂的数据结构或者算法来生成有序ID,例如Snowflake算法、MongoDB的ObjectID等。这些算法和结构可以提供更高的并发性能和扩展性。 总结来说,"自动生成主键uuid.zip...

    分布式ID生成策略_snowflake算法

    在实际应用中,开发者需要根据项目需求和系统环境来评估是否采用Snowflake算法,或者考虑其他分布式ID生成策略,如Twitter的另一算法“V4 UUID”、MongoDB的ObjectID等。同时,提供的压缩包文件可能包含了项目的配置...

    spring boot整合mongodb

    4. **Repository接口**: Spring Data MongoDB 支持定义Repository接口,通过接口的方法名自动生成对应的数据库操作。例如,定义一个`UserRepository`: ```java public interface UserRepository extends Mongo...

    SpringBoot mongoDB 项目 [免费]

    这里的`MongoRepository`是SpringData MongoDB提供的基础接口,`User`是我们的实体类,`String`是主键类型。`findByUsername`方法是自定义查询,SpringData会自动将其转换为MongoDB的查询语句。 接着,我们来看实体...

    Spring-data + MongoDb源码

    每个字段可以使用`@Id`注解来指定主键,或者让MongoDB自动生成。 3. **Repository接口**: Spring Data MongoDB提供了一个通用的Repository接口,如`MongoRepository`,它包含了常见的CRUD操作。我们可以定义自己的...

    关于C#生成MongoDB中ObjectId的实现方法

    ObjectId介绍在MongoDB中,文档(document)在集合(collection)中的存储需要一个唯一的_id字段作为主键。这个_id默认使用ObjectId来定义,因为ObjectId定义的足够短小,并尽最大可能的保持唯一性,同时能被快速的...

    Spring-Mongodb例子

    6. **聚合框架**:Spring Data MongoDB提供了聚合框架,可以方便地执行MongoDB的聚合操作,用于数据分析和报表生成。 7. **地理空间支持**:MongoDB支持地理空间索引和查询,Spring Data MongoDB也提供了相应的API...

    使用MyBatisPlus的方式,优雅的操作MongoDB

    使用MyBatisPlus的方式,优雅的操作MongoDB。无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑。损耗小:启动即会自动注入基本 CURD,性能基本无损耗,直接面向对象操作。强大的 CRUD 操作:...

    MongoDB 权威指南.pdf

    4. **主键(_id)**:每个文档都有一个_id字段作为唯一标识符,默认情况下MongoDB会自动生成ObjectId类型的_id字段。 #### 三、MongoDB安装与配置 - **安装步骤**: - 下载MongoDB安装包。 - 解压并安装服务。 ...

    mongodb在java中小试牛刀

    只需要定义一个接口,继承自MongoRepository,并提供泛型参数(对应MongoDB集合的实体类型和ID类型),Spring会自动为你生成实现,包括CRUD操作和自定义查询方法。 ```java public interface UserRepository ...

    Spring集成Mongodb配置含jar包

    Spring自动为这些接口生成实现,极大地简化了数据访问层的开发工作。 4. **实体类和映射**: 在Spring集成MongoDB的过程中,我们需要定义Java对象(实体类)来表示MongoDB中的文档。这些实体类通常使用`@Document`...

    spring data mongodb

    1. **Repository接口**:Spring Data MongoDB通过Repository接口提供了一种声明式的方式来定义数据库操作。例如,你可以定义一个继承自`MongoRepository`的接口,其中包含CRUD(创建、读取、更新、删除)操作的方法...

    mongoDB入门到精通.txt

    4. **主键**(_id):MongoDB为每个文档自动生成一个唯一的_id字段作为主键。 #### 四、MongoDB的安装与配置 1. **下载与安装**:可以从MongoDB官网下载适合不同操作系统的版本,按照指引完成安装过程。 2. **启动...

    springboot+mongodb

    3. **创建模型类**:为了与 MongoDB 存储数据进行交互,你需要定义一个 Java 类作为文档模型,使用注解如 `@Document` 来标记这个类为 MongoDB 文档,`@Id` 来标识主键。 ```java @Document(collection = "users...

    Spring3.1 MongoDB整合实例(含jar包)已测

    4. **Repository接口**:通过定义MongoDB Repository接口,你可以声明查询方法,Spring Data MongoDB会自动为这些方法生成实现。例如,你可以创建一个`UserRepository`,并声明一个`findAll()`方法来获取所有用户。 ...

    demo13-mongodb.rar

    3. **MongoDB实体映射**:使用`@Document`注解定义MongoDB的集合,`@Id`标注主键,其他的字段可以使用`@Field`进行映射。 4. **Repository接口**:Spring Data MongoDB允许我们定义基于CRUD操作的Repository接口,...

    MongoDB与Spring整合

    通过这样的方式,Spring Data MongoDB大大简化了MongoDB在Java应用中的使用。它提供了自动化的数据访问功能,使得开发人员可以更专注于业务逻辑,而不是底层数据访问细节。同时,由于Spring框架的灵活性和扩展性,...

Global site tag (gtag.js) - Google Analytics