我是如何把 Java 项目移植到 .NET 5.0 的
伴隨著?IP 位置庫?的上線,筆者的“童年夢想”又成真了一個。為了分發這份來之不易的數據庫,筆者找到了?ip2region?項目。該項目提供了一種體積小且查詢速度極快的離線IP位置數據庫文件格式,同時提供了多種語言支持的查詢客戶端。但 ip2region 項目的作者并未提供除 Java 以外的數據庫文件生成代碼,筆者打算為該項目移植 .NET 5.0 的數據庫文件生成器,并在本文中記錄下移植過程。
移植前準備
ip2region 的 Java 版數據庫生成器?代碼并不復雜,源代碼文件只有 8 個。以筆者粗淺的 Java 經驗來看,因為 C# 與 Java 大體相似,移植過程中無需對程序的結構和命名進行變更,也無需對處理邏輯進行調整。移植需要做的就是讓程序可以編譯通過,基本上就算成功。
開始移植
筆者新建了一個名為 IP2RegionDotNetDbMaker 的 .NET 5.0 控制臺應用程序,刪掉 Program.cs 文件并將所有的 Java 文件復制到項目中。
下一步操作很暴力,就是直接將源代碼的后綴從 .java 改為 .cs 。為此,筆者在 LINQPad 中寫了一段小代碼,來完成這個操作:
var dir = @"D:\coderbusy.com\demo\IP2RegionDotNetDbMaker\IP2RegionDotNetDbMaker";var javaFiles = Directory.GetFiles(dir, "*.java");foreach (var javaFile in javaFiles){var _ = Path.GetDirectoryName(javaFile);var fileName = Path.GetFileNameWithoutExtension(javaFile);var csFile = Path.Combine(_, fileName + ".cs");File.Move(javaFile, csFile);}在暴力改名之后的源代碼文件里,不出意外的報了很多錯誤:
需要先把 package 和 import 這兩種語句去掉,然后把缺失的命名空間給加上。
var dir = @"D:\coderbusy.com\demo\IP2RegionDotNetDbMaker\IP2RegionDotNetDbMaker";var files = Directory.GetFiles(dir, "*.cs");foreach (var file in files){var lines = File.ReadAllLines(file, Encoding.UTF8);var builder = new StringBuilder();builder.AppendLine($"using System;{Environment.NewLine}namespace IP2RegionDotNetDbMaker{Environment.NewLine}{{");foreach (var line in lines){if (line.StartsWith("package ")){continue;}if (line.StartsWith("import ")){continue;}builder.AppendLine(line);}builder.AppendLine("}");var content = builder.ToString();File.WriteAllText(file, content, Encoding.UTF8);}異常聲明在 C# 中不支持,可以通過正則將其替換掉:
在替換時,確定開啟“使用正則表達式”,查找項為:throws ([\w ,]+)Exception?替換項保持為空。之后,替換掉所有的?@Override?和?final?關鍵字。
在 C# 中 out 是一個關鍵字不能被當作類型使用,Java 編程中常用的?System.out.println?方法需要被替換成?Console.WriteLine?,直接全局替換搞定。
重構 DbMakerConfigException 類型:
using System;namespace IP2RegionDotNetDbMaker {/*** configuration exception* * @author chenxin<chenxin619315@gmail.com>*/public class DbMakerConfigException : Exception{public DbMakerConfigException(string info) : base(info){}}}在 .NET 5.0 中 Mock 實現 Java 所需的 API
新建 Mock.cs 文件,用于存放 Java API 到 C# API 的Mock 代碼。使用擴展方法對 String 類型進行擴展,并實現 Java API 所用的方法:
public static class Extensions{public static Int32 length(this string str){if (String.IsNullOrWhiteSpace(str)){return 0;}return str.Length;}public static string trim(this string str){return str.Trim();}public static char charAt(this string str, Int32 i){return str[i];}public static int indexOf(this string str, string value){return str.IndexOf(value);}public static int indexOf(this string str, char value, Int32 start){return str.IndexOf(value, start);}public static string substring(this string str, Int32 startIndex){return str.Substring(startIndex);}public static string substring(this string str, Int32 startIndex, Int32 endIndex){return str.Substring(startIndex, endIndex - startIndex);}public static bool equals(this string str1, string str2){return String.Equals(str1, str2, StringComparison.InvariantCultureIgnoreCase);}public static string[] split(this string str, string separator){return str.Split(separator);}public static byte[] getBytes(this string str){return Encoding.UTF8.GetBytes(str);}public static byte[] getBytes(this string str, string encoding){return Encoding.GetEncoding(encoding).GetBytes(str);}public static bool endsWith(this string str, string value){return str.EndsWith(value);}}Mock 實現 StringBuilder 類型:
public class StringBuilder{private readonly System.Text.StringBuilder _builder = new System.Text.StringBuilder();internal StringBuilder append(object value){_builder.Append(value);return this;}internal string toString(){return _builder.ToString();}}Mock 實現 File 類型:
public class File{public File(string ipSrcFile){_fileInfo = new FileInfo(ipSrcFile);}private FileInfo _fileInfo;public FileInfo FileInfo => _fileInfo;internal bool exists(){return _fileInfo.Exists;}}Mock 實現 LinkedList 類型:
public class LinkedList<T> : List<T>{internal T getFirst(){return this[0];}internal void add(T item){this.Add(item);}internal T getLast(){return this[this.Count - 1];}internal IEnumerable<T> iterator(){return this;}}Mock 實現 HashMap 類型:
public class HashMap<K, V> : Dictionary<K, V>{internal void put(K k, V v){this[k] = v;}internal bool containsKey(K key){return this.ContainsKey(key);}internal V get(K k){if (containsKey(k)){return this[k];}return default;}}Mock 實現 FileReader 類型:
public class FileReader{private File globalRegionFile;private Queue<String> _lines = new Queue<string>();public FileReader(File file){this.globalRegionFile = file;using (var fs = file.FileInfo.OpenRead()){using (var sr = new StreamReader(fs)){while (!sr.EndOfStream){var line = sr.ReadLine();_lines.Enqueue(line);}}}}internal string readLine(){if (_lines.TryDequeue(out var line)){return line;}return null;}internal void close(){}}Mock 實現 BufferedReader 類型:
public class BufferedReader{private FileReader fileReader;public BufferedReader(FileReader fileReader){this.fileReader = fileReader;}internal string readLine(){return fileReader.readLine();}internal void close(){fileReader.close();}}Mock 實現 RandomAccessFile 類型:
public class RandomAccessFile{private string dbFile;private Stream stream;internal void seek(long v){stream.Seek(v, SeekOrigin.Begin);}internal void write(byte[] vs){stream.Write(vs);}internal void readFully(byte[] dbBinStr, int v, int length){stream.Read(dbBinStr, v, length);}private string v;public RandomAccessFile(string dbFile, string v){this.dbFile = dbFile;this.v = v;this.stream = new FileStream(dbFile, FileMode.OpenOrCreate, FileAccess.ReadWrite);}public long length(){return this.stream.Length;}internal void close(){if (stream == null){return;}this.stream.Flush();this.stream.Close();this.stream = null;}internal long getFilePointer(){return stream.Position;}internal void write(int v){var bytes = BitConverter.GetBytes(v);stream.Write(bytes);}}語法與屬性修正
經過以上的 Mock 操作,報錯部分便僅僅涉及語法和部分屬性。
C# 中并不存在“擴展屬性”類似的東西,所以 Java 中以“小駝峰”命名的?length?字段調用,據需要改為“大駝峰”方式的?Length?。位運算符?>>>?也需要改為?>>?,同時,還有幾個語法錯誤需要修正。比如:C# 中并不支持 Java 中的?for(Type e:collection)?語法,需要用?foreach?來替代。之后,項目就可以編譯通過了。
結果驗證
將 data 目錄拷貝至 bin 目錄,使用以下命令便可啟動生成:
dbMaker -src ./data/ip.merge.txt -region ./data/global_region.csv伴隨著大量的控制臺輸出,筆者似乎找到了黑客帝國的感覺。經過一小會兒的等待,生成已經成功執行。
通過二進制對比,該結果文件僅在行尾的日期存儲部分與源文件不同:
通過閱讀代碼,文件末尾部分的數據是生成的時間戳和一小段聲明信息。文件尾的不一致并不會對使用造成影響。這表示,這次移植是成功的。
開源地址
目前,該代碼已經上傳至 Gitee ,地址是:
https://gitee.com/coderbusy/demo/tree/master/IP2RegionDotNetDbMaker
總結
以上是生活随笔為你收集整理的我是如何把 Java 项目移植到 .NET 5.0 的的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: EntityFramework Core
- 下一篇: 如何在 Asp.Net Core 中 管