目的:为了生成一个不重复的主键,主键生成通过客户端程序生成。
规则主要是根据机器的网络接口信息、线程信息、时间和随机数生成一个不会重复的主键。
详细类如下,改了下名字,根据自己理解加了些注释
最后输出的结果是,前四位是时间标记,然后三位是机器标记,然后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 ); } } }