package cn.codepub.redis.directory.io; import cn.codepub.redis.directory.RedisDirectory; import cn.codepub.redis.directory.RedisFile; import cn.codepub.redis.directory.util.Constants; import lombok.extern.log4j.Log4j2; import org.apache.lucene.store.BufferedChecksum; import org.apache.lucene.store.IndexOutput; import java.io.IOException; import java.util.List; import java.util.zip.CRC32; import java.util.zip.Checksum; /** * <p> * Created by wangxu on 16/10/27 18:11. * </p> * <p> * Description: TODO * </p> * * @author Wang Xu * @version V1.0.0 * @since V1.0.0 <br></br> * WebSite: http://codepub.cn <br></br> * Licence: Apache v2 License */ @Log4j2 public class RedisOutputStream extends IndexOutput { private InputOutputStream inputOutputStream; private RedisFile redisFile; private String indexFileName; private final Checksum crc; private byte[] currentBuffer; private int currentBufferIndex; private int bufferPosition;//记录当前buffer写到哪里了 private long bufferStart;//记录当前buffer在总buffers的起始位置 private int bufferLength;//记录当前buffer的长度 public RedisOutputStream(String indexFileName, InputOutputStream inputOutputStream) { this(indexFileName, inputOutputStream, true); } private RedisOutputStream(String indexFileName, InputOutputStream inputOutputStream, boolean checksum) { super(indexFileName); this.currentBufferIndex = -1; this.currentBuffer = null; this.indexFileName = indexFileName; this.redisFile = new RedisFile(); this.inputOutputStream = inputOutputStream; if (checksum) { crc = new BufferedChecksum(new CRC32()); } else { crc = null; } } /** * first write to redis file, and now when close the stream, it will flush redis files to redis db * * @throws IOException if an I/O error occurs */ @Override public void close() throws IOException { flushBuffers(); } private void flushBuffers() { //先flush刷新索引文件长度 setFileLength(); List<byte[]> buffers = redisFile.getBuffers(); inputOutputStream.saveFile(Constants.DIRECTORY_METADATA, Constants.FILE_METADATA, indexFileName, buffers, redisFile .getFileLength()); RedisDirectory.getFilesMap().remove(indexFileName); RedisDirectory.getSizeInBytes().getAndAdd(-redisFile.ramBytesUsed()); redisFile = null; } /** * update file length */ private void setFileLength() { long pointer = bufferStart + bufferPosition; if (pointer > redisFile.getFileLength()) { redisFile.setLength(pointer); } } @Override public void writeByte(byte b) throws IOException { if (bufferPosition == bufferLength) { currentBufferIndex++; switchCurrentBuffer(); } if (crc != null) { crc.update(b); } currentBuffer[bufferPosition++] = b; } private void switchCurrentBuffer() { if (currentBufferIndex == redisFile.numBuffers()) { currentBuffer = redisFile.addBuffer(Constants.BUFFER_SIZE); } else { currentBuffer = redisFile.getBuffer(currentBufferIndex); } bufferPosition = 0; bufferStart = (long) Constants.BUFFER_SIZE * (long) currentBufferIndex; bufferLength = currentBuffer.length; } @Override public void writeBytes(byte[] b, int offset, int len) throws IOException { if (len == 0) { return; } if (crc != null) { crc.update(b, offset, len); } while (len > 0) { if (bufferPosition == bufferLength) { currentBufferIndex++; switchCurrentBuffer(); } int remainInBuffer = currentBuffer.length - bufferPosition; int bytesToCopy = len < remainInBuffer ? len : remainInBuffer; System.arraycopy(b, offset, currentBuffer, bufferPosition, bytesToCopy); offset += bytesToCopy; len -= bytesToCopy; bufferPosition += bytesToCopy; } } @Override public long getFilePointer() { return currentBufferIndex < 0 ? 0 : bufferStart + bufferPosition; } @Override public long getChecksum() throws IOException { if (crc == null) { throw new IllegalStateException("internal RedisOutputStream created with checksum disabled"); } else { return crc.getValue(); } } @Override public String toString() { try { return indexFileName + "@" + getFilePointer(); } catch (Exception e) { throw new RuntimeException(e); } } }