private NodeRecord _ReadNode ( bool isLeaf, Stream stream, long offset ) { stream.Position = offset; NodeRecord result = new NodeRecord(isLeaf) { _stream = stream, Leader = { Number = stream.ReadInt32Network(), Previous = stream.ReadInt32Network(), Next = stream.ReadInt32Network(), TermCount = stream.ReadInt16Network(), FreeOffset = stream.ReadInt16Network() } }; for (int i = 0; i < result.Leader.TermCount; i++) { NodeItem item = new NodeItem { Length = stream.ReadInt16Network(), KeyOffset = stream.ReadInt16Network(), LowOffset = stream.ReadInt32Network(), HighOffset = stream.ReadInt32Network() }; result.Items.Add(item); } foreach (NodeItem item in result.Items) { stream.Position = offset + item.KeyOffset; byte[] buffer = stream.ReadBytes(item.Length); string text = _encoding.GetString(buffer); item.Text = text; } return(result); }
/// <summary> /// ibatrak добавление новой или обновление существующей записи /// </summary> public static int Write(Stream stream, NodeItem item, TermLink[] links, long offset, bool padding) { int size = 0; IfpRecord ifpRecord = null; //повторное использование существующих записей long oldOffset = item.FullOffset; //если элемент новый - отметить в нем ссылку на запись ifp if (item.FullOffset == 0) { item.FullOffset = offset; } int capacity = 0; //требуется писать записи обыкновенного формата, в случае расширения старой записи bool expanding = false; if (oldOffset > 0) { bool topRecord = true; int linksCount = links.Length; //перезаписываем в цикле все записи по цепочке while (true) { //начитаем старую версию записи с нужным объемом или последнюю в цепочке ifpRecord = Read(stream, oldOffset); //в записи со спец блоком реальная вместимость записи в ссылках считается по вложенным записям capacity = ifpRecord.Special ? ifpRecord._NestedRecordLeaders.Sum(r => r.Capacity) : ifpRecord.Capacity; bool last = ifpRecord.Last; //если в старой записи хватает места, перезапишем ее if (links.Length <= capacity) { ifpRecord._changed = false; ReplaceLinks(ifpRecord, links, links.Length); //счетчик ссылок обновим ifpRecord.TotalLinkCount = links.Length; if (!ifpRecord.Special) { ifpRecord.BlockLinkCount = links.Length; } //запись в файл только при наличии изменений if (ifpRecord._changed) { Write(stream, ifpRecord, oldOffset, false /*при обновлении записей всегда padding = false*/); } links = null; } else //перезапись старой записи при расширении { expanding = true; //ирбис до упора не заполняет, при расширении делит пополам //уменьшение размеров последнего блока возможно, если //количество новых элементов плюс половина последнего блока не превышают размер блока int countToAdd = 0; //запись со спец блоком в цепочке быть не может //деление записей происходит только в последней по цепочке записи int newLinksCount = links.Length - capacity; if (!ifpRecord.Special) { //половина последнего блока + количество новых ссылок вмещается в блок, тогда делим if (last && capacity > 1 && ((int)Math.Ceiling((double)capacity / 2d) + newLinksCount) <= PostingsInBlock) { countToAdd = (int)Math.Ceiling((double)capacity / 2d); } else { countToAdd = capacity; } } else { //запись со спец блоком //половина последнего блока + количество новых ссылок вмещается в блок, тогда делим if (((int)Math.Ceiling((double)ifpRecord._NestedRecordLeaders[ifpRecord._NestedRecordLeaders.Length - 1].Capacity / 2d) + newLinksCount) <= PostingsInBlock) { //в записи со спец блоком последняя делится пополам countToAdd = capacity - (int)Math.Ceiling((double)ifpRecord._NestedRecordLeaders[ifpRecord._NestedRecordLeaders.Length - 1].Capacity / 2d); } else { countToAdd = capacity; } } //if (!last) // countToAdd = capacity; //else //{ // if (!ifpRecord.Special) // countToAdd = capacity > 1 ? (int)Math.Ceiling((double)capacity / 2d) : capacity; // else //в записи со спец блоком прследняя делится пополам // countToAdd = capacity - (int)Math.Ceiling((double)ifpRecord._NestedRecordLeaders[ifpRecord._NestedRecordLeaders.Length - 1].Capacity / 2d); //} //если в старой записи не хватает места, перезапишем в нее сколько влезет //остальное запишем в новую запись ifpRecord._changed = false; ReplaceLinks(ifpRecord, links, countToAdd); //ifpRecord.Links.Clear(); //ifpRecord.Links.AddRange(links.Take(countToAdd).ToArray()); //счетчик ссылок обновим //верхняя в цепочке запись получает в заголовке полное количество ссылок if (topRecord) { ifpRecord.TotalLinkCount = linksCount; } else { ifpRecord.TotalLinkCount = countToAdd; } //в обычной записи здесь ставится количество записей в блоке if (!ifpRecord.Special) { ifpRecord.BlockLinkCount = countToAdd; } //так быстрее, чем ToArray Array.Copy(links, countToAdd, links, 0, links.Length - countToAdd); Array.Resize(ref links, links.Length - countToAdd); //links = links.Skip(countToAdd).ToArray(); //установка ссылки на следующую запись если мы достигли конца цепочки для обычных записей //для записи со спец блоком будет расширение спец блока и там установка ссылки на следующую запись int newBlocks = (int)Math.Ceiling((double)links.Length / (double)PostingsInBlock); //расширение записи со спец блоком идет не на одну запись, а по количеству блоков //if (last || (ifpRecord.Special && ifpRecord._NestedRecordLeaders.Length < ifpRecord.Capacity)) if (last || (ifpRecord.Special && ifpRecord._NestedRecordLeaders.Length + newBlocks <= ifpRecord.Capacity)) { SetNext(ifpRecord, offset, links, newBlocks); //SetNext(stream, ifpRecord, offset, links[0], newBlocks); } //если расширяется запись со спец блоком, то лидер записи и новый спец блок помещаются в другое место //в новом спец блоке добавляется 4 блока //else if (ifpRecord.Special && ifpRecord._NestedRecordLeaders.Length >= ifpRecord.Capacity) else if (ifpRecord.Special && ifpRecord._NestedRecordLeaders.Length + newBlocks > ifpRecord.Capacity) { var newLeader = new IfpRecord(); newLeader.Special = true; var specialBlock = ifpRecord._SpecialBlock; int count = specialBlock.Length; int newCapacity = 4 * (int)Math.Ceiling(((double)ifpRecord._NestedRecordLeaders.Length + (double)newBlocks) / 4d); Array.Resize(ref specialBlock, newCapacity); for (int i = count; i < specialBlock.Length; i++) { specialBlock[i] = new SpecialPostingBlockEntry(); } newLeader.Capacity = newCapacity; newLeader.TotalLinkCount = linksCount; //count - это количество элементов в спец блоке всего, надо количество непустых элементов //newLeader.BlockLinkCount = count /*при вызове SetNext будет инкремент*/; newLeader.BlockLinkCount = ifpRecord.BlockLinkCount /*при вызове SetNext будет инкремент*/; int newLeaderSize = LeaderSize + newLeader.Capacity * SpecialPostingBlockEntry.RecordSize; newLeader._SpecialBlock = specialBlock; newLeader._NestedRecordLeaders = ifpRecord._NestedRecordLeaders; //newLeader._NestedRecordLeaders используется для SetNext SetNext(newLeader, offset + newLeaderSize, links, newBlocks); //SetNext(stream, newLeader, offset + newLeaderSize, links[0], newBlocks); newLeader._NestedRecordLeaders = null; //в элементе надо заменить адрес старой записи item.FullOffset = offset; Write(stream, newLeader, offset, true); offset += newLeaderSize; size += newLeaderSize; } if (ifpRecord._changed) { Write(stream, ifpRecord, oldOffset, false /*при обновлении записей всегда padding = false*/); } } if (last || ifpRecord.FullOffset <= 0 || links == null || links.Length == 0) { //если будем расширять запись со спец блоком, то максимальный размер у нее не более PostingsInBlock if (ifpRecord.Special) { capacity = PostingsInBlock; } break; } oldOffset = ifpRecord.FullOffset; topRecord = false; } } //добавление новой записи if (links != null && links.Length > 0) { //при расширении записи всегда пишем записи обычного формата //здесь назначаем емкость записи //if (ifpRecord.Links.Count < MinPostingsInBlock) if (expanding || links.Length < MinPostingsInBlock) { //делаем несколько блоков только если идет расширение старой записи int blockCount = expanding ? (int)Math.Ceiling((double)links.Length / (double)PostingsInBlock) : 1; for (int i = 0; i < blockCount; i++) { var lastBlock = i == blockCount - 1; ifpRecord = new IfpRecord(); ifpRecord.Links.AddRange(blockCount == 1 ? links : links.Skip(i * PostingsInBlock).Take(PostingsInBlock)); //при расширении записи следующая запись получает столько ссылок, сколько емкость предыдущей записи + 1 int count = ifpRecord.Links.Count; int recCapacity = 0; if (lastBlock && capacity > 0) { recCapacity = Math.Max(capacity, ifpRecord.Links.Count) + 1; for (int j = count; j < recCapacity; j++) { ifpRecord.Links.Add(new TermLink()); } } else { recCapacity = count; } ifpRecord.TotalLinkCount = count; ifpRecord.BlockLinkCount = count; ifpRecord.Capacity = recCapacity; ifpRecord.Last = lastBlock; //у непоследней записи нужно установить ссылку на следующую if (!lastBlock) { ifpRecord.FullOffset = offset + /*размер ifp записи обычного формата*/ LeaderSize + count * TermLink.RecordSize; } int written = Write(stream, ifpRecord, offset, padding); offset += written; size += written; } } else { //новая запись со спецблоками только в случае создания новой, //при расширении всегда идут записи обыкновенного формата ifpRecord = new IfpRecord(); ifpRecord.Links.AddRange(links); //для новой записи здесь нужно обозначить спец блок //при создании новой записи со спец блоком ирбис не выравнивает количество записей под размер блока //если в последствии такую запись расширяют, //то добавляется еще одна запись обычного формата и дальше по обычной схеме ifpRecord.Special = true; ifpRecord.TotalLinkCount = links.Length; //в записи со спец блоком здесь количество вложенных записей ifpRecord.BlockLinkCount = (int)Math.Ceiling((double)links.Length / (double)PostingsInBlock); //в записи со спецблоками в поле емкость ставится количество вложенных записей кратное 4 //емкость выставляется кратно 4, реальное количество блоков в BlockLinkCount ifpRecord.Capacity = 4 * (int)Math.Ceiling((double)links.Length / (double)PostingsInBlock / 4d); size += Write(stream, ifpRecord, offset, padding); } } ////если элемент новый - отметить в нем ссылку на запись ifp //if (item.FullOffset == 0) // item.FullOffset = offset; return(size); }
public TermLink[] SearchStart(string key) { List <TermLink> result = new List <TermLink>(); if (string.IsNullOrEmpty(key)) { return(new TermLink[0]); } key = key.ToUpperInvariant(); NodeRecord firstNode = ReadNode(1); NodeRecord rootNode = ReadNode(firstNode.Leader.Number); NodeRecord currentNode = rootNode; NodeItem goodItem = null; while (true) { bool found = false; bool beyond = false; foreach (NodeItem item in currentNode.Items) { int compareResult = string.CompareOrdinal(item.Text, key); if (compareResult > 0) { beyond = true; break; } goodItem = item; found = true; } if (goodItem == null) { break; } if (found) { if (beyond || (currentNode.Leader.Next == -1)) { if (goodItem.RefersToLeaf) { goto FOUND; } currentNode = ReadNode(goodItem.LowOffset); } else { currentNode = ReadNext(currentNode); } } else { if (goodItem.RefersToLeaf) { goto FOUND; } currentNode = ReadNode(goodItem.LowOffset); } //Console.WriteLine(currentNode); } FOUND: if (goodItem != null) { currentNode = ReadLeaf(goodItem.LowOffset); while (true) { foreach (NodeItem item in currentNode.Items) { int compareResult = string.CompareOrdinal(item.Text, key); if (compareResult >= 0) { bool starts = item.Text.StartsWith(key); if ((compareResult > 0) && !starts) { goto DONE; } if (starts) { IfpRecord ifp = ReadIfpRecord(item.FullOffset); result.AddRange(ifp.Links); } } } if (currentNode.Leader.Next > 0) { currentNode = ReadNext(currentNode); } } } DONE: return(result .Distinct() .ToArray()); }
public TermLink[] SearchExact(string key) { if (string.IsNullOrEmpty(key)) { return(new TermLink[0]); } key = key.ToUpperInvariant(); NodeRecord firstNode = ReadNode(1); NodeRecord rootNode = ReadNode(firstNode.Leader.Number); NodeRecord currentNode = rootNode; NodeItem goodItem = null; while (true) { bool found = false; bool beyond = false; foreach (NodeItem item in currentNode.Items) { int compareResult = string.CompareOrdinal(item.Text, key); if (compareResult > 0) { beyond = true; break; } goodItem = item; found = true; if ((compareResult == 0) && currentNode.IsLeaf) { goto FOUND; } } if (goodItem == null) { break; } if (found) { if (beyond || (currentNode.Leader.Next == -1)) { currentNode = goodItem.RefersToLeaf ? ReadLeaf(goodItem.LowOffset) : ReadNode(goodItem.LowOffset); } else { currentNode = ReadNext(currentNode); } } else { currentNode = goodItem.RefersToLeaf ? ReadLeaf(goodItem.LowOffset) : ReadNode(goodItem.LowOffset); } //Console.WriteLine(currentNode); } FOUND: if (goodItem != null) { IfpRecord ifp = ReadIfpRecord(goodItem.FullOffset); return(ifp.Links.ToArray()); } return(new TermLink[0]); }