java中“冷门”工具类的总结

news2025/1/22 9:06:50

文章目录

  • 前言
  • 一些不常用的工具类
    • 不可变集合
    • 多值Map
    • Table表
    • Lists、Maps、Sets
    • 字符串操作
    • Bag
    • LazyList
    • 双向Map
  • 并发集合小总结
    • CopyOnWriteArrayList
    • ConcurrentHashMap

前言

最近挖掘了一些在项目中不常用的工具类,有些实用性还是很高的,特此总结一下。
另外又顺便看了一下常用并发集合的相关知识,也在此自我总结一下。

一些不常用的工具类

不可变集合

不可变集合包括 ImmutableList, ImmutableMap,ImmutableSet,ImmutableSortedSet等,当其创建之后就不会发生变化,可以在一些只读的场景来使用他们,减少空间的浪费。

    public static void main(String[] args) {
        //初始化一个不可变集合
        ImmutableList<String> immutableList = ImmutableList.of("12");
        List<String> list = new ArrayList<String>() {{
            add("321");
        }};
        //copy 一个list
        ImmutableList<String> immutableList1 = ImmutableList.copyOf(list);
    }

因其是不可变的,所以无法进行add等操作。
在这里插入图片描述

多值Map

Map是一个key对应一个value,当key重复时,再进行put则会进行覆盖。但有些场景需要一个key对应多个value,比如一个人可以有多个职位,key是人的id,value是人的职位id,这种类似的场景可以使用多值Map。

   public static void main(String[] args) {
        ArrayListMultimap<String, String> multiMap = ArrayListMultimap.create();
        multiMap.put("adam","teacher");
        multiMap.put("adam","player");
        multiMap.put("adam","player");
        System.out.println(multiMap.get("adam"));
    }

以下是输出的结果,可以看到,返回的是一个List,并且value值重复也并不会被过滤掉。

[teacher, player, player]

Table表

Map是通过一个key来决定value,但某些场景我们想通过两个key来决定value,比如通过经度、纬度才能确定当前的位置。

 public static void main(String[] args) {
        HashBasedTable<String, String, String> table = HashBasedTable.create();
        //通过经纬度来确定当前位置
        table.put("12", "25", "position");
        table.put("12", "26", "position");
        table.put("21", "25", "position1");
        //根据第一个key来获取value
        Collection<String> values0 = table.row("12").values();
        System.out.println(values0);
        //根据两个key来获取value
        String s = table.get("12", "25");
        System.out.println(s);
        //获取table中的所有值
        Collection<String> values1 = table.values();
        System.out.println(values1);
    }

打印结果如下:

[position0, position1]
position0
[position0, position1, position2]

Lists、Maps、Sets

Lists的简单使用

  public static void main(String[] args) {
        List<String> list = new ArrayList<String>() {{
            add("123");
            add("1234");
            add("12345");
        }};
        //分页操作
        Lists.partition(list,1).forEach(System.out::println);
    }

打印结果:

[123]
[1234]
[12345]

Sets简单使用

  public static void main(String[] args) {
        Set<String> set1 = new HashSet<String>() {{
            add("1");
            add("2");
        }};

        Set<String> set2 = new HashSet<String>() {{
            add("1");
        }};

        //找出不同
        Sets.SetView<String> difference = Sets.difference(set1, set2);
        System.out.println(difference);

        //找出相同
        Sets.SetView<String> intersection = Sets.intersection(set1, set2);
        System.out.println(intersection);
    }

打印结果:

[2]
[1]

Maps的简单使用

  public static void main(String[] args) {
        Map<String,String> map = new HashMap<String,String>(){{
            put("123", "20");
            put("1234", "201");
            put("12345", "202");
        }};

        //过滤key
        Map<String, String> map1 = Maps.filterKeys(map, "123"::equals);
        System.out.println(map1);
    }

结果:

{123=20}

其余的方法大家可以自行研究~

字符串操作

Joiner的简单使用

  public static void main(String[] args) {
        /*
         * 比如拼接redisKey的场景
         */
        Joiner joiner = Joiner.on("_");
        String key = joiner.skipNulls().join(Arrays.asList("tenantId", null, "appId", "funcId"));
        System.out.println(key);

        /*
         * 打印map中key和value的场景
         */
        Map<String, String> map = new HashMap<String, String>() {{
            put("张三", "帅哥");
            put("李四", "美女");
        }};

        String join = Joiner.on("\n").withKeyValueSeparator("是").join(map);
        System.out.println(join);
    }

以下是打印结果

tenantId_appId_funcId
李四是美女
张三是帅哥

Splitter与Joiner相对,它可实现字符串的分割

 public static void main(String[] args) {
        /*
         * 比如拼接redisKey的场景
         */
        Joiner joiner = Joiner.on("_");
        String key = joiner.skipNulls().join(Arrays.asList("tenantId", null, "appId", "funcId"));

        //使用Splitter进行分割
        Iterable<String> split = Splitter.on("_").split(key);
        split.forEach(System.out::println);

        String key1 = "宁教我负天下人休教天下人负我";

        //按照指定长度进行分割
        Iterable<String> split1 = Splitter.fixedLength(7).split(key1);
        split1.forEach(System.out::println);
    }

两次打印结果如下

tenantId
appId
funcId
宁教我负天下人
休教天下人负我

Bag

Bag和List很相似,但它可以统计出重复元素的数量。

   public static void main(String[] args) {
        Bag box = new HashBag(Arrays.asList("1", "1", "2", "3"));
        box.add("1");
        //查看“1”有多少个
        int count = box.getCount("1");
        System.out.println(count);
    }

打印结果如下:

3

LazyList

LazyList可以延迟某元素的生成,在集合被访问的时候再生成,是一种懒加载的方式,一定程度上提高了性能。

 public static void main(String[] args) {

        List<String> list = new ArrayList<String>() {{
            add("1");
            add("2");
            add("3");
        }};

        List<String> lazy = LazyList.lazyList(list, () -> "4");

        System.out.println(lazy);

        //只有用到的时候才存入;类似于orElseGet
        lazy.get(3);
        System.out.println(lazy);
    }

打印结果如下:

[1, 2, 3]
[1, 2, 3, 4]

双向Map

jdk中的Map要求键唯一,双向Map则要求键、值都必须唯一,这样它既可以根据key来获取value,也可以通过value来获取key,所以叫双向Map。

  public static void main(String[] args) {
        BidiMap bidiMap = new TreeBidiMap<String, String>();

        bidiMap.put("key", "value");
        //key value存在相同则进行覆盖
        bidiMap.put("key1", "value");
        bidiMap.put("key2", "value2");
        System.out.println(bidiMap);

        //根据 key获取value
        System.out.println(bidiMap.get("key2"));
        //根据value获取key
        System.out.println(bidiMap.getKey("value2"));
        
    }
{key1=value, key2=value2}
value2
key2

并发集合小总结

CopyOnWriteArrayList

ArrayList一般都是在方法内部使用,所以相对来说是安全的,但是多线程环境下是非安全的,先来看一下ArrayList的源码。
在jdk8下,如果new一个ArrayList,不指定其大小,默认为空:

    public ArrayList(int initialCapacity) {
    	//指定大小
        if (initialCapacity > 0) {
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
        //不指定大小默认为空
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }

当执行add操作时:

  public boolean add(E e) {
  		//根据加上当前元素之后该集合元素的数量来判断是否扩容
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

    private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }
    
    //入参为当前数组以及加上当前元素后当前数组的大小
    private static int calculateCapacity(Object[] elementData, int minCapacity) {
    	//如果是第一次add操作,且初始化时没有指定ArrayList大小
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        	//则默认大小为DEFAULT_CAPACITY 10
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        //如果非第一次add或者new的时候指定了容量大小,则返回集合当前的大小+1
        return minCapacity;
    }
    
   private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        // 如果集合大小小于add该元素之后集大小则进行扩容操作。
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }
    
    private void grow(int minCapacity) {
        int oldCapacity = elementData.length;
        //扩容后的容量大小为扩容前的(1+0.5)倍
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        //如果扩容后的大小小于当前大小的话,则新集合大小为当前大小。
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // 拷贝一个新的数组。
        elementData = Arrays.copyOf(elementData, newCapacity);
    }    

以上有两个比较重要的名词:
1、minCapacity:预期集合大小,即要想把当前元素加到该集合中,该集合需要的大小。
2、elementData:集合中存放元素的数组
用文字来描述一下add流程:
1、判断是否需要扩容
  1.1、如果集合预期的大小超过了当前集合大小,则进行扩容
  1.2、将集合扩容1.5倍
  1.3、如果扩容之后的的大小小于预期大小,则集合大小为预期大小,但如果扩容之后大小大于集合最大值,则进行huge扩容。
2、将集合中的元素加1(i++操作,非原子性)。
在以上过程中,不安全性体现在两个地方:
1、数组越界
当集合元素数量为9,线程A和线程B同时进行add操作,A执行时发现预期集合大小为10,等于当前容量,不需要扩容(也就是上述1.1步骤),线程B此时和线程A运行到同一行代码处,发现也不需要扩容,则当A把元素添加到集合之后,B再添加就会出现数组越界的情况。
2、覆盖操作
因为向数组中存入的操作方式为size++,也就是分了三步
1、从主存中拿到当前size的值放到本地高速缓存中
2、把size值进行加1
3、再把size值放到主存中
当线程A和线程B同时进行size++,A进行到第1步,B也进行到第1步,当A和B都执行到第3步时,则会发生覆盖的情况。
以上就是ArrayList多线程下不安全的原因,解决方法是使用Vector或CopyOnWriteArrayList,前者主要是用了内部锁,这次主要讨论一下后者。

看一下CopyOnWriteArrayList的源码:


 private transient volatile Object[] array;
 
 public boolean add(E e) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] elements = getArray();
            int len = elements.length;
            //将元素拷贝到一个新的数组中
            Object[] newElements = Arrays.copyOf(elements, len + 1);
            newElements[len] = e;
            //然后把新数组给之前的数组
            setArray(newElements);
            return true;
        } finally {
            lock.unlock();
        }
    }
  final Object[] getArray() {
        return array;
    }
  final void setArray(Object[] a) {
        array = a;
    }

CopyOnWriteArrayList的add操作虽然没有使用内部锁,但使用了可重入锁,每次只能有一个线程执行add操作,这点和Vector的add操作其实是一样的,但CopyOnWriteArrayList在add的时候新建了一个新的数组,在新数组中添加好元素后再将引用给之前的数组,这样CopyOnWriteArrayList在进行读操作时读的是之前的数组,保证了线程的安全性,因此其适用于读多写少的场景。

ConcurrentHashMap

我们先来看一下Map的源码,分析一下Map的不安全性体现在哪里。

	transient Node<K,V>[] table;
    //hashMap有3个构造,无参构造在初始化的时,负载因子的值为0.75
    public HashMap() {
        this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
    }
	
   public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }
    
   final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        //如果在初始化时未设置容量大小,则默认为16
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
        //15与当前元素的hash值进行与运算,如果得到的位置为空,则创建一个新的节点。
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        else {
            Node<K,V> e; K k;
            //当前元素的hash值与之前元素的hash值相同且key相同,还equels,就覆盖之前的节点
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
             //如果之前节点的数据结构是红黑树,则将节点放到树中
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
            //元素不重复,也不是红黑树,则是单纯的发生了hash冲突
                for (int binCount = 0; ; ++binCount) {
                	//如果发生冲突节点的下一个节点为null,则就将节点放进去
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        //走到这里说明比较了8次,则将链表转红黑树。
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    //发生冲突节点的下一个几点和当前节点元素重复,则直接覆盖。
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    //再进行链表的下一个节点重新判断。
                    p = e;
                }
            }
            //e不为null说明发生了覆盖,则返回当前元素。
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        //如果当前元素数量大于了阈值,则进行扩容。
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }

用文字来描述jdk1.8的扩容机制如下:
1、初始化容量
2、获取要put元素的数组下标
  2.1判断该下标是否发生了碰撞
    2.1.1如果没有碰撞则直接put
    2.1.2如果发生了碰撞就判断是否和要put的元素一样
      2.1.2.1如果一样就直接覆盖
      2.1.2.2如果不一样就判断是不是树
        如果是树就放到树节点
        如果不是树就将创建新的节点并和上一个节点连接上
          然后判断连接之后的链表长度是不是大于8,
            如果大于8就转树
            小于8就put
3、判断容量是否达到阈值
达到了就扩容
没达到就不扩容

jdk1.8的话会出现覆盖现象:
即线程A和线程B同时扩容,但它俩同时put元素时key的hash值是一样的,当A和B都运行到了2.1.1步骤时,A判断没有hash冲突时直接put,B线程也是同样操作,则就会出现了覆盖的现象。

jdk1.7扩容的话会有循环链的情况,在此不做过多描述了,扩容图如下:
在这里插入图片描述

为了解决以上问题,我们可以使用ConcurrentHashMap,我们看一下它的jdk1.8源码:

 transient volatile Node<K,V>[] table;
 
 final V putVal(K key, V value, boolean onlyIfAbsent) {
        if (key == null || value == null) throw new NullPointerException();
        //获取hash值,方式是将hashCode转成二进制右移16位后与hashCode进行异或运算再与int最大值进行与运算。
        int hash = spread(key.hashCode());
        int binCount = 0;
        for (Node<K,V>[] tab = table;;) {
            Node<K,V> f; int n, i, fh;
            //如果当前数组为空,则进行初始化
            if (tab == null || (n = tab.length) == 0)
                tab = initTable();
            //hash算法来计算当前位置是否为空
            else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
                //如果当前位置为空则进行cas机制进行创建节点。
                if (casTabAt(tab, i, null,
                             new Node<K,V>(hash, key, value, null)))
                    break;                   // no lock when adding to empty bin
            }
           //如果是MOVED,也就是-1,则进行多线程扩容
            else if ((fh = f.hash) == MOVED)
                tab = helpTransfer(tab, f);
            else {
            //走到这个else,则说明了hash冲突
                V oldVal = null;
                //当出现hash冲突,就锁住当前元素,保证只有一个线程操作
                synchronized (f) {
                    if (tabAt(tab, i) == f) {
                        if (fh >= 0) {
                            binCount = 1;
                            //遍历当前索引下的节点
                            for (Node<K,V> e = f;; ++binCount) {
                                K ek;
                                //如果存在元素相同的情况下则覆盖
                                if (e.hash == hash &&
                                    ((ek = e.key) == key ||
                                     (ek != null && key.equals(ek)))) {
                                    oldVal = e.val;
                                    if (!onlyIfAbsent)
                                        e.val = value;
                                    break;
                                }
                                Node<K,V> pred = e;
                                //没有相同元素则尾插到链表最后
                                if ((e = e.next) == null) {
                                    pred.next = new Node<K,V>(hash, key,
                                                              value, null);
                                    break;
                                }
                            }
                        }
                        //如果节点转化成了树,则添加节点
                        else if (f instanceof TreeBin) {
                            Node<K,V> p;
                            binCount = 2;
                            if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
                                                           value)) != null) {
                                oldVal = p.val;
                                if (!onlyIfAbsent)
                                    p.val = value;
                            }
                        }
                    }
                }
                if (binCount != 0) {
                	//链表长度大于8则转树
                    if (binCount >= TREEIFY_THRESHOLD)
                        treeifyBin(tab, i);
                    if (oldVal != null)
                        return oldVal;
                    break;
                }
            }
        }
        addCount(1L, binCount);
        return null;
    }

当没有出现hash冲突时,通过cas机制保证线程安全,当出现hash冲突后,则加了一把内部锁,锁住当前要操作的元素。以上只是jdk1.8的情况。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/40595.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

mysql中的这些日志,你都知道吗 2?

上一篇文章&#xff0c;我们介绍了binlog和redo log这两种日志&#xff0c;对这两种日志不熟悉的老铁可以看下"mysql中的这些日志&#xff0c;你都知道吗",在上篇文章的末尾&#xff0c;作者还留了一个问题&#xff1a;binlog 和 redo log两个相互独立的日志模块&…

kube-ovn安装与卸载

1.环境准备 Kube-OVN 是一个符合 CNI 规范的网络组件&#xff0c;其运行需要依赖 Kubernetes 环境及对应的内核网络模块。 以下是通过测试的操作系统和软件版本&#xff0c;环境配置和所需要开放的端口信息。1.1 软件版本 Kubernetes > 1.16 且 < 1.24&#xff0c;推荐…

RK3568平台开发系列讲解(音频篇)Android AudioRecord 采集音频

🚀返回专栏总目录 文章目录 一、Android 平台的音频采集技术选型1.1、SDK 层提供的采集方法1.2、NDK 层提供的采集方法二、AudioRecord 采集音频沉淀、分享、成长,让自己和他人都能有所收获!😄 📢 Android 平台音频采集的技术选型,在 SDK 层和 NDK 层各有两套音频采集…

【MySQL】MySQL参数调优与实战详解(调优篇)(实战篇)(MySQL专栏启动)

&#x1f4eb;作者简介&#xff1a;小明java问道之路&#xff0c;专注于研究 Java/ Liunx内核/ C及汇编/计算机底层原理/源码&#xff0c;就职于大型金融公司后端高级工程师&#xff0c;擅长交易领域的高安全/可用/并发/性能的架构设计与演进、系统优化与稳定性建设。 &#x1…

dataloader重构与keras入门体验

原创文章第117篇&#xff0c;专注“个人成长与财富自由、世界运作的逻辑&#xff0c; AI量化投资”。 北京疫情昨天突破4000&#xff0c;社会面800。看来三天的预期过于乐观了&#xff0c;不知道如何发展。如同资本市场的短期走势&#xff0c;没有人可以预测。但往前看三年&am…

Python性能优化

正文 python为什么性能差&#xff1a; 当我们提到一门编程语言的效率时&#xff1a;通常有两层意思&#xff0c;第一是开发效率&#xff0c;这是对程序员而言&#xff0c;完成编码所需要的时间&#xff1b;另一个是运行效率&#xff0c;这是对计算机而言&#xff0c;完成计算任…

Elasticsearch 8.X DSL 如何优化更有助于提升检索性能?

1、企业级实战 DSL&#xff08;数据已经脱敏&#xff09; 2、大家可以看一下&#xff0c;能发现哪些问题&#xff1f; 根据我的实战和咨询经验&#xff0c;我发现如下几个问题。当然&#xff0c;这是在和球友交流确认问题之后总结出来的。2.1 问题1&#xff1a;bool 组合嵌套过…

动态加载布局的技巧

文章目录动态加载布局的技巧使用限定符使用最小宽度限定符动态加载布局的技巧 使用限定符 在平板上面大多数时候采用的双页的模式,程序会在左侧列表上显示一个包含子项列表,右侧的面版会显示详细的内容的因为平板具有足够大的屏幕.完全能够显示两页的内容.但是在手机上手机只能…

事业编招聘:雄安新区公开选聘专业骨干人才

河北雄安新区公开选聘专业骨干人才公告 根据河北雄安新区建设发展工作需要&#xff0c;决定面向全国机关、企事业单位选聘部分专业骨干人才&#xff0c;现将有关事项公告如下&#xff1a; 一、选聘计划 共选聘20名专业骨干人才。 二、选聘范围 全国机关和企事业单位工作人员。…

Java包装类

为什么有包装类&#xff1f; 在Java中&#xff0c;基本数据类型不是继承自Object&#xff0c;为了在泛型中可以支持基本数据类型&#xff0c;Java给每个基本数据类型都对应了一个包装类。【基本数据类型不符合面向对象思想&#xff0c;基本类型不是对象&#xff0c;从而基本数据…

机器学习中的数学原理——梯度下降法(最速下降法)

好久没更新了&#xff0c;确实是有点懒了&#xff0c;主要是这两天返乡在隔离&#xff08;借口&#xff09;。这个专栏主要是用来分享一下我在机器学习中的学习笔记及一些感悟&#xff0c;也希望对你的学习有帮助哦&#xff01;感兴趣的小伙伴欢迎私信或者评论区留言&#xff0…

开放一批PCB资源(二)

这些板卡&#xff0c;都已经停产&#xff0c;现其PCB和原理图对外开放&#xff0c;都是cadence格式。 有需要的加我微信联系。&#xff08;微信&#xff1a;18633364981&#xff09; 这是开放的第二批&#xff0c;后续还有。这一批的价格象征性的收费每个 2000元。 这些板卡…

【操作系统基础】系统接口与系统调用

本文参考MOOC哈工大操作系统课程与课件 主要基于Linux 0.11系统展开 ”Author&#xff1a;Mayiming“ 文章目录一、操作系统接口1. 什么是操作系统接口&#xff1f;2. 操作系统接口体现在哪&#xff1f;3. 命令行是怎么执行代码的&#xff1f;4. 图形界面是怎么执行代码的&…

【Pytorch】Anaconda安装Pytorch详解教程(踩坑经验)

文章目录1、查看本机的CUDA版本2、更新NVIDIA驱动程序3、创建并激活Anaconda虚拟环境4、安装Pytorch5、安装过程中的错误6、检验安装结果未来可期1、查看本机的CUDA版本 cmd命令行输入nvidia-smi 2、更新NVIDIA驱动程序 NVIDIA官网&#xff1a;https://www.nvidia.cn/ 根据…

12、Mybatis搭建流程

Mybatis搭建流程 第一步&#xff1a;引入jar包依赖 第二步&#xff1a;搭建mybatis核心配置文件mybatis-config.xml 一般就四个配置 <properties<typeAliases<Environments<mappers里面内容&#xff1a; mybatis核心配置文件的标签顺序 "(properties?,s…

什么是网站权重?网站权重查询方法有哪些?

什么是网站权重&#xff1f;网站权重查询方法有哪些&#xff1f; 什么是网站权重&#xff1f; 网站权重是搜索引擎给网站赋予的权威值。 网站权重不等于网站排名&#xff0c;但是影响网站排名。 网站权重查询的方法&#xff1a; 方法一&#xff1a;用SEO查询工具。 具体操作如下…

进程通信(1) ----- 无名管道和有名管道

文章目录一、实验目的二、实验内容三、实验要求四、实验步骤及操作五、程序源码1. 普通管道 piperw.c2. 无名管道 wrfifo.c3. 有名管道 rdfifo.c一、实验目的 1.了解操作系统中的无名管道和有名管道 2.掌握进程通信中的管道编程模型 二、实验内容 管道是一种进程间通信的方式…

day13_面向对象的三大特征之一(封装)

封装概述 为什么需要封装&#xff1f; 现实生活中&#xff0c;每一个个体与个体之间是有边界的&#xff0c;每一个团体与团体之间是有边界的&#xff0c;而同一个个体、团体内部的信息是互通的&#xff0c;只是对外有所隐瞒。例如&#xff1a;我们使用的电脑&#xff0c;内部…

Java框架篇(来自硅谷的面试题)

目录 一 简单的谈一下SpringMVC的工作流程&#xff1f; 二 说出Spring或者SpringMVC中常用的5个注解&#xff0c;并解释含义 三 简述SpringMVC中如何返回JSON数据&#xff08;北京&#xff09; 四 谈谈你对Spring的理解 五 Spring中常用的设计模式 六 Spring循环…

IDA安装使用

最近学逆向&#xff0c;先备一套工具吧&#xff0c;IDA名声在外&#xff0c;首当其冲&#xff01;&#xff01; 内容主要整理自别的博客&#xff0c; Linux下安装IDA 链接: https://pan.baidu.com/s/1p9elz1a34872LsY1WLJmlA?pwdis2u 这个ida版本为32bit Linux系统准备的&…