再见 UUID !!!

原文作者:Charuka Herath

原文地址:Why is NanoID Replacing UUID?  

译文出自:掘金翻译计划

UUID 是软件开发中最常用的通用标识符之一。然而,在过去的几年里,其他的竞品挑战了它的存在。

其中,NanoID 是 UUID 的主要竞争对手之一。

因此,在本文中,我们将展开讨论 NanoID 的功能、它的亮点以及它的局限性,以便让我们更好地了解何时使用它。

了解 NanoID 及其用法

对于 JavaScript,生成 UUID 或 NanoID 都非常简单。它们都有对应的 NPM 包来帮助我们实现生成。

我们所需要做的就是运行 npm i nanoid 命令安装 NanoID NPM 库 并在我们的项目中使用它:

import { nanoid } from 'nanoid';  
model.id = nanoid();

“你是否知道 NanoID 每周的 NPM 下载量超过 1175.4 万,并且运行起来比 UUID 快 60%?”

此外,NanoID 比 UUID 年轻了将近 7 年,而且它的 GitHub 星数已经比 UUID 多。

下图显示了这两个之间的 npm 趋势比较,我们可以看到 NanoID 的上升趋势与 UUID 的平坦进展有强烈的对比。

a3192a7e8d27522ee4930782c2b7785e.png

https://www.npmtrends.com/nanoid-vs-uuid

我希望这些数字已经说服你去尝试 NanoID。

但是,这两者之间的主要区别很简单。它归结为键使用的字母表。

由于 NanoID 使用比 UUID 更大的字母表,因此较短的 ID 可以用于与较长的 UUID 相同的目的。

1. NanoID 只有 108 个字节那么大

与 UUID 不同,NanoID 的大小要小 4.5 倍,并且没有任何依赖关系。此外,大小限制已用于将大小从另外 35% 减小。

大小减少直接影响数据的大小。例如,使用 NanoID 的对象小而紧凑,能够用于数据传输和存储。随着应用程序的增长,这些数字变得明显起来。

2. 更安全

在大多数随机生成器中,它们使用不安全的 Math.random()。但是,NanoID 使用 crypto module 和 Web Crypto API,意味着 NanoID 更安全。

此外,NanoID 在 ID 生成器的实现过程中使用了自己的算法,称为 统一算法,而不是使用“随机 % 字母表” random % alphabet。

我们创建了一个高质量的技术交流群,与优秀的人在一起,自己也会优秀起来,赶紧点击加群,享受一起成长的快乐。

3. 它既快速又紧凑

NanoID 比 UUID 快 60%。与 UUID 字母表中的 36 个字符不同,NanoID 只有 21 个字符。

0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz-

此外,NanoID 支持 14 种不同的编程语言,它们分别是:

C#、C++、Clojure 和 ClojureScript、Crystal、Dart & Flutter、Deno、Go、Elixir、Haskell、Janet、Java、Nim、Perl、PHP、带字典的 Python、Ruby、Rust、Swift

4. 兼容性

它还支持 PouchDB、CouchDB WebWorkers、Rollup 以及 React 和 Reach-Native 等库。

我们可以使用 npx nanoid 在终端中获得唯一 ID。在 JavaScript 中使用 NanoID 唯一的要求是要先安装 NodeJS。

6e0ab3077ae5c4fb6d1943c92cc5bccc.png

此外,我们还可以在 Redux toolkit 中找到 NanoID,并将其用于其他用例,如下所示;

import { nanoid } from ‘@reduxjs/toolkit’  
console.log(nanoid()) //‘dgPXxUz_6fWIQBD8XmiSy’

5. 自定义字母

NanoID 的另一个现有功能是它允许开发人员使用自定义字母表。我们可以更改文字或 id 的大小,如下所示:

import { customAlphabet } from 'nanoid';  
const nanoid = customAlphabet('ABCDEF1234567890', 12);  
model.id = nanoid();

在上面的示例中,我将自定义字母表定义为 ABCDEF1234567890,并将 Id 的大小定义为 12。

6. 没有第三方依赖

由于 NanoID 不依赖任何第三方依赖,随着时间的推移,它能够变得更加稳定自治。

从长远来看,这有利于优化包的大小,并使其不太容易出现依赖项带来的问题。

局限性和未来重点

根据 StackOverflow 中的许多专家意见,使用 NanoID 没有明显的缺点或限制。

非人类可读是许多开发人员在 NanoID 中看到的主要缺点,因为它使调试变得更加困难。但是,与 UUID 相比,NanoID 更短且可读。

另外,如果你使用 NanoID 作为表的主键,如果你使用相同的列作为聚集索引也会出现问题。这是因为 NanoID 不是连续的。

在将来……

NanoID 正逐渐成为 JavaScript 最受欢迎的唯一 id 生成器,大多数开发人员更喜欢选择它而不是更喜欢 UUID。

5981b5ee9c23bd63e9510e6704c8424a.png

来源:https://www.npmjs.com/package/nanoid

上述基准测试显示了 NanoID 与其他主要 id 生成器相比的性能。

“使用默认字母表每秒可生成超过 220 万个唯一 ID,使用自定义字母表每秒可生成超过 180 万个唯一 ID。”

根据我使用 UUID 和 NanoID 的经验,考虑到它的小尺寸、URL 友好性、安全性和速度,我建议在任何未来的项目中使用 NanoID 而不是 UUID。

因此,我邀请您在下一个项目中试用 NanoID,并在评论部分与其他人分享您的想法。

Exception in thread "main" java.lang.Error: Unresolved compilation problems: Syntax error on tokens, delete these tokens Syntax error on tokens, delete these tokens Syntax error on tokens, delete these tokens _PATH cannot be resolved to a variable at fsaf/ser.DishService.<init>(DishService.java:10) at fsaf/ss.Main.<clinit>(Main.java:13) package fsaf; import java.util.ArrayList; import java.util.List; import java.util.UUID; public class Order { private String orderId; // 订单编号(UUID自动生成) private List<OrderItem> orderItems; // 订单项列表 public Order() { this.orderId = "ORD" + UUID.randomUUID().toString().substring(0, 8).toUpperCase(); this.orderItems = new ArrayList<>(); } // 添加订单项 public void addOrderItem(Dish dish, int quantity) { for (OrderItem item : orderItems) { if (item.getDish().getId().equals(dish.getId())) { // 若菜品已存在,更新数量 item.setQuantity(item.getQuantity() + quantity); return; } } orderItems.add(new OrderItem(dish, quantity)); } // 删除订单项 public boolean removeOrderItem(String dishId) { for (OrderItem item : orderItems) { if (item.getDish().getId().equals(dishId)) { orderItems.remove(item); return true; } } return false; // 未找到菜品 } // 修改订单项数量 public boolean updateQuantity(String dishId, int quantity) { if (quantity <= 0) return false; // 数量不能为负 for (OrderItem item : orderItems) { if (item.getDish().getId().equals(dishId)) { item.setQuantity(quantity); return true; } } return false; } // 计算订单总金额 public double getTotalAmount() { double total = 0; for (OrderItem item : orderItems) { total += item.getSubtotal(); } return total; } // Getter public String getOrderId() { return orderId; } public List<OrderItem> getOrderItems() { return new ArrayList<>(orderItems); } }package fsaf; public class OrderItem { private Dish dish; // 菜品对象 private int quantity; // 数量 public OrderItem(Dish dish, int quantity) { this.dish = dish; this.quantity = quantity; } // 计算小计(数量 × 单价) public double getSubtotal() { return quantity * dish.getPrice(); } // Getter和Setter public Dish getDish() { return dish; } public int getQuantity() { return quantity; } public void setQuantity(int quantity) { this.quantity = quantity; } }package sd; import fsaf.Dish; import java.io.*; import java.util.ArrayList; import java.util.List; public class FileUtil { // 从CSV文件加载菜品数据 public static List<Dish> loadDishesFromCSV(String filePath) { List<Dish> dishes = new ArrayList<>(); try (BufferedReader br = new BufferedReader(new FileReader(filePath))) { String line; br.readLine(); // 跳过表头行 while ((line = br.readLine()) != null) { String[] data = line.split(","); if (data.length >= 4) { String id = data[0].trim(); String name = data[1].trim(); double price = Double.parseDouble(data[2].trim()); String category = data[3].trim(); String description = data.length > 4 ? data[4].trim() : ""; dishes.add(new Dish(id, name, price, category, description)); } } } catch (IOException e) { System.out.println("菜品数据加载失败:" + e.getMessage()); } return dishes; } // 保存菜品数据到CSV文件(供管理员使用) public static void saveDishesToCSV(List<Dish> dishes, String filePath) { try (BufferedWriter bw = new BufferedWriter(new FileWriter(filePath))) { // 写入表头 bw.write("id,name,price,category,description"); bw.newLine(); // 写入菜品数据 for (Dish dish : dishes) { bw.write(String.format("%s,%s,%.2f,%s,%s", dish.getId(), dish.getName(), dish.getPrice(), dish.getCategory(), dish.getDescription())); bw.newLine(); } } catch (IOException e) { System.out.println("菜品数据保存失败:" + e.getMessage()); } } }package ser; import fsaf.Dish; import sd.FileUtil; import java.util.List; import java.util.stream.Collectors; public class DishService { private List<Dish> allDishes; // 所有菜品数据 private static final String DISH\_FILE\_PATH = "dishes.csv"; // 菜品数据文件路径 public DishService() { // 初始化时从CSV文件加载菜品数据 allDishes = FileUtil.loadDishesFromCSV(DISH\_FILE\_PATH); // 若文件不存在或为空,添加默认测试数据(实际项目中可删除) if (allDishes.isEmpty()) { initTestDishes(); } } // 获取所有菜品分类(去重) public List<String> getAllCategories() { return allDishes.stream() .map(Dish::getCategory) .distinct() .collect(Collectors.toList()); } // 根据分类获取菜品 public List<Dish> getDishesByCategory(String category) { return allDishes.stream() .filter(dish -> dish.getCategory().equalsIgnoreCase(category)) .collect(Collectors.toList()); } // 根据ID查找菜品 public Dish getDishById(String dishId) { for (Dish dish : allDishes) { if (dish.getId().equalsIgnoreCase(dishId)) { return dish; } } return null; // 菜品不存在 } // 添加测试数据(实际项目中可通过管理员界面添加) private void initTestDishes() { allDishes.add(new Dish("d01", "宫保鸡丁", 38.0, "热菜", "经典川菜,微辣")); allDishes.add(new Dish("d02", "鱼香肉丝", 32.0, "热菜", "酸甜口味,配米饭")); allDishes.add(new Dish("d03", "可乐", 6.0, "饮料", "冰镇碳酸饮料")); allDishes.add(new Dish("d04", "米饭", 3.0, "主食", "五常大米")); // 可继续添加更多菜品(至少50个) FileUtil.saveDishesToCSV(allDishes, DISH\_FILE\_PATH); // 保存到文件 } }package ser; import fsaf.Order; import fsaf.OrderItem; import fsaf.Dish; public class OrderService { private Order currentOrder; // 当前订单(未结算) public OrderService() { this.currentOrder = new Order(); // 初始化新订单 } // 添加菜品到订单 public boolean addDishToOrder(Dish dish, int quantity) { if (dish == null || quantity <= 0) return false; currentOrder.addOrderItem(dish, quantity); return true; } // 从订单中删除菜品 public boolean removeDishFromOrder(String dishId) { return currentOrder.removeOrderItem(dishId); } // 修改订单中菜品数量 public boolean updateDishQuantity(String dishId, int quantity) { return currentOrder.updateQuantity(dishId, quantity); } // 获取当前订单详情 public Order getCurrentOrder() { return currentOrder; } // 结算订单(返回账单字符串) public String checkout() { if (currentOrder.getOrderItems().isEmpty()) { return "订单为空,无法结算!"; } // 构建账单 StringBuilder bill = new StringBuilder(); bill.append("===================== 账单详情 =====================\n"); bill.append("订单编号:").append(currentOrder.getOrderId()).append("\n"); bill.append("-----------------------------------------------------\n"); bill.append(String.format("%-5s|%-15s|%-8s|%-5s|%-8s%n", "序号", "菜品名称", "单价", "数量", "小计")); int index = 1; for (OrderItem item : currentOrder.getOrderItems()) { Dish dish = item.getDish(); bill.append(String.format("%-5d|%-15s|%-8.2f|%-5d|%-8.2f%n", index++, dish.getName(), dish.getPrice(), item.getQuantity(), item.getSubtotal())); } bill.append("-----------------------------------------------------\n"); bill.append(String.format("总金额:%.2f元%n", currentOrder.getTotalAmount())); bill.append("=====================================================\n"); bill.append("感谢光临,祝您用餐愉!"); // 结算后创建新订单 currentOrder = new Order(); return bill.toString(); } } package ss; import ser.DishService; import ser.OrderService; import fsaf.Dish; import fsaf.Order; import fsaf.OrderItem; import java.util.List; import java.util.Scanner; public class Main { private static final Scanner scanner = new Scanner(System.in); private static final DishService dishService = new DishService(); private static final OrderService orderService = new OrderService(); public static void main(String[] args) { System.out.println("===== 欢迎使用餐馆点餐系统 ====="); while (true) { showMainMenu(); int choice = getIntInput("请输入选项:"); switch (choice) { case 1: browseDishes(); // 浏览菜品 break; case 2: manageOrder(); // 管理订单 break; case 3: System.out.println("退出系统,再见!"); return; default: System.out.println("无效选项,请重新输入!"); } } } // 主菜单 private static void showMainMenu() { System.out.println("\n----- 主菜单 -----"); System.out.println("1. 浏览菜品"); System.out.println("2. 管理订单"); System.out.println("3. 退出系统"); } // 浏览菜品并点餐 private static void browseDishes() { System.out.println("\n----- 菜品分类 -----"); List<String> categories = dishService.getAllCategories(); for (int i = 0; i < categories.size(); i++) { System.out.println((i + 1) + ". " + categories.get(i)); } int categoryChoice = getIntInput("请选择分类(输入序号):"); if (categoryChoice < 1 || categoryChoice > categories.size()) { System.out.println("无效分类!"); return; } String selectedCategory = categories.get(categoryChoice - 1); List<Dish> dishes = dishService.getDishesByCategory(selectedCategory); System.out.println("\n----- " + selectedCategory + " 菜品 -----"); System.out.println(String.format("%-5s|%-15s|%-8s|%-20s", "ID", "名称", "价格", "描述")); for (Dish dish : dishes) { System.out.println(String.format("%-5s|%-15s|%-8.2f|%-20s", dish.getId(), dish.getName(), dish.getPrice(), dish.getDescription())); } // 点餐 String dishId = getStringInput("请输入要添加的菜品ID(输入0返回):"); if (dishId.equals("0")) return; Dish dish = dishService.getDishById(dishId); if (dish == null) { System.out.println("菜品ID不存在!"); return; } int quantity = getIntInput("请输入数量:"); if (orderService.addDishToOrder(dish, quantity)) { System.out.println("添加成功!当前订单共有 " + orderService.getCurrentOrder().getOrderItems().size() + " 项菜品"); } else { System.out.println("添加失败!"); } } // 管理订单(修改/删除/结算) private static void manageOrder() { Order currentOrder = orderService.getCurrentOrder(); List<OrderItem> items = currentOrder.getOrderItems(); if (items.isEmpty()) { System.out.println("当前订单为空!"); return; } // 显示订单 System.out.println("\n----- 当前订单 -----"); System.out.println(String.format("%-5s|%-15s|%-8s|%-5s|%-8s", "序号", "菜品名称", "单价", "数量", "小计")); int index = 1; for (OrderItem item : items) { Dish dish = item.getDish(); System.out.println(String.format("%-5d|%-15s|%-8.2f|%-5d|%-8.2f", index++, dish.getName(), dish.getPrice(), item.getQuantity(), item.getSubtotal())); } System.out.println("-----------------------------------------------------"); System.out.println("总金额:" + currentOrder.getTotalAmount() + "元"); // 订单操作 System.out.println("\n----- 订单操作 -----"); System.out.println("1. 修改菜品数量"); System.out.println("2. 删除菜品"); System.out.println("3. 结算订单"); System.out.println("4. 返回主菜单"); int choice = getIntInput("请输入选项:"); switch (choice) { case 1: String dishId = getStringInput("请输入要修改的菜品ID:"); int newQuantity = getIntInput("请输入新数量:"); if (orderService.updateDishQuantity(dishId, newQuantity)) { System.out.println("修改成功!"); } else { System.out.println("修改失败(菜品ID不存在或数量无效)!"); } break; case 2: dishId = getStringInput("请输入要删除的菜品ID:"); if (orderService.removeDishFromOrder(dishId)) { System.out.println("删除成功!"); } else { System.out.println("删除失败(菜品ID不存在)!"); } break; case 3: System.out.println(orderService.checkout()); // 结算并打印账单 break; case 4: return; default: System.out.println("无效选项!"); } } // 工具方法:获取整数输入 private static int getIntInput(String prompt) { System.out.print(prompt); while (!scanner.hasNextInt()) { System.out.print("输入错误,请重新输入:"); scanner.next(); } int input = scanner.nextInt(); scanner.nextLine(); // consume newline return input; } // 工具方法:获取字符串输入 private static String getStringInput(String prompt) { System.out.print(prompt); return scanner.nextLine().trim(); } }优化代码减少冗余代码
最新发布
09-23
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值