Java – 五大数据结构
- 了解一下ArrayList和CopyOnWriteArrayList的增删改查实现原理
- 看看为什么说ArrayList查询快而增删慢?
- CopyOnWriteArrayList 与 Vector 的选择
- LinkedList 与 ArrayList
- Arrays.asList(….) 的使用问题
- Collections这个工具类
- java9+ List.of()方法 map , set 同理 都有,不多写了
- 我们查看源码发现 arraylist 的 CRUD 操作 并么有涉及到锁之类的东西
- 底层是数组,初始大小为10
- 插入时会判断数组容量是否足够,不够的话会进行扩容
- 所谓扩容就是新建一个新的数组,然后将老的数据里面的元素复制到新的数组里面(所以增加较慢)
- 它是List接口的一个实现类,在 java.util.concurrent( 简称 JUC ,后面我全部改成 juc ,大家注意下)
- 内部持有一个ReentrantLock lock = new ReentrantLock(); 对于 增删改 操作都是 先加锁 再 释放锁 线程安全.并且锁只有一把,而读操作不需要获得锁,支持并发。
- 读写分离,写时复制出一个新的数组,完成插入、修改或者移除操作后将新数组赋值给array
- Vector是增删改查方法都加了synchronized,保证同步,但是每个方法执行的时候都要去获得锁,性能就会大大下降,而CopyOnWriteArrayList 只是在增删改上加锁,但是读不加锁,在读方面的性能就好于Vector,CopyOnWriteArrayList支持读多写少的并发情况。
- Vector和CopyOnWriteArrayList都是 List接口 的一个实现类
- 我们看源码不难发现他每次增加一个元素都要进行一次拷贝,此时严重影响了增删改的性能,其中和arraylist 差了好几百倍 我自己测试过,
- 所以对于读多写少的操作 CopyOnWriteArrayList 更加适合 ,而且线程安全
- DriverManager 这个类 就使用到了CopyOnWriteArrayList
addFirst和addLast 方法很清楚 ,
push 方法的话 ,默认是andFirst实现
add 方法默认是addLast 实现 ….
所以上面总结一下就是 add和last , push和first ,
其实我们要明白一下 , 链表相对于数组来说, 链表的添加和删除速度很快 , 是顺序添加删除很快,因为一个linkedList会保存第一个节点和最后一个节点,时间复杂度为O(1) , 但是你要指定位置添加add(intindex,E element) , 那么此时他会先遍历, 然后找到改位置的节点, 将你的节点添加到他前面 , 此时时间复杂度最大值为O(n) ,
数组呢 , 我们知道ArrayList底层实现就是数组 , 数组优点就是由于内存地址是顺序的, 属于一块整的 , 此时遍历起来很快 , 添加删除的话 ,他会复制数组, 当数组长度特别大时,所消耗的时间会很长
这是一张图 , 大家可以看一下 ,
翻转有很多方法java.util.Collections , 可以去学一下, 有学习能力的可以去学习一下 Google的Guava 很强的工具类 , 里面很多
- HashSet、TreeSet和 LinkedHashSet三种类型什么时候使用它们
- Hashset的实现方式? HashSet去重方式 ? TreeSet 去重方式?
- 那怎么实现一个线程安全的 HashSet , 因为JDK没有 ConcurrentHashSet
- CopyOnWriteArraySet的实现
- 如你的需求是要一个能快速访问的Set,那么就要用HashSet , HashSet底层是HashMap实现的,其中的元素没有按顺序排列.
- 如果你要一个可排序Set,那么你应该用TreeSet, *TreeSet的底层实现是TreeMap
- 如果你要记录下插入时的顺序时,你应该使用LinedHashSet
- Set集合中不能包含重复的元素,每个元素必须是唯一的,你只要将元素加入set中,重复的元素会自动移除。所以可以去重, 很多情况下都需要使用 (但是去重方式不同)
- LinkedHashSet正好介于HashSet和TreeSet之间,它也是一个基于HashMap和双向链表的集合,但它同时维护了一个双链表来记录插入的顺序,基本方法的复杂度为O(1)。
- 三者都是线程不安全的, 需要使用 Collections.synchronizedSet(new HashSet(…));
- 会先去执行hashCode() 方法 ,判断是否重复
- 如果hashCode() 返回值相同 , 就会去判断equals方法,
- 如果equals() 方法还是相同, 那么就认为重复
TreeSet的元素必须是实现了java.lang.Comparable<T> 接口 , 所以他是根据此个接口的方法compareTo方法进行判断重复的, 当返回值一样的时,认定重复
我们看源码 会发现 他里面有一个 HashMap ,那为什么要用(用transient关键字标记的成员变量不参与序列化过程。) 为什么呢 ,因为 HashMap 已经实现了Serializable,
怎么实现一个 ConcurrentHashSet
- 自己写一个 实现类 实现 Set ,里面 定义一个 ConcurrentHashSet ,和 hashset的方式一样
- 直接用 第三方库 —– com.alibaba.dubbo.common.utils.ConcurrentHashSet ,阿里的库 ….
很显然跟我说的好像 一模一样 ,哈哈哈 ,我也是看别人学的,只是看你用的巧不巧,他继承了AbstractSet这个抽象类,重写了 他部分想要改的方法, 同时也实现了 set接口
很显然翻源码我们发现 他实现了 CopyOnWriteArrayList();
- 最常见的问题就是 HashMap的底层实现 , JDK1.7和JDK1.8的差别 ,这个我不讲了,如果想要看,自己百度我提供一个我自己写的一个HashMap简单实现
- Hashtable、HashMap 以及ConcurrentHashMap 的区别
- 深度学习 ConcurrentHashMap 和 HashMap 靠你们自己了 ,这俩研究透, 你已经向大神进阶了
- ConcurrentSkipListMap 与 TreeMap 的选择
- LinkedHashMap的使用
- Hashtable和ConcurrentHashMap以及ConcurrentSkipListMap 以及TreeMap 不允许key 和 value值 为空,但是 HashMap 可以 key 和value值都可以为空,
- Hashtable的方法 都加了Synchronized 关键字修饰 , 所以线程安全
- 它是 数组+链表的实现
- 取消segments字段,直接采用transient volatile HashEntry[] table保存数据,
- 采用table数组元素作为锁,从而实现了对每一行数据进行加锁,进一步减少并发冲突的概率。
- 把Table数组+单向链表的数据结构 变成为 Table数组 + 单向链表 + 红黑树的结构。
- 当链表长度超过8以后,单向链表变成了红黑数; 在哈希表扩容时,如果发现链表长度小于 6,则会由红黑树重新退化为链表。
- 对于其他详细我不吹,看懂的么几个 ,他比HashMap 还要难,
- 对于线程安全环境下 介意使用 ConcurrentHashMap 而不去使用 Hashtable
HashTable容器使用synchronized来保证线程安全,但在线程竞争激烈的情况下HashTable的效率非常低下。因为当一个线程访问HashTable的同步方法时,其他线程访问HashTable的同步方法时,可能会进入阻塞或轮询状态。如线程1使用put进行添加元素,线程2不但不能使用put方法添加元素,并且也不能使用get方法来获取元素,所以竞争越激烈效率越低。
- 其中部分信息咱们还能聊聊,不会的我就算了
- 内部节点分为Node<K,V>和TreeNode<K,V> , 都直接间接的实现与Map.Entry<K,V> , 后者所占用的空间较大,所以是一种空间换时间的想法 , 前者只要保存两个节点信息, 后者需要保存四个
- 存储结构是数组+链表 或者数组+红黑树 实现,有个阈值,当链表长度大于8,大于8的话把链表转换为红黑树,当小于等于6时会自动转成链表
原因: (反正我看不懂,只是解决碰撞概率的问题,数学问题这个是)
红黑树的平均查找长度是log(n),长度为8,查找长度为log(8)=3,链表的平均查找长度为n/2,当长度为8时,平均查找长度为8/2=4,这才有转换成树的必要;链表长度如果是小于等于6,6/2=3,虽然速度也很快的,但是转化为树结构和生成树的时间并不会太短。
还有选择6和8的原因是:
中间有个差值7可以防止链表和树之间频繁的转换。假设一下,如果设计成链表个数超过8则链表转换成树结构,链表个数小于8则树结构转换成链表,如果一个HashMap不停的插入、删除元素,链表个数在8左右徘徊,就会频繁的发生树转链表、链表转树,效率会很低。
- Node[] table的初始化长度length(默认值是16),LoadFactor为负载因子(默认值是0.75), 例如为1,虽然减少了空间开销,提高了空间利用率,但同时也增加了查询时间成本;加载因子过低,例如0.5,虽然可以减少查询时间成本,但是空间利用率很低,同时提高了rehash操作的次数
- 实现链接,大家不会写可以看看
- HashMap是非synchronized ,线程不安全
- 大家可以看看高能讲解:
- ConcurrentSkipListMap提供了一种线程安全的并发访问的排序映射表。内部是SkipList(跳表)结构实现,利用底层的插入、删除的CAS原子性操作,通过死循环不断获取最新的结点指针来保证不会出现竞态条件。在理论上能够在O(log(n))时间内完成查找、插入、删除操作。调用ConcurrentSkipListMap的size时,由于多个线程可以同时对映射表进行操作,所以映射表需要遍历整个链表才能返回元素个数,这个操作是个O(log(n))的操作。
- 在JDK1.8中,ConcurrentHashMap的性能和存储空间要优于ConcurrentSkipListMap,但是ConcurrentSkipListMap有一个功能: 它会按照键的自然顺序进行排序。
- 故需要对键值排序,则我们可以使用TreeMap,在并发场景下可以使用ConcurrentSkipListMap。
- 所以 我们并不会去 纠结 ConcurrentSkipListMap 和 ConcurrentHashMap 两者的选择,因为我解释的很好了
- 主要是为了解决读取的有序性,
- 基于 HashMap 实现的
- 可以看看我的这篇文章 : https://anthony-dong.github.io/post/linkedhashmap-yuan-li-fen-xi/
队列在于你走向高级工程师必须走的一步 . 一开始我们对于他并不了解,但是你会发现并发包里面一堆关于队列的类,你就知道了他的关键所在,先进先出的使用场景很常见的
通过我这段时间的学习,我发现在线程池这块,还有这消息队列,还有在数据库连接池这块都需要队列.这些中间件对于队列的依赖性太过于强烈.
所以学会队列是很重要的一步.这些内容我会慢慢补充的.
我们都知道队列(Queue)是一种先进先出(FIFO)的数据结构,Java中定义了java.util.Queue接口用来表示队列。Java中的Queue与List、Set属于同一个级别接口,它们都是实现了Collection接口。注意: HashMap没有实现Collection接口
- 它是一个双端队列
- 我们用到的 linkedlist 就是 实现了 deque的接口
- 支持在两端插入和移除元素
- 区别与 循环队列 循环队列实现讲解
LinkedList是链表结构,队列呢也是一个列表结构,继承关系上 , LinkedList实现了Queue , 所以对于Queue来说 ,
添加是offer(obj) , 删除是poll() , 获取队头(不删除)是peek() .
PriorityQueue维护了一个有序列表,插入或者移除对象会进行Heapfy操作,默认情况下可以称之为小顶堆。当然,我们也可以给它指定一个实现了java.util.Comparator 接口的排序类来指定元素排列的顺序。
PriorityQueue 是一个队列 , 当你设置初始化大小还是不设置 , 都不影响他继续添加元素
ConcurrentLinkedQueue 是基于链接节点的并且线程安全的队列。因为它在队列的尾部添加元素并从头部删除它们,所以只要不需要知道队列的大小ConcurrentLinkedQueue 对公共集合的共享访问就可以工作得很好。收集关于队列大小的信息会很慢,需要遍历队列。
- ArrayBlockingQueue 是有界队列
- LinkedBlockingQueue 看构造方法区分 , 默认构造方法最大值是 2^31-1
- 但是当 take 和 put操作时 ,ArrayBlockingQueue速度要快于 LinkedBlockingQueue原因是什么
1.队列中的锁的实现不同
ArrayBlockingQueue中的锁是没有分离的,即生产和消费用的是同一个锁;
LinkedBlockingQueue中的锁是分离的,即生产用的是putLock,消费是takeLock
2.在生产或消费时操作不同
ArrayBlockingQueue基于数组,在生产和消费的时候,是直接将枚举对象插入或移除的,不会产生或销毁任何额外的对象实例;
LinkedBlockingQueue基于链表,在生产和消费的时候,需要把枚举对象转换为Node进行插入或移除,会生成一个额外的Node对象,这在长时间内需要高效并发地处理大批量数据的系统中,其对于GC的影响还是存在一定的区别。
- 问题有哪些
在使用LinkedBlockingQueue时,若用默认大小且当生产速度大于消费速度时候,有可能会内存溢出。
在使用ArrayBlockingQueue和LinkedBlockingQueue分别对1000000个简单字符做入队操作时,
LinkedBlockingQueue的消耗是ArrayBlockingQueue消耗的10倍左右,
即LinkedBlockingQueue消耗在1500毫秒左右,而ArrayBlockingQueue只需150毫秒左右。
按照实现原理来分析,ArrayBlockingQueue完全可以采用分离锁,从而实现生产者和消费者操作的完全并行运行。Doug Lea之所以没这样去做,也许是因为ArrayBlockingQueue的数据写入和获取操作已经足够轻巧,以至于引入独立的锁机制,除了给代码带来额外的复杂性外,其在性能上完全占不到任何便宜。
- 我们测试的是 ArrayBlockingQueue 会比 LinkedBlockingQueue性能好 , 好差不多50%起步 ,
- BlockingQueue 可以是限定容量的。
- BlockingQueue 实现主要用于生产者-使用者队列,但它另外还支持collection接口。
- BlockingQueue 实现是线程安全的
- BlockingQueue 是阻塞队列 (看你使用的方法) ,ConcurrentLinkedQueue是非阻塞队列区别
LinkedBlockingQueue是一个线程安全的阻塞队列,基于链表实现,一般用于生产者与消费者模型的开发中。采用锁机制来实现多线程同步,提供了一个构造方法用来指定队列的大小,如果不指定大小,队列采用默认大小(Integer.MAX_VALUE,即整型最大值)。
ConcurrentLinkedQueue是一个线程安全的非阻塞队列,基于链表实现。java并没有提供构造方法来指定队列的大小,因此它是的。为了提高并发量,它通过使用更细的锁机制,使得在多线程环境中只对部分数据进行锁定,从而提高运行效率。他并没有阻塞方法,take和put方法.注意这一点
有一个是 JDK1.7才加入的, 所以常见的就六个
构造函数必须传入指定大小, 所以他是一个有界队列
分为两种情况 , 第一种构造函数指定大小, 他是一个有界队列 , 第二种情况,不指定大小他可以称之为队列, 队列最大值为Integer.MAX_VALUE
他是一个队列 , 不管你使用什么构造函数 ..
一个内部由优先级堆支持的、基于时间的调度队列。队列中存放Delayed元素,只有在延迟期满后才能从队列中提取元素。当一个元素的getDelay()方法返回值小于等于0时才能从队列中poll中元素,否则poll()方法会返回null。
这个队列类似于Golang的channel , 也就是chan ,跟无缓冲区的chan很相似. 比如take和put操作就跟chan一模一样. 但是区别在于他的poll和offer操作可以设置等待时间.
如果你学过golang的话. 应该理解 . 我写个例子
那么换而言之 , Java呢
但是他和chan不同的是, 他的poll操作吧, (类似于golang的 select case 操作) , 等不到放弃, 返回一个null.
但是唯一不同的是 他可以指定等待时间.超过等待时间再放弃.
这个就是等待1000ms , 等不到放弃了 .
像线程池中用SynchronousQueue 使用的是offer(obj)操作, 也就是说干脆插入不进去.因为他懒得等 , 但是offer可以指定等待时间的.
总结一下. take 和put 一对,是死等待 , poll和offer灵活,活着来
Java延迟队列提供了在指定时间才能获取队列元素的功能,队列头元素是最接近过期的元素。没有过期元素的话,使用poll()方法会返回null值,超时判定是通过getDelay(TimeUnit.NANOSECONDS)方法的返回值小于等于0来判断。延时队列不能存放空元素。
添加的元素必须实现java.util.concurrent.Delayed 接口
JDK1.7 加入的队列 , 亮点就是无锁实现的,性能高 .
Doug Lea 说这个是最有用的 BlockingQueue 了 , 性能最好的一个 . Doug Lea说从功能角度来讲,LinkedTransferQueue实际上是ConcurrentLinkedQueue、SynchronousQueue(公平模式)和LinkedBlockingQueue的超集。
他的 transfer方法 表示生产必须等到消费者消费才会停止阻塞. 生产者会一直阻塞直到所添加到队列的元素被某一个消费者所消费(不仅仅是添加到队列里就完事)
同时我们知道 上面那些BlockingQueue使用了大量的 condition和 lock , 这样子效率很低 , 而LinkedTransferQueue则是无锁队列.
他的核心方法其实就是xfer()方法,基本所有方法都是围绕着这个进行的 , 一般就是 SYNC ,ASYNC,NOW ,来区分状态量. 像put,offer,add 都是ASYNC , 所以不会阻塞. 下面几个状态对应的变量.
小顶堆是什么 : 任意一个非叶子节点的权值,都不大于其左右子节点的权值
- PriorityQueue是非线程安全的,PriorityBlockingQueue是线程安全的
- 两者都使用了堆,算法原理相同
- PriorityQueue 的逻辑结构是一棵完全二叉树,就是因为完全二叉树的特点, 他实际存储确实可以为一个数组的, 所以他的存储结构其实是一个数组。
1. 首先java 中的 PriorityQueue 是优先队列,使用的是小顶堆实现
什么是小顶堆 (父节点,永远小于左右子节点) ,因此结果不一定是完全升序
什么是大顶堆 跟 小顶堆相反,
优先队列中 对于当offer操作,当插入的元素此时长度大于默认长度会进行数组扩容(system.copyarr()方法) 所以他其实是一个数列
所以 优先队列 是数组实现,他不需要占用太大的物理空间,而是进行了深度的排序
- offer 添加一个元素并返回true 如果队列已满,则返回false
- poll 移除并返问队列头部的元素 如果队列为空,则返回null
- peek 返回队列头部的元素 如果队列为空,则返回null
- put 添加一个元素 如果队列满,则阻塞 BlockQueue特有的
- take 移除并返回队列头部的元素 如果队列为空,则阻塞 (像队头移除一个元素,并且整体向前移动,保证对头不为空) BlockQueue特有的
栈结构属于一种先进者后出,类似于一个瓶子 , 先进去的会压到栈低(push操作) , 出去的时候只有一个出口就是栈顶 , 返回栈顶元素,这个操作称为pop ,
stack 继承自Vector , 所有方法都加入了 sync 修饰, 使得效率很低 ,线程安全.
但是LInkedList很好的实现了这个 , 同时他是个线程不安全的类.
JAVA常见的数据结构
JAVA提供了比较常见的数据结构包括数组、链表、栈、队列、树、哈希表、堆、图等。
常见数据结构
数组
在java中,数组(Array)是在内存中存储相同数据类型的连续的空间。数组的大小在创建之后就确定了,无法扩容。数组按照索引查询元素,速度很快,时间复杂度为O(1) 。添加、删除元素的操作很耗时间,因为要移动其他元素,时间复杂度为O(n)。
链表
与数组不同的是,链表(Link)是一种物理存储单元上非连续空间的存储结构。每一个链表都包含多个节点元素,链表的节点元素的逻辑顺序是通过链表中的指针连接次序实现的。节点元素包含两个信息,一个是元素节点含有的数据信息,一个是元素节点指向地址信息(该元素节点的下一节点或者上一节点的地址)。链表在插入、删除的时候可以达到 O(1) 的时间复杂度(只需要重新指向引用即可,不需要像数组那样移动其他元素)。链表查询需要遍历整个链表,耗时,时间复杂度为O(n) 。链表有单向链表和双向链表两类。
栈
栈(Stack)是限制线性表中元素的插入和删除只能在同一端进行的一种特殊线性表。允许插入和删除的一端,为变化的一端,称为栈顶,另一端为固定的一端,称为栈底。栈按照“后进先出”、“先进后出”的原则来存储数据,先插入的数据被压入栈底,后插入的数据在栈顶,读出数据的时候,从栈顶开始依次读出。
队列
队列(Queue)是一种只允许在一端进行插入,在另一端进行删除的线性表结构。允许插入的一端叫队尾,允许删除的一端叫队头。队列在生活中很常见,例如:食堂排队打饭、车站进站排队等。栈按照“先进先出”、“后进后出”的原则来存储数据。
树
树(Tree)是一种典型的非线性结构,它是由 n(n>0)个有限节点组成的一个具有层次关系的集合。把它叫做“树”是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。树的种类有很多,我们接触到的树有二叉树、平衡二叉树、二叉查找树、B树、B+树、哈夫曼树、红黑树等。
红黑树
树形数据结构有以下这些特点:
- 有且只有一个根节点,没有父节点的节点称为根节点(如图节点12);
- 每个节点都只有有限个子节点或无子节点;
- 每一个非根节点有且只有一个父节点。
哈希表
哈希表(Hash Table),也叫散列表,是一种可以通过关键码值(key-value)直接访问的数据结构,也就是说它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度,这个映射函数叫做散列函数。它最大的特点就是可以快速实现查找、插入和删除。我们知道,数组的最大特点就是查找容易,插入和删除困难;而链表正好相反,查找困难,而插入和删除容易。哈希表很完美地结合了两者的优点, Java 的 HashMap 在此基础上还加入了树的优点。由于存储空间有限,hash计算以后可能不同的关键字映射到同一个哈希地址上,这种现象称之为哈希冲突,例如:h(16)=16%11=5,h(27)=27%11=5,两个哈希地址就冲突了。
堆
堆(Heap)就是将一个集合的数据按照完全二叉树的顺序结构存储在一个一维数组中,堆在逻辑上是一棵完全二叉树,在物理结构上是一个一维数组。将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。
大根堆
小根堆
图
图(Graph)是一种复杂的非线性结构,是一种以网络形式相互连接的节点,与树有些相像,。在线性结构中,数据元素之间有唯一的线性关系,每个数据元素(除第一个和最后一个外)均有唯一的“前驱”和“后继”。在树形结构中,数据元素之间有着明显的层次关系,并且每个数据元素只与上一层中的一个元素(父节点)及下一层的多个元素(子节点)相关。而在图形结构中,节点之间的关系是任意的,图中任意两个数据元素之间都有可能相关。
一文详解 Java 的八大基本类型
自从Java发布以来,基本数据类型就是Java语言中重要的一部分,本文就来详细介绍下每种基本类型的具体使用方法和限制。
作者 | Jeremy Grifski
译者 | 弯月,责编 | 郭芮
出品 | CSDN(CSDNnews)
以下为译文:
几年前,我开始编写了一系列有关Java入门的文章,我觉得有必要将其中一些非常细节的内容单独拿出来写成文章。这样,那些入门内容就更容易理解了。首先,我来介绍一下有关Java 8中的基本类型。
如题所述,Java语言本身有8种基本类型。在下面几节中,就让我们一起来看看这8种基本类型。我将针对每种基本类型,介绍具体的使用方法和限制。
int基本类型
首先,Java的整数是32位有符号(即包括正值和负值)整数,由int关键字表示:
当然,像所有基本类型一样,整型有自己的限制。由于它只有32位,所以其取值范围为-2147483648到2147483647。这数字很大嘛!当然,我们可以在DrJava的交互面板中用下述技巧来确认:
自然地,对于简单的计算而言,int是最常用的整数类型。如果你需要更大的数字范围,请参照下面的long。
double基本类型
与int不同,Java的双精度类型是64位浮点数,由double关键字表示:
需要提醒的是,浮点数实际上就是实数。换句话说,双精度浮点数中包含小数点。
由于双精度类型是64位,它能表示的数字要比整型多很多。同样,我们可以利用交互面板来确认双精度类型的范围:
需要注意的是,负的指数表示的是非常小的数字,而不是非常大的负数。所以这里的取值范围跟整数不是完全一样。
一般而言,double是在Java中使用浮点数的默认选择。另一个选择是float。
char基本类型
我们已经看到,Java的字符类型表示16位字符,由char关键字表示:
Java中所有的字符都用单引号表示。同时,双引号用来表示字符串。我们稍后会讨论字符串。
与往常一样,我们可以通过下面的代码找出字符的范围:
为了让这个范围有意义,我们可以将结果转换成整数(稍后会更多地介绍):
可见,char类型是Java中唯一的无符号类型。换句话说,字符的取值范围为0到65535,每个值映射到特定的字符。如果需要创建该范围之外的字符,可以将一对字符组合起来。参见“在Java中反转字符串”(https://therenegadecoder/code/reverse-a-string-in-java/)这篇文章中的例子。
byte基本类型
当我们讨论二进制时,我们讨论的实际上是比特的概念。而8个比特组成一个字节,字节是Java支持的基本类型之一。本质上,byte类型只不过是取值范围为-128到127的8位整数。可以猜到,字节由byte关键字表示:
同样,可以利用下面的代码片段来确认byte类型的取值范围:
根据我的经验,byte类型在读取和处理原始数据时非常有用。但是一般而言,我们不会使用它,因为取值范围太小了。
short基本类型
short是另一种整数类型,但它占用的空间要比int类型更小。实际上,它的占用空间正好是int类型的一半,为16位,由short关键字表示:
short类型的取值范围也只有整数的一半,我们可以用下述代码确认:
在实际应用中,short只有65546个可能的值。在内存空间和磁盘空间受限的情况下,我们会使用byte和short。但在其他情况下,在定义整数时默认使用int更为安全。
long基本类型
与short相反的是long基本类型,即长整数。该类型用来表示比int类型还要大的非常大的数。long类型是64位有符号整数,其取值范围超过了10的18次方。
通常,长整数用long关键字表示:
下面的代码可以查看64位值究竟有多大:
也许,long可以用来计算光在一定时间内走过的距离。光在一秒内大约传播万千米。如果编写一个程序来跟踪光走过的距离,那么7秒后int类型就超出范围类,而long类型能够计算大约975年。不相信吗?可以看看这个gist(https://gist.github/jrg94/820d3f0f482dd19f0170964346381df0)中的计算。
float基本类型
虽然我们通常使用64位浮点数类型double,但Java还支持另一种浮点数类型,叫做float。但与int类似,Java默认情况下使用double表示浮点数。不管怎样,我们可以用float来表示32位浮点数类型:
float类型的范围如下:
可见,32位浮点数的范围和精度都要小得多。如果不需要double的精度,同时节省一半的空间,那么可以选择float类型。
boolean基本类型
最后我们来讨论一下boolean类型。定义布尔类型可以使用boolean关键字:
布尔类型有些特殊,不像其他基本类型那样,它们表示的不是数字值。实际上,之前使用的MAX_VALUE和MIN_VALUE技巧在这里不能使用。相反,它表示的是true或false,即真和假。
在此,我不打算详细介绍布尔类型,因为在Java中做任何事情都会涉及到布尔类型。尽管如此,我们通常不会明确地声明布尔类型。相反,许多代码逻辑中的比较操作的结果都是布尔类型。
原文:https://dev.to/renegadecoder94/the-8-primitive-types-in-java-10cl
本文为 CSDN 翻译,
本文作者及来源:Renderbus瑞云渲染农场https://www.renderbus.com
文章为作者独立观点不代本网立场,未经允许不得转载。