note: 代码版本:Lucene 最新版本 --> https://github.com/apache/lucene

文章目录

  • Graph Abstract:
  • 1. Index –> Segments (segments.gen, segments_N) –> Field(fnm, fdx, fdt) –> Term (tvx, tvd, tvf)。
    • 1.1 Segments信息及其文件读写(SegmentInfos)
      • Format:索引文件格式的版本号
      • Version:记录了索引被修改的次数
      • Counter字段:用来生成下一个新段(Segment)的段名
      • SegmentCount和SegmentName:Segments的数量和每个segment的filename
      • User map data:用户数据字段
      • Footer字段:此文件 segment_N 的校验和
    • 1.2 Segment信息及其文件读写 (单个Segment)
      • softDeleteCount、delCount和delGen

Graph Abstract:

1. Index –> Segments (segments.gen, segments_N) –> Field(fnm, fdx, fdt) –> Term (tvx, tvd, tvf)。

1.1 Segments信息及其文件读写(SegmentInfos)

上面的层次结构不是十分的准确,因为 segments.gen 和 segments_N 保存的是段(segment)的元数据信息(metadata),其实是每个 Index 一个的,而段的真正的数据信息,是保存在域(Field)和词(Term)中的。

因在存储时有多个段,所以在加载时加在那个则成了一个问题,默认情况下加载的是segment_N,N最大的分段,代表加载最近提交(Commit)的分段:

  public static final String SEGMENTS = "segments";  /*** Get the generation of the most recent commit to the index in this directory (N in the* segments_N file).** @param directory -- directory to search for the latest segments_N file*/public static String getLastCommitSegmentsFileName(Directory directory) throws IOException {return IndexFileNames.fileNameFromGeneration(IndexFileNames.SEGMENTS, "", getLastCommitGeneration(directory));}public static long getLastCommitGeneration(Directory directory) throws IOException {return getLastCommitGeneration(directory.listAll());}public static long getLastCommitGeneration(String[] files) {long max = -1;for (String file : files) {if (file.startsWith(IndexFileNames.SEGMENTS)&&// skipping this file here helps deliver the right exception when opening an old indexfile.startsWith(OLD_SEGMENTS_GEN) == false) {long gen = generationFromSegmentsFileName(file);if (gen > max) {max = gen;}}}return max;}public static long generationFromSegmentsFileName(String fileName) {// 老版本有segments.gen,新版本没有这个文件if (fileName.equals(OLD_SEGMENTS_GEN)) {throw new IllegalArgumentException("\"" + OLD_SEGMENTS_GEN + "\" is not a valid segment file name since 4.0");} else if (fileName.equals(IndexFileNames.SEGMENTS)) {return 0;} else if (fileName.startsWith(IndexFileNames.SEGMENTS)) {return Long.parseLong(fileName.substring(1 + IndexFileNames.SEGMENTS.length()), Character.MAX_RADIX);} else {throw new IllegalArgumentException("fileName \"" + fileName + "\" is not a segments file");}}

那Segment_N的格式是怎么样的呢?

在新版的公司使用的版本存储segments分段信息的文件后缀为.si,命名一般如_0.si,这些文件中包含了这个分段的Id,以及其对应的版本号,一般为lucene的版本号,以及上图的一些内容。

Format:索引文件格式的版本号

Lucene版本迭代导致其索引文件格式也不尽相同,于是规定一个版本号。这也就对应了Lucene第一个字段format需要校验,目前有多重版本格式的索引文件,所以在使用时应注意Lucene版本号,毕竟ES的核心能力也是Lucene提供的:

当用某个版本号的 IndexReader 读取另一个版本号生成的索引的时候,会因为此值不同而报错。

Version:记录了索引被修改的次数

其初始值大多数情况下从索引文件里面读出,仅仅在索引开始创建的时候,被赋予当前的时间,已取得一个唯一值。Version字段初始值之所最初取一个时间,是因为我们并不关心 IndexWriter 将修改提交到索引的具体次数,而更关心到底哪个是最新的。IndexReader 中常比较自己的 version和索引文件中的 version 是否相同来判断此 IndexReader 被打开后,还有没有被IndexWriter 更新。(下面代码块中IMPORTANT位置):
对于值放入改变则是通过IndexWriter来完成的:
至此,一般情况下,写入和读取是可逆的,也就是说程序写出的文件,程序自己也会按照同样的格式读取。所以,借助Version值更改的过程,也大体了解下Segment是如何完成提交存储过程的,整个调用链路可以概括为:

IndexWriter.commit->IndexWriter.startCommit->SegmentInfos.prepareCommit->SegmentInfos.write->writeLong(++version)
  public final long commit() throws IOException {ensureOpen();return commitInternal(config.getMergePolicy());}private long commitInternal(MergePolicy mergePolicy) throws IOException {......do check ......long seqNo;synchronized (commitLock) {......do check ......if (pendingCommit == null) {if (infoStream.isEnabled("IW")) {infoStream.message("IW", "commit: now prepare");}seqNo = prepareCommitInternal(); // prepareCommit --> 两阶段提交协议} else {if (infoStream.isEnabled("IW")) {infoStream.message("IW", "commit: already prepared");}seqNo = pendingSeqNo;}finishCommit();}private long prepareCommitInternal() throws IOException {startCommitTime = System.nanoTime();synchronized (commitLock) {......do check......doBeforeFlush();testPoint("startDoFlush");SegmentInfos toCommit = null;boolean anyChanges = false;long seqNo;MergePolicy.MergeSpecification pointInTimeMerges = null;AtomicBoolean stopAddingMergedSegments = new AtomicBoolean(false);final long maxCommitMergeWaitMillis = config.getMaxFullFlushMergeWaitMillis();..... 提交之前准备,merge 是否需要发送对应的Events通知,是否按照制定时间刷新索引······// do this after handling any pointInTimeMerges since the files will have changed if any// merges// did completefilesToCommit = toCommit.files(false);try {if (anyChanges) {maybeMerge.set(true);}startCommit(toCommit); // 开始真正做commit操作if (pendingCommit == null) {return -1;} else {return seqNo;}} catch (Throwable t) {synchronized (this) {if (filesToCommit != null) {try {deleter.decRef(filesToCommit);} catch (Throwable t1) {t.addSuppressed(t1);} finally {filesToCommit = null;}}}throw t;}}}//在这里提交的是 SegmentInfos的 toCommit对象// 切换到SegmentInfos对象的prepareCommit方法private void startCommit(final SegmentInfos toSync) throws IOException {try {if (infoStream.isEnabled("IW")) {infoStream.message("IW", "startCommit(): start");}...... docheck  (sychorized method 用来保证并发情况下检查的有效性,)......boolean pendingCommitSet = false;try {synchronized (this) {assert pendingCommit == null;// 获取到对应的段的gneration 也就是 segment_N的 N的值,一般默认无异常的情况下// segmentInfos(在初始化IndexWriter时已经创建)和此时提交版本的N值应该是相同的// IMPORTANT!assert segmentInfos.getGeneration() == toSync.getGeneration();// 真正做prepareCommit操作toSync.prepareCommit(directory);if (infoStream.isEnabled("IW")) {infoStream.message("IW","startCommit: wrote pending segments file \""+ IndexFileNames.fileNameFromGeneration(IndexFileNames.PENDING_SEGMENTS, "", toSync.getGeneration())+ "\"");}pendingCommitSet = true;pendingCommit = toSync;}// 同步到其他的segments_N(startCommit方法在indexWriter的sync块中)boolean success = false;final Collection<String> filesToSync;try {filesToSync = toSync.files(false);directory.sync(filesToSync);success = true;} finally {if (!success) {pendingCommitSet = false;pendingCommit = null;toSync.rollbackCommit(directory);}}if (infoStream.isEnabled("IW")) {infoStream.message("IW", "done all syncs: " + filesToSync);}testPoint("midStartCommitSuccess");} catch (Throwable t) {......异常处理} finally {synchronized (this) {segmentInfos.updateGeneration(toSync);}}} catch (VirtualMachineError tragedy) {tragicEvent(tragedy, "startCommit");throw tragedy;}testPoint("finishStartCommit");}// 对于目录directory而言需要同步元信息,也就是segments_N 文件的内容final void prepareCommit(Directory dir) throws IOException {if (pendingCommit) {throw new IllegalStateException("prepareCommit was already called");}dir.syncMetaData();write(dir);}//如果是实际存储设备,例如FileDirectory@Overridepublic void syncMetaData() throws IOException {// TODO: to improve listCommits(), IndexFileDeleter could call this after deleting segments_NsensureOpen();IOUtils.fsync(directory, true);maybeDeletePendingFiles();}// write 方法private void write(Directory directory) throws IOException {//private long getNextPendingGeneration() {//  if (generation == -1) {//    return 1;//  } else {//    return generation + 1;//  }//}long nextGeneration = getNextPendingGeneration();String segmentFileName =IndexFileNames.fileNameFromGeneration(IndexFileNames.PENDING_SEGMENTS, "", nextGeneration);// Always advance the generation on write:generation = nextGeneration;IndexOutput segnOutput = null;boolean success = false;try {segnOutput = directory.createOutput(segmentFileName, IOContext.DEFAULT);write(segnOutput);segnOutput.close();directory.sync(Collections.singleton(segmentFileName));success = true;} finally {if (success) {pendingCommit = true;} else {// We hit an exception above; try to close the file// but suppress any exception:IOUtils.closeWhileHandlingException(segnOutput);// Try not to leave a truncated segments_N file in// the index:IOUtils.deleteFilesIgnoringExceptions(directory, segmentFileName);}}}// NIO 方式写入存储/*** Open for read access.* 读取一个已存在的文件,如果文件不存在或被占用则抛出异常*READ* Open for write access.* 以追加到文件头部的方式,写入一个已存在的文件,如果文件不存在或被占用则抛出异常**WRITE*/public static void fsync(Path fileToSync, boolean isDir) throws IOException {if (Files.exists(fileToSync) == false) {// yet do not suppress trying to fsync directories that do not existthrow new NoSuchFileException(fileToSync.toString());}return;}try (final FileChannel file =FileChannel.open(fileToSync, isDir ? StandardOpenOption.READ : StandardOpenOption.WRITE)) {try {file.force(true);} catch (final IOException e) {if (isDir) {assert (Constants.LINUX || Constants.MAC_OS_X) == false: "On Linux and MacOSX fsyncing a directory should not throw IOException, "+ "we just don't want to rely on that in production (undocumented). Got: "+ e;// Ignore exception if it is a directoryreturn;}// Throw original exceptionthrow e;}}}到这可能会有疑惑,我并没看到序列化或者其他的过程,怎么就直接写入文件了呢?看下面的代码:```java
// 上述方法的 wirte方法中有这个部分private void write(Directory directory) throws IOException{......try {segnOutput = directory.createOutput(segmentFileName, IOContext.DEFAULT);write(segnOutput);  // 在这里,其使用了CodecUtil来完成了向Buffer中写入的过程,如果跟踪源码,继续往下则是DataOutput接口完成writeBytes的过程,而最终的存储位置则为//   private ByteBuffer currentBlock = EMPTY;//   通过不断的put操作,则实现了将数据写入curentBlock这个Buffer中。//   private static final ByteBuffer EMPTY = ByteBuffer.allocate(0).order(ByteOrder.LITTLE_ENDIAN);// 默认的存储是小端存储,不需要大小端转换segnOutput.close();directory.sync(Collections.singleton(segmentFileName));success = true;} finally {if (success) {pendingCommit = true;} else {// We hit an exception above; try to close the file// but suppress any exception:IOUtils.closeWhileHandlingException(segnOutput);// Try not to leave a truncated segments_N file in// the index:IOUtils.deleteFilesIgnoringExceptions(directory, segmentFileName);}}......
}
/** Write ourselves to the provided {@link IndexOutput} */public void write(IndexOutput out) throws IOException {CodecUtil.writeIndexHeader(out,"segments",VERSION_CURRENT,StringHelper.randomId(),Long.toString(generation, Character.MAX_RADIX));out.writeVInt(Version.LATEST.major);out.writeVInt(Version.LATEST.minor);out.writeVInt(Version.LATEST.bugfix);// System.out.println(Thread.currentThread().getName() + ": now write " + out.getName() + " with// version=" + version);out.writeVInt(indexCreatedVersionMajor);CodecUtil.writeBELong(out, version);out.writeVLong(counter); // write counterCodecUtil.writeBEInt(out, size());if (size() > 0) {Version minSegmentVersion = null;// We do a separate loop up front so we can write the minSegmentVersion before// any SegmentInfo; this makes it cleaner to throw IndexFormatTooOldExc at read time:for (SegmentCommitInfo siPerCommit : this) {Version segmentVersion = siPerCommit.info.getVersion();if (minSegmentVersion == null || segmentVersion.onOrAfter(minSegmentVersion) == false) {minSegmentVersion = segmentVersion;}}out.writeVInt(minSegmentVersion.major);out.writeVInt(minSegmentVersion.minor);out.writeVInt(minSegmentVersion.bugfix);}// write infosfor (SegmentCommitInfo siPerCommit : this) {SegmentInfo si = siPerCommit.info;if (indexCreatedVersionMajor >= 7 && si.minVersion == null) {throw new IllegalStateException("Segments must record minVersion if they have been created on or after Lucene 7: "+ si);}out.writeString(si.name);byte[] segmentID = si.getId();if (segmentID.length != StringHelper.ID_LENGTH) {throw new IllegalStateException("cannot write segment: invalid id segment="+ si.name+ "id="+ StringHelper.idToString(segmentID));}out.writeBytes(segmentID, segmentID.length);out.writeString(si.getCodec().getName());CodecUtil.writeBELong(out, siPerCommit.getDelGen());int delCount = siPerCommit.getDelCount();if (delCount < 0 || delCount > si.maxDoc()) {throw new IllegalStateException("cannot write segment: invalid maxDoc segment="+ si.name+ " maxDoc="+ si.maxDoc()+ " delCount="+ delCount);}CodecUtil.writeBEInt(out, delCount);CodecUtil.writeBELong(out, siPerCommit.getFieldInfosGen());CodecUtil.writeBELong(out, siPerCommit.getDocValuesGen());int softDelCount = siPerCommit.getSoftDelCount();if (softDelCount < 0 || softDelCount > si.maxDoc()) {throw new IllegalStateException("cannot write segment: invalid maxDoc segment="+ si.name+ " maxDoc="+ si.maxDoc()+ " softDelCount="+ softDelCount);}CodecUtil.writeBEInt(out, softDelCount);// we ensure that there is a valid ID for this SCI just in case// this is manually upgraded outside of IWbyte[] sciId = siPerCommit.getId();if (sciId != null) {out.writeByte((byte) 1);assert sciId.length == StringHelper.ID_LENGTH: "invalid SegmentCommitInfo#id: " + Arrays.toString(sciId);out.writeBytes(sciId, 0, sciId.length);} else {out.writeByte((byte) 0);}out.writeSetOfStrings(siPerCommit.getFieldInfosFiles());final Map<Integer, Set<String>> dvUpdatesFiles = siPerCommit.getDocValuesUpdatesFiles();CodecUtil.writeBEInt(out, dvUpdatesFiles.size());for (Entry<Integer, Set<String>> e : dvUpdatesFiles.entrySet()) {CodecUtil.writeBEInt(out, e.getKey());out.writeSetOfStrings(e.getValue());}}out.writeMapOfStrings(userData);CodecUtil.writeFooter(out);}

Counter字段:用来生成下一个新段(Segment)的段名

// IndexWriter中
private final String newSegmentName() {// Cannot synchronize on IndexWriter because that causes// deadlocksynchronized (segmentInfos) {// Important to increment changeCount so that the// segmentInfos is written on close.  Otherwise we// could close, re-open and re-return the same segment// name that was previously returned which can cause// problems at least with ConcurrentMergeScheduler.changeCount.incrementAndGet();segmentInfos.changed();return "_" + Long.toString(segmentInfos.counter++, Character.MAX_RADIX);}}

SegmentCount和SegmentName:Segments的数量和每个segment的filename

以读取Segment文件中的信息为例,解释单个Segment里需要读取那些信息:

  private static void parseSegmentInfos(Directory directory, DataInput input, SegmentInfos infos, int format) throws IOException {infos.version = CodecUtil.readBELong(input);// System.out.println("READ sis version=" + infos.version);if (format > VERSION_70) {infos.counter = input.readVLong();} else {infos.counter = CodecUtil.readBEInt(input);}int numSegments = CodecUtil.readBEInt(input);if (numSegments < 0) {throw new CorruptIndexException("invalid segment count: " + numSegments, input);}if (numSegments > 0) {infos.minSegmentLuceneVersion =Version.fromBits(input.readVInt(), input.readVInt(), input.readVInt());} else {// else leave as null: no segments}long totalDocs = 0;for (int seg = 0; seg < numSegments; seg++) {// 获取segmentNameString segName = input.readString();// 获取SegmentIdbyte[] segmentID = new byte[StringHelper.ID_LENGTH];input.readBytes(segmentID, 0, segmentID.length);Codec codec = readCodec(input);// 读取单个Segment,使用name和Id来读取SegmentInfo info =codec.segmentInfoFormat().read(directory, segName, segmentID, IOContext.READ);//  处理单个Segment// ...... doProcess SegmentInfo (Single) ......}// 写用户信息infos.userData = input.readMapOfStrings();// LUCENE-6299: check we are in boundsif (totalDocs > IndexWriter.getActualMaxDocs()) {throw new CorruptIndexException("Too many documents: an index cannot exceed "+ IndexWriter.getActualMaxDocs()+ " but readers have total maxDoc="+ totalDocs,input);}}

对此,在解析加载Segments 时,需要用到Segments 里面记录的Id和Name来读取对应Segment的信息,所以可以看出SegName和SegCount其实做了一个flag标志的作用,便于找到对应的Segment的信息进行读取。

User map data:用户数据字段

保存了用户从字符串到字符串的映射 Map<String,String>

// 在 SegmentsInfos中的定义为
public Map<String, String> userData = Collections.emptyMap();public void write(IndexOutput out) throws IOException {// 写 header 以及其他字段,如counter,version以及各个segments_x的信息......out.writeMapOfStrings(userData);CodecUtil.writeFooter(out);}  public void writeMapOfStrings(Map<String, String> map) throws IOException {writeVInt(map.size());for (Map.Entry<String, String> entry : map.entrySet()) {writeString(entry.getKey());// 点用DataOutput接口中的方法完成 buffer的写入,最后通过fsync方法写入到文件中writeString(entry.getValue());}}

可以看到,这里是使用String字段保存用户数据的。

Footer字段:此文件 segment_N 的校验和

  public static void writeFooter(IndexOutput out) throws IOException {writeBEInt(out, FOOTER_MAGIC); // 尾部magic值,需要注意的是,这里的magic值在写入时候进行了大小端转换 writeBEInt(out, 0);writeCRC(out); // CRC32 校验结果}static void writeCRC(IndexOutput output) throws IOException {long value = output.getChecksum();if ((value & 0xFFFFFFFF00000000L) != 0) {throw new IllegalStateException("Illegal CRC-32 checksum: " + value + " (resource=" + output + ")");}writeBELong(output, value);}

1.2 Segment信息及其文件读写 (单个Segment)

SegmentName:分段的名称(也可以理解为filename),一般类似是“_0”,“_1”这样的filename。

SegmentInfo的结构如下:

然而需要注意的是,此文档数(下面代码中的totalCount)是包括已经删除,又没有 optimize 的文档的,因为在 optimize之前,Lucene的段中包含了所有被索引过的文档,而被删除的文档是保存在.del文件中的,在搜索的过程中,是先从段中读到了被删除的文档,然后再用.del中的标志,将这篇文档过滤掉。

softDeleteCount、delCount和delGen

softDeleteCount是软删除,再lucene 7.5.0之后添加了软删除操作,之前的版本都是硬删除操作,如果展开这个说,则又是一个主题了,目前还没详细了解。

delCount 为已经删除的数量,而delGen则为删除时生成的文件的fileName,例如(_0.del)

  private static void parseSegmentInfos(Directory directory, DataInput input, SegmentInfos infos, int format) throws IOException {// 获取 segmentId和SegmentName,Check版本long totalDocs = 0;for (int seg = 0; seg < numSegments; seg++) {String segName = input.readString();byte[] segmentID = new byte[StringHelper.ID_LENGTH];input.readBytes(segmentID, 0, segmentID.length);Codec codec = readCodec(input);// IMPORTANT: 读取SegmentInfo的内容 step 1SegmentInfo info = codec.segmentInfoFormat().read(directory, segName, segmentID, IOContext.READ);info.setCodec(codec);totalDocs += info.maxDoc();long delGen = CodecUtil.readBELong(input);int delCount = CodecUtil.readBEInt(input);if (delCount < 0 || delCount > info.maxDoc()) {throw new CorruptIndexException("invalid deletion count: " + delCount + " vs maxDoc=" + info.maxDoc(), input);}long fieldInfosGen = CodecUtil.readBELong(input);long dvGen = CodecUtil.readBELong(input);int softDelCount = format > VERSION_72 ? CodecUtil.readBEInt(input) : 0;if (softDelCount < 0 || softDelCount > info.maxDoc()) {throw new CorruptIndexException("invalid deletion count: " + softDelCount + " vs maxDoc=" + info.maxDoc(), input);}if (softDelCount + delCount > info.maxDoc()) {throw new CorruptIndexException("invalid deletion count: " + (softDelCount + delCount) + " vs maxDoc=" + info.maxDoc(),input);}final byte[] sciId;if (format > VERSION_74) {byte marker = input.readByte();switch (marker) {case 1:sciId = new byte[StringHelper.ID_LENGTH];input.readBytes(sciId, 0, sciId.length);break;case 0:sciId = null;break;default:throw new CorruptIndexException("invalid SegmentCommitInfo ID marker: " + marker, input);}} else {sciId = null;}// 创建一个readCommit请求,获取对应的字段信息SegmentCommitInfo siPerCommit =new SegmentCommitInfo(info, delCount, softDelCount, delGen, fieldInfosGen, dvGen, sciId);// set 字段信息文件siPerCommit.setFieldInfosFiles(input.readSetOfStrings());// docValue需要更新的files(segments)final Map<Integer, Set<String>> dvUpdateFiles;// docCValue字段的数量final int numDVFields = CodecUtil.readBEInt(input);if (numDVFields == 0) {dvUpdateFiles = Collections.emptyMap();} else {Map<Integer, Set<String>> map = new HashMap<>(numDVFields);for (int i = 0; i < numDVFields; i++) {map.put(CodecUtil.readBEInt(input), input.readSetOfStrings());}dvUpdateFiles = Collections.unmodifiableMap(map);}// 设置 需要更新segment中docValue的文件siPerCommit.setDocValuesUpdatesFiles(dvUpdateFiles);infos.add(siPerCommit);Version segmentVersion = info.getVersion();// ...... check version 校验版本号,每个Segment的版本是否符合要求}// ...... 读取用户信息......}@Overridepublic SegmentInfo read(Directory dir, String segment, byte[] segmentID, IOContext context)throws IOException {final String fileName = IndexFileNames.segmentFileName(segment, "", SI_EXTENSION);try (ChecksumIndexInput input = dir.openChecksumInput(fileName, context)) {Throwable priorE = null;SegmentInfo si = null;try {CodecUtil.checkIndexHeader(input, CODEC_NAME, VERSION_START, VERSION_CURRENT, segmentID, "");// IMPORTANT: 读取SegmentInfo的内容 step 2si = parseSegmentInfo(dir, input, segment, segmentID);} catch (Throwable exception) {priorE = exception;} finally {CodecUtil.checkFooter(input, priorE);}return si;}}// 解析组装SegmentInfo对象private SegmentInfo parseSegmentInfo(Directory dir, DataInput input, String segment, byte[] segmentID) throws IOException {final Version version = Version.fromBits(input.readInt(), input.readInt(), input.readInt());byte hasMinVersion = input.readByte();final Version minVersion;switch (hasMinVersion) {case 0:minVersion = null;break;case 1:minVersion = Version.fromBits(input.readInt(), input.readInt(), input.readInt());break;default:throw new CorruptIndexException("Illegal boolean value " + hasMinVersion, input);}final int docCount = input.readInt();if (docCount < 0) {throw new CorruptIndexException("invalid docCount: " + docCount, input);}final boolean isCompoundFile = input.readByte() == SegmentInfo.YES;final Map<String, String> diagnostics = input.readMapOfStrings();final Set<String> files = input.readSetOfStrings();final Map<String, String> attributes = input.readMapOfStrings();int numSortFields = input.readVInt();Sort indexSort;if (numSortFields > 0) {SortField[] sortFields = new SortField[numSortFields];for (int i = 0; i < numSortFields; i++) {String name = input.readString();sortFields[i] = SortFieldProvider.forName(name).readSortField(input);}indexSort = new Sort(sortFields);} else if (numSortFields < 0) {throw new CorruptIndexException("invalid index sort field count: " + numSortFields, input);} else {indexSort = null;}SegmentInfo si =new SegmentInfo(dir,version,minVersion,segment,docCount,isCompoundFile,null,diagnostics,segmentID,attributes,indexSort);si.setFiles(files);return si;}

餐中餐(5)Lucene--存储文件加载(Part 1: Segments加载)相关推荐

  1. 小程序一次性上传多个本地图片,上拉加载照片以及图片加载延迟解决之道

    一:小程序之一次性上传多个本地相片 最近由于项目需要所以学了下小程序,也做了一些东西,随后便有了以下的一些总结了,现在说说如何使用小程序一次性上传多个本地相片. 问题描述 最近做项目的时候要实现一个上 ...

  2. 006-spring cloud gateway-GatewayAutoConfiguration核心配置-GatewayProperties初始化加载、Route初始化加载...

    一.GatewayProperties 1.1.在GatewayAutoConfiguration中加载 在Spring-Cloud-Gateway初始化时,同时GatewayAutoConfigur ...

  3. 【Android 逆向】加壳技术简介 ( 动态加载 | 第一代加壳技术 - DEX 整体加固 | 第二代加壳技术 - 函数抽取 | 第三代加壳技术 - VMP / Dex2C | 动态库加壳技术 )

    文章目录 一.动态加载 二.第一代加壳技术 ( DEX 整体加固 ) 三.第二代加壳技术 ( 函数抽取 ) 四.第三代加壳技术 ( Java 函数 -> Native 函数 ) 五.so 动态库 ...

  4. 5首页加载慢_UIViewController 预加载方案浅谈

    作者 | hite,目前在网易严选iOS 组,主要工作内容 webview 相关,业余时间会写一些胡思乱想产品策划稿,各类游戏云玩家. 一. 引子 预加载作为常规性能优化手段,在所有性能敏感的场景都有 ...

  5. aspx ttf文件加载不出来_加载页面信息,刷不出来心态都崩了

    出品 | 51Testing软件测试网 只要访问过网页的地球人都知道,很多时候页面内容的加载并非与你的访问操作实时同步显现.是什么原因导致的呢?这是由于大多数Web应用程序都结合Ajax/Javasc ...

  6. android预加载布局,Android 懒加载优化

    目录介绍 1.什么是懒加载 1.1 什么是预加载 1.2 懒加载介绍 1.3 懒加载概括 2.实际应用中有哪些懒加载案例 2.1 ViewPager+Fragment组合 2.2 分析源码 3.Vie ...

  7. html加载less,javascript – 动态加载less.js规则

    我正在看使用 less. js(看起来不错),但我们的网站要求在初始页面加载后,动态加载一些样式.但是,似乎所有的LESS样式表都必须在less.js脚本加载之前加载.即这样做 但是如果行被交换,则它 ...

  8. 28 Java类的加载机制、什么是类的加载、类的生命周期、加载:查找并加载类的二进制数据、连接、初始化、类加载器、双亲委派模型、自定义类加载器

    28Java类的加载机制 28.1.什么是类的加载 28.2.类的生命周期 28.2.1.加载:查找并加载类的二进制数据 28.2.2.连接 28.2.3.初始化 28.3.类加载器 28.4.类的加 ...

  9. JVM学习笔记之-类加载子系统,类的加载与类的加载过程,双亲委派机制

    一 类加载器与类加载过程 类加载子系统作用 类加载器子系统负责从文件系统或者网络中加载class文件,class文件在文件开头有特定的文件标识. ClassLoader只负责class文件的加载,至于 ...

  10. jQuery Mobile 手动显示ajax加载器,提示加载中...

    在使用jQuery Mobile开发时,有时候我们需要在请求ajax期间,显示加载提示框(例如:一个旋转图片+一个提示:加载中...).这个时候,我们可以手动显示jQuery Mobile的加载器,大 ...

最新文章

  1. 【TensorFlow2.0】(4) 维度变换、广播
  2. RHEL 6 关闭ThinkPad 触摸板
  3. mysql 8添加账号赋予权限
  4. 返回指定月份的周列表 包含 周序号、开始日期、结束日期(不包含周末)
  5. mysql-初识MySQL
  6. mysql路径猜解_猜解数据库(MYSQL)信息
  7. 安装指定版本的GPU版本的tensorflow小技巧
  8. 我们公司不会用分布式事务!
  9. 华为刷原生android,教你如何体验(刷)原生安卓8.0
  10. linu安装mysql5.7
  11. 如果刷新网页或者下拉出现白屏可能是什么原因以及url相关问题
  12. OpenGL LookAt、Camera摄像机
  13. Ubuntu下使用NTP同步对时
  14. json和jsonp区别与讲解
  15. 如何在图数据库中训练图卷积网络模型
  16. 可取性、适用性、可行性:内存计算技术的影响
  17. Java程序员简历内容及格式--Java程序员简历内容
  18. 《细胞》:植物缺水或受伤后真的会哭!科学家录音为证
  19. opengl入门基础-画正方形
  20. FFmpeg视频编码步骤

热门文章

  1. 最新代码大全《让你成为空间明星》
  2. 店梯erp系统规范化的仓库管理功能如何操作?
  3. 即刻app 点赞效果实现
  4. ABC161 E - Yutori
  5. 互联网发展十几年,你错过了哪些创业机会
  6. 最后一个人可以挽救360和QQ的——马云
  7. iPhone7如何远程控制Linux,iPhone 7成功引导postmarketOS,首款苹果Linux智能手机
  8. 网站关键词优化技术:如何限制关键词挖掘的范围
  9. Xilinx 官方论坛帖子、AR记录
  10. 仙剑奇侠传4狐仙打法